| 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 | |