diff --git a/vips/govips.go b/vips/govips.go index 6969b62a..2036ebbe 100644 --- a/vips/govips.go +++ b/vips/govips.go @@ -96,8 +96,6 @@ func Startup(config *Config) { panic(fmt.Sprintf("Failed to start vips code=%v", err)) } - initializeICCProfiles() - running = true if config != nil { @@ -185,7 +183,9 @@ func Shutdown() { return } - os.RemoveAll(temporaryDirectory) + if temporaryDirectory != "" { + os.RemoveAll(temporaryDirectory) + } C.vips_shutdown() disableLogging() diff --git a/vips/icc_profiles.go b/vips/icc_profiles.go index f10c4292..94f467bb 100644 --- a/vips/icc_profiles.go +++ b/vips/icc_profiles.go @@ -1,9 +1,9 @@ package vips import ( - "fmt" "os" "path/filepath" + "sync" ) var ( @@ -644,32 +644,83 @@ var ( 0x00, 0x20, 0x63, 0xcf, 0x8f, 0xf0, 0x65, 0x87, 0x4e, 0xf6, 0x00, 0x00, } - temporaryDirectory = temporaryDirectoryOrPanic() - SRGBV2MicroICCProfilePath = filepath.Join(temporaryDirectory, "srgb_v2_micro.icc") - SGrayV2MicroICCProfilePath = filepath.Join(temporaryDirectory, "sgray_v2_micro.icc") - SRGBIEC6196621ICCProfilePath = filepath.Join(temporaryDirectory, "srgb_iec61966_2_1.icc") - GenericGrayGamma22ICCProfilePath = filepath.Join(temporaryDirectory, "generic_gray_gamma_2_2.icc") + sRGBV2MicroICCProfilePathToken = "\x00srgb_v2_micro.icc" + sGrayV2MicroICCProfilePathToken = "\x00sgray_v2_micro.icc" + sRGBIEC6196621ICCProfilePathToken = "\x00srgb_iec61966_2_1.icc" + genericGrayGamma22ICCProfilePathToken = "\x00generic_gray_gamma_2_2.icc" + + temporaryDirectory = "" + SRGBV2MicroICCProfilePath = sRGBV2MicroICCProfilePathToken + SGrayV2MicroICCProfilePath = sGrayV2MicroICCProfilePathToken + SRGBIEC6196621ICCProfilePath = sRGBIEC6196621ICCProfilePathToken + GenericGrayGamma22ICCProfilePath = genericGrayGamma22ICCProfilePathToken ) -func initializeICCProfiles() { - storeIccProfile(SRGBV2MicroICCProfilePath, sRGBV2MicroICCProfile) - storeIccProfile(SGrayV2MicroICCProfilePath, sGrayV2MicroICCProfile) - storeIccProfile(SRGBIEC6196621ICCProfilePath, sRGBIEC6196621ICCProfile) - storeIccProfile(GenericGrayGamma22ICCProfilePath, genericGrayGamma22ICCProfile) +// Back support +func ensureLoadICCPath(name *string) (err error) { + if len(*name) > 0 && (*name)[0] == 0 { + switch *name { + case sRGBV2MicroICCProfilePathToken: + *name, err = GetSRGBV2MicroICCProfilePath() + return + case sGrayV2MicroICCProfilePathToken: + *name, err = GetSGrayV2MicroICCProfilePath() + return + case sRGBIEC6196621ICCProfilePathToken: + *name, err = GetSRGBIEC6196621ICCProfilePath() + return + case genericGrayGamma22ICCProfilePathToken: + *name, err = GetGenericGrayGamma22ICCProfilePath() + return + } + } + return } -func storeIccProfile(path string, data []byte) { - err := os.WriteFile(path, data, 0600) +func getTemporaryDirectory() (string, error) { + if temporaryDirectory != "" { + return temporaryDirectory, nil + } + var err error + temporaryDirectory, err = os.MkdirTemp("", "govips-") if err != nil { - panic(fmt.Sprintf("Couldn't store temporary file for ICC profile in '%v': %v", path, err.Error())) + return "", err } + return temporaryDirectory, nil } -func temporaryDirectoryOrPanic() string { - temporaryDirectory, err := os.MkdirTemp("", "govips-") - if err != nil { - panic(fmt.Sprintf("Couldn't create temporary directory: %v", err.Error())) +var lockIcc sync.Mutex + +func GetSRGBV2MicroICCProfilePath() (string, error) { + return getOrLoad(&SRGBV2MicroICCProfilePath, "srgb_v2_micro.icc", sRGBV2MicroICCProfile) +} + +func GetSGrayV2MicroICCProfilePath() (string, error) { + return getOrLoad(&SGrayV2MicroICCProfilePath, "sgray_v2_micro.icc", sGrayV2MicroICCProfile) +} + +func GetSRGBIEC6196621ICCProfilePath() (string, error) { + return getOrLoad(&SRGBIEC6196621ICCProfilePath, "srgb_iec61966_2_1.icc", sRGBIEC6196621ICCProfile) +} + +func GetGenericGrayGamma22ICCProfilePath() (string, error) { + return getOrLoad(&GenericGrayGamma22ICCProfilePath, "generic_gray_gamma_2_2.icc", genericGrayGamma22ICCProfile) +} + +func getOrLoad(pathFile *string, name string, fileBytes []byte) (string, error) { + lockIcc.Lock() + defer lockIcc.Unlock() + if len(*pathFile) > 0 && (*pathFile)[0] != 0 { + return *pathFile, nil + } + + if _, err := getTemporaryDirectory(); err != nil { + return "", err } - return temporaryDirectory + *pathFile = filepath.Join(temporaryDirectory, name) + if err := os.WriteFile(*pathFile, fileBytes, 0600); err != nil { + return "", err + } + return *pathFile, nil } diff --git a/vips/icc_profiles_test.go b/vips/icc_profiles_test.go index 6a06aac1..8bf971bb 100644 --- a/vips/icc_profiles_test.go +++ b/vips/icc_profiles_test.go @@ -10,12 +10,33 @@ import ( ) func Test_ICCProfileInitialisation(t *testing.T) { - initializeICCProfiles() + nonDefaultProfile := "non-default" + err := ensureLoadICCPath(&nonDefaultProfile) + require.NoError(t, err) + + err = ensureLoadICCPath(&sRGBV2MicroICCProfilePathToken) + require.NoError(t, err) + path, err := GetSRGBV2MicroICCProfilePath() + require.NoError(t, err) + assertIccProfile(t, sRGBV2MicroICCProfile, path) + + err = ensureLoadICCPath(&sGrayV2MicroICCProfilePathToken) + require.NoError(t, err) + path, err = GetSGrayV2MicroICCProfilePath() + require.NoError(t, err) + assertIccProfile(t, sGrayV2MicroICCProfile, path) + + err = ensureLoadICCPath(&sRGBIEC6196621ICCProfilePathToken) + require.NoError(t, err) + path, err = GetSRGBIEC6196621ICCProfilePath() + require.NoError(t, err) + assertIccProfile(t, sRGBIEC6196621ICCProfile, path) - assertIccProfile(t, sRGBV2MicroICCProfile, SRGBV2MicroICCProfilePath) - assertIccProfile(t, sGrayV2MicroICCProfile, SGrayV2MicroICCProfilePath) - assertIccProfile(t, sRGBIEC6196621ICCProfile, SRGBIEC6196621ICCProfilePath) - assertIccProfile(t, genericGrayGamma22ICCProfile, GenericGrayGamma22ICCProfilePath) + err = ensureLoadICCPath(&genericGrayGamma22ICCProfilePathToken) + require.NoError(t, err) + path, err = GetGenericGrayGamma22ICCProfilePath() + require.NoError(t, err) + assertIccProfile(t, genericGrayGamma22ICCProfile, path) } func assertIccProfile(t *testing.T, expectedProfile []byte, path string) { diff --git a/vips/image.go b/vips/image.go index 1da6855d..1eb1d6fd 100644 --- a/vips/image.go +++ b/vips/image.go @@ -1402,6 +1402,13 @@ func (r *ImageRef) RemoveICCProfile() error { // TransformICCProfileWithFallback transforms from the embedded ICC profile of the image to the ICC profile at the given path. // The fallback ICC profile is used if the image does not have an embedded ICC profile. func (r *ImageRef) TransformICCProfileWithFallback(targetProfilePath, fallbackProfilePath string) error { + if err := ensureLoadICCPath(&targetProfilePath); err != nil { + return err + } + if err := ensureLoadICCPath(&fallbackProfilePath); err != nil { + return err + } + depth := 16 if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet { depth = 8 @@ -1437,6 +1444,10 @@ func (r *ImageRef) OptimizeICCProfile() error { r.optimizedIccProfile = SGrayV2MicroICCProfilePath } + if err := ensureLoadICCPath(&r.optimizedIccProfile); err != nil { + return err + } + embedded := r.HasICCProfile() && (inputProfile == "") depth := 16