1#include "mupdf/fitz.h"
2
3#if FZ_ENABLE_ICC
4
5#ifndef LCMS_USE_FLOAT
6#define LCMS_USE_FLOAT 0
7#endif
8
9#ifdef HAVE_LCMS2MT
10#define GLOINIT cmsContext glo = ctx->colorspace->icc_instance;
11#define GLO glo,
12#include "lcms2mt.h"
13#include "lcms2mt_plugin.h"
14#else
15#define GLOINIT
16#define GLO
17#include "lcms2.h"
18#endif
19
20static void fz_premultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s)
21{
22 unsigned char a;
23 int k;
24 int n1 = n-1;
25 for (; w > 0; w--)
26 {
27 a = s[n1];
28 for (k = 0; k < c; k++)
29 s[k] = fz_mul255(s[k], a);
30 s += n;
31 }
32}
33
34static void fz_unmultiply_row(fz_context *ctx, int n, int c, int w, unsigned char *s, const unsigned char *in)
35{
36 int a, inva;
37 int k;
38 int n1 = n-1;
39 for (; w > 0; w--)
40 {
41 a = in[n1];
42 inva = a ? 255 * 256 / a : 0;
43 for (k = 0; k < c; k++)
44 s[k] = (in[k] * inva) >> 8;
45 for (;k < n1; k++)
46 s[k] = in[k];
47 s[n1] = a;
48 s += n;
49 in += n;
50 }
51}
52
53struct fz_icc_link_s
54{
55 fz_storable storable;
56 void *handle;
57};
58
59#ifdef HAVE_LCMS2MT
60
61static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text)
62{
63 fz_context *ctx = (fz_context *)cmsGetContextUserData(id);
64 fz_warn(ctx, "lcms: %s.", error_text);
65}
66
67static void *fz_lcms_malloc(cmsContext id, unsigned int size)
68{
69 fz_context *ctx = cmsGetContextUserData(id);
70 return fz_malloc_no_throw(ctx, size);
71}
72
73static void *fz_lcms_realloc(cmsContext id, void *ptr, unsigned int size)
74{
75 fz_context *ctx = cmsGetContextUserData(id);
76 return fz_realloc_no_throw(ctx, ptr, size);
77}
78
79static void fz_lcms_free(cmsContext id, void *ptr)
80{
81 fz_context *ctx = cmsGetContextUserData(id);
82 fz_free(ctx, ptr);
83}
84
85static cmsPluginMemHandler fz_lcms_memhandler =
86{
87 {
88 cmsPluginMagicNumber,
89 LCMS_VERSION,
90 cmsPluginMemHandlerSig,
91 NULL
92 },
93 fz_lcms_malloc,
94 fz_lcms_free,
95 fz_lcms_realloc,
96 NULL,
97 NULL,
98 NULL,
99};
100
101void fz_new_icc_context(fz_context *ctx)
102{
103 cmsContext glo = cmsCreateContext(&fz_lcms_memhandler, ctx);
104 if (!glo)
105 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateContext failed");
106 ctx->colorspace->icc_instance = glo;
107 cmsSetLogErrorHandler(glo, fz_lcms_log_error);
108}
109
110void fz_drop_icc_context(fz_context *ctx)
111{
112 cmsContext glo = ctx->colorspace->icc_instance;
113 if (glo)
114 cmsDeleteContext(glo);
115 ctx->colorspace->icc_instance = NULL;
116}
117
118#else
119
120static fz_context *glo_ctx = NULL;
121
122static void fz_lcms_log_error(cmsContext id, cmsUInt32Number error_code, const char *error_text)
123{
124 fz_warn(glo_ctx, "lcms: %s.", error_text);
125}
126
127void fz_new_icc_context(fz_context *ctx)
128{
129 if (glo_ctx != NULL)
130 fz_throw(ctx, FZ_ERROR_GENERIC, "Stock LCMS2 library cannot be used in multiple contexts!");
131 glo_ctx = ctx;
132 cmsSetLogErrorHandler(fz_lcms_log_error);
133}
134
135void fz_drop_icc_context(fz_context *ctx)
136{
137 glo_ctx = NULL;
138 cmsSetLogErrorHandler(NULL);
139}
140
141#endif
142
143fz_icc_profile *fz_new_icc_profile(fz_context *ctx, unsigned char *data, size_t size)
144{
145 GLOINIT
146 fz_icc_profile *profile;
147 profile = cmsOpenProfileFromMem(GLO data, (cmsUInt32Number)size);
148 if (profile == NULL)
149 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsOpenProfileFromMem failed");
150 return profile;
151}
152
153int fz_icc_profile_is_lab(fz_context *ctx, fz_icc_profile *profile)
154{
155 GLOINIT
156 if (profile == NULL)
157 return 0;
158 return (cmsGetColorSpace(GLO profile) == cmsSigLabData);
159}
160
161void fz_drop_icc_profile(fz_context *ctx, fz_icc_profile *profile)
162{
163 GLOINIT
164 if (profile)
165 cmsCloseProfile(GLO profile);
166}
167
168void fz_icc_profile_name(fz_context *ctx, fz_icc_profile *profile, char *name, size_t size)
169{
170 GLOINIT
171 cmsMLU *descMLU;
172 descMLU = cmsReadTag(GLO profile, cmsSigProfileDescriptionTag);
173 name[0] = 0;
174 cmsMLUgetASCII(GLO descMLU, "en", "US", name, size);
175}
176
177int fz_icc_profile_components(fz_context *ctx, fz_icc_profile *profile)
178{
179 GLOINIT
180 return cmsChannelsOf(GLO cmsGetColorSpace(GLO profile));
181}
182
183void fz_drop_icc_link_imp(fz_context *ctx, fz_storable *storable)
184{
185 GLOINIT
186 fz_icc_link *link = (fz_icc_link*)storable;
187 cmsDeleteTransform(GLO link->handle);
188 fz_free(ctx, link);
189}
190
191void fz_drop_icc_link(fz_context *ctx, fz_icc_link *link)
192{
193 fz_drop_storable(ctx, &link->storable);
194}
195
196fz_icc_link *
197fz_new_icc_link(fz_context *ctx,
198 fz_colorspace *src, int src_extras,
199 fz_colorspace *dst, int dst_extras,
200 fz_colorspace *prf,
201 fz_color_params rend,
202 int format,
203 int copy_spots)
204{
205 GLOINIT
206 cmsHPROFILE src_pro = src->u.icc.profile;
207 cmsHPROFILE dst_pro = dst->u.icc.profile;
208 cmsHPROFILE prf_pro = prf ? prf->u.icc.profile : NULL;
209 int src_bgr = (src->type == FZ_COLORSPACE_BGR);
210 int dst_bgr = (dst->type == FZ_COLORSPACE_BGR);
211 cmsColorSpaceSignature src_cs, dst_cs;
212 cmsUInt32Number src_fmt, dst_fmt;
213 cmsUInt32Number flags;
214 cmsHTRANSFORM transform;
215 fz_icc_link *link;
216
217 flags = cmsFLAGS_LOWRESPRECALC;
218
219 src_cs = cmsGetColorSpace(GLO src_pro);
220 src_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO src_cs));
221 src_fmt |= CHANNELS_SH(cmsChannelsOf(GLO src_cs));
222 src_fmt |= DOSWAP_SH(src_bgr);
223 src_fmt |= SWAPFIRST_SH(src_bgr && (src_extras > 0));
224#if LCMS_USE_FLOAT
225 src_fmt |= BYTES_SH(format ? 4 : 1);
226 src_fmt |= FLOAT_SH(format ? 1 : 0)
227#else
228 src_fmt |= BYTES_SH(format ? 2 : 1);
229#endif
230 src_fmt |= EXTRA_SH(src_extras);
231
232 dst_cs = cmsGetColorSpace(GLO dst_pro);
233 dst_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO dst_cs));
234 dst_fmt |= CHANNELS_SH(cmsChannelsOf(GLO dst_cs));
235 dst_fmt |= DOSWAP_SH(dst_bgr);
236 dst_fmt |= SWAPFIRST_SH(dst_bgr && (dst_extras > 0));
237#if LCMS_USE_FLOAT
238 dst_fmt |= BYTES_SH(format ? 4 : 1);
239 dst_fmt |= FLOAT_SH(format ? 1 : 0);
240#else
241 dst_fmt |= BYTES_SH(format ? 2 : 1);
242#endif
243 dst_fmt |= EXTRA_SH(dst_extras);
244
245 /* flags */
246 if (rend.bp)
247 flags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
248
249 if (copy_spots)
250 flags |= cmsFLAGS_COPY_ALPHA;
251
252 if (prf_pro == NULL)
253 {
254 transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, rend.ri, flags);
255 if (!transform)
256 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateTransform(%s,%s) failed", src->name, dst->name);
257 }
258
259 /* LCMS proof creation links don't work properly with the Ghent test files. Handle this in a brutish manner. */
260 else if (src_pro == prf_pro)
261 {
262 transform = cmsCreateTransform(GLO src_pro, src_fmt, dst_pro, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags);
263 if (!transform)
264 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateTransform(src=proof,dst) failed");
265 }
266 else if (prf_pro == dst_pro)
267 {
268 transform = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, dst_fmt, rend.ri, flags);
269 if (!transform)
270 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateTransform(src,proof=dst) failed");
271 }
272 else
273 {
274 cmsHPROFILE src_to_prf_pro;
275 cmsHTRANSFORM src_to_prf_link;
276 cmsColorSpaceSignature prf_cs;
277 cmsUInt32Number prf_fmt;
278 cmsHPROFILE hProfiles[3];
279
280 prf_cs = cmsGetColorSpace(GLO prf_pro);
281 prf_fmt = COLORSPACE_SH(_cmsLCMScolorSpace(GLO prf_cs));
282 prf_fmt |= CHANNELS_SH(cmsChannelsOf(GLO prf_cs));
283#if LCMS_USE_FLOAT
284 prf_fmt |= BYTES_SH(format ? 4 : 1);
285 prf_fmt |= FLOAT_SH(format ? 1 : 0);
286#else
287 prf_fmt |= BYTES_SH(format ? 2 : 1);
288#endif
289
290 src_to_prf_link = cmsCreateTransform(GLO src_pro, src_fmt, prf_pro, prf_fmt, rend.ri, flags);
291 if (!src_to_prf_link)
292 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateTransform(src,proof) failed");
293 src_to_prf_pro = cmsTransform2DeviceLink(GLO src_to_prf_link, 3.4, flags);
294 cmsDeleteTransform(GLO src_to_prf_link);
295 if (!src_to_prf_pro)
296 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsTransform2DeviceLink(src,proof) failed");
297
298 hProfiles[0] = src_to_prf_pro;
299 hProfiles[1] = prf_pro;
300 hProfiles[2] = dst_pro;
301 transform = cmsCreateMultiprofileTransform(GLO hProfiles, 3, src_fmt, dst_fmt, INTENT_RELATIVE_COLORIMETRIC, flags);
302 cmsCloseProfile(GLO src_to_prf_pro);
303 if (!transform)
304 fz_throw(ctx, FZ_ERROR_GENERIC, "cmsCreateMultiprofileTransform(src,proof,dst) failed");
305 }
306
307 fz_try(ctx)
308 {
309 link = fz_malloc_struct(ctx, fz_icc_link);
310 FZ_INIT_STORABLE(link, 1, fz_drop_icc_link_imp);
311 link->handle = transform;
312 }
313 fz_catch(ctx)
314 {
315 cmsDeleteTransform(GLO link);
316 fz_rethrow(ctx);
317 }
318 return link;
319}
320
321void
322fz_icc_transform_color(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst)
323{
324 GLOINIT
325#if LCMS_USE_FLOAT
326 cmsDoTransform(GLO cc->link->handle, src, dst, 1);
327#else
328 uint16_t s16[FZ_MAX_COLORS];
329 uint16_t d16[FZ_MAX_COLORS];
330 int dn = cc->ds->n;
331 int i;
332 if (cc->ss->type == FZ_COLORSPACE_LAB)
333 {
334 s16[0] = src[0] * 655.35f;
335 s16[1] = (src[1] + 128) * 257;
336 s16[2] = (src[2] + 128) * 257;
337 }
338 else
339 {
340 int sn = cc->ss->n;
341 for (i = 0; i < sn; ++i)
342 s16[i] = src[i] * 65535;
343 }
344 cmsDoTransform(GLO cc->link->handle, s16, d16, 1);
345 for (i = 0; i < dn; ++i)
346 dst[i] = d16[i] / 65535.0f;
347#endif
348}
349
350void
351fz_icc_transform_pixmap(fz_context *ctx, fz_icc_link *link, fz_pixmap *src, fz_pixmap *dst, int copy_spots)
352{
353 GLOINIT
354 int cmm_num_src, cmm_num_dst, cmm_extras;
355 unsigned char *inputpos, *outputpos, *buffer;
356 int ss = src->stride;
357 int ds = dst->stride;
358 int sw = src->w;
359 int dw = dst->w;
360 int sn = src->n;
361 int dn = dst->n;
362 int sa = src->alpha;
363 int da = dst->alpha;
364 int ssp = src->s;
365 int dsp = dst->s;
366 int sc = sn - ssp - sa;
367 int dc = dn - dsp - da;
368 int h = src->h;
369 cmsUInt32Number src_format, dst_format;
370
371 /* check the channels. */
372 src_format = cmsGetTransformInputFormat(GLO link->handle);
373 dst_format = cmsGetTransformOutputFormat(GLO link->handle);
374 cmm_num_src = T_CHANNELS(src_format);
375 cmm_num_dst = T_CHANNELS(dst_format);
376 cmm_extras = T_EXTRA(src_format);
377 if (cmm_num_src != sc || cmm_num_dst != dc || cmm_extras != ssp+sa || sa != da || (copy_spots && ssp != dsp))
378 fz_throw(ctx, FZ_ERROR_GENERIC, "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);
379
380 inputpos = src->samples;
381 outputpos = dst->samples;
382 if (sa)
383 {
384 buffer = fz_malloc(ctx, ss);
385 for (; h > 0; h--)
386 {
387 fz_unmultiply_row(ctx, sn, sc, sw, buffer, inputpos);
388 cmsDoTransform(GLO link->handle, buffer, outputpos, sw);
389 fz_premultiply_row(ctx, dn, dc, dw, outputpos);
390 inputpos += ss;
391 outputpos += ds;
392 }
393 fz_free(ctx, buffer);
394 }
395 else
396 {
397 for (; h > 0; h--)
398 {
399 cmsDoTransform(GLO link->handle, inputpos, outputpos, sw);
400 inputpos += ss;
401 outputpos += ds;
402 }
403 }
404}
405
406#endif
407