comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2024 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24
25 #include "color-imp.h"
26
27 #include <string.h>
28
29 #if FZ_ENABLE_ICC
30
31 #ifndef LCMS_USE_FLOAT
32 #define LCMS_USE_FLOAT 0
33 #endif
34
35 #ifdef HAVE_LCMS2MT
36 #define GLOINIT cmsContext glo = ctx->colorspace->icc_instance;
37 #define GLO glo,
38 #include "lcms2mt.h"
39 #include "lcms2mt_plugin.h"
40 #else
41 #define GLOINIT
42 #define GLO
43 #include "lcms2.h"
44 #endif
45
46 static void fz_premultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s)
47 {
48 unsigned char a;
49 int k;
50 int n1 = n-1;
51 for (; w > 0; w--)
52 {
53 a = s[n1];
54 if (a == 0)
55 memset(s, 0, c);
56 else if (a != 255)
57 for (k = 0; k < c; k++)
58 s[k] = fz_mul255(s[k], a);
59 s += n;
60 }
61 }
62
63 static void fz_premultiply_row_0or1(fz_context *ctx, int n, int c, int w, unsigned char *s)
64 {
65 unsigned char a;
66 int n1 = n-1;
67 for (; w > 0; w--)
68 {
69 a = s[n1];
70 if (a == 0)
71 memset(s, 0, c);
72 s += n;
73 }
74 }
75
76 /* Returns 0 for all the alphas being 0, 1 for them being 0 or 255, 2 otherwise. */
77 static int fz_unmultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s, const unsigned char *in)
78 {
79 int a, inva;
80 int k;
81 int n1 = n-1;
82 for (; w > 0; w--)
83 {
84 a = in[n1];
85 if (a != 0)
86 goto nonzero;
87 for (k = 0; k < c; k++)
88 s[k] = 0;
89 for (;k < n1; k++)
90 s[k] = in[k];
91 s[n1] = 0;
92 s += n;
93 in += n;
94 }
95 return 0;
96 for (; w > 0; w--)
97 {
98 a = in[n1];
99 nonzero:
100 if (a != 0 && a != 255)
101 goto varying;
102 k = 0;
103 if (a == 0)
104 for (; k < c; k++)
105 s[k] = 0;
106 for (;k < n; k++)
107 s[k] = in[k];
108 s += n;
109 in += n;
110 }
111 return 1;
112 for (; w > 0; w--)
113 {
114 a = in[n1];
115 varying:
116 if (a == 0)
117 {
118 for (k = 0; k < c; k++)
119 s[k] = 0;
120 for (;k < n1; k++)
121 s[k] = in[k];
122 s[k] = 0;
123 }
124 else if (a == 255)
125 {
126 memcpy(s, in, n);
127 }
128 else
129 {
130 inva = 255 * 256 / a;
131 for (k = 0; k < c; k++)
132 s[k] = (in[k] * inva) >> 8;
133 for (;k < n1; k++)
134 s[k] = in[k];
135 s[n1] = a;
136 }
137 s += n;
138 in += n;
139 }
140 return 2;
141 }
142
143 struct fz_icc_link
144 {
145 fz_storable storable;
146 void *handle;
147 };
148
149 #ifdef HAVE_LCMS2MT
150
151 static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text)
152 {
153 fz_context *ctx = (fz_context *)cmsGetContextUserData(id);
154 fz_warn(ctx, "lcms: %s.", error_text);
155 }
156
157 static void *fz_lcms_malloc(cmsContext id, unsigned int size)
158 {
159 fz_context *ctx = cmsGetContextUserData(id);
160 return Memento_label(fz_malloc_no_throw(ctx, size), "lcms");
161 }
162
163 static void *fz_lcms_realloc(cmsContext id, void *ptr, unsigned int size)
164 {
165 fz_context *ctx = cmsGetContextUserData(id);
166 return Memento_label(fz_realloc_no_throw(ctx, ptr, size), "lcms");
167 }
168
169 static void fz_lcms_free(cmsContext id, void *ptr)
170 {
171 fz_context *ctx = cmsGetContextUserData(id);
172 fz_free(ctx, ptr);
173 }
174
175 static cmsPluginMemHandler fz_lcms_memhandler =
176 {
177 {
178 cmsPluginMagicNumber,
179 LCMS_VERSION,
180 cmsPluginMemHandlerSig,
181 NULL
182 },
183 fz_lcms_malloc,
184 fz_lcms_free,
185 fz_lcms_realloc,
186 NULL,
187 NULL,
188 NULL,
189 };
190
191 void fz_new_icc_context(fz_context *ctx)
192 {
193 cmsContext glo = cmsCreateContext(&fz_lcms_memhandler, ctx);
194 if (!glo)
195 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateContext failed");
196 ctx->colorspace->icc_instance = glo;
197 cmsSetLogErrorHandler(glo, fz_lcms_log_error);
198 }
199
200 void fz_drop_icc_context(fz_context *ctx)
201 {
202 cmsContext glo = ctx->colorspace->icc_instance;
203 if (glo)
204 cmsDeleteContext(glo);
205 ctx->colorspace->icc_instance = NULL;
206 }
207
208 #else
209
210 static fz_context *glo_ctx = NULL;
211
212 static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text)
213 {
214 fz_warn(glo_ctx, "lcms: %s.", error_text);
215 }
216
217 void fz_new_icc_context(fz_context *ctx)
218 {
219 if (glo_ctx != NULL)
220 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Stock LCMS2 library cannot be used in multiple contexts!");
221 glo_ctx = ctx;
222 cmsSetLogErrorHandler(fz_lcms_log_error);
223 }
224
225 void fz_drop_icc_context(fz_context *ctx)
226 {
227 glo_ctx = NULL;
228 cmsSetLogErrorHandler(NULL);
229 }
230
231 #endif
232
233 fz_icc_profile *fz_new_icc_profile(fz_context *ctx, unsigned char *data, size_t size)
234 {
235 GLOINIT
236 fz_icc_profile *profile;
237 profile = cmsOpenProfileFromMem(GLO data, (cmsUInt32Number)size);
238 if (profile == NULL)
239 fz_throw(ctx, FZ_ERROR_FORMAT, "cmsOpenProfileFromMem failed");
240 return profile;
241 }
242
243 int fz_icc_profile_is_lab(fz_context *ctx, fz_icc_profile *profile)
244 {
245 GLOINIT
246 if (profile == NULL)
247 return 0;
248 return (cmsGetColorSpace(GLO profile) == cmsSigLabData);
249 }
250
251 void fz_drop_icc_profile(fz_context *ctx, fz_icc_profile *profile)
252 {
253 GLOINIT
254 if (profile)
255 cmsCloseProfile(GLO profile);
256 }
257
258 void fz_icc_profile_name(fz_context *ctx, fz_icc_profile *profile, char *name, size_t size)
259 {
260 GLOINIT
261 cmsMLU *descMLU;
262 descMLU = cmsReadTag(GLO profile, cmsSigProfileDescriptionTag);
263 name[0] = 0;
264 cmsMLUgetASCII(GLO descMLU, "en", "US", name, (cmsUInt32Number)size);
265 }
266
267 int fz_icc_profile_components(fz_context *ctx, fz_icc_profile *profile)
268 {
269 GLOINIT
270 return cmsChannelsOf(GLO cmsGetColorSpace(GLO profile));
271 }
272
273 void fz_drop_icc_link_imp(fz_context *ctx, fz_storable *storable)
274 {
275 GLOINIT
276 fz_icc_link *link = (fz_icc_link*)storable;
277 cmsDeleteTransform(GLO link->handle);
278 fz_free(ctx, link);
279 }
280
281 void fz_drop_icc_link(fz_context *ctx, fz_icc_link *link)
282 {
283 fz_drop_storable(ctx, &link->storable);
284 }
285
286 fz_icc_link *
287 fz_new_icc_link(fz_context *ctx,
288 fz_colorspace *src, int src_extras,
289 fz_colorspace *dst, int dst_extras,
290 fz_colorspace *prf,
291 fz_color_params rend,
292 int format,
293 int copy_spots,
294 int premult)
295 {
296 GLOINIT
297 cmsHPROFILE src_pro = src->u.icc.profile;
298 cmsHPROFILE dst_pro = dst->u.icc.profile;
299 cmsHPROFILE prf_pro = prf ? prf->u.icc.profile : NULL;
300 int src_bgr = (src->type == FZ_COLORSPACE_BGR);
301 int dst_bgr = (dst->type == FZ_COLORSPACE_BGR);
302 cmsColorSpaceSignature src_cs, dst_cs;
303 cmsUInt32Number src_fmt, dst_fmt;
304 cmsUInt32Number flags;
305 cmsHTRANSFORM transform;
306 fz_icc_link *link;
307
308 flags = cmsFLAGS_LOWRESPRECALC;
309
310 src_cs = cmsGetColorSpace(GLO src_pro);
311 src_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO src_cs));
312 src_fmt |= CHANNELS_SH(cmsChannelsOf(GLO src_cs));
313 src_fmt |= DOSWAP_SH(src_bgr);
314 src_fmt |= SWAPFIRST_SH(src_bgr && (src_extras > 0));
315 #if LCMS_USE_FLOAT
316 src_fmt |= BYTES_SH(format ? 4 : 1);
317 src_fmt |= FLOAT_SH(format ? 1 : 0)
318 #else
319 src_fmt |= BYTES_SH(format ? 2 : 1);
320 #endif
321 src_fmt |= EXTRA_SH(src_extras);
322
323 dst_cs = cmsGetColorSpace(GLO dst_pro);
324 dst_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO dst_cs));
325 dst_fmt |= CHANNELS_SH(cmsChannelsOf(GLO dst_cs));
326 dst_fmt |= DOSWAP_SH(dst_bgr);
327 dst_fmt |= SWAPFIRST_SH(dst_bgr && (dst_extras > 0));
328 #if LCMS_USE_FLOAT
329 dst_fmt |= BYTES_SH(format ? 4 : 1);
330 dst_fmt |= FLOAT_SH(format ? 1 : 0);
331 #else
332 dst_fmt |= BYTES_SH(format ? 2 : 1);
333 #endif
334 dst_fmt |= EXTRA_SH(dst_extras);
335
336 /* flags */
337 if (rend.bp)
338 flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
339
340 if (copy_spots)
341 flags |= cmsFLAGS_COPY_ALPHA;
342
343 #ifdef cmsFLAGS_PREMULT
344 if (premult)
345 flags |= cmsFLAGS_PREMULT;
346 #endif
347
348 if (prf_pro == NULL)
349 {
350 transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, rend.ri, flags);
351 if (!transform)
352 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(%s,%s) failed", src->name, dst->name);
353 }
354
355 /* LCMS proof creation links don't work properly with the Ghent test files. Handle this in a brutish manner. */
356 else if (src_pro == prf_pro)
357 {
358 transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags);
359 if (!transform)
360 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src=proof,dst) failed");
361 }
362 else if (prf_pro == dst_pro)
363 {
364 transform = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, dst_fmt, rend.ri, flags);
365 if (!transform)
366 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src,proof=dst) failed");
367 }
368 else
369 {
370 cmsHPROFILE src_to_prf_pro;
371 cmsHTRANSFORM src_to_prf_link;
372 cmsColorSpaceSignature prf_cs;
373 cmsUInt32Number prf_fmt;
374 cmsHPROFILE hProfiles[3];
375
376 prf_cs = cmsGetColorSpace(GLO prf_pro);
377 prf_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO prf_cs));
378 prf_fmt |= CHANNELS_SH(cmsChannelsOf(GLO prf_cs));
379 #if LCMS_USE_FLOAT
380 prf_fmt |= BYTES_SH(format ? 4 : 1);
381 prf_fmt |= FLOAT_SH(format ? 1 : 0);
382 #else
383 prf_fmt |= BYTES_SH(format ? 2 : 1);
384 #endif
385
386 src_to_prf_link = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, prf_fmt, rend.ri, flags);
387 if (!src_to_prf_link)
388 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateTransform(src,proof) failed");
389 src_to_prf_pro = cmsTransform2DeviceLink(GLO src_to_prf_link, 3.4, flags);
390 cmsDeleteTransform(GLO src_to_prf_link);
391 if (!src_to_prf_pro)
392 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsTransform2DeviceLink(src,proof) failed");
393
394 hProfiles[0] = src_to_prf_pro;
395 hProfiles[1] = prf_pro;
396 hProfiles[2] = dst_pro;
397 transform = cmsCreateMultiprofileTransform(GLO hProfiles, 3, src_fmt, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags);
398 cmsCloseProfile(GLO src_to_prf_pro);
399 if (!transform)
400 fz_throw(ctx, FZ_ERROR_LIBRARY, "cmsCreateMultiprofileTransform(src,proof,dst) failed");
401 }
402
403 fz_try(ctx)
404 {
405 link = fz_malloc_struct(ctx, fz_icc_link);
406 FZ_INIT_STORABLE(link, 1, fz_drop_icc_link_imp);
407 link->handle = transform;
408 }
409 fz_catch(ctx)
410 {
411 cmsDeleteTransform(GLO transform);
412 fz_rethrow(ctx);
413 }
414 return link;
415 }
416
417 void
418 fz_icc_transform_color(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst)
419 {
420 GLOINIT
421 #if LCMS_USE_FLOAT
422 cmsDoTransform(GLO cc->link->handle, src, dst, 1);
423 #else
424 uint16_t s16[FZ_MAX_COLORS];
425 uint16_t d16[FZ_MAX_COLORS];
426 int dn = cc->dst_n;
427 int i;
428 if (cc->ss->type == FZ_COLORSPACE_LAB)
429 {
430 s16[0] = src[0] * 655.35f;
431 s16[1] = (src[1] + 128) * 257;
432 s16[2] = (src[2] + 128) * 257;
433 }
434 else
435 {
436 int sn = cc->ss->n;
437 for (i = 0; i < sn; ++i)
438 s16[i] = src[i] * 65535;
439 }
440 cmsDoTransform(GLO cc->link->handle, s16, d16, 1);
441 for (i = 0; i < dn; ++i)
442 dst[i] = d16[i] / 65535.0f;
443 #endif
444 }
445
446 void
447 fz_icc_transform_pixmap(fz_context *ctx, fz_icc_link *link, const fz_pixmap *src, fz_pixmap *dst, int copy_spots)
448 {
449 GLOINIT
450 int cmm_num_src, cmm_num_dst, cmm_extras;
451 unsigned char *inputpos, *outputpos, *buffer;
452 int ss = src->stride;
453 int ds = dst->stride;
454 int sw = src->w;
455 int dw = dst->w;
456 int sn = src->n;
457 int dn = dst->n;
458 int sa = src->alpha;
459 int da = dst->alpha;
460 int ssp = src->s;
461 int dsp = dst->s;
462 int sc = sn - ssp - sa;
463 int dc = dn - dsp - da;
464 int h = src->h;
465 cmsUInt32Number src_format, dst_format;
466
467 /* check the channels. */
468 src_format = cmsGetTransformInputFormat(GLO link->handle);
469 dst_format = cmsGetTransformOutputFormat(GLO link->handle);
470 cmm_num_src = T_CHANNELS(src_format);
471 cmm_num_dst = T_CHANNELS(dst_format);
472 cmm_extras = T_EXTRA(src_format);
473 if (cmm_num_src != sc || cmm_num_dst != dc || cmm_extras != ssp+sa || sa != da || (copy_spots && ssp != dsp))
474 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);
475
476 inputpos = src->samples;
477 outputpos = dst->samples;
478
479 #ifdef cmsFLAGS_PREMULT
480 /* LCMS2MT can only handle premultiplied data if the number of 'extra'
481 * channels is the same. If not, do it by steam. */
482 if (sa && cmm_extras != (int)T_EXTRA(dst_format))
483 #else
484 /* Vanilla LCMS2 cannot handle premultiplied data. If present, do it by steam. */
485 if (sa)
486 #endif
487 {
488 buffer = fz_malloc(ctx, ss);
489 for (; h > 0; h--)
490 {
491 int mult = fz_unmultiply_row(ctx, sn, sc, sw, buffer, inputpos);
492 if (mult == 0)
493 {
494 /* Solid transparent row. No point in doing the transform
495 * because it will premultiplied back to 0. */
496 memset(outputpos, 0, ds);
497 }
498 else
499 {
500 cmsDoTransform(GLO link->handle, buffer, outputpos, sw);
501 if (!copy_spots)
502 {
503 /* Copy the alpha by steam */
504 unsigned char *d = outputpos + dn - 1;
505 const unsigned char *s = inputpos + sn -1;
506 int w = sw;
507
508 while (w--)
509 {
510 *d = *s;
511 d += dn;
512 s += sn;
513 }
514 }
515 if (mult == 1)
516 fz_premultiply_row_0or1(ctx, dn, dc, dw, outputpos);
517 else if (mult == 2)
518 fz_premultiply_row(ctx, dn, dc, dw, outputpos);
519 }
520 inputpos += ss;
521 outputpos += ds;
522 }
523 fz_free(ctx, buffer);
524 }
525 else
526 for (; h > 0; h--)
527 {
528 cmsDoTransform(GLO link->handle, inputpos, outputpos, sw);
529 inputpos += ss;
530 outputpos += ds;
531 }
532 }
533
534 #endif