diff --git a/resources/jpg-24bit-icc-adobe-rgb.TransformICCProfile_RGB_Embedded-macos-12.golden.jpeg b/resources/jpg-24bit-icc-adobe-rgb.TransformICCProfile_RGB_Embedded-macos-12.golden.jpeg new file mode 100644 index 00000000..a29188a4 Binary files /dev/null and b/resources/jpg-24bit-icc-adobe-rgb.TransformICCProfile_RGB_Embedded-macos-12.golden.jpeg differ diff --git a/resources/jpg-24bit.TransformICCProfile_RGB_No_Profile-macos-12.golden.jpeg b/resources/jpg-24bit.TransformICCProfile_RGB_No_Profile-macos-12.golden.jpeg new file mode 100644 index 00000000..966ac41b Binary files /dev/null and b/resources/jpg-24bit.TransformICCProfile_RGB_No_Profile-macos-12.golden.jpeg differ diff --git a/vips/image.go b/vips/image.go index 1964f455..a72553c6 100644 --- a/vips/image.go +++ b/vips/image.go @@ -1133,6 +1133,24 @@ func (r *ImageRef) RemoveICCProfile() error { return nil } +// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path. +func (r *ImageRef) TransformICCProfile(outputProfilePath string) error { + + // If the image has an embedded profile, that will be used and the input profile ignored. + // Otherwise, images without an input profile are assumed to use a standard RGB profile. + embedded := r.HasICCProfile() + inputProfile := SRGBIEC6196621ICCProfilePath + + out, err := vipsICCTransform(r.image, outputProfilePath, inputProfile, IntentPerceptual, 0, embedded) + if err != nil { + govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error())) + return err + } + + r.setImage(out) + return nil +} + // OptimizeICCProfile optimizes the ICC color profile of the image. // For two color channel images, it sets a grayscale profile. // For color images, it sets a CMYK or non-CMYK profile based on the image metadata. diff --git a/vips/image_golden_test.go b/vips/image_golden_test.go index 1f3ec7c2..202943bb 100644 --- a/vips/image_golden_test.go +++ b/vips/image_golden_test.go @@ -92,6 +92,28 @@ func TestImage_EmbedBackground_NoAlpha(t *testing.T) { }, nil) } +func TestImage_TransformICCProfile_RGB_No_Profile(t *testing.T) { + goldenTest(t, resources+"jpg-24bit.jpg", + func(img *ImageRef) error { + return img.TransformICCProfile(SRGBIEC6196621ICCProfilePath) + }, + func(result *ImageRef) { + assert.True(t, result.HasICCProfile()) + assert.Equal(t, InterpretationSRGB, result.Interpretation()) + }, nil) +} + +func TestImage_TransformICCProfile_RGB_Embedded(t *testing.T) { + goldenTest(t, resources+"jpg-24bit-icc-adobe-rgb.jpg", + func(img *ImageRef) error { + return img.TransformICCProfile(SRGBIEC6196621ICCProfilePath) + }, + func(result *ImageRef) { + assert.True(t, result.HasICCProfile()) + assert.Equal(t, InterpretationSRGB, result.Interpretation()) + }, nil) +} + func TestImage_OptimizeICCProfile_CMYK(t *testing.T) { goldenTest(t, resources+"jpg-32bit-cmyk-icc-swop.jpg", func(img *ImageRef) error { diff --git a/vips/image_test.go b/vips/image_test.go index e26c862e..f662d677 100644 --- a/vips/image_test.go +++ b/vips/image_test.go @@ -428,6 +428,22 @@ func TestImageRef_RemoveICCProfile(t *testing.T) { assert.True(t, image.HasIPTC()) } +func TestImageRef_TransformICCProfile(t *testing.T) { + Startup(nil) + + image, err := NewImageFromFile(resources + "jpg-24bit-icc-adobe-rgb.jpg") + require.NoError(t, err) + + require.True(t, image.HasIPTC()) + require.True(t, image.HasICCProfile()) + + err = image.TransformICCProfile(SRGBIEC6196621ICCProfilePath) + require.NoError(t, err) + + assert.True(t, image.HasIPTC()) + assert.True(t, image.HasICCProfile()) +} + func TestImageRef_Close(t *testing.T) { Startup(nil)