Load AdobeRGB Nikon JPEGs correctly
This commit is contained in:
parent
51ca3f8e2e
commit
84269b2ba2
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
|
127
fiv-io.c
127
fiv-io.c
|
@ -1,7 +1,7 @@
|
||||||
//
|
//
|
||||||
// fiv-io.c: image operations
|
// fiv-io.c: image operations
|
||||||
//
|
//
|
||||||
// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>
|
// Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
//
|
//
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
// purpose with or without fee is hereby granted.
|
// purpose with or without fee is hereby granted.
|
||||||
|
@ -316,29 +316,44 @@ fiv_io_profile_new_sRGB(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
FivIoProfile
|
FivIoProfile
|
||||||
fiv_io_profile_new_sRGB_gamma(double gamma)
|
fiv_io_profile_new_parametric(
|
||||||
|
double gamma, double whitepoint[2], double primaries[6])
|
||||||
{
|
{
|
||||||
#ifdef HAVE_LCMS2
|
#ifdef HAVE_LCMS2
|
||||||
// TODO(p): Make sure to use the library in a thread-safe manner.
|
// TODO(p): Make sure to use the library in a thread-safe manner.
|
||||||
cmsContext context = NULL;
|
cmsContext context = NULL;
|
||||||
|
|
||||||
static const cmsCIExyY D65 = {0.3127, 0.3290, 1.0};
|
const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0};
|
||||||
static const cmsCIExyYTRIPLE primaries = {
|
const cmsCIExyYTRIPLE cmsP = {
|
||||||
{0.6400, 0.3300, 1.0}, {0.3000, 0.6000, 1.0}, {0.1500, 0.0600, 1.0}};
|
{primaries[0], primaries[1], 1.0},
|
||||||
|
{primaries[2], primaries[3], 1.0},
|
||||||
|
{primaries[4], primaries[5], 1.0},
|
||||||
|
};
|
||||||
|
|
||||||
cmsToneCurve *curve = cmsBuildGamma(context, gamma);
|
cmsToneCurve *curve = cmsBuildGamma(context, gamma);
|
||||||
if (!curve)
|
if (!curve)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
cmsHPROFILE profile = cmsCreateRGBProfileTHR(
|
cmsHPROFILE profile = cmsCreateRGBProfileTHR(
|
||||||
context, &D65, &primaries, (cmsToneCurve *[3]){curve, curve, curve});
|
context, &cmsWP, &cmsP, (cmsToneCurve *[3]){curve, curve, curve});
|
||||||
cmsFreeToneCurve(curve);
|
cmsFreeToneCurve(curve);
|
||||||
return profile;
|
return profile;
|
||||||
#else
|
#else
|
||||||
(void) gamma;
|
(void) gamma;
|
||||||
|
(void) whitepoint;
|
||||||
|
(void) primaries;
|
||||||
return NULL;
|
return NULL;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FivIoProfile
|
||||||
|
fiv_io_profile_new_sRGB_gamma(double gamma)
|
||||||
|
{
|
||||||
|
return fiv_io_profile_new_parametric(gamma,
|
||||||
|
(double[2]){0.3127, 0.3290},
|
||||||
|
(double[6]){0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600});
|
||||||
|
}
|
||||||
|
|
||||||
static FivIoProfile
|
static FivIoProfile
|
||||||
fiv_io_profile_new_from_bytes(GBytes *bytes)
|
fiv_io_profile_new_from_bytes(GBytes *bytes)
|
||||||
{
|
{
|
||||||
|
@ -1329,6 +1344,100 @@ parse_mpf(
|
||||||
|
|
||||||
// --- JPEG --------------------------------------------------------------------
|
// --- JPEG --------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct exif_profile {
|
||||||
|
double whitepoint[2]; ///< TIFF_WhitePoint
|
||||||
|
double primaries[6]; ///< TIFF_PrimaryChromaticities
|
||||||
|
enum Exif_ColorSpace colorspace; ///< Exif_ColorSpace
|
||||||
|
double gamma; ///< Exif_Gamma
|
||||||
|
|
||||||
|
bool have_whitepoint;
|
||||||
|
bool have_primaries;
|
||||||
|
bool have_colorspace;
|
||||||
|
bool have_gamma;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_exif_profile_reals(
|
||||||
|
const struct tiffer *T, struct tiffer_entry *entry, double *out)
|
||||||
|
{
|
||||||
|
while (tiffer_real(T, entry, out++))
|
||||||
|
if (!tiffer_next_value(entry))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
parse_exif_profile_subifd(
|
||||||
|
struct exif_profile *params, const struct tiffer *T, uint32_t offset)
|
||||||
|
{
|
||||||
|
struct tiffer subT = {};
|
||||||
|
if (!tiffer_subifd(T, offset, &subT))
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct tiffer_entry entry = {};
|
||||||
|
while (tiffer_next_entry(&subT, &entry)) {
|
||||||
|
int64_t value = 0;
|
||||||
|
if (G_UNLIKELY(entry.tag == Exif_ColorSpace) &&
|
||||||
|
entry.type == TIFFER_SHORT && entry.remaining_count == 1 &&
|
||||||
|
tiffer_integer(&subT, &entry, &value)) {
|
||||||
|
params->have_colorspace = true;
|
||||||
|
params->colorspace = value;
|
||||||
|
} else if (G_UNLIKELY(entry.tag == Exif_Gamma) &&
|
||||||
|
entry.type == TIFFER_RATIONAL && entry.remaining_count == 1 &&
|
||||||
|
tiffer_real(&subT, &entry, ¶ms->gamma)) {
|
||||||
|
params->have_gamma = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FivIoProfile
|
||||||
|
parse_exif_profile(const void *data, size_t len)
|
||||||
|
{
|
||||||
|
struct tiffer T = {};
|
||||||
|
if (!tiffer_init(&T, (const uint8_t *) data, len) || !tiffer_next_ifd(&T))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct exif_profile params = {};
|
||||||
|
struct tiffer_entry entry = {};
|
||||||
|
while (tiffer_next_entry(&T, &entry)) {
|
||||||
|
int64_t offset = 0;
|
||||||
|
if (G_UNLIKELY(entry.tag == TIFF_ExifIFDPointer) &&
|
||||||
|
entry.type == TIFFER_LONG && entry.remaining_count == 1 &&
|
||||||
|
tiffer_integer(&T, &entry, &offset) &&
|
||||||
|
offset >= 0 && offset <= UINT32_MAX) {
|
||||||
|
parse_exif_profile_subifd(¶ms, &T, offset);
|
||||||
|
} else if (G_UNLIKELY(entry.tag == TIFF_WhitePoint) &&
|
||||||
|
entry.type == TIFFER_RATIONAL &&
|
||||||
|
entry.remaining_count == G_N_ELEMENTS(params.whitepoint)) {
|
||||||
|
params.have_whitepoint =
|
||||||
|
parse_exif_profile_reals(&T, &entry, params.whitepoint);
|
||||||
|
} else if (G_UNLIKELY(entry.tag == TIFF_PrimaryChromaticities) &&
|
||||||
|
entry.type == TIFFER_RATIONAL &&
|
||||||
|
entry.remaining_count == G_N_ELEMENTS(params.primaries)) {
|
||||||
|
params.have_primaries =
|
||||||
|
parse_exif_profile_reals(&T, &entry, params.primaries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!params.have_colorspace)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// If sRGB is claimed, assume all parameters are standard.
|
||||||
|
if (params.colorspace == Exif_ColorSpace_sRGB)
|
||||||
|
return fiv_io_profile_new_sRGB();
|
||||||
|
|
||||||
|
// AdobeRGB Nikon JPEGs provide all of these.
|
||||||
|
if (params.colorspace != Exif_ColorSpace_Uncalibrated ||
|
||||||
|
!params.have_gamma ||
|
||||||
|
!params.have_whitepoint ||
|
||||||
|
!params.have_primaries)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return fiv_io_profile_new_parametric(
|
||||||
|
params.gamma, params.whitepoint, params.primaries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
struct jpeg_metadata {
|
struct jpeg_metadata {
|
||||||
GByteArray *exif; ///< Exif buffer or NULL
|
GByteArray *exif; ///< Exif buffer or NULL
|
||||||
GByteArray *icc; ///< ICC profile buffer or NULL
|
GByteArray *icc; ///< ICC profile buffer or NULL
|
||||||
|
@ -1481,6 +1590,9 @@ load_jpeg_finalize(FivIoImage *image, bool cmyk,
|
||||||
if (icc_profile)
|
if (icc_profile)
|
||||||
source = fiv_io_profile_new(
|
source = fiv_io_profile_new(
|
||||||
g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile));
|
g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile));
|
||||||
|
else if (image->exif)
|
||||||
|
source = parse_exif_profile(
|
||||||
|
g_bytes_get_data(image->exif, NULL), g_bytes_get_size(image->exif));
|
||||||
|
|
||||||
if (cmyk)
|
if (cmyk)
|
||||||
fiv_io_profile_cmyk(image, source, ctx->screen_profile);
|
fiv_io_profile_cmyk(image, source, ctx->screen_profile);
|
||||||
|
@ -2123,6 +2235,9 @@ load_tiff_ep(
|
||||||
orientation >= 1 && orientation <= 8) {
|
orientation >= 1 && orientation <= 8) {
|
||||||
image->orientation = orientation;
|
image->orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: AdobeRGB Nikon NEFs can only be distinguished by a ColorSpace tag
|
||||||
|
// from within their MakerNote.
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue