Skip to content

Commit

Permalink
add Image#TransformICCProfileWithFallback (#373)
Browse files Browse the repository at this point in the history
* add Image#TransformICCProfileWithFallback

This fixes #314.

* add tests for Image#TransformICCProfileWithFallback()

* remove optimization from TransformICCProfileWithFallback

The code tried to avoid a no-op when transforming from profile A to profile A.
But it did not taken into account that the result should always contain
an ICC profile and that the no-op actually changes the image by applying
the target profile without modifying anything else.
  • Loading branch information
toaster authored Mar 8, 2024
1 parent 302974d commit 143c4c1
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 13 deletions.
Binary file added resources/adobe-rgb.icc
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/jpg-24bit-rgb-no-icc.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/jpg-32bit-cmyk-no-icc.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 10 additions & 9 deletions vips/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -1372,19 +1372,15 @@ 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

// 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 {
depth := 16
if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet {
depth = 8
}

out, err := vipsICCTransform(r.image, outputProfilePath, inputProfile, IntentPerceptual, depth, embedded)
out, err := vipsICCTransform(r.image, targetProfilePath, fallbackProfilePath, IntentPerceptual, depth, true)
if err != nil {
govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error()))
return err
Expand All @@ -1394,13 +1390,18 @@ func (r *ImageRef) TransformICCProfile(outputProfilePath string) 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 {
return r.TransformICCProfileWithFallback(outputProfilePath, SRGBIEC6196621ICCProfilePath)
}

// 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.
func (r *ImageRef) OptimizeICCProfile() error {
inputProfile := r.determineInputICCProfile()
if !r.HasICCProfile() && (inputProfile == "") {
//No embedded ICC profile in the input image and no input profile determined, nothing to do.
// No embedded ICC profile in the input image and no input profile determined, nothing to do.
return nil
}

Expand Down
57 changes: 54 additions & 3 deletions vips/image_golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,57 @@ func TestImage_TransformICCProfile_RGB_Embedded(t *testing.T) {
}, nil)
}

func TestImage_TransformICCProfileWithFallback(t *testing.T) {
t.Run("RGB source without ICC", func(t *testing.T) {
goldenTest(t, resources+"jpg-24bit-rgb-no-icc.jpg",
func(img *ImageRef) error {
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, resources+"adobe-rgb.icc")
},
func(result *ImageRef) {
assert.True(t, result.HasICCProfile())
iccProfileData := result.GetICCProfile()
assert.Greater(t, len(iccProfileData), 0)
assert.Equal(t, InterpretationSRGB, result.Interpretation())
}, nil)
})
t.Run("RGB source with ICC", func(t *testing.T) {
goldenTest(t, resources+"jpg-24bit-icc-adobe-rgb.jpg",
func(img *ImageRef) error {
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
},
func(result *ImageRef) {
assert.True(t, result.HasICCProfile())
iccProfileData := result.GetICCProfile()
assert.Greater(t, len(iccProfileData), 0)
assert.Equal(t, InterpretationSRGB, result.Interpretation())
}, nil)
})
t.Run("CMYK source without ICC", func(t *testing.T) {
goldenTest(t, resources+"jpg-32bit-cmyk-no-icc.jpg",
func(img *ImageRef) error {
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, "cmyk")
},
func(result *ImageRef) {
assert.True(t, result.HasICCProfile())
iccProfileData := result.GetICCProfile()
assert.Greater(t, len(iccProfileData), 0)
assert.Equal(t, InterpretationSRGB, result.Interpretation())
}, nil)
})
t.Run("CMYK source with ICC", func(t *testing.T) {
goldenTest(t, resources+"jpg-32bit-cmyk-icc-swop.jpg",
func(img *ImageRef) error {
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, "cmyk")
},
func(result *ImageRef) {
assert.True(t, result.HasICCProfile())
iccProfileData := result.GetICCProfile()
assert.Greater(t, len(iccProfileData), 0)
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 {
Expand Down Expand Up @@ -319,7 +370,7 @@ func TestImage_AutoRotate_6__jpeg_to_webp(t *testing.T) {
// expected should be 1
// Known issue: libvips does not write EXIF into WebP:
// https://github.com/libvips/libvips/pull/1745
//assert.Equal(t, 0, result.Orientation())
// assert.Equal(t, 0, result.Orientation())
},
exportWebp(nil),
)
Expand Down Expand Up @@ -366,7 +417,7 @@ func TestImage_TIF_16_Bit_To_AVIF_12_Bit(t *testing.T) {

func TestImage_Sharpen_24bit_Alpha(t *testing.T) {
goldenTest(t, resources+"png-24bit+alpha.png", func(img *ImageRef) error {
//usm_0.66_1.00_0.01
// usm_0.66_1.00_0.01
sigma := 1 + (0.66 / 2)
x1 := 0.01 * 100
m2 := 1.0
Expand All @@ -377,7 +428,7 @@ func TestImage_Sharpen_24bit_Alpha(t *testing.T) {

func TestImage_Sharpen_8bit_Alpha(t *testing.T) {
goldenTest(t, resources+"png-8bit+alpha.png", func(img *ImageRef) error {
//usm_0.66_1.00_0.01
// usm_0.66_1.00_0.01
sigma := 1 + (0.66 / 2)
x1 := 0.01 * 100
m2 := 1.0
Expand Down
32 changes: 31 additions & 1 deletion vips/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,36 @@ func TestImageRef_TransformICCProfile(t *testing.T) {
assert.True(t, image.HasICCProfile())
}

func TestImageRef_TransformICCProfileWithFallback(t *testing.T) {
Startup(nil)

t.Run("source with ICC", func(t *testing.T) {
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.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
require.NoError(t, err)

assert.True(t, image.HasIPTC())
assert.True(t, image.HasICCProfile())
})

t.Run("source without ICC", func(t *testing.T) {
image, err := NewImageFromFile(resources + "jpg-24bit.jpg")
require.NoError(t, err)

require.False(t, image.HasICCProfile())

err = image.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
require.NoError(t, err)

assert.True(t, image.HasICCProfile())
})
}

func TestImageRef_Close(t *testing.T) {
Startup(nil)

Expand Down Expand Up @@ -661,7 +691,7 @@ func TestImageRef_CompositeMulti(t *testing.T) {
image, err := NewImageFromFile(resources + uri)
require.NoError(t, err)

//add offset test
// add offset test
images[i] = &ImageComposite{image, BlendModeOver, (i + 1) * 20, (i + 2) * 20}
}

Expand Down

0 comments on commit 143c4c1

Please sign in to comment.