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