Skip to content

Commit 143c4c1

Browse files
authored
add Image#TransformICCProfileWithFallback (#373)
* 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.
1 parent 302974d commit 143c4c1

10 files changed

+95
-13
lines changed

resources/adobe-rgb.icc

560 Bytes
Binary file not shown.
297 KB
Loading
297 KB
Loading

resources/jpg-24bit-rgb-no-icc.jpg

1.24 MB
Loading
38 KB
Loading
31.9 KB
Loading

resources/jpg-32bit-cmyk-no-icc.jpg

223 KB
Loading

vips/image.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,19 +1372,15 @@ func (r *ImageRef) RemoveICCProfile() error {
13721372
return nil
13731373
}
13741374

1375-
// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path.
1376-
func (r *ImageRef) TransformICCProfile(outputProfilePath string) error {
1377-
// If the image has an embedded profile, that will be used and the input profile ignored.
1378-
// Otherwise, images without an input profile are assumed to use a standard RGB profile.
1379-
embedded := r.HasICCProfile()
1380-
inputProfile := SRGBIEC6196621ICCProfilePath
1381-
1375+
// TransformICCProfileWithFallback transforms from the embedded ICC profile of the image to the ICC profile at the given path.
1376+
// The fallback ICC profile is used if the image does not have an embedded ICC profile.
1377+
func (r *ImageRef) TransformICCProfileWithFallback(targetProfilePath, fallbackProfilePath string) error {
13821378
depth := 16
13831379
if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet {
13841380
depth = 8
13851381
}
13861382

1387-
out, err := vipsICCTransform(r.image, outputProfilePath, inputProfile, IntentPerceptual, depth, embedded)
1383+
out, err := vipsICCTransform(r.image, targetProfilePath, fallbackProfilePath, IntentPerceptual, depth, true)
13881384
if err != nil {
13891385
govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error()))
13901386
return err
@@ -1394,13 +1390,18 @@ func (r *ImageRef) TransformICCProfile(outputProfilePath string) error {
13941390
return nil
13951391
}
13961392

1393+
// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path.
1394+
func (r *ImageRef) TransformICCProfile(outputProfilePath string) error {
1395+
return r.TransformICCProfileWithFallback(outputProfilePath, SRGBIEC6196621ICCProfilePath)
1396+
}
1397+
13971398
// OptimizeICCProfile optimizes the ICC color profile of the image.
13981399
// For two color channel images, it sets a grayscale profile.
13991400
// For color images, it sets a CMYK or non-CMYK profile based on the image metadata.
14001401
func (r *ImageRef) OptimizeICCProfile() error {
14011402
inputProfile := r.determineInputICCProfile()
14021403
if !r.HasICCProfile() && (inputProfile == "") {
1403-
//No embedded ICC profile in the input image and no input profile determined, nothing to do.
1404+
// No embedded ICC profile in the input image and no input profile determined, nothing to do.
14041405
return nil
14051406
}
14061407

vips/image_golden_test.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,57 @@ func TestImage_TransformICCProfile_RGB_Embedded(t *testing.T) {
126126
}, nil)
127127
}
128128

129+
func TestImage_TransformICCProfileWithFallback(t *testing.T) {
130+
t.Run("RGB source without ICC", func(t *testing.T) {
131+
goldenTest(t, resources+"jpg-24bit-rgb-no-icc.jpg",
132+
func(img *ImageRef) error {
133+
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, resources+"adobe-rgb.icc")
134+
},
135+
func(result *ImageRef) {
136+
assert.True(t, result.HasICCProfile())
137+
iccProfileData := result.GetICCProfile()
138+
assert.Greater(t, len(iccProfileData), 0)
139+
assert.Equal(t, InterpretationSRGB, result.Interpretation())
140+
}, nil)
141+
})
142+
t.Run("RGB source with ICC", func(t *testing.T) {
143+
goldenTest(t, resources+"jpg-24bit-icc-adobe-rgb.jpg",
144+
func(img *ImageRef) error {
145+
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
146+
},
147+
func(result *ImageRef) {
148+
assert.True(t, result.HasICCProfile())
149+
iccProfileData := result.GetICCProfile()
150+
assert.Greater(t, len(iccProfileData), 0)
151+
assert.Equal(t, InterpretationSRGB, result.Interpretation())
152+
}, nil)
153+
})
154+
t.Run("CMYK source without ICC", func(t *testing.T) {
155+
goldenTest(t, resources+"jpg-32bit-cmyk-no-icc.jpg",
156+
func(img *ImageRef) error {
157+
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, "cmyk")
158+
},
159+
func(result *ImageRef) {
160+
assert.True(t, result.HasICCProfile())
161+
iccProfileData := result.GetICCProfile()
162+
assert.Greater(t, len(iccProfileData), 0)
163+
assert.Equal(t, InterpretationSRGB, result.Interpretation())
164+
}, nil)
165+
})
166+
t.Run("CMYK source with ICC", func(t *testing.T) {
167+
goldenTest(t, resources+"jpg-32bit-cmyk-icc-swop.jpg",
168+
func(img *ImageRef) error {
169+
return img.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, "cmyk")
170+
},
171+
func(result *ImageRef) {
172+
assert.True(t, result.HasICCProfile())
173+
iccProfileData := result.GetICCProfile()
174+
assert.Greater(t, len(iccProfileData), 0)
175+
assert.Equal(t, InterpretationSRGB, result.Interpretation())
176+
}, nil)
177+
})
178+
}
179+
129180
func TestImage_OptimizeICCProfile_CMYK(t *testing.T) {
130181
goldenTest(t, resources+"jpg-32bit-cmyk-icc-swop.jpg",
131182
func(img *ImageRef) error {
@@ -319,7 +370,7 @@ func TestImage_AutoRotate_6__jpeg_to_webp(t *testing.T) {
319370
// expected should be 1
320371
// Known issue: libvips does not write EXIF into WebP:
321372
// https://github.com/libvips/libvips/pull/1745
322-
//assert.Equal(t, 0, result.Orientation())
373+
// assert.Equal(t, 0, result.Orientation())
323374
},
324375
exportWebp(nil),
325376
)
@@ -366,7 +417,7 @@ func TestImage_TIF_16_Bit_To_AVIF_12_Bit(t *testing.T) {
366417

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

378429
func TestImage_Sharpen_8bit_Alpha(t *testing.T) {
379430
goldenTest(t, resources+"png-8bit+alpha.png", func(img *ImageRef) error {
380-
//usm_0.66_1.00_0.01
431+
// usm_0.66_1.00_0.01
381432
sigma := 1 + (0.66 / 2)
382433
x1 := 0.01 * 100
383434
m2 := 1.0

vips/image_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,36 @@ func TestImageRef_TransformICCProfile(t *testing.T) {
469469
assert.True(t, image.HasICCProfile())
470470
}
471471

472+
func TestImageRef_TransformICCProfileWithFallback(t *testing.T) {
473+
Startup(nil)
474+
475+
t.Run("source with ICC", func(t *testing.T) {
476+
image, err := NewImageFromFile(resources + "jpg-24bit-icc-adobe-rgb.jpg")
477+
require.NoError(t, err)
478+
479+
require.True(t, image.HasIPTC())
480+
require.True(t, image.HasICCProfile())
481+
482+
err = image.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
483+
require.NoError(t, err)
484+
485+
assert.True(t, image.HasIPTC())
486+
assert.True(t, image.HasICCProfile())
487+
})
488+
489+
t.Run("source without ICC", func(t *testing.T) {
490+
image, err := NewImageFromFile(resources + "jpg-24bit.jpg")
491+
require.NoError(t, err)
492+
493+
require.False(t, image.HasICCProfile())
494+
495+
err = image.TransformICCProfileWithFallback(SRGBIEC6196621ICCProfilePath, SRGBV2MicroICCProfilePath)
496+
require.NoError(t, err)
497+
498+
assert.True(t, image.HasICCProfile())
499+
})
500+
}
501+
472502
func TestImageRef_Close(t *testing.T) {
473503
Startup(nil)
474504

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

664-
//add offset test
694+
// add offset test
665695
images[i] = &ImageComposite{image, BlendModeOver, (i + 1) * 20, (i + 2) * 20}
666696
}
667697

0 commit comments

Comments
 (0)