mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Thumbs: Enhance embedding of ICC profiles based on InteropIndex #5178
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
78
internal/thumb/vips_icc.go
Normal file
78
internal/thumb/vips_icc.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package thumb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davidbyttow/govips/v2/vips"
|
||||
)
|
||||
|
||||
// InteroperabilityIndex EXIF codes used by some cameras to hint at the color space.
|
||||
// Sources: EXIF TagNames (R03=Adobe RGB, R98=sRGB, THM=thumbnail)
|
||||
// https://unpkg.com/exiftool-vendored.pl@10.50.0/bin/html/TagNames/EXIF.html
|
||||
// Additional context: https://regex.info/blog/photo-tech/color-spaces-page7
|
||||
const (
|
||||
// InteropIndexAdobeRGB is the EXIF code for Adobe RGB (1998) ("R03").
|
||||
InteropIndexAdobeRGB = "R03"
|
||||
// InteropIndexSRGB is the EXIF code for sRGB ("R98").
|
||||
InteropIndexSRGB = "R98"
|
||||
// InteropIndexThumb marks a thumbnail image; treated as sRGB ("THM").
|
||||
InteropIndexThumb = "THM"
|
||||
)
|
||||
|
||||
// vipsSetIccProfileForInteropIndex embeds an ICC profile when a JPEG declares
|
||||
// its color space via the EXIF InteroperabilityIndex tag (e.g., "R03"/Adobe RGB)
|
||||
// but lacks an embedded profile. If an ICC profile is already present, it
|
||||
// leaves the image untouched.
|
||||
func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) (err error) {
|
||||
// Some cameras signal color space via EXIF InteroperabilityIndex instead of
|
||||
// embedding an ICC profile. Browsers and libvips ignore this tag, so we
|
||||
// inject a matching ICC profile to produce correct thumbnails.
|
||||
iiFull := img.GetString("exif-ifd4-InteroperabilityIndex")
|
||||
|
||||
if iiFull == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EXIF InteroperabilityIndex is 4 bytes including null; libvips returns
|
||||
// a string with a trailing space. Using the first three bytes covers the
|
||||
// meaningful code (e.g., "R03", "R98").
|
||||
if len(iiFull) < 3 {
|
||||
log.Debugf("interopindex: %s has unexpected interop index %q", logName, iiFull)
|
||||
return nil
|
||||
}
|
||||
|
||||
ii := iiFull[:3]
|
||||
log.Tracef("interopindex: %s read exif and got interopindex %s, %s", logName, ii, iiFull)
|
||||
|
||||
if img.HasICCProfile() {
|
||||
log.Debugf("interopindex: %s already has an embedded ICC profile; skipping fallback.", logName)
|
||||
return nil
|
||||
}
|
||||
|
||||
profilePath := ""
|
||||
|
||||
switch ii {
|
||||
case InteropIndexAdobeRGB:
|
||||
// Use Adobe RGB 1998 compatible profile.
|
||||
profilePath, err = GetIccProfile(IccAdobeRGBCompat, IccAdobeRGBCompatV2, IccAdobeRGBCompatV4)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("interopindex %s: %w", ii, err)
|
||||
}
|
||||
case InteropIndexSRGB:
|
||||
// sRGB: browsers and libvips assume sRGB by default, so no embed needed.
|
||||
case InteropIndexThumb:
|
||||
// Thumbnail file; specification unclear—treat as sRGB and do nothing.
|
||||
default:
|
||||
log.Debugf("interopindex: %s has unknown interop index %s", logName, ii)
|
||||
}
|
||||
|
||||
if profilePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Embed ICC profile. govips expects both an input (fallback) and output
|
||||
// profile; using the same path injects the chosen profile when none is
|
||||
// embedded and keeps colors consistent otherwise.
|
||||
return img.TransformICCProfileWithFallback(profilePath, profilePath)
|
||||
}
|
||||
Reference in New Issue
Block a user