Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/color-lcms.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/color-lcms.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,534 @@ +// Copyright (C) 2004-2024 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF 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 Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include "color-imp.h" + +#include <string.h> + +#if FZ_ENABLE_ICC + +#ifndef LCMS_USE_FLOAT +#define LCMS_USE_FLOAT 0 +#endif + +#ifdef HAVE_LCMS2MT +#define GLOINIT cmsContext glo = ctx->colorspace->icc_instance; +#define GLO glo, +#include "lcms2mt.h" +#include "lcms2mt_plugin.h" +#else +#define GLOINIT +#define GLO +#include "lcms2.h" +#endif + +static void fz_premultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s) +{ + unsigned char a; + int k; + int n1 = n-1; + for (; w > 0; w--) + { + a = s[n1]; + if (a == 0) + memset(s, 0, c); + else if (a != 255) + for (k = 0; k < c; k++) + s[k] = fz_mul255(s[k], a); + s += n; + } +} + +static void fz_premultiply_row_0or1(fz_context *ctx, int n, int c, int w, unsigned char *s) +{ + unsigned char a; + int n1 = n-1; + for (; w > 0; w--) + { + a = s[n1]; + if (a == 0) + memset(s, 0, c); + s += n; + } +} + +/* Returns 0 for all the alphas being 0, 1 for them being 0 or 255, 2 otherwise. */ +static int fz_unmultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s, const unsigned char *in) +{ + int a, inva; + int k; + int n1 = n-1; + for (; w > 0; w--) + { + a = in[n1]; + if (a != 0) + goto nonzero; + for (k = 0; k < c; k++) + s[k] = 0; + for (;k < n1; k++) + s[k] = in[k]; + s[n1] = 0; + s += n; + in += n; + } + return 0; + for (; w > 0; w--) + { + a = in[n1]; +nonzero: + if (a != 0 && a != 255) + goto varying; + k = 0; + if (a == 0) + for (; k < c; k++) + s[k] = 0; + for (;k < n; k++) + s[k] = in[k]; + s += n; + in += n; + } + return 1; + for (; w > 0; w--) + { + a = in[n1]; +varying: + if (a == 0) + { + for (k = 0; k < c; k++) + s[k] = 0; + for (;k < n1; k++) + s[k] = in[k]; + s[k] = 0; + } + else if (a == 255) + { + memcpy(s, in, n); + } + else + { + inva = 255 * 256 / a; + for (k = 0; k < c; k++) + s[k] = (in[k] * inva) >> 8; + for (;k < n1; k++) + s[k] = in[k]; + s[n1] = a; + } + s += n; + in += n; + } + return 2; +} + +struct fz_icc_link +{ + fz_storable storable; + void *handle; +}; + +#ifdef HAVE_LCMS2MT + +static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text) +{ + fz_context *ctx = (fz_context *)cmsGetContextUserData(id); + fz_warn(ctx, "lcms: %s.", error_text); +} + +static void *fz_lcms_malloc(cmsContext id, unsigned int size) +{ + fz_context *ctx = cmsGetContextUserData(id); + return Memento_label(fz_malloc_no_throw(ctx, size), "lcms"); +} + +static void *fz_lcms_realloc(cmsContext id, void *ptr, unsigned int size) +{ + fz_context *ctx = cmsGetContextUserData(id); + return Memento_label(fz_realloc_no_throw(ctx, ptr, size), "lcms"); +} + +static void fz_lcms_free(cmsContext id, void *ptr) +{ + fz_context *ctx = cmsGetContextUserData(id); + fz_free(ctx, ptr); +} + +static cmsPluginMemHandler fz_lcms_memhandler = +{ + { + cmsPluginMagicNumber, + LCMS_VERSION, + cmsPluginMemHandlerSig, + NULL + }, + fz_lcms_malloc, + fz_lcms_free, + fz_lcms_realloc, + NULL, + NULL, + NULL, +}; + +void fz_new_icc_context(fz_context *ctx) +{ + cmsContext glo = cmsCreateContext(&fz_lcms_memhandler, ctx); + if (!glo) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateContext failed"); + ctx->colorspace->icc_instance = glo; + cmsSetLogErrorHandler(glo, fz_lcms_log_error); +} + +void fz_drop_icc_context(fz_context *ctx) +{ + cmsContext glo = ctx->colorspace->icc_instance; + if (glo) + cmsDeleteContext(glo); + ctx->colorspace->icc_instance = NULL; +} + +#else + +static fz_context *glo_ctx = NULL; + +static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text) +{ + fz_warn(glo_ctx, "lcms: %s.", error_text); +} + +void fz_new_icc_context(fz_context *ctx) +{ + if (glo_ctx != NULL) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Stock LCMS2 library cannot be used in multiple contexts!"); + glo_ctx = ctx; + cmsSetLogErrorHandler(fz_lcms_log_error); +} + +void fz_drop_icc_context(fz_context *ctx) +{ + glo_ctx = NULL; + cmsSetLogErrorHandler(NULL); +} + +#endif + +fz_icc_profile *fz_new_icc_profile(fz_context *ctx, unsigned char *data, size_t size) +{ + GLOINIT + fz_icc_profile *profile; + profile = cmsOpenProfileFromMem(GLO data, (cmsUInt32Number)size); + if (profile == NULL) + fz_throw(ctx, FZ_ERROR_FORMAT, "cmsOpenProfileFromMem failed"); + return profile; +} + +int fz_icc_profile_is_lab(fz_context *ctx, fz_icc_profile *profile) +{ + GLOINIT + if (profile == NULL) + return 0; + return (cmsGetColorSpace(GLO profile) == cmsSigLabData); +} + +void fz_drop_icc_profile(fz_context *ctx, fz_icc_profile *profile) +{ + GLOINIT + if (profile) + cmsCloseProfile(GLO profile); +} + +void fz_icc_profile_name(fz_context *ctx, fz_icc_profile *profile, char *name, size_t size) +{ + GLOINIT + cmsMLU *descMLU; + descMLU = cmsReadTag(GLO profile, cmsSigProfileDescriptionTag); + name[0] = 0; + cmsMLUgetASCII(GLO descMLU, "en", "US", name, (cmsUInt32Number)size); +} + +int fz_icc_profile_components(fz_context *ctx, fz_icc_profile *profile) +{ + GLOINIT + return cmsChannelsOf(GLO cmsGetColorSpace(GLO profile)); +} + +void fz_drop_icc_link_imp(fz_context *ctx, fz_storable *storable) +{ + GLOINIT + fz_icc_link *link = (fz_icc_link*)storable; + cmsDeleteTransform(GLO link->handle); + fz_free(ctx, link); +} + +void fz_drop_icc_link(fz_context *ctx, fz_icc_link *link) +{ + fz_drop_storable(ctx, &link->storable); +} + +fz_icc_link * +fz_new_icc_link(fz_context *ctx, + fz_colorspace *src, int src_extras, + fz_colorspace *dst, int dst_extras, + fz_colorspace *prf, + fz_color_params rend, + int format, + int copy_spots, + int premult) +{ + GLOINIT + cmsHPROFILE src_pro = src->u.icc.profile; + cmsHPROFILE dst_pro = dst->u.icc.profile; + cmsHPROFILE prf_pro = prf ? prf->u.icc.profile : NULL; + int src_bgr = (src->type == FZ_COLORSPACE_BGR); + int dst_bgr = (dst->type == FZ_COLORSPACE_BGR); + cmsColorSpaceSignature src_cs, dst_cs; + cmsUInt32Number src_fmt, dst_fmt; + cmsUInt32Number flags; + cmsHTRANSFORM transform; + fz_icc_link *link; + + flags = cmsFLAGS_LOWRESPRECALC; + + src_cs = cmsGetColorSpace(GLO src_pro); + src_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO src_cs)); + src_fmt |= CHANNELS_SH(cmsChannelsOf(GLO src_cs)); + src_fmt |= DOSWAP_SH(src_bgr); + src_fmt |= SWAPFIRST_SH(src_bgr && (src_extras > 0)); +#if LCMS_USE_FLOAT + src_fmt |= BYTES_SH(format ? 4 : 1); + src_fmt |= FLOAT_SH(format ? 1 : 0) +#else + src_fmt |= BYTES_SH(format ? 2 : 1); +#endif + src_fmt |= EXTRA_SH(src_extras); + + dst_cs = cmsGetColorSpace(GLO dst_pro); + dst_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO dst_cs)); + dst_fmt |= CHANNELS_SH(cmsChannelsOf(GLO dst_cs)); + dst_fmt |= DOSWAP_SH(dst_bgr); + dst_fmt |= SWAPFIRST_SH(dst_bgr && (dst_extras > 0)); +#if LCMS_USE_FLOAT + dst_fmt |= BYTES_SH(format ? 4 : 1); + dst_fmt |= FLOAT_SH(format ? 1 : 0); +#else + dst_fmt |= BYTES_SH(format ? 2 : 1); +#endif + dst_fmt |= EXTRA_SH(dst_extras); + + /* flags */ + if (rend.bp) + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + + if (copy_spots) + flags |= cmsFLAGS_COPY_ALPHA; + +#ifdef cmsFLAGS_PREMULT + if (premult) + flags |= cmsFLAGS_PREMULT; +#endif + + if (prf_pro == NULL) + { + transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, rend.ri, flags); + if (!transform) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(%s,%s) failed", src->name, dst->name); + } + + /* LCMS proof creation links don't work properly with the Ghent test files. Handle this in a brutish manner. */ + else if (src_pro == prf_pro) + { + transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags); + if (!transform) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src=proof,dst) failed"); + } + else if (prf_pro == dst_pro) + { + transform = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, dst_fmt, rend.ri, flags); + if (!transform) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src,proof=dst) failed"); + } + else + { + cmsHPROFILE src_to_prf_pro; + cmsHTRANSFORM src_to_prf_link; + cmsColorSpaceSignature prf_cs; + cmsUInt32Number prf_fmt; + cmsHPROFILE hProfiles[3]; + + prf_cs = cmsGetColorSpace(GLO prf_pro); + prf_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO prf_cs)); + prf_fmt |= CHANNELS_SH(cmsChannelsOf(GLO prf_cs)); +#if LCMS_USE_FLOAT + prf_fmt |= BYTES_SH(format ? 4 : 1); + prf_fmt |= FLOAT_SH(format ? 1 : 0); +#else + prf_fmt |= BYTES_SH(format ? 2 : 1); +#endif + + src_to_prf_link = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, prf_fmt, rend.ri, flags); + if (!src_to_prf_link) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src,proof) failed"); + src_to_prf_pro = cmsTransform2DeviceLink(GLO src_to_prf_link, 3.4, flags); + cmsDeleteTransform(GLO src_to_prf_link); + if (!src_to_prf_pro) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsTransform2DeviceLink(src,proof) failed"); + + hProfiles[0] = src_to_prf_pro; + hProfiles[1] = prf_pro; + hProfiles[2] = dst_pro; + transform = cmsCreateMultiprofileTransform(GLO hProfiles, 3, src_fmt, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags); + cmsCloseProfile(GLO src_to_prf_pro); + if (!transform) + fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateMultiprofileTransform(src,proof,dst) failed"); + } + + fz_try(ctx) + { + link = fz_malloc_struct(ctx, fz_icc_link); + FZ_INIT_STORABLE(link, 1, fz_drop_icc_link_imp); + link->handle = transform; + } + fz_catch(ctx) + { + cmsDeleteTransform(GLO transform); + fz_rethrow(ctx); + } + return link; +} + +void +fz_icc_transform_color(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst) +{ + GLOINIT +#if LCMS_USE_FLOAT + cmsDoTransform(GLO cc->link->handle, src, dst, 1); +#else + uint16_t s16[FZ_MAX_COLORS]; + uint16_t d16[FZ_MAX_COLORS]; + int dn = cc->dst_n; + int i; + if (cc->ss->type == FZ_COLORSPACE_LAB) + { + s16[0] = src[0] * 655.35f; + s16[1] = (src[1] + 128) * 257; + s16[2] = (src[2] + 128) * 257; + } + else + { + int sn = cc->ss->n; + for (i = 0; i < sn; ++i) + s16[i] = src[i] * 65535; + } + cmsDoTransform(GLO cc->link->handle, s16, d16, 1); + for (i = 0; i < dn; ++i) + dst[i] = d16[i] / 65535.0f; +#endif +} + +void +fz_icc_transform_pixmap(fz_context *ctx, fz_icc_link *link, const fz_pixmap *src, fz_pixmap *dst, int copy_spots) +{ + GLOINIT + int cmm_num_src, cmm_num_dst, cmm_extras; + unsigned char *inputpos, *outputpos, *buffer; + int ss = src->stride; + int ds = dst->stride; + int sw = src->w; + int dw = dst->w; + int sn = src->n; + int dn = dst->n; + int sa = src->alpha; + int da = dst->alpha; + int ssp = src->s; + int dsp = dst->s; + int sc = sn - ssp - sa; + int dc = dn - dsp - da; + int h = src->h; + cmsUInt32Number src_format, dst_format; + + /* check the channels. */ + src_format = cmsGetTransformInputFormat(GLO link->handle); + dst_format = cmsGetTransformOutputFormat(GLO link->handle); + cmm_num_src = T_CHANNELS(src_format); + cmm_num_dst = T_CHANNELS(dst_format); + cmm_extras = T_EXTRA(src_format); + if (cmm_num_src != sc || cmm_num_dst != dc || cmm_extras != ssp+sa || sa != da || (copy_spots && ssp != dsp)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "bad setup in ICC pixmap transform: src: %d vs %d+%d+%d, dst: %d vs %d+%d+%d", cmm_num_src, sc, ssp, sa, cmm_num_dst, dc, dsp, da); + + inputpos = src->samples; + outputpos = dst->samples; + +#ifdef cmsFLAGS_PREMULT + /* LCMS2MT can only handle premultiplied data if the number of 'extra' + * channels is the same. If not, do it by steam. */ + if (sa && cmm_extras != (int)T_EXTRA(dst_format)) +#else + /* Vanilla LCMS2 cannot handle premultiplied data. If present, do it by steam. */ + if (sa) +#endif + { + buffer = fz_malloc(ctx, ss); + for (; h > 0; h--) + { + int mult = fz_unmultiply_row(ctx, sn, sc, sw, buffer, inputpos); + if (mult == 0) + { + /* Solid transparent row. No point in doing the transform + * because it will premultiplied back to 0. */ + memset(outputpos, 0, ds); + } + else + { + cmsDoTransform(GLO link->handle, buffer, outputpos, sw); + if (!copy_spots) + { + /* Copy the alpha by steam */ + unsigned char *d = outputpos + dn - 1; + const unsigned char *s = inputpos + sn -1; + int w = sw; + + while (w--) + { + *d = *s; + d += dn; + s += sn; + } + } + if (mult == 1) + fz_premultiply_row_0or1(ctx, dn, dc, dw, outputpos); + else if (mult == 2) + fz_premultiply_row(ctx, dn, dc, dw, outputpos); + } + inputpos += ss; + outputpos += ds; + } + fz_free(ctx, buffer); + } + else + for (; h > 0; h--) + { + cmsDoTransform(GLO link->handle, inputpos, outputpos, sw); + inputpos += ss; + outputpos += ds; + } +} + +#endif
