diff --git a/.gitignore b/.gitignore index 6461c54e87..aa745ec7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ build/ debug/ release/ +.cache/ # Executables *.out @@ -43,6 +44,11 @@ tags TAGS ### End of VIM +### macOS +# Temporary files +.DS_Store +### End of macOS + ### Distribution vips-*.tar.gz ### End of Distribution diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 1aa650a513..11d3f0a0a7 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -6285,6 +6285,14 @@ class VImage : public VObject { */ VImage transpose3d(VOption *options = nullptr) const; + /** + * Convert CICP to scRGB. + * + * @param options Set of options. + * @return Output image. + */ + VImage CICP2scRGB(VOption *options = nullptr) const; + /** * Transform uhdr to scrgb. * @param options Set of options. diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp index a2cb102ee1..4b78141f7a 100644 --- a/cplusplus/vips-operators.cpp +++ b/cplusplus/vips-operators.cpp @@ -3807,6 +3807,18 @@ VImage::transpose3d(VOption *options) const return out; } +VImage +VImage::CICP2scRGB(VOption *options) const +{ + VImage out; + + call("CICP2scRGB", (options ? options : VImage::option()) + ->set("in", *this) + ->set("out", &out)); + + return out; +} + VImage VImage::uhdr2scRGB(VOption *options) const { diff --git a/doc/function-list.md b/doc/function-list.md index eb9bbc8b10..d9cb2bb62c 100644 --- a/doc/function-list.md +++ b/doc/function-list.md @@ -86,6 +86,7 @@ API docs each function links to for more details. | `canny` | Canny edge detector | [method@Image.canny] | | `case` | Use pixel values to pick cases from an array of images | [method@Image.case] | | `cast` | Cast an image | [method@Image.cast], [method@Image.cast_uchar], [method@Image.cast_char], [method@Image.cast_ushort], [method@Image.cast_short], [method@Image.cast_uint], [method@Image.cast_int], [method@Image.cast_float], [method@Image.cast_double], [method@Image.cast_complex], [method@Image.cast_dpcomplex] | +| `CICP2scRGB` | Transform image with CICP signals to scRGB | [method@Image.CICP2scRGB] | | `clamp` | Clamp values of an image | [method@Image.clamp] | | `colourspace` | Convert to a new colorspace | [method@Image.colourspace] | | `compass` | Convolve with rotating mask | [method@Image.compass] | diff --git a/doc/libvips-colour.md b/doc/libvips-colour.md index 6721947c1f..3bb9f6d880 100644 --- a/doc/libvips-colour.md +++ b/doc/libvips-colour.md @@ -34,6 +34,8 @@ slightly different format: Use [method@Image.colourspace] to move an image to a target colourspace using the best sequence of colour transform operations. +Use [method@Image.CICP2scRGB] to convert an image with CICP signals to scRGB. + Use [method@Image.uhdr2scRGB] to convert an SDR image with an attached gainmap to a full HDR scRGB image. @@ -133,6 +135,7 @@ The colour spaces supported by libvips are: * [method@Image.CMYK2XYZ] * [method@Image.XYZ2CMYK] * [method@Image.uhdr2scRGB] +* [method@Image.CICP2scRGB] * [ctor@Blob.profile_load] * [func@icc_present] * [method@Image.icc_transform] diff --git a/libvips/colour/CICP2scRGB.c b/libvips/colour/CICP2scRGB.c new file mode 100644 index 0000000000..038155b923 --- /dev/null +++ b/libvips/colour/CICP2scRGB.c @@ -0,0 +1,375 @@ +/* Turn image with CICP metadata to scRGB colourspace. + * + * 26/11/25 [Starbix] + * - from uhdr2scRGB.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - https://github.com/libvips/libvips + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include + +#include "pcolour.h" + +typedef struct _VipsCICP2scRGB { + VipsColour parent; + + VipsImage *in; + + VipsCICPColourPrimaries colour_primaries; + VipsCICPTransferCharacteristics transfer_characteristics; + VipsCICPMatrixCoefficients matrix_coefficients; + + int full_range_flag; // unused + + /* Conversion matrix from source primaries to BT.709 (scRGB) */ + float conversion_matrix[9]; + +} VipsCICP2scRGB; + +#define SDR_WHITE 80.0f + +typedef VipsColourClass VipsCICP2scRGBClass; + +G_DEFINE_TYPE(VipsCICP2scRGB, vips_CICP2scRGB, VIPS_TYPE_COLOUR); + +static const float BT709_to_BT709[9] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f +}; + +static const float BT2020_to_BT709[9] = { + 1.660491f, -0.58764114f, -0.07284986f, + -0.12455047f, 1.1328999f, -0.00834942f, + -0.01815076f, -0.1005789f, 1.11872966f +}; + +// Bradford chromatic adaptation +static const float DCI_P3_to_BT709[9] = { + 1.15751641f, -0.15496238f, -0.00255403f, + -0.04150007f, 1.04556792f, -0.00406785f, + -0.01805004f, -0.07857827f, 1.09662831f +}; + +static const float Display_P3_to_BT709[9] = { + 1.22494018f, -0.224940176f, -6.95071840e-17f, + -0.0420569547f, 1.04205695f, 3.05868274e-17f, + -0.0196375546f, -0.0786360456f, 1.09827360f +}; + +static void +vips_CICP2scRGB_init_matrix(VipsCICP2scRGB *cicp) +{ + const float *matrix; + + switch (cicp->colour_primaries) { + case VIPS_CICP_COLOUR_PRIMARIES_BT709: + matrix = BT709_to_BT709; + break; + + case VIPS_CICP_COLOUR_PRIMARIES_BT2020: + matrix = BT2020_to_BT709; + break; + + case VIPS_CICP_COLOUR_PRIMARIES_SMPTE431: + matrix = DCI_P3_to_BT709; + break; + + case VIPS_CICP_COLOUR_PRIMARIES_SMPTE432: + matrix = Display_P3_to_BT709; + break; + + default: + /* For unspecified or unimplemented primaries, use identity */ + matrix = BT709_to_BT709; + break; + } + + memcpy(cicp->conversion_matrix, matrix, 9 * sizeof(float)); +} + +static inline void +vips_apply_matrix(const float *matrix, float r, float g, float b, + float *out_r, float *out_g, float *out_b) +{ + *out_r = matrix[0] * r + matrix[1] * g + matrix[2] * b; + *out_g = matrix[3] * r + matrix[4] * g + matrix[5] * b; + *out_b = matrix[6] * r + matrix[7] * g + matrix[8] * b; +} + +static inline float +vips_pq_eotf(float E) +{ + const float m1 = 2610.0f / 16384.0f; + const float m2 = 2523.0f / 4096.0f * 128.0f; + const float c1 = 3424.0f / 4096.0f; + const float c2 = 2413.0f / 4096.0f * 32.0f; + const float c3 = 2392.0f / 4096.0f * 32.0f; + + if (E <= 0.0f) + return 0.0f; + + float Em2 = powf(E, 1.0f / m2); + float numerator = fmaxf(Em2 - c1, 0.0f); + float denominator = c2 - c3 * Em2; + + if (denominator <= 0.0f) + return 0.0f; + + float linear = powf(numerator / denominator, 1.0f / m1); + return linear * SDR_WHITE; +} + +static inline float +vips_hlg_eotf(float E) +{ + + if (E < 0.0f) + return 0.0f; + + if (E <= 1.0f / 12) { + return sqrtf(3 * E); + } + else { // assuming E <=1 + const float a = 0.17883277f; + const float b = 1 - 4 * a; + const float c = 0.5f - a * logf(4 * a); + + return a * logf(12 * E - b) + c; + } +} + +static inline float +vips_bt2020_eotf(float E) +{ + const float alpha = 1.0993f; + const float beta = 0.0181f; + + if (E < 0.0f) + return 0.0f; + + if (E < beta) + return E / 4.5f; + else + return powf((E + (alpha - 1.0f)) / alpha, 1.0f / 0.45f); +} + +static inline float +vips_CICP2scRGB_transfer(VipsCICPTransferCharacteristics transfer, float in) +{ + switch (transfer) { + case VIPS_CICP_TRANSFER_PQ: + return vips_pq_eotf(in); + case VIPS_CICP_TRANSFER_HLG: + return vips_hlg_eotf(in); + case VIPS_CICP_TRANSFER_BT709: + case VIPS_CICP_TRANSFER_BT2020_10BIT: + case VIPS_CICP_TRANSFER_BT2020_12BIT: + return vips_bt2020_eotf(in); + + default: + // identity + return in; + } +} + +/* Process 8-bit RGB image with CICP transfer function. + */ +static void +vips_CICP2scRGB_uchar(VipsCICP2scRGB *cicp, + VipsPel *out, VipsPel **in, int width) +{ + VipsPel *restrict p = in[0]; + float *restrict q = (float *) out; + const VipsCICPTransferCharacteristics transfer = cicp->transfer_characteristics; + const float *matrix = cicp->conversion_matrix; + + for (int i = 0; i < width; i++) { + float r = p[0] / 255.0f; + float g = p[1] / 255.0f; + float b = p[2] / 255.0f; + + p += 3; + + r = vips_CICP2scRGB_transfer(transfer, r); + g = vips_CICP2scRGB_transfer(transfer, g); + b = vips_CICP2scRGB_transfer(transfer, b); + + vips_apply_matrix(matrix, r, g, b, &q[0], &q[1], &q[2]); + + q += 3; + } +} + +static void +vips_CICP2scRGB_ushort(VipsCICP2scRGB *cicp, + VipsPel *out, VipsPel **in, int width) +{ + unsigned short *restrict p = (unsigned short *) in[0]; + float *restrict q = (float *) out; + const VipsCICPTransferCharacteristics transfer = cicp->transfer_characteristics; + const float *matrix = cicp->conversion_matrix; + + for (int i = 0; i < width; i++) { + float r = p[0] / 65535.0f; + float g = p[1] / 65535.0f; + float b = p[2] / 65535.0f; + p += 3; + + r = vips_CICP2scRGB_transfer(transfer, r); + g = vips_CICP2scRGB_transfer(transfer, g); + b = vips_CICP2scRGB_transfer(transfer, b); + + vips_apply_matrix(matrix, r, g, b, &q[0], &q[1], &q[2]); + + q += 3; + } +} + +static void +vips_CICP2scRGB_line(VipsColour *colour, VipsPel *out, VipsPel **in, int width) +{ + VipsCICP2scRGB *cicp = (VipsCICP2scRGB *) colour; + + if (cicp->in->BandFmt == VIPS_FORMAT_UCHAR) { + vips_CICP2scRGB_uchar(cicp, out, in, width); + } + else if (cicp->in->BandFmt == VIPS_FORMAT_USHORT) { + vips_CICP2scRGB_ushort(cicp, out, in, width); + } +} + +static int +vips_CICP2scRGB_build(VipsObject *object) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object); + VipsColour *colour = VIPS_COLOUR(object); + VipsCICP2scRGB *cicp = (VipsCICP2scRGB *) object; + int colour_primaries; + int transfer_characteristics; + int matrix_coefficients; + + /* Don't process alpha + */ + colour->input_bands = 3; + colour->n = 1; + colour->in = (VipsImage **) vips_object_local_array(object, 1); + + if (cicp->in) { + + if (cicp->in->BandFmt != VIPS_FORMAT_UCHAR && + cicp->in->BandFmt != VIPS_FORMAT_USHORT) { + vips_error(class->nickname, "%s", _("image must be uchar or ushort")); + return -1; + } + + if (vips_image_get_int(cicp->in, "cicp-colour-primaries", &colour_primaries) || + vips_image_get_int(cicp->in, "cicp-transfer-characteristics", &transfer_characteristics) || + vips_image_get_int(cicp->in, "cicp-matrix-coefficients", &matrix_coefficients) || + vips_image_get_int(cicp->in, "cicp-full-range-flag", &cicp->full_range_flag)) + return -1; + + cicp->colour_primaries = colour_primaries; + cicp->transfer_characteristics = transfer_characteristics; + cicp->matrix_coefficients = matrix_coefficients; + + vips_CICP2scRGB_init_matrix(cicp); + + colour->in[0] = cicp->in; + g_object_ref(cicp->in); + } + + if (VIPS_OBJECT_CLASS(vips_CICP2scRGB_parent_class)->build(object)) + return -1; + + return 0; +} + +static void +vips_CICP2scRGB_class_init(VipsCICP2scRGBClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsColourClass *colour_class = VIPS_COLOUR_CLASS(class); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "CICP2scRGB"; + object_class->description = _("transform CICP to scRGB"); + object_class->build = vips_CICP2scRGB_build; + + colour_class->process_line = vips_CICP2scRGB_line; + + VIPS_ARG_IMAGE(class, "in", 1, + _("Input"), + _("Input image"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsColourTransform, in)); +} + +static void +vips_CICP2scRGB_init(VipsCICP2scRGB *cicp) +{ + VipsColour *colour = VIPS_COLOUR(cicp); + + colour->interpretation = VIPS_INTERPRETATION_scRGB; + colour->format = VIPS_FORMAT_FLOAT; + colour->bands = 3; +} + +/** + * vips_CICP2scRGB: (method) + * @in: input image + * @out: (out): output image + * @...: `NULL`-terminated list of optional named arguments + * + * Transform an image with CICP signal to scRGB. + * + * Returns: 0 on success, -1 on error + */ +int +vips_CICP2scRGB(VipsImage *in, VipsImage **out, ...) +{ + va_list ap; + int result; + + va_start(ap, out); + result = vips_call_split("CICP2scRGB", ap, in, out); + va_end(ap); + + return result; +} diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index 18d0699309..77bc0f651b 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -643,6 +643,7 @@ vips_colour_operation_init(void) extern GType vips_Oklab2Oklch_get_type(void); extern GType vips_Oklch2Oklab_get_type(void); extern GType vips_uhdr2scRGB_get_type(void); + extern GType vips_CICP2scRGB_get_type(void); extern GType vips_profile_load_get_type(void); #ifdef HAVE_LCMS2 extern GType vips_icc_import_get_type(void); @@ -685,6 +686,7 @@ vips_colour_operation_init(void) vips_CMYK2XYZ_get_type(); vips_XYZ2CMYK_get_type(); vips_uhdr2scRGB_get_type(); + vips_CICP2scRGB_get_type(); vips_profile_load_get_type(); #ifdef HAVE_LCMS2 vips_icc_import_get_type(); diff --git a/libvips/colour/colourspace.c b/libvips/colour/colourspace.c index 9d6dd9ccae..11d4a0c59b 100644 --- a/libvips/colour/colourspace.c +++ b/libvips/colour/colourspace.c @@ -217,6 +217,7 @@ typedef struct _VipsColourRoute { #define YXY VIPS_INTERPRETATION_YXY #define OKLAB VIPS_INTERPRETATION_OKLAB #define OKLCH VIPS_INTERPRETATION_OKLCH +#define CICP VIPS_INTERPRETATION_CICP /* All the routes we know about. */ @@ -494,6 +495,24 @@ static VipsColourRoute vips_colour_routes[] = { { OKLCH, OKLAB, { vips_Oklch2Oklab, NULL } }, { OKLCH, OKLCH, { vips_cast_float, NULL } }, + { CICP, XYZ, {vips_CICP2scRGB, vips_scRGB2XYZ, NULL}, }, + { CICP, LAB, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Lab, NULL} }, + { CICP, LABQ, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL} }, + { CICP, LCH, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL} }, + { CICP, CMC, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL} }, + { CICP, LABS, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL} }, + { CICP, CMYK, {vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2CMYK, NULL} }, + { CICP, scRGB, { vips_CICP2scRGB, NULL } }, + { CICP, sRGB, { vips_CICP2scRGB, vips_scRGB2sRGB, NULL } }, + { CICP, HSV, { vips_CICP2scRGB, vips_scRGB2sRGB, vips_sRGB2HSV, NULL } }, + { CICP, BW, { vips_CICP2scRGB, vips_scRGB2BW, NULL } }, + { CICP, RGB16, { vips_CICP2scRGB, vips_scRGB2RGB16, NULL } }, + { CICP, GREY16, { vips_CICP2scRGB, vips_scRGB2BW16, NULL } }, + { CICP, YXY, { vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Yxy, NULL } }, + { CICP, OKLAB, { vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Oklab, NULL } }, + { CICP, OKLCH, { vips_CICP2scRGB, vips_scRGB2XYZ, vips_XYZ2Oklab, vips_Oklab2Oklch, NULL } }, + { CICP, CICP, { NULL } }, + }; /* Is an image in a supported colourspace. diff --git a/libvips/colour/meson.build b/libvips/colour/meson.build index 13f8ff8c32..b9897584af 100644 --- a/libvips/colour/meson.build +++ b/libvips/colour/meson.build @@ -1,4 +1,5 @@ colour_sources = files( + 'CICP2scRGB.c', 'CMYK2XYZ.c', 'colour.c', 'colourspace.c', diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index fa3fdcb05c..dc8bda380d 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1473,6 +1473,16 @@ vips_foreign_apply_saveable(VipsImage *in, VipsImage **ready, in = out; } + /*If the saver supports CICP, */ + if (saveable & VIPS_FOREIGN_SAVEABLE_CICP) { + // only if input is in CICP + if (vips_image_guess_interpretation(in) == VIPS_INTERPRETATION_CICP){ + interpretation = VIPS_INTERPRETATION_CICP; + *ready = in; + return 0; + } + } + /* If the saver supports RGB, go to RGB, or RGB16 if this is a ushort * source. */ diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index 4d94888027..a878a6c18e 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -660,6 +660,7 @@ vips_foreign_load_heif_set_header(VipsForeignLoadHeif *heif, VipsImage *out) */ vips_autorot_remove_angle(out); + // if both nclx and ICC are present, ICC is returned enum heif_color_profile_type profile_type = heif_image_handle_get_color_profile_type(heif->handle); @@ -716,8 +717,41 @@ vips_foreign_load_heif_set_header(VipsForeignLoadHeif *heif, VipsImage *out) vips_image_set_blob(out, VIPS_META_ICC_NAME, (VipsCallbackFn) vips_area_free_cb, data, length); } - else if (profile_type == heif_color_profile_type_nclx) { - g_info("heifload: ignoring nclx profile"); + /* is only set if no ICC profile is present + * TODO: also set if ICC is present, but describes HDR transfer characteristics + * as ICC profiles cannot describe PQ or HLG apart from "cicp" tag as of 2025 + * and the "cicp" tag in ICC is ignored by lcms2 + */ + if (profile_type == heif_color_profile_type_nclx) { + g_info("heifload: setting CICP from nclx"); + + struct heif_color_profile_nclx *nclx = heif_nclx_color_profile_alloc(); + if (!nclx) { + vips_error("heifload", "%s", _("unable to allocate nclx")); + return -1; + } + + error = heif_image_handle_get_nclx_color_profile(heif->handle, &nclx); + if (error.code) { + heif_nclx_color_profile_free(nclx); + vips__heif_error(&error); + return -1; + } + +#ifdef DEBUG + printf("\tnclx: %p\n", nclx); + printf("\tnclx->color_primaries: %d\n", nclx->color_primaries); + printf("\tnclx->transfer_characteristics: %d\n", nclx->transfer_characteristics); + printf("\tnclx->matrix_coefficients: %d\n", nclx->matrix_coefficients); + printf("\tnclx->full_range_flag: %d\n", nclx->full_range_flag); +#endif + + vips_image_set_int(out, "cicp-colour-primaries", nclx->color_primaries); + vips_image_set_int(out, "cicp-transfer-characteristics", nclx->transfer_characteristics); + vips_image_set_int(out, "cicp-matrix-coefficients", 0); // converted to RGB by libheif already + vips_image_set_int(out, "cicp-full-range-flag", nclx->full_range_flag); + + heif_nclx_color_profile_free(nclx); } vips_image_set_int(out, "heif-primary", heif->primary_page); @@ -760,6 +794,12 @@ vips_foreign_load_heif_set_header(VipsForeignLoadHeif *heif, VipsImage *out) format = VIPS_FORMAT_UCHAR; } + /* If we have no ICC profile but CICP, set interpretation to CICP + */ + if (profile_type == heif_color_profile_type_nclx) { + interpretation = VIPS_INTERPRETATION_CICP; + } + /* FIXME .. we always decode to RGB in generate. We should check for * all grey images, perhaps. */ diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 49f9cae0b0..808b17585c 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -14,8 +14,10 @@ * - rename "speed" as "effort" for consistency with other savers * 22/12/21 * - add >8 bit support - * 22/10/11 - * - improve rules for 16-bit write [johntrunc] + * 10/11/22 + * - improve rules for 16-bit write [johntrunc] + * xx/01/26 [Starbix] + * - write nclx tag if in CICP colour space */ /* @@ -292,10 +294,33 @@ vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page) options->save_alpha_channel = save->ready->Bands > 3; #ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE + + int colour_primaries; + int transfer_characteristics; + int matrix_coefficients; + int full_range_flag; + + if (vips_image_get_int(save->ready, "cicp-colour-primaries", &colour_primaries) == 0 && + vips_image_get_int(save->ready, "cicp-transfer-characteristics", &transfer_characteristics) == 0 && + vips_image_get_int(save->ready, "cicp-matrix-coefficients", &matrix_coefficients) == 0 && + vips_image_get_int(save->ready, "cicp-full-range-flag", &full_range_flag) == 0) { + + if (!(nclx = heif_nclx_color_profile_alloc())) { + heif_encoding_options_free(options); + return -1; + } + + nclx->color_primaries = colour_primaries; + nclx->transfer_characteristics = transfer_characteristics; + nclx->matrix_coefficients = matrix_coefficients; + nclx->full_range_flag = full_range_flag; + + options->output_nclx_profile = nclx; + } /* Matrix coefficients have to be identity (CICP x/y/0) in lossless * mode. */ - if (heif->lossless) { + else if (heif->lossless) { if (!(nclx = heif_nclx_color_profile_alloc())) { heif_encoding_options_free(options); return -1; @@ -400,6 +425,10 @@ vips_foreign_save_heif_pack(VipsForeignSaveHeif *heif, int vips_bitdepth = save->ready->Type == VIPS_INTERPRETATION_RGB16 || save->ready->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8; + if (save->ready->Type == VIPS_INTERPRETATION_CICP) + // TODO: check type + vips_bitdepth = 16; + int shift = vips_bitdepth - heif->bitdepth; for (i = 0; i < ne; i++) { @@ -417,6 +446,9 @@ vips_foreign_save_heif_pack(VipsForeignSaveHeif *heif, int vips_bitdepth = save->ready->Type == VIPS_INTERPRETATION_RGB16 || save->ready->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8; + if (save->ready->Type == VIPS_INTERPRETATION_CICP) + // TODO: check type + vips_bitdepth = 16; int shift = vips_bitdepth - heif->bitdepth; for (i = 0; i < ne; i++) { @@ -776,7 +808,7 @@ vips_foreign_save_heif_class_init(VipsForeignSaveHeifClass *class) object_class->build = vips_foreign_save_heif_build; save_class->saveable = - VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA; + VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_ALPHA | VIPS_FOREIGN_SAVEABLE_CICP; save_class->format_table = vips_heif_bandfmt; VIPS_ARG_INT(class, "Q", 10, diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index e1fa67c80f..588134b2a9 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -103,6 +103,8 @@ typedef struct _VipsForeignLoadJxl { size_t xmp_size; uint8_t *xmp_data; + JxlColorEncoding color_encoding; + int frame_count; GArray *delay; @@ -845,6 +847,73 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) jxl->icc_size = 0; } + // CICP only covers RGB + if (jxl->color_encoding.color_space == JXL_COLOR_SPACE_RGB) { + + double gamma = jxl->color_encoding.gamma; + + switch (jxl->color_encoding.primaries) { + case JXL_PRIMARIES_SRGB: + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_BT709); + break; + case JXL_PRIMARIES_2100: + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_BT2020); + break; + case JXL_PRIMARIES_P3: + if (jxl->color_encoding.white_point == JXL_WHITE_POINT_D65) { + // Display P3 + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_SMPTE432); + } else if (jxl->color_encoding.white_point == JXL_WHITE_POINT_DCI) { + // DCI-P3 + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_SMPTE431); + } else { + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_UNSPECIFIED); + } + break; + default: + vips_image_set_int(out, "cicp-colour-primaires", VIPS_CICP_COLOUR_PRIMARIES_UNSPECIFIED); + break; + } + switch (jxl->color_encoding.transfer_function) { + case JXL_TRANSFER_FUNCTION_709: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_BT709); + break; + case JXL_TRANSFER_FUNCTION_UNKNOWN: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_UNSPECIFIED); + break; + case JXL_TRANSFER_FUNCTION_LINEAR: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_LINEAR); + break; + case JXL_TRANSFER_FUNCTION_SRGB: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_SRGB); + break; + case JXL_TRANSFER_FUNCTION_PQ: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_PQ); + break; + case JXL_TRANSFER_FUNCTION_DCI: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_SMPTE428); + break; + case JXL_TRANSFER_FUNCTION_HLG: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_HLG); + break; + case JXL_TRANSFER_FUNCTION_GAMMA: + if (gamma == 2.2) + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_BT470M); + else if (gamma == 2.8) + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_BT470BG); + else + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_UNSPECIFIED); + break; + default: + vips_image_set_int(out, "cicp-transfer-characteristics", VIPS_CICP_TRANSFER_UNSPECIFIED); + break; + + } + + vips_image_set_int(out, "cicp-matrix-coefficients", 0); // RGB, identity + vips_image_set_int(out, "cicp-full-range-flag", 1); + } + if (jxl->exif_data && jxl->exif_size > 0) { vips_image_set_blob(out, VIPS_META_EXIF_NAME, diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index ecf675d0c2..7167d99902 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -112,12 +112,16 @@ vips_foreign_save_png_build(VipsObject *object) /* If no output bitdepth has been specified, use input Type to pick. */ - if (!vips_object_argument_isset(object, "bitdepth")) + if (!vips_object_argument_isset(object, "bitdepth")) { png->bitdepth = in->Type == VIPS_INTERPRETATION_RGB16 || in->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8; + if (in->Type == VIPS_INTERPRETATION_CICP) + //TODO: look at format + png->bitdepth = 16; + } /* Deprecated "colours" arg just sets bitdepth large enough to hold * that many colours. @@ -143,6 +147,18 @@ vips_foreign_save_png_build(VipsObject *object) interpretation = VIPS_INTERPRETATION_B_W; } + if (in->Type == VIPS_INTERPRETATION_CICP) { + interpretation = VIPS_INTERPRETATION_CICP; + + VipsBandFormat target_format = + png->bitdepth > 8 ? VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR; + + if (vips_cast(in, &x, target_format, NULL)) { + g_object_unref(in); + return -1; + } + } + if (vips_colourspace(in, &x, interpretation, NULL)) { g_object_unref(in); return -1; @@ -232,7 +248,8 @@ vips_foreign_save_png_class_init(VipsForeignSavePngClass *class) save_class->saveable = VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB | - VIPS_FOREIGN_SAVEABLE_ALPHA; + VIPS_FOREIGN_SAVEABLE_ALPHA | + VIPS_FOREIGN_SAVEABLE_CICP; save_class->format_table = bandfmt_png; VIPS_ARG_INT(class, "compression", 6, diff --git a/libvips/foreign/spngsave.c b/libvips/foreign/spngsave.c index 73e95844ea..0df039ff0d 100644 --- a/libvips/foreign/spngsave.c +++ b/libvips/foreign/spngsave.c @@ -708,7 +708,8 @@ vips_foreign_save_spng_class_init(VipsForeignSaveSpngClass *class) save_class->saveable = VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB | - VIPS_FOREIGN_SAVEABLE_ALPHA; + VIPS_FOREIGN_SAVEABLE_ALPHA | + VIPS_FOREIGN_SAVEABLE_CICP; save_class->format_table = bandfmt_spng; VIPS_ARG_INT(class, "compression", 6, diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index f5b6c93268..e47d2a5579 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -89,6 +89,8 @@ * - add exif read/write * 3/2/23 MathemanFlo * - add bits per sample metadata + * 23/12/25 Starbix + * - add support for reading cICP chunk */ /* @@ -546,6 +548,22 @@ png2vips_header(Read *read, VipsImage *out) VIPS_META_ICC_NAME, profile, proflen); } + /* Read cICP chunk and set if present */ +#if PNG_LIBPNG_VER >= 10645 + png_byte colour_primaries; + png_byte transfer_characteristics; + png_byte matrix_coefficients; + png_byte full_range_flag; + + if (png_get_cICP(read->pPng, read->pInfo, + &colour_primaries, &transfer_characteristics, &matrix_coefficients, &full_range_flag)) { + vips_image_set_int(out, "cicp-colour-primaries", colour_primaries); + vips_image_set_int(out, "cicp-transfer-characteristics", transfer_characteristics); + vips_image_set_int(out, "cicp-matrix-coefficients", matrix_coefficients); + vips_image_set_int(out, "cicp-full-range-flag", full_range_flag); + } +#endif + /* Some libpng warn you to call png_set_interlace_handling(); here, but * that can actually break interlace on older libpngs. * @@ -1220,6 +1238,27 @@ write_vips(Write *write, return -1; } + #if PNG_LIBPNG_VER >= 10645 + if (vips_image_get_typeof(in, "cicp-colour-primaries")) { + int colour_primaries; + int transfer_characteristics; + int matrix_coefficients; + int full_range_flag; + + if (vips_image_get_int(in, "cicp-colour-primaries", &colour_primaries) == 0 && + vips_image_get_int(in, "cicp-transfer-characteristics", &transfer_characteristics) == 0 && + vips_image_get_int(in, "cicp-matrix-coefficients", &matrix_coefficients) == 0 && + vips_image_get_int(in, "cicp-full-range-flag", &full_range_flag) == 0) { + + png_set_cICP(write->pPng, write->pInfo, + (png_byte) colour_primaries, + (png_byte) transfer_characteristics, + (png_byte) matrix_coefficients, + (png_byte) full_range_flag); + } + } +#endif + // the profile writers grab the setjmp, restore it if (setjmp(png_jmpbuf(write->pPng))) return -1; diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index a9e3d7f513..07fca3af3a 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -114,6 +114,63 @@ typedef enum { VIPS_PCS_LAST /*< skip >*/ } VipsPCS; +/* ITU-T H.273 + */ +typedef enum { + VIPS_CICP_COLOUR_PRIMARIES_BT709 = 1, // sRGB + VIPS_CICP_COLOUR_PRIMARIES_UNSPECIFIED = 2, + VIPS_CICP_COLOUR_PRIMARIES_BT470M = 4, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_BT470BG = 5, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_BT601 = 6, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_SMPTE240 = 7, // same as 6 + VIPS_CICP_COLOUR_PRIMARIES_GENERIC_FILM = 8, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_BT2020 = 9, + VIPS_CICP_COLOUR_PRIMARIES_SMPTE428 = 10, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_SMPTE431 = 11, //< nick=DCI-P3 > + VIPS_CICP_COLOUR_PRIMARIES_SMPTE432 = 12, //< nick=Display-P3 > + VIPS_CICP_COLOUR_PRIMARIES_EBU3213 = 22, // unsupported + VIPS_CICP_COLOUR_PRIMARIES_LAST /*< skip >*/ +} VipsCICPColourPrimaries; + +typedef enum { + VIPS_CICP_TRANSFER_BT709 = 1, + VIPS_CICP_TRANSFER_UNSPECIFIED = 2, + VIPS_CICP_TRANSFER_BT470M = 4, /* gamma 2.2 */ + VIPS_CICP_TRANSFER_BT470BG = 5, /* gamma 2.8 */ + VIPS_CICP_TRANSFER_BT601 = 6, + VIPS_CICP_TRANSFER_SMPTE240 = 7, + VIPS_CICP_TRANSFER_LINEAR = 8, + VIPS_CICP_TRANSFER_LOG_100 = 9, + VIPS_CICP_TRANSFER_LOG_100_SQRT10 = 10, + VIPS_CICP_TRANSFER_IEC61966 = 11, + VIPS_CICP_TRANSFER_BT1361 = 12, + VIPS_CICP_TRANSFER_SRGB = 13, + VIPS_CICP_TRANSFER_BT2020_10BIT = 14, + VIPS_CICP_TRANSFER_BT2020_12BIT = 15, + VIPS_CICP_TRANSFER_PQ = 16, /*< nick=PQ >*/ /* ITU-R BT.2100 PQ */ + VIPS_CICP_TRANSFER_SMPTE428 = 17, + VIPS_CICP_TRANSFER_HLG = 18, /*< nick=HLG >*/ /* ITU-R BT.2100 HLG */ + VIPS_CICP_TRANSFER_LAST /*< skip >*/ +} VipsCICPTransferCharacteristics; + +typedef enum { + VIPS_CICP_MATRIX_RGB = 0, /* Identity */ + VIPS_CICP_MATRIX_BT709 = 1, + VIPS_CICP_MATRIX_UNSPECIFIED = 2, + VIPS_CICP_MATRIX_FCC = 4, + VIPS_CICP_MATRIX_BT470BG = 5, + VIPS_CICP_MATRIX_BT601 = 6, + VIPS_CICP_MATRIX_SMPTE240 = 7, + VIPS_CICP_MATRIX_YCGCO = 8, + VIPS_CICP_MATRIX_BT2020_NCL = 9, + VIPS_CICP_MATRIX_BT2020_CL = 10, + VIPS_CICP_MATRIX_SMPTE2085 = 11, + VIPS_CICP_MATRIX_CHROMA_NCL = 12, + VIPS_CICP_MATRIX_CHROMA_CL = 13, + VIPS_CICP_MATRIX_ICTCP = 14, + VIPS_CICP_MATRIX_LAST /*< skip >*/ +} VipsCICPMatrixCoefficients; + VIPS_API gboolean vips_colourspace_issupported(const VipsImage *image); VIPS_API @@ -220,105 +277,109 @@ VIPS_API int vips_uhdr2scRGB(VipsImage *in, VipsImage **out, ...); G_GNUC_NULL_TERMINATED -VIPS_API -int vips_profile_load(const char *name, VipsBlob **profile, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_icc_present(void); -VIPS_API -int vips_icc_transform(VipsImage *in, VipsImage **out, - const char *output_profile, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_icc_import(VipsImage *in, VipsImage **out, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_icc_export(VipsImage *in, VipsImage **out, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_icc_ac2rc(VipsImage *in, VipsImage **out, - const char *profile_filename); -VIPS_API -gboolean vips_icc_is_compatible_profile(VipsImage *image, - const void *data, size_t data_length); - -VIPS_API -int vips_dE76(VipsImage *left, VipsImage *right, VipsImage **out, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_dE00(VipsImage *left, VipsImage *right, VipsImage **out, ...) - G_GNUC_NULL_TERMINATED; -VIPS_API -int vips_dECMC(VipsImage *left, VipsImage *right, VipsImage **out, ...) - G_GNUC_NULL_TERMINATED; - -VIPS_API -void vips_col_Lab2XYZ(float L, float a, float b, - float *X, float *Y, float *Z); -VIPS_API -void vips_col_XYZ2Lab(float X, float Y, float Z, - float *L, float *a, float *b); -VIPS_API -double vips_col_ab2h(double a, double b); -VIPS_API -void vips_col_ab2Ch(float a, float b, float *C, float *h); -VIPS_API -void vips_col_Ch2ab(float C, float h, float *a, float *b); - -VIPS_API -float vips_col_L2Lcmc(float L); -VIPS_API -float vips_col_C2Ccmc(float C); -VIPS_API -float vips_col_Ch2hcmc(float C, float h); - -VIPS_API -void vips_col_make_tables_CMC(void); -VIPS_API -float vips_col_Lcmc2L(float Lcmc); -VIPS_API -float vips_col_Ccmc2C(float Ccmc); -VIPS_API -float vips_col_Chcmc2h(float C, float hcmc); - -VIPS_API -int vips_col_sRGB2scRGB_8(int r, int g, int b, float *R, float *G, float *B); -VIPS_API -int vips_col_sRGB2scRGB_16(int r, int g, int b, float *R, float *G, float *B); -VIPS_API -int vips_col_sRGB2scRGB_8_noclip(int r, int g, int b, - float *R, float *G, float *B); -VIPS_API -int vips_col_sRGB2scRGB_16_noclip(int r, int g, int b, - float *R, float *G, float *B); - -VIPS_API -int vips_col_scRGB2XYZ(float R, float G, float B, - float *X, float *Y, float *Z); -VIPS_API -int vips_col_XYZ2scRGB(float X, float Y, float Z, - float *R, float *G, float *B); - -VIPS_API -int vips_col_scRGB2sRGB_8(float R, float G, float B, - int *r, int *g, int *b, int *og); -VIPS_API -int vips_col_scRGB2sRGB_16(float R, float G, float B, - int *r, int *g, int *b, int *og); -VIPS_API -int vips_col_scRGB2BW_16(float R, float G, float B, int *g, int *og); -VIPS_API -int vips_col_scRGB2BW_8(float R, float G, float B, int *g, int *og); + VIPS_API + int vips_CICP2scRGB(VipsImage *in, VipsImage **out, ...); + G_GNUC_NULL_TERMINATED -VIPS_API -float vips_pythagoras(float L1, float a1, float b1, - float L2, float a2, float b2); -VIPS_API -float vips_col_dE00( - float L1, float a1, float b1, float L2, float a2, float b2); + VIPS_API + int vips_profile_load(const char *name, VipsBlob **profile, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_icc_present(void); + VIPS_API + int vips_icc_transform(VipsImage *in, VipsImage **out, + const char *output_profile, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_icc_import(VipsImage *in, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_icc_export(VipsImage *in, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_icc_ac2rc(VipsImage *in, VipsImage **out, + const char *profile_filename); + VIPS_API + gboolean vips_icc_is_compatible_profile(VipsImage *image, + const void *data, size_t data_length); + + VIPS_API + int vips_dE76(VipsImage *left, VipsImage *right, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_dE00(VipsImage *left, VipsImage *right, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API + int vips_dECMC(VipsImage *left, VipsImage *right, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + + VIPS_API + void vips_col_Lab2XYZ(float L, float a, float b, + float *X, float *Y, float *Z); + VIPS_API + void vips_col_XYZ2Lab(float X, float Y, float Z, + float *L, float *a, float *b); + VIPS_API + double vips_col_ab2h(double a, double b); + VIPS_API + void vips_col_ab2Ch(float a, float b, float *C, float *h); + VIPS_API + void vips_col_Ch2ab(float C, float h, float *a, float *b); + + VIPS_API + float vips_col_L2Lcmc(float L); + VIPS_API + float vips_col_C2Ccmc(float C); + VIPS_API + float vips_col_Ch2hcmc(float C, float h); + + VIPS_API + void vips_col_make_tables_CMC(void); + VIPS_API + float vips_col_Lcmc2L(float Lcmc); + VIPS_API + float vips_col_Ccmc2C(float Ccmc); + VIPS_API + float vips_col_Chcmc2h(float C, float hcmc); + + VIPS_API + int vips_col_sRGB2scRGB_8(int r, int g, int b, float *R, float *G, float *B); + VIPS_API + int vips_col_sRGB2scRGB_16(int r, int g, int b, float *R, float *G, float *B); + VIPS_API + int vips_col_sRGB2scRGB_8_noclip(int r, int g, int b, + float *R, float *G, float *B); + VIPS_API + int vips_col_sRGB2scRGB_16_noclip(int r, int g, int b, + float *R, float *G, float *B); + + VIPS_API + int vips_col_scRGB2XYZ(float R, float G, float B, + float *X, float *Y, float *Z); + VIPS_API + int vips_col_XYZ2scRGB(float X, float Y, float Z, + float *R, float *G, float *B); + + VIPS_API + int vips_col_scRGB2sRGB_8(float R, float G, float B, + int *r, int *g, int *b, int *og); + VIPS_API + int vips_col_scRGB2sRGB_16(float R, float G, float B, + int *r, int *g, int *b, int *og); + VIPS_API + int vips_col_scRGB2BW_16(float R, float G, float B, int *g, int *og); + VIPS_API + int vips_col_scRGB2BW_8(float R, float G, float B, int *g, int *og); + + VIPS_API + float vips_pythagoras(float L1, float a1, float b1, + float L2, float a2, float b2); + VIPS_API + float vips_col_dE00( + float L1, float a1, float b1, float L2, float a2, float b2); #ifdef __cplusplus -} + } #endif /*__cplusplus*/ #endif /*VIPS_COLOUR_H*/ diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index abf0b16688..ebcaf9ac7b 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -314,6 +314,7 @@ void vips_foreign_load_invalidate(VipsImage *image); * @VIPS_FOREIGN_SAVEABLE_RGB: 3 bands * @VIPS_FOREIGN_SAVEABLE_CMYK: 4 bands * @VIPS_FOREIGN_SAVEABLE_ALPHA: an extra band + * @VIPS_FOREIGN_SAVEABLE_CICP: saver can write CICP tag * * The set of image types supported by a saver. * @@ -326,11 +327,13 @@ typedef enum /*< flags >*/ { VIPS_FOREIGN_SAVEABLE_RGB = 2, VIPS_FOREIGN_SAVEABLE_CMYK = 4, VIPS_FOREIGN_SAVEABLE_ALPHA = 8, + VIPS_FOREIGN_SAVEABLE_CICP = 16, VIPS_FOREIGN_SAVEABLE_ALL = (VIPS_FOREIGN_SAVEABLE_MONO | VIPS_FOREIGN_SAVEABLE_RGB | VIPS_FOREIGN_SAVEABLE_CMYK | - VIPS_FOREIGN_SAVEABLE_ALPHA) + VIPS_FOREIGN_SAVEABLE_ALPHA | + VIPS_FOREIGN_SAVEABLE_CICP) } VipsForeignSaveable; /** diff --git a/libvips/include/vips/image.h b/libvips/include/vips/image.h index 5f766ab9e8..e23f14a81f 100644 --- a/libvips/include/vips/image.h +++ b/libvips/include/vips/image.h @@ -114,7 +114,8 @@ typedef enum { VIPS_INTERPRETATION_HSV = 29, VIPS_INTERPRETATION_OKLAB = 30, VIPS_INTERPRETATION_OKLCH = 31, - VIPS_INTERPRETATION_LAST = 32 /*< skip >*/ + VIPS_INTERPRETATION_CICP = 32, + VIPS_INTERPRETATION_LAST = 33 /*< skip >*/ } VipsInterpretation; typedef enum { diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 4cdb20cacd..3f866f33e5 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -235,6 +235,7 @@ vips_interpretation_bands(VipsInterpretation interpretation) case VIPS_INTERPRETATION_HSV: case VIPS_INTERPRETATION_OKLAB: case VIPS_INTERPRETATION_OKLCH: + case VIPS_INTERPRETATION_CICP: return 3; case VIPS_INTERPRETATION_CMYK: diff --git a/po/POTFILES.in b/po/POTFILES.in index f5e06b6b8d..e4214dc86d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -128,6 +128,7 @@ libvips/colour/sRGB2HSV.c libvips/colour/sRGB2scRGB.c libvips/colour/UCS2LCh.c libvips/colour/uhdr2scRGB.c +libvips/colour/CICP2scRGB.c libvips/colour/XYZ2CMYK.c libvips/colour/XYZ2Lab.c libvips/colour/XYZ2Oklab.c