mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
JPEG: Embed Adobe RGB ICC profile with an InteropIndex tag #5178
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
54
assets/profiles/icc/NOTICE
Normal file
54
assets/profiles/icc/NOTICE
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
Files: compatibleWithAdobeRGB1998.icc
|
||||
Source: Debian icc-profiles-free
|
||||
https://salsa.debian.org/debian/icc-profiles-free/-/tree/a7a3c11b8a6d3bc2937447183b87dc89de9d2388/icc-profiles-openicc/default_profiles/base
|
||||
Copyright: Kai-Uwe Behrmann <www.behrmann.name>
|
||||
Marti Maria <www.littlecms.com>
|
||||
Photogamut <www.photogamut.org>
|
||||
Graeme Gill <www.argyllcms.com>
|
||||
ColorSolutions <www.basICColor.com>
|
||||
License: Zlib
|
||||
|
||||
License: Zlib
|
||||
The zlib/libpng License
|
||||
.
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
.
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
.
|
||||
NO WARRANTY
|
||||
.
|
||||
BECAUSE THE DATA IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE DATA, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE DATA "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE DATA IS WITH YOU. SHOULD THE
|
||||
DATA PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
.
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE DATA AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE DATA (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE DATA TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
BIN
assets/profiles/icc/adobe_rgb_compat.icc
Normal file
BIN
assets/profiles/icc/adobe_rgb_compat.icc
Normal file
Binary file not shown.
21
internal/thumb/icc.go
Normal file
21
internal/thumb/icc.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package thumb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
/*
|
||||
Possible TODO: move this into a shared pkg/ so non-thumb
|
||||
consumers can also use it. However, it looks fiddly to hook that
|
||||
up to `assets`, so I'm punting on that for now.
|
||||
*/
|
||||
|
||||
func MustGetAdobeRGB1998Path() string {
|
||||
p := path.Join(IccProfilesPath, "adobe_rgb_compat.icc")
|
||||
_, err := os.Stat(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
BIN
internal/thumb/testdata/interop_index.jpg
vendored
Normal file
BIN
internal/thumb/testdata/interop_index.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 528 KiB |
@@ -77,6 +77,10 @@ func Vips(imageName string, imageBuffer []byte, hash, thumbPath string, width, h
|
||||
size = vips.SizeBoth
|
||||
}
|
||||
|
||||
if err = vipsSetIccProfileForInteropIndex(img, clean.Log(filepath.Base(imageName))); err != nil {
|
||||
log.Debugf("vips: %s in %s (set icc profile for interop index tag)", err, clean.Log(filepath.Base(imageName)))
|
||||
}
|
||||
|
||||
// Create thumbnail image.
|
||||
if err = img.ThumbnailWithSize(width, height, crop, size); err != nil {
|
||||
log.Debugf("vips: %s in %s (create thumbnail)", err, clean.Log(filepath.Base(imageName)))
|
||||
@@ -157,3 +161,47 @@ func VipsJpegExportParams(width, height int) *vips.JpegExportParams {
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) error {
|
||||
|
||||
// Many cameras will define a JPEG's colour space by setting the InteroperabilityIndex
|
||||
// tag instead of embedding an inline ICC profile.
|
||||
// We detect this and embed explicit icc profiles for thumbs of such images, for the benefit of Vips
|
||||
// and web browsers, none of which pay any attention to the InteropIndex tag.
|
||||
iiFull := img.GetString("exif-ifd4-InteroperabilityIndex")
|
||||
if iiFull == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// according to my reading, I think [:4] should be e.g. "R98\x00".
|
||||
// However, vips always returns [:4] = "R98 ", e.g. space instead of null.
|
||||
// I'm pulling [:3] instead to paper over this - the exif spec says "4 bytes
|
||||
// incl null terminator" so I think this is safe.
|
||||
ii := iiFull[:3]
|
||||
log.Tracef("interopindex: %s read exif and got interopindex %s, %s", logName, ii, iiFull)
|
||||
|
||||
if img.HasICCProfile() {
|
||||
log.Debugf("interopindex: %s has both an interop index tag and an embedded ICC profile. ignoring.", logName)
|
||||
return nil
|
||||
}
|
||||
|
||||
fallbackProfile := ""
|
||||
switch ii {
|
||||
case "R03":
|
||||
// adobe rgb
|
||||
fallbackProfile = MustGetAdobeRGB1998Path()
|
||||
case "R98":
|
||||
// srgb
|
||||
// we could logically embed an srgb profile in the image here, but
|
||||
// there's no value in doing so; everything assumes srgb anyway.
|
||||
case "THM":
|
||||
// a thumbnail file. I can't find a ref on what colour space
|
||||
// this is, so I'm assuming without evidence that they are also srgb.
|
||||
default:
|
||||
log.Debugf("interopindex: %s has unknown interop index %s", logName, ii)
|
||||
}
|
||||
if fallbackProfile == "" {
|
||||
return nil
|
||||
}
|
||||
return img.TransformICCProfileWithFallback(fallbackProfile, fallbackProfile) // icc profile gets embedded here
|
||||
}
|
||||
|
||||
@@ -25,6 +25,28 @@ func TestVips(t *testing.T) {
|
||||
assert.True(t, strings.HasSuffix(fileName, dst))
|
||||
assert.FileExists(t, dst)
|
||||
})
|
||||
t.Run("InteropIndexColors", func(t *testing.T) {
|
||||
thumb := Sizes[Tile500]
|
||||
src := "testdata/interop_index.jpg"
|
||||
dst := "testdata/vips/1/3/3/133456789098765432_500x500_center.jpg"
|
||||
|
||||
assert.FileExists(t, src)
|
||||
|
||||
fileName, _, err := Vips(src, nil, "133456789098765432", "testdata/vips", thumb.Width, thumb.Height, thumb.Options...)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.True(t, strings.HasSuffix(fileName, dst))
|
||||
assert.Equal(t, fileName, dst)
|
||||
assert.FileExists(t, dst)
|
||||
|
||||
dstimg, err := vips.LoadImageFromFile(dst, vips.NewImportParams())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, dstimg.HasICCProfile())
|
||||
assert.True(t, dstimg.IsColorSpaceSupported())
|
||||
})
|
||||
t.Run("Left224", func(t *testing.T) {
|
||||
thumb := SizeLeft224
|
||||
src := "testdata/fixed.jpg"
|
||||
|
||||
Reference in New Issue
Block a user