Skip to content

Commit

Permalink
colour: add support for auto-selecting the rendering intent
Browse files Browse the repository at this point in the history
Introduce the `VIPS_INTENT_AUTO` enum value and use it as the default
for `vips_icc_transform()` and friends.

Resolves: libvips#3475.
  • Loading branch information
kleisauke committed Jan 24, 2025
1 parent 7949f5b commit 87a2944
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 20 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- tiffload: add support for fail_on flag [lovell]
- tiffload: add support for unlimited flag (requires libtiff 4.7.0+) [lovell]
- much more reliable operation caching
- colour: add support for auto-selecting the rendering intent [kleisauke]

8.16.1

Expand Down
35 changes: 21 additions & 14 deletions libvips/colour/icc_transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@

/**
* VipsIntent:
* @VIPS_INTENT_AUTO: the rendering intent that the profile suggests
* @VIPS_INTENT_PERCEPTUAL: perceptual rendering intent
* @VIPS_INTENT_RELATIVE: relative colorimetric rendering intent
* @VIPS_INTENT_SATURATION: saturation rendering intent
Expand Down Expand Up @@ -159,6 +160,8 @@ typedef struct _VipsIcc {
int depth;
gboolean black_point_compensation;

VipsIntent selected_intent;

VipsBlob *in_blob;
cmsHPROFILE in_profile;
VipsBlob *out_blob;
Expand Down Expand Up @@ -446,7 +449,7 @@ vips_icc_build(VipsObject *object)
if (!(icc->trans = cmsCreateTransform(
icc->in_profile, icc->in_icc_format,
icc->out_profile, icc->out_icc_format,
icc->intent, flags)))
icc->selected_intent, flags)))
return -1;

if (VIPS_OBJECT_CLASS(vips_icc_parent_class)->build(object))
Expand Down Expand Up @@ -596,8 +599,8 @@ vips_image_is_profile_compatible(VipsImage *image, int profile_bands)
* Don't set any errors since this is used to test compatibility.
*/
static cmsHPROFILE
vips_icc_load_profile_blob(VipsBlob *blob,
VipsImage *image, VipsIntent intent, int direction)
vips_icc_load_profile_blob(VipsIcc *icc, VipsBlob *blob,
VipsImage *image, int direction)
{
const void *data;
size_t size;
Expand All @@ -607,7 +610,7 @@ vips_icc_load_profile_blob(VipsBlob *blob,
#ifdef DEBUG
printf("loading %s profile, intent %s, from blob %p\n",
direction == LCMS_USED_AS_INPUT ? _("input") : _("output"),
vips_enum_nick(VIPS_TYPE_INTENT, intent),
vips_enum_nick(VIPS_TYPE_INTENT, icc->intent),
blob);
#endif /*DEBUG*/

Expand All @@ -617,6 +620,10 @@ vips_icc_load_profile_blob(VipsBlob *blob,
return NULL;
}

icc->selected_intent = icc->intent == VIPS_INTENT_AUTO
? (VipsIntent) cmsGetHeaderRenderingIntent(profile)
: icc->intent;

#ifdef DEBUG
vips_icc_print_profile("loaded from blob to make", profile);
#endif /*DEBUG*/
Expand All @@ -634,10 +641,10 @@ vips_icc_load_profile_blob(VipsBlob *blob,
return NULL;
}

if (!cmsIsIntentSupported(profile, intent, direction)) {
if (!cmsIsIntentSupported(profile, icc->selected_intent, direction)) {
VIPS_FREEF(cmsCloseProfile, profile);
g_warning(_("profile does not support %s %s intent"),
vips_enum_nick(VIPS_TYPE_INTENT, intent),
vips_enum_nick(VIPS_TYPE_INTENT, icc->selected_intent),
direction == LCMS_USED_AS_INPUT ? _("input") : _("output"));
return NULL;
}
Expand All @@ -654,8 +661,8 @@ vips_icc_verify_blob(VipsIcc *icc, VipsBlob **blob)
{
if (*blob) {
VipsColourCode *code = (VipsColourCode *) icc;
cmsHPROFILE profile = vips_icc_load_profile_blob(*blob,
code->in, icc->intent, LCMS_USED_AS_INPUT);
cmsHPROFILE profile = vips_icc_load_profile_blob(icc, *blob,
code->in, LCMS_USED_AS_INPUT);

if (!profile) {
vips_area_unref((VipsArea *) *blob);
Expand Down Expand Up @@ -763,7 +770,7 @@ vips_icc_class_init(VipsIccClass *class)
_("Rendering intent"),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET(VipsIcc, intent),
VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE);
VIPS_TYPE_INTENT, VIPS_INTENT_AUTO);

VIPS_ARG_ENUM(class, "pcs", 6,
_("PCS"),
Expand All @@ -785,7 +792,7 @@ vips_icc_class_init(VipsIccClass *class)
static void
vips_icc_init(VipsIcc *icc)
{
icc->intent = VIPS_INTENT_RELATIVE;
icc->intent = VIPS_INTENT_AUTO;
icc->pcs = VIPS_PCS_LAB;
icc->depth = 8;
}
Expand Down Expand Up @@ -1024,8 +1031,8 @@ vips_icc_export_build(VipsObject *object)
}

if (icc->out_blob &&
!(icc->out_profile = vips_icc_load_profile_blob(icc->out_blob,
NULL, icc->intent, LCMS_USED_AS_OUTPUT))) {
!(icc->out_profile = vips_icc_load_profile_blob(icc, icc->out_blob,
NULL, LCMS_USED_AS_OUTPUT))) {
vips_error(class->nickname, "%s", _("no output profile"));
return -1;
}
Expand Down Expand Up @@ -1188,8 +1195,8 @@ vips_icc_transform_build(VipsObject *object)
}

if (icc->out_blob)
icc->out_profile = vips_icc_load_profile_blob(icc->out_blob,
NULL, icc->intent, LCMS_USED_AS_OUTPUT);
icc->out_profile = vips_icc_load_profile_blob(icc, icc->out_blob,
NULL, LCMS_USED_AS_OUTPUT);

if (!icc->out_profile) {
vips_error(class->nickname, "%s", _("no output profile"));
Expand Down
6 changes: 5 additions & 1 deletion libvips/include/vips/colour.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ extern "C" {
#define VIPS_D3250_Y0 (100.0)
#define VIPS_D3250_Z0 (45.8501)

/* Note: constants align with those defined in lcms2.h, except for
* VIPS_INTENT_AUTO, which is libvips-specific.
*/
typedef enum {
VIPS_INTENT_PERCEPTUAL = 0,
VIPS_INTENT_AUTO = -1,
VIPS_INTENT_PERCEPTUAL,
VIPS_INTENT_RELATIVE,
VIPS_INTENT_SATURATION,
VIPS_INTENT_ABSOLUTE,
Expand Down
6 changes: 3 additions & 3 deletions libvips/resample/thumbnail.c
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ vips_thumbnail_class_init(VipsThumbnailClass *class)
_("Rendering intent"),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET(VipsThumbnail, intent),
VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE);
VIPS_TYPE_INTENT, VIPS_INTENT_AUTO);

VIPS_ARG_ENUM(class, "fail_on", 121,
_("Fail on"),
Expand Down Expand Up @@ -1060,7 +1060,7 @@ vips_thumbnail_init(VipsThumbnail *thumbnail)
thumbnail->width = 1;
thumbnail->height = 1;
thumbnail->auto_rotate = TRUE;
thumbnail->intent = VIPS_INTENT_RELATIVE;
thumbnail->intent = VIPS_INTENT_AUTO;
thumbnail->fail_on = VIPS_FAIL_ON_NONE;
}

Expand Down Expand Up @@ -1273,7 +1273,7 @@ vips_thumbnail_file_init(VipsThumbnailFile *file)
* input image is broken.
*
* Use @intent to set the rendering intent for any ICC transform. The default
* is #VIPS_INTENT_RELATIVE.
* is #VIPS_INTENT_AUTO, ie. use the rendering intent that the profile suggests.
*
* Use @fail_on to control the types of error that will cause loading to fail.
* The default is #VIPS_FAIL_ON_NONE, ie. thumbnail is permissive.
Expand Down
2 changes: 1 addition & 1 deletion test/test-suite/test_resample.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_thumbnail(self):
@pytest.mark.skipif(not pyvips.at_least_libvips(8, 5),
reason="requires libvips >= 8.5")
def test_thumbnail_icc(self):
im = pyvips.Image.thumbnail(JPEG_FILE_XYB, 442, export_profile="srgb", intent="perceptual")
im = pyvips.Image.thumbnail(JPEG_FILE_XYB, 442, export_profile="srgb")

assert im.width == 290
assert im.height == 442
Expand Down
2 changes: 1 addition & 1 deletion tools/vipsthumbnail.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ thumbnail_process(VipsObject *process, const char *name)
interesting = n;
}

intent = VIPS_INTENT_RELATIVE;
intent = VIPS_INTENT_AUTO;
if (thumbnail_intent) {
int n;

Expand Down

0 comments on commit 87a2944

Please sign in to comment.