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 | |
20 | static 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 | |
34 | static 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 | |
53 | struct fz_icc_link_s |
54 | { |
55 | fz_storable storable; |
56 | void *handle; |
57 | }; |
58 | |
59 | #ifdef HAVE_LCMS2MT |
60 | |
61 | static 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 | |
67 | static 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 | |
73 | static 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 | |
79 | static void fz_lcms_free(cmsContext id, void *ptr) |
80 | { |
81 | fz_context *ctx = cmsGetContextUserData(id); |
82 | fz_free(ctx, ptr); |
83 | } |
84 | |
85 | static 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 | |
101 | void 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 | |
110 | void 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 | |
120 | static fz_context *glo_ctx = NULL; |
121 | |
122 | static 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 | |
127 | void 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 | |
135 | void fz_drop_icc_context(fz_context *ctx) |
136 | { |
137 | glo_ctx = NULL; |
138 | cmsSetLogErrorHandler(NULL); |
139 | } |
140 | |
141 | #endif |
142 | |
143 | fz_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 | |
153 | int 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 | |
161 | void fz_drop_icc_profile(fz_context *ctx, fz_icc_profile *profile) |
162 | { |
163 | GLOINIT |
164 | if (profile) |
165 | cmsCloseProfile(GLO profile); |
166 | } |
167 | |
168 | void 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 | |
177 | int fz_icc_profile_components(fz_context *ctx, fz_icc_profile *profile) |
178 | { |
179 | GLOINIT |
180 | return cmsChannelsOf(GLO cmsGetColorSpace(GLO profile)); |
181 | } |
182 | |
183 | void 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 | |
191 | void fz_drop_icc_link(fz_context *ctx, fz_icc_link *link) |
192 | { |
193 | fz_drop_storable(ctx, &link->storable); |
194 | } |
195 | |
196 | fz_icc_link * |
197 | fz_new_icc_link(fz_context *ctx, |
198 | fz_colorspace *src, int , |
199 | fz_colorspace *dst, int , |
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 | |
321 | void |
322 | fz_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 | |
350 | void |
351 | fz_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, ; |
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 | |