Browse Source

Fix handling ICC profiles with vips 8.15+

DarthSim 6 months ago
parent
commit
edb050ed21
6 changed files with 88 additions and 3 deletions
  1. 1 0
      CHANGELOG.md
  2. 4 0
      processing/export_color_profile.go
  3. 9 3
      processing/import_color_profile.go
  4. 52 0
      vips/vips.c
  5. 20 0
      vips/vips.go
  6. 2 0
      vips/vips.h

+ 1 - 0
CHANGELOG.md

@@ -23,6 +23,7 @@
 - Set `Error` status for errorred traces in OpenTelemetry.
 - Fix URL parsing error when a non-http(s) URL contains a `%` symbol outside of the percent-encoded sequence.
 - Fix importing ICC profiles for 16-bit images with an alpha channel.
+- Fix handling ICC profiles with vips 8.15+.
 - (pro) Fix opject detection accuracy when using YOLOv8 or YOLOv10 models.
 - (pro) Fix usage of the `obj` and `objw` gravity types inside the `crop` processing option.
 - (pro) Fix detecting of width and height when orientation is specified in EXIF but EXIF info is not requested.

+ 4 - 0
processing/export_color_profile.go

@@ -15,6 +15,10 @@ func exportColorProfile(pctx *pipelineContext, img *vips.Image, po *options.Proc
 		}
 	}
 
+	// vips 8.15+ tends to lose the colour profile during some color conversions.
+	// We probably have a backup of the colour profile, so we need to restore it.
+	img.RestoreColourProfile()
+
 	if img.ColourProfileImported() {
 		if keepProfile {
 			// We imported ICC profile and want to keep it,

+ 9 - 3
processing/import_color_profile.go

@@ -22,9 +22,15 @@ func importColorProfile(pctx *pipelineContext, img *vips.Image, po *options.Proc
 		// The image is linear. If we keep its ICC, we'll get wrong colors after
 		// converting it to sRGB
 		img.RemoveColourProfile()
-	} else if convertToLinear || !img.IsRGB() {
-		if err := img.ImportColourProfile(); err != nil {
-			return err
+	} else {
+		// vips 8.15+ tends to lose the colour profile during some color conversions.
+		// We need to backup the colour profile before the conversion and restore it later.
+		img.BackupColourProfile()
+
+		if convertToLinear || !img.IsRGB() {
+			if err := img.ImportColourProfile(); err != nil {
+				return err
+			}
 		}
 	}
 

+ 52 - 0
vips/vips.c

@@ -6,6 +6,8 @@
 
 #define VIPS_META_PALETTE_BITS_DEPTH "palette-bit-depth"
 
+#define IMGPROXY_META_ICC_NAME "imgproxy-icc-profile"
+
 int
 vips_initialize()
 {
@@ -430,6 +432,55 @@ vips_has_embedded_icc(VipsImage *in)
   return vips_image_get_typeof(in, VIPS_META_ICC_NAME) != 0;
 }
 
+int
+vips_icc_backup(VipsImage *in, VipsImage **out)
+{
+  if (vips_copy(in, out, NULL))
+    return 1;
+
+  if (!vips_image_get_typeof(in, VIPS_META_ICC_NAME))
+    return 0;
+
+  const void *data = NULL;
+  size_t data_len = 0;
+
+  if (vips_image_get_blob(in, VIPS_META_ICC_NAME, &data, &data_len))
+    return 0;
+
+  if (!data || data_len < 128)
+    return 0;
+
+  vips_image_remove(*out, IMGPROXY_META_ICC_NAME);
+  vips_image_set_blob_copy(*out, IMGPROXY_META_ICC_NAME, data, data_len);
+
+  return 0;
+}
+
+int
+vips_icc_restore(VipsImage *in, VipsImage **out)
+{
+  if (vips_copy(in, out, NULL))
+    return 1;
+
+  if (vips_image_get_typeof(in, VIPS_META_ICC_NAME) ||
+      !vips_image_get_typeof(in, IMGPROXY_META_ICC_NAME))
+    return 0;
+
+  const void *data = NULL;
+  size_t data_len = 0;
+
+  if (vips_image_get_blob(in, IMGPROXY_META_ICC_NAME, &data, &data_len))
+    return 0;
+
+  if (!data || data_len < 128)
+    return 0;
+
+  vips_image_remove(*out, VIPS_META_ICC_NAME);
+  vips_image_set_blob_copy(*out, VIPS_META_ICC_NAME, data, data_len);
+
+  return 0;
+}
+
 int
 vips_icc_import_go(VipsImage *in, VipsImage **out)
 {
@@ -506,6 +557,7 @@ vips_icc_remove(VipsImage *in, VipsImage **out)
     return 1;
 
   vips_image_remove(*out, VIPS_META_ICC_NAME);
+  vips_image_remove(*out, IMGPROXY_META_ICC_NAME);
   vips_image_remove(*out, "exif-ifd0-WhitePoint");
   vips_image_remove(*out, "exif-ifd0-PrimaryChromaticities");
   vips_image_remove(*out, "exif-ifd2-ColorSpace");

+ 20 - 0
vips/vips.go

@@ -736,6 +736,26 @@ func (img *Image) IsLinear() bool {
 	return C.vips_image_guess_interpretation(img.VipsImage) == C.VIPS_INTERPRETATION_scRGB
 }
 
+func (img *Image) BackupColourProfile() {
+	var tmp *C.VipsImage
+
+	if C.vips_icc_backup(img.VipsImage, &tmp) == 0 {
+		C.swap_and_clear(&img.VipsImage, tmp)
+	} else {
+		log.Warningf("Can't backup ICC profile: %s", Error())
+	}
+}
+
+func (img *Image) RestoreColourProfile() {
+	var tmp *C.VipsImage
+
+	if C.vips_icc_restore(img.VipsImage, &tmp) == 0 {
+		C.swap_and_clear(&img.VipsImage, tmp)
+	} else {
+		log.Warningf("Can't restore ICC profile: %s", Error())
+	}
+}
+
 func (img *Image) ImportColourProfile() error {
 	var tmp *C.VipsImage
 

+ 2 - 0
vips/vips.h

@@ -48,6 +48,8 @@ int vips_resize_go(VipsImage *in, VipsImage **out, double wscale, double hscale)
 
 int vips_icc_is_srgb_iec61966(VipsImage *in);
 int vips_has_embedded_icc(VipsImage *in);
+int vips_icc_backup(VipsImage *in, VipsImage **out);
+int vips_icc_restore(VipsImage *in, VipsImage **out);
 int vips_icc_import_go(VipsImage *in, VipsImage **out);
 int vips_icc_export_go(VipsImage *in, VipsImage **out);
 int vips_icc_export_srgb(VipsImage *in, VipsImage **out);