Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/colorspace.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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/colorspace.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1941 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include "color-imp.h" + +#include <assert.h> +#include <math.h> +#include <string.h> + +#if FZ_ENABLE_ICC + +#include "icc/gray.icc.h" +#include "icc/rgb.icc.h" +#include "icc/cmyk.icc.h" +#include "icc/lab.icc.h" + +void fz_new_colorspace_context(fz_context *ctx) +{ + fz_colorspace_context *cct; + + fz_buffer *gray = NULL; + fz_buffer *rgb = NULL; + fz_buffer *cmyk = NULL; + fz_buffer *lab = NULL; + + fz_var(gray); + fz_var(rgb); + fz_var(cmyk); + fz_var(lab); + + cct = ctx->colorspace = fz_malloc_struct(ctx, fz_colorspace_context); + cct->ctx_refs = 1; + + fz_new_icc_context(ctx); + + ctx->icc_enabled = 1; + + fz_try(ctx) + { + gray = fz_new_buffer_from_shared_data(ctx, resources_icc_gray_icc, resources_icc_gray_icc_len); + rgb = fz_new_buffer_from_shared_data(ctx, resources_icc_rgb_icc, resources_icc_rgb_icc_len); + cmyk = fz_new_buffer_from_shared_data(ctx, resources_icc_cmyk_icc, resources_icc_cmyk_icc_len); + lab = fz_new_buffer_from_shared_data(ctx, resources_icc_lab_icc, resources_icc_lab_icc_len); + cct->gray = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_GRAY, FZ_COLORSPACE_IS_DEVICE, "DeviceGray", gray); + cct->rgb = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_RGB, FZ_COLORSPACE_IS_DEVICE, "DeviceRGB", rgb); + cct->bgr = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_BGR, FZ_COLORSPACE_IS_DEVICE, "DeviceBGR", rgb); + cct->cmyk = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_CMYK, FZ_COLORSPACE_IS_DEVICE, "DeviceCMYK", cmyk); + cct->lab = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_LAB, FZ_COLORSPACE_IS_DEVICE, "Lab", lab); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, gray); + fz_drop_buffer(ctx, rgb); + fz_drop_buffer(ctx, cmyk); + fz_drop_buffer(ctx, lab); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +void fz_enable_icc(fz_context *ctx) +{ + ctx->icc_enabled = 1; +} + +void fz_disable_icc(fz_context *ctx) +{ + ctx->icc_enabled = 0; +} + +#else + +void fz_new_colorspace_context(fz_context *ctx) +{ + fz_colorspace_context *cct; + + cct = ctx->colorspace = fz_malloc_struct(ctx, fz_colorspace_context); + cct->ctx_refs = 1; + + cct->gray = fz_new_colorspace(ctx, FZ_COLORSPACE_GRAY, FZ_COLORSPACE_IS_DEVICE, 1, "DeviceGray"); + cct->rgb = fz_new_colorspace(ctx, FZ_COLORSPACE_RGB, FZ_COLORSPACE_IS_DEVICE, 3, "DeviceRGB"); + cct->bgr = fz_new_colorspace(ctx, FZ_COLORSPACE_BGR, FZ_COLORSPACE_IS_DEVICE, 3, "DeviceBGR"); + cct->cmyk = fz_new_colorspace(ctx, FZ_COLORSPACE_CMYK, FZ_COLORSPACE_IS_DEVICE, 4, "DeviceCMYK"); + cct->lab = fz_new_colorspace(ctx, FZ_COLORSPACE_LAB, FZ_COLORSPACE_IS_DEVICE, 3, "Lab"); +} + +void fz_enable_icc(fz_context *ctx) +{ + fz_warn(ctx, "ICC support is not available"); +} + +void fz_disable_icc(fz_context *ctx) +{ +} + +#endif + +fz_colorspace_context *fz_keep_colorspace_context(fz_context *ctx) +{ + fz_keep_imp(ctx, ctx->colorspace, &ctx->colorspace->ctx_refs); + return ctx->colorspace; +} + +void fz_drop_colorspace_context(fz_context *ctx) +{ + if (fz_drop_imp(ctx, ctx->colorspace, &ctx->colorspace->ctx_refs)) + { + fz_drop_colorspace(ctx, ctx->colorspace->gray); + fz_drop_colorspace(ctx, ctx->colorspace->rgb); + fz_drop_colorspace(ctx, ctx->colorspace->bgr); + fz_drop_colorspace(ctx, ctx->colorspace->cmyk); + fz_drop_colorspace(ctx, ctx->colorspace->lab); +#if FZ_ENABLE_ICC + fz_drop_icc_context(ctx); +#endif + fz_free(ctx, ctx->colorspace); + ctx->colorspace = NULL; + } +} + +fz_colorspace *fz_device_gray(fz_context *ctx) +{ + return ctx->colorspace->gray; +} + +fz_colorspace *fz_device_rgb(fz_context *ctx) +{ + return ctx->colorspace->rgb; +} + +fz_colorspace *fz_device_bgr(fz_context *ctx) +{ + return ctx->colorspace->bgr; +} + +fz_colorspace *fz_device_cmyk(fz_context *ctx) +{ + return ctx->colorspace->cmyk; +} + +fz_colorspace *fz_device_lab(fz_context *ctx) +{ + return ctx->colorspace->lab; +} + +/* Same order as needed by LCMS */ +static const char *fz_intent_names[] = +{ + "Perceptual", + "RelativeColorimetric", + "Saturation", + "AbsoluteColorimetric", +}; + +int fz_lookup_rendering_intent(const char *name) +{ + int i; + for (i = 0; i < (int)nelem(fz_intent_names); i++) + if (!strcmp(name, fz_intent_names[i])) + return i; + return FZ_RI_RELATIVE_COLORIMETRIC; +} + +const char *fz_rendering_intent_name(int ri) +{ + if (ri >= 0 && ri < (int)nelem(fz_intent_names)) + return fz_intent_names[ri]; + return "RelativeColorimetric"; +} + +/* Colorspace feature tests */ + +const char *fz_colorspace_name(fz_context *ctx, fz_colorspace *cs) +{ + return cs ? cs->name : "None"; +} + +enum fz_colorspace_type fz_colorspace_type(fz_context *ctx, fz_colorspace *cs) +{ + return cs ? cs->type : FZ_COLORSPACE_NONE; +} + +int fz_colorspace_n(fz_context *ctx, fz_colorspace *cs) +{ + return cs ? cs->n : 0; +} + +int fz_colorspace_is_gray(fz_context *ctx, fz_colorspace *cs) +{ + return cs && cs->type == FZ_COLORSPACE_GRAY; +} + +int fz_colorspace_is_rgb(fz_context *ctx, fz_colorspace *cs) +{ + return cs && cs->type == FZ_COLORSPACE_RGB; +} + +int fz_colorspace_is_cmyk(fz_context *ctx, fz_colorspace *cs) +{ + return cs && cs->type == FZ_COLORSPACE_CMYK; +} + +int fz_colorspace_is_lab(fz_context *ctx, fz_colorspace *cs) +{ + return cs && cs->type == FZ_COLORSPACE_LAB; +} + +int fz_colorspace_is_indexed(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->type == FZ_COLORSPACE_INDEXED); +} + +int fz_colorspace_is_device_n(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->type == FZ_COLORSPACE_SEPARATION); +} + +int fz_colorspace_is_subtractive(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->type == FZ_COLORSPACE_CMYK || cs->type == FZ_COLORSPACE_SEPARATION); +} + +int fz_colorspace_is_device(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->flags & FZ_COLORSPACE_IS_DEVICE); +} + +int fz_colorspace_is_icc(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->flags & FZ_COLORSPACE_IS_ICC); +} + +int fz_colorspace_is_lab_icc(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->type == FZ_COLORSPACE_LAB) && (cs->flags & FZ_COLORSPACE_IS_ICC); +} + +int fz_colorspace_is_device_gray(fz_context *ctx, fz_colorspace *cs) +{ + return fz_colorspace_is_device(ctx, cs) && fz_colorspace_is_gray(ctx, cs); +} + +int fz_colorspace_is_device_cmyk(fz_context *ctx, fz_colorspace *cs) +{ + return fz_colorspace_is_device(ctx, cs) && fz_colorspace_is_cmyk(ctx, cs); +} + +int fz_colorspace_device_n_has_only_cmyk(fz_context *ctx, fz_colorspace *cs) +{ + return cs && ((cs->flags & FZ_COLORSPACE_HAS_CMYK_AND_SPOTS) == FZ_COLORSPACE_HAS_CMYK); +} + +int fz_colorspace_device_n_has_cmyk(fz_context *ctx, fz_colorspace *cs) +{ + return cs && (cs->flags & FZ_COLORSPACE_HAS_CMYK); +} + +int fz_is_valid_blend_colorspace(fz_context *ctx, fz_colorspace *cs) +{ + return cs == NULL || + cs->type == FZ_COLORSPACE_GRAY || + cs->type == FZ_COLORSPACE_RGB || + cs->type == FZ_COLORSPACE_CMYK; +} + +fz_colorspace *fz_base_colorspace(fz_context *ctx, fz_colorspace *cs) +{ + if (cs == NULL) + return NULL; + if (cs->type == FZ_COLORSPACE_INDEXED) + return cs->u.indexed.base; + return cs; +} + +fz_colorspace * +fz_keep_colorspace(fz_context *ctx, fz_colorspace *cs) +{ + return fz_keep_key_storable(ctx, &cs->key_storable); +} + +void +fz_drop_colorspace(fz_context *ctx, fz_colorspace *cs) +{ + fz_drop_key_storable(ctx, &cs->key_storable); +} + +fz_colorspace * +fz_keep_colorspace_store_key(fz_context *ctx, fz_colorspace *cs) +{ + return fz_keep_key_storable_key(ctx, &cs->key_storable); +} + +void +fz_drop_colorspace_store_key(fz_context *ctx, fz_colorspace *cs) +{ + fz_drop_key_storable_key(ctx, &cs->key_storable); +} + +void +fz_drop_colorspace_imp(fz_context *ctx, fz_storable *cs_) +{ + fz_colorspace *cs = (fz_colorspace *)cs_; + int i; + + if (cs->type == FZ_COLORSPACE_INDEXED) + { + fz_drop_colorspace(ctx, cs->u.indexed.base); + fz_free(ctx, cs->u.indexed.lookup); + } + if (cs->type == FZ_COLORSPACE_SEPARATION) + { + fz_drop_colorspace(ctx, cs->u.separation.base); + cs->u.separation.drop(ctx, cs->u.separation.tint); + for (i = 0; i < FZ_MAX_COLORS; i++) + fz_free(ctx, cs->u.separation.colorant[i]); + } +#if FZ_ENABLE_ICC + if (cs->flags & FZ_COLORSPACE_IS_ICC) + { + fz_drop_icc_profile(ctx, cs->u.icc.profile); + fz_drop_buffer(ctx, cs->u.icc.buffer); + } +#endif + + fz_free(ctx, cs->name); + fz_free(ctx, cs); +} + +fz_colorspace * +fz_new_colorspace(fz_context *ctx, enum fz_colorspace_type type, int flags, int n, const char *name) +{ + fz_colorspace *cs = fz_malloc_struct(ctx, fz_colorspace); + FZ_INIT_KEY_STORABLE(cs, 1, fz_drop_colorspace_imp); + + if (n > FZ_MAX_COLORS) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "too many color components (%d > %d)", n, FZ_MAX_COLORS); + if (n < 1) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "too few color components (%d < 1)", n); + + fz_try(ctx) + { + cs->type = type; + cs->flags = flags; + cs->n = n; + cs->name = Memento_label(fz_strdup(ctx, name ? name : "UNKNOWN"), "cs_name"); + } + fz_catch(ctx) + { + fz_free(ctx, cs); + fz_rethrow(ctx); + } + + return cs; +} + +fz_colorspace * +fz_new_indexed_colorspace(fz_context *ctx, fz_colorspace *base, int high, unsigned char *lookup) +{ + fz_colorspace *cs; + char name[100]; + if (high < 0 || high > 255) + fz_throw(ctx, FZ_ERROR_SYNTAX, "invalid maximum value in indexed colorspace"); + fz_snprintf(name, sizeof name, "Indexed(%d,%s)", high, base->name); + cs = fz_new_colorspace(ctx, FZ_COLORSPACE_INDEXED, 0, 1, name); + cs->u.indexed.base = fz_keep_colorspace(ctx, base); + cs->u.indexed.high = high; + cs->u.indexed.lookup = lookup; + return cs; +} + +fz_colorspace * +fz_new_icc_colorspace(fz_context *ctx, enum fz_colorspace_type type, int flags, const char *name, fz_buffer *buf) +{ +#if FZ_ENABLE_ICC + fz_icc_profile *profile = NULL; + fz_colorspace *cs = NULL; + unsigned char *data; + char name_buf[100]; + size_t size; + int n; + + fz_var(profile); + fz_var(cs); + fz_var(type); + + fz_try(ctx) + { + size = fz_buffer_storage(ctx, buf, &data); + profile = fz_new_icc_profile(ctx, data, size); + n = fz_icc_profile_components(ctx, profile); + switch (type) + { + default: + fz_throw(ctx, FZ_ERROR_SYNTAX, "invalid colorspace type for ICC profile"); + case FZ_COLORSPACE_NONE: + switch (n) + { + default: + fz_throw(ctx, FZ_ERROR_SYNTAX, "ICC profile has unexpected number of channels: %d", n); + case 1: + type = FZ_COLORSPACE_GRAY; + break; + case 3: + if (fz_icc_profile_is_lab(ctx, profile)) + type = FZ_COLORSPACE_LAB; + else + type = FZ_COLORSPACE_RGB; + break; + case 4: + type = FZ_COLORSPACE_CMYK; + break; + } + break; + case FZ_COLORSPACE_GRAY: + if (n != 1) + fz_throw(ctx, FZ_ERROR_SYNTAX, "ICC profile (N=%d) is not Gray", n); + break; + case FZ_COLORSPACE_RGB: + case FZ_COLORSPACE_BGR: + if (n != 3 || fz_icc_profile_is_lab(ctx, profile)) + fz_throw(ctx, FZ_ERROR_SYNTAX, "ICC profile (N=%d) is not RGB", n); + break; + case FZ_COLORSPACE_LAB: + if (n != 3 || !fz_icc_profile_is_lab(ctx, profile)) + fz_throw(ctx, FZ_ERROR_SYNTAX, "ICC profile (N=%d) is not Lab", n); + break; + case FZ_COLORSPACE_CMYK: + if (n != 4) + fz_throw(ctx, FZ_ERROR_SYNTAX, "ICC profile (N=%d) is not CMYK", n); + break; + } + + if (!name) + { + char cmm_name[100]; + fz_icc_profile_name(ctx, profile, cmm_name, sizeof cmm_name); + switch (type) + { + default: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(%d,%s)", n, cmm_name); break; + case FZ_COLORSPACE_GRAY: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(Gray,%s)", cmm_name); break; + case FZ_COLORSPACE_RGB: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(RGB,%s)", cmm_name); break; + case FZ_COLORSPACE_BGR: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(BGR,%s)", cmm_name); break; + case FZ_COLORSPACE_CMYK: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(CMYK,%s)", cmm_name); break; + case FZ_COLORSPACE_LAB: fz_snprintf(name_buf, sizeof name_buf, "ICCBased(Lab,%s)", cmm_name); break; + } + name = name_buf; + } + + cs = fz_new_colorspace(ctx, type, flags | FZ_COLORSPACE_IS_ICC, n, name); + cs->u.icc.buffer = fz_keep_buffer(ctx, buf); + cs->u.icc.profile = profile; + fz_md5_buffer(ctx, buf, cs->u.icc.md5); + } + fz_catch(ctx) + { + fz_drop_icc_profile(ctx, profile); + fz_drop_colorspace(ctx, cs); + fz_rethrow(ctx); + } + return cs; +#else + switch (type) + { + default: fz_throw(ctx, FZ_ERROR_SYNTAX, "unknown colorspace type"); + case FZ_COLORSPACE_GRAY: return fz_keep_colorspace(ctx, fz_device_gray(ctx)); + case FZ_COLORSPACE_RGB: return fz_keep_colorspace(ctx, fz_device_rgb(ctx)); + case FZ_COLORSPACE_BGR: return fz_keep_colorspace(ctx, fz_device_bgr(ctx)); + case FZ_COLORSPACE_CMYK: return fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); + case FZ_COLORSPACE_LAB: return fz_keep_colorspace(ctx, fz_device_lab(ctx)); + } +#endif +} + +fz_colorspace *fz_new_cal_gray_colorspace(fz_context *ctx, float wp[3], float bp[3], float gamma) +{ +#if FZ_ENABLE_ICC + fz_buffer *buf = fz_new_icc_data_from_cal(ctx, wp, bp, &gamma, NULL, 1); + fz_colorspace *cs; + fz_try(ctx) + cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_GRAY, 0, "CalGray", buf); + fz_always(ctx) + fz_drop_buffer(ctx, buf); + fz_catch(ctx) + fz_rethrow(ctx); + return cs; +#else + return fz_keep_colorspace(ctx, fz_device_gray(ctx)); +#endif +} + +fz_colorspace *fz_new_cal_rgb_colorspace(fz_context *ctx, float wp[3], float bp[3], float gamma[3], float matrix[9]) +{ +#if FZ_ENABLE_ICC + fz_buffer *buf = fz_new_icc_data_from_cal(ctx, wp, bp, gamma, matrix, 3); + fz_colorspace *cs; + fz_try(ctx) + cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_RGB, 0, "CalRGB", buf); + fz_always(ctx) + fz_drop_buffer(ctx, buf); + fz_catch(ctx) + fz_rethrow(ctx); + return cs; +#else + return fz_keep_colorspace(ctx, fz_device_rgb(ctx)); +#endif +} + +void fz_colorspace_name_colorant(fz_context *ctx, fz_colorspace *cs, int i, const char *name) +{ + if (i < 0 || i >= cs->n) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Attempt to name out of range colorant"); + if (cs->type != FZ_COLORSPACE_SEPARATION) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Attempt to name colorant for non-separation colorspace"); + + fz_free(ctx, cs->u.separation.colorant[i]); + cs->u.separation.colorant[i] = NULL; + cs->u.separation.colorant[i] = fz_strdup(ctx, name); + + if (!strcmp(name, "Cyan") || !strcmp(name, "Magenta") || !strcmp(name, "Yellow") || !strcmp(name, "Black")) + cs->flags |= FZ_COLORSPACE_HAS_CMYK; + else + cs->flags |= FZ_COLORSPACE_HAS_SPOTS; +} + +const char *fz_colorspace_colorant(fz_context *ctx, fz_colorspace *cs, int i) +{ + if (!cs || i < 0 || i >= cs->n) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Colorant out of range"); + switch (cs->type) + { + case FZ_COLORSPACE_NONE: + return "None"; + case FZ_COLORSPACE_GRAY: + return "Gray"; + case FZ_COLORSPACE_RGB: + if (i == 0) return "Red"; + if (i == 1) return "Green"; + if (i == 2) return "Blue"; + break; + case FZ_COLORSPACE_BGR: + if (i == 0) return "Blue"; + if (i == 1) return "Green"; + if (i == 2) return "Red"; + break; + case FZ_COLORSPACE_CMYK: + if (i == 0) return "Cyan"; + if (i == 1) return "Magenta"; + if (i == 2) return "Yellow"; + if (i == 3) return "Black"; + break; + case FZ_COLORSPACE_LAB: + if (i == 0) return "L*"; + if (i == 1) return "a*"; + if (i == 2) return "b*"; + break; + case FZ_COLORSPACE_INDEXED: + return "Index"; + case FZ_COLORSPACE_SEPARATION: + return cs->u.separation.colorant[i]; + } + return "None"; +} + +void +fz_clamp_color(fz_context *ctx, fz_colorspace *cs, const float *in, float *out) +{ + if (cs->type == FZ_COLORSPACE_LAB) + { + out[0] = fz_clamp(in[0], 0, 100); + out[1] = fz_clamp(in[1], -128, 127); + out[2] = fz_clamp(in[2], -128, 127); + } + else if (cs->type == FZ_COLORSPACE_INDEXED) + { + /* round color index to integer before rescaling to hival */ + out[0] = fz_clamp((int)(in[0]+0.5), 0, cs->u.indexed.high) / 255.0f; + } + else + { + int i, n = cs->n; + for (i = 0; i < n; ++i) + out[i] = fz_clamp(in[i], 0, 1); + } +} + +const fz_color_params fz_default_color_params = { FZ_RI_RELATIVE_COLORIMETRIC, 1, 0, 0 }; + +fz_default_colorspaces *fz_new_default_colorspaces(fz_context *ctx) +{ + fz_default_colorspaces *default_cs = fz_malloc_struct(ctx, fz_default_colorspaces); + default_cs->refs = 1; + default_cs->gray = fz_keep_colorspace(ctx, fz_device_gray(ctx)); + default_cs->rgb = fz_keep_colorspace(ctx, fz_device_rgb(ctx)); + default_cs->cmyk = fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); + default_cs->oi = NULL; + return default_cs; +} + +fz_default_colorspaces *fz_clone_default_colorspaces(fz_context *ctx, fz_default_colorspaces *base) +{ + fz_default_colorspaces *default_cs = fz_malloc_struct(ctx, fz_default_colorspaces); + default_cs->refs = 1; + if (base) + { + default_cs->gray = fz_keep_colorspace(ctx, base->gray); + default_cs->rgb = fz_keep_colorspace(ctx, base->rgb); + default_cs->cmyk = fz_keep_colorspace(ctx, base->cmyk); + default_cs->oi = fz_keep_colorspace(ctx, base->oi); + } + return default_cs; +} + +fz_default_colorspaces *fz_keep_default_colorspaces(fz_context *ctx, fz_default_colorspaces *default_cs) +{ + return fz_keep_imp(ctx, default_cs, &default_cs->refs); +} + +void +fz_drop_default_colorspaces(fz_context *ctx, fz_default_colorspaces *default_cs) +{ + if (fz_drop_imp(ctx, default_cs, &default_cs->refs)) + { + fz_drop_colorspace(ctx, default_cs->gray); + fz_drop_colorspace(ctx, default_cs->rgb); + fz_drop_colorspace(ctx, default_cs->cmyk); + fz_drop_colorspace(ctx, default_cs->oi); + fz_free(ctx, default_cs); + } +} + +fz_colorspace *fz_default_gray(fz_context *ctx, const fz_default_colorspaces *default_cs) +{ + return (default_cs && default_cs->gray) ? default_cs->gray : fz_device_gray(ctx); +} + +fz_colorspace *fz_default_rgb(fz_context *ctx, const fz_default_colorspaces *default_cs) +{ + return (default_cs && default_cs->rgb) ? default_cs->rgb : fz_device_rgb(ctx); +} + +fz_colorspace *fz_default_cmyk(fz_context *ctx, const fz_default_colorspaces *default_cs) +{ + return (default_cs && default_cs->cmyk) ? default_cs->cmyk : fz_device_cmyk(ctx); +} + +fz_colorspace *fz_default_output_intent(fz_context *ctx, const fz_default_colorspaces *default_cs) +{ + return default_cs ? default_cs->oi : NULL; +} + +void fz_set_default_gray(fz_context *ctx, fz_default_colorspaces *default_cs, fz_colorspace *cs) +{ + if (cs->type == FZ_COLORSPACE_GRAY && cs->n == 1) + { + fz_drop_colorspace(ctx, default_cs->gray); + default_cs->gray = fz_keep_colorspace(ctx, cs); + } +} + +void fz_set_default_rgb(fz_context *ctx, fz_default_colorspaces *default_cs, fz_colorspace *cs) +{ + if (cs->type == FZ_COLORSPACE_RGB && cs->n == 3) + { + fz_drop_colorspace(ctx, default_cs->rgb); + default_cs->rgb = fz_keep_colorspace(ctx, cs); + } +} + +void fz_set_default_cmyk(fz_context *ctx, fz_default_colorspaces *default_cs, fz_colorspace *cs) +{ + if (cs->type == FZ_COLORSPACE_CMYK && cs->n == 4) + { + fz_drop_colorspace(ctx, default_cs->cmyk); + default_cs->cmyk = fz_keep_colorspace(ctx, cs); + } +} + +void fz_set_default_output_intent(fz_context *ctx, fz_default_colorspaces *default_cs, fz_colorspace *cs) +{ + fz_drop_colorspace(ctx, default_cs->oi); + default_cs->oi = NULL; + + /* FIXME: Why do we set DefaultXXX along with the output intent?! */ + switch (cs->type) + { + default: + fz_warn(ctx, "Ignoring incompatible output intent: %s.", cs->name); + break; + case FZ_COLORSPACE_GRAY: + default_cs->oi = fz_keep_colorspace(ctx, cs); + if (default_cs->gray == fz_device_gray(ctx)) + fz_set_default_gray(ctx, default_cs, cs); + break; + case FZ_COLORSPACE_RGB: + default_cs->oi = fz_keep_colorspace(ctx, cs); + if (default_cs->rgb == fz_device_rgb(ctx)) + fz_set_default_rgb(ctx, default_cs, cs); + break; + case FZ_COLORSPACE_CMYK: + default_cs->oi = fz_keep_colorspace(ctx, cs); + if (default_cs->cmyk == fz_device_cmyk(ctx)) + fz_set_default_cmyk(ctx, default_cs, cs); + break; + } +} + +/* Link cache */ + +#if FZ_ENABLE_ICC + +typedef struct { + int refs; + unsigned char src_md5[16]; + unsigned char dst_md5[16]; + fz_color_params rend; + unsigned char src_extras; + unsigned char dst_extras; + unsigned char copy_spots; + unsigned char format; + unsigned char proof; + unsigned char bgr; +} fz_link_key; + +static void * +fz_keep_link_key(fz_context *ctx, void *key_) +{ + fz_link_key *key = (fz_link_key *)key_; + return fz_keep_imp(ctx, key, &key->refs); +} + +static void +fz_drop_link_key(fz_context *ctx, void *key_) +{ + fz_link_key *key = (fz_link_key *)key_; + if (fz_drop_imp(ctx, key, &key->refs)) + fz_free(ctx, key); +} + +static int +fz_cmp_link_key(fz_context *ctx, void *k0_, void *k1_) +{ + fz_link_key *k0 = (fz_link_key *)k0_; + fz_link_key *k1 = (fz_link_key *)k1_; + return + memcmp(k0->src_md5, k1->src_md5, 16) == 0 && + memcmp(k0->dst_md5, k1->dst_md5, 16) == 0 && + k0->src_extras == k1->src_extras && + k0->dst_extras == k1->dst_extras && + k0->rend.bp == k1->rend.bp && + k0->rend.ri == k1->rend.ri && + k0->copy_spots == k1->copy_spots && + k0->format == k1->format && + k0->proof == k1->proof && + k0->bgr == k1->bgr; +} + +static void +fz_format_link_key(fz_context *ctx, char *s, size_t n, void *key_) +{ + static const char *hex = "0123456789abcdef"; + fz_link_key *key = (fz_link_key *)key_; + char sm[33], dm[33]; + int i; + for (i = 0; i < 16; ++i) + { + sm[i*2+0] = hex[key->src_md5[i]>>4]; + sm[i*2+1] = hex[key->src_md5[i]&15]; + dm[i*2+0] = hex[key->dst_md5[i]>>4]; + dm[i*2+1] = hex[key->dst_md5[i]&15]; + } + sm[32] = 0; + dm[32] = 0; + fz_snprintf(s, n, "(link src_md5=%s dst_md5=%s)", sm, dm); +} + +static int +fz_make_hash_link_key(fz_context *ctx, fz_store_hash *hash, void *key_) +{ + fz_link_key *key = (fz_link_key *)key_; + memcpy(hash->u.link.dst_md5, key->dst_md5, 16); + memcpy(hash->u.link.src_md5, key->src_md5, 16); + hash->u.link.ri = key->rend.ri; + hash->u.link.bp = key->rend.bp; + hash->u.link.src_extras = key->src_extras; + hash->u.link.dst_extras = key->dst_extras; + hash->u.link.format = key->format; + hash->u.link.proof = key->proof; + hash->u.link.copy_spots = key->copy_spots; + hash->u.link.bgr = key->bgr; + return 1; +} + +static fz_store_type fz_link_store_type = +{ + "fz_icc_link", + fz_make_hash_link_key, + fz_keep_link_key, + fz_drop_link_key, + fz_cmp_link_key, + fz_format_link_key, + NULL +}; + +fz_icc_link * +fz_find_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, + int premult) +{ + fz_icc_link *link, *old_link; + fz_link_key key, *new_key; + + fz_var(link); + + /* Check the storable to see if we have a copy. */ + key.refs = 1; + memcpy(&key.src_md5, src->u.icc.md5, 16); + memcpy(&key.dst_md5, dst->u.icc.md5, 16); + key.rend = rend; + key.src_extras = src_extras; + key.dst_extras = dst_extras; + key.copy_spots = copy_spots; + key.format = (format & 1) | (premult*2); + key.proof = (prf != NULL); + key.bgr = (dst->type == FZ_COLORSPACE_BGR); + + link = fz_find_item(ctx, fz_drop_icc_link_imp, &key, &fz_link_store_type); + if (!link) + { + new_key = fz_malloc_struct(ctx, fz_link_key); + memcpy(new_key, &key, sizeof (fz_link_key)); + fz_try(ctx) + { + link = fz_new_icc_link(ctx, src, src_extras, dst, dst_extras, prf, rend, format, copy_spots, premult); + old_link = fz_store_item(ctx, new_key, link, 1000, &fz_link_store_type); + if (old_link) + { + /* Found one while adding! Perhaps from another thread? */ + fz_drop_icc_link(ctx, link); + link = old_link; + } + } + fz_always(ctx) + { + fz_drop_link_key(ctx, new_key); + } + fz_catch(ctx) + { + fz_drop_icc_link(ctx, link); + fz_rethrow(ctx); + } + } + return link; +} + +#endif + +/* Color conversions */ + +static void indexed_via_base(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst) +{ + fz_colorspace *ss = cc->ss_via; + const unsigned char *lookup = ss->u.indexed.lookup; + int high = ss->u.indexed.high; + int n = ss->u.indexed.base->n; + float base[4]; + int i, k; + + i = src[0] * 255; + i = fz_clampi(i, 0, high); + if (ss->u.indexed.base->type == FZ_COLORSPACE_LAB) + { + base[0] = lookup[i * 3 + 0] * 100 / 255.0f; + base[1] = lookup[i * 3 + 1] - 128; + base[2] = lookup[i * 3 + 2] - 128; + } + else + { + for (k = 0; k < n; ++k) + base[k] = lookup[i * n + k] / 255.0f; + } + + cc->convert_via(ctx, cc, base, dst); +} + +static void separation_via_base(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst) +{ + fz_colorspace *ss = cc->ss_via; + float base[4]; + ss->u.separation.eval(ctx, ss->u.separation.tint, src, ss->n, base, ss->u.separation.base->n); + cc->convert_via(ctx, cc, base, dst); +} + +static void indexed_via_separation_via_base(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst) +{ + fz_colorspace *ss = cc->ss_via; + fz_colorspace *ssep = cc->ss_via->u.indexed.base; + const unsigned char *lookup = ss->u.indexed.lookup; + int high = ss->u.indexed.high; + int n = ss->u.indexed.base->n; + float base[4], mid[FZ_MAX_COLORS]; + int i, k; + + /* First map through the index. */ + i = src[0] * 255; + i = fz_clampi(i, 0, high); + for (k = 0; k < n; ++k) + mid[k] = lookup[i * n + k] / 255.0f; + + /* Then map through the separation. */ + ssep->u.separation.eval(ctx, ssep->u.separation.tint, mid, ssep->n, base, ssep->u.separation.base->n); + + /* Then convert in the base. */ + cc->convert_via(ctx, cc, base, dst); +} + +static void +fz_init_process_color_converter(fz_context *ctx, fz_color_converter *cc, fz_colorspace *ss, fz_colorspace *ds, fz_colorspace *is, fz_color_params params) +{ + if (ss->type == FZ_COLORSPACE_INDEXED) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "base colorspace must not be indexed"); + if (ss->type == FZ_COLORSPACE_SEPARATION) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "base colorspace must not be separation"); + +#if FZ_ENABLE_ICC + if (ctx->icc_enabled) + { + /* Handle identity case. */ + if (ss == ds || (!memcmp(ss->u.icc.md5, ds->u.icc.md5, 16))) + { + cc->convert = fz_lookup_fast_color_converter(ctx, ss, ds); + return; + } + + /* Handle DeviceGray to CMYK as K only. See note in Section 6.3 of PDF spec 1.7. */ + if (ss->type == FZ_COLORSPACE_GRAY && (ss->flags & FZ_COLORSPACE_IS_DEVICE)) + { + if (ds->type == FZ_COLORSPACE_CMYK) + { + cc->convert = fz_lookup_fast_color_converter(ctx, ss, ds); + return; + } + } + + fz_try(ctx) + { + cc->link = fz_find_icc_link(ctx, ss, 0, ds, 0, is, params, 1, 0, 0); + cc->convert = fz_icc_transform_color; + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "cannot create ICC link, falling back to fast color conversion"); + cc->convert = fz_lookup_fast_color_converter(ctx, ss, ds); + } + } + else + { + cc->convert = fz_lookup_fast_color_converter(ctx, ss, ds); + } +#else + cc->convert = fz_lookup_fast_color_converter(ctx, ss, ds); +#endif +} + +void +fz_find_color_converter(fz_context *ctx, fz_color_converter *cc, fz_colorspace *ss, fz_colorspace *ds, fz_separations *dseps, fz_colorspace *is, fz_color_params params) +{ + cc->ds = ds; + cc->dseps = NULL; + cc->dst_n = ds->n; +#if FZ_ENABLE_ICC + cc->link = NULL; +#endif + + if (ds->type == FZ_COLORSPACE_INDEXED) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot convert into Indexed colorspace."); + if (ds->type == FZ_COLORSPACE_SEPARATION) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Cannot convert into Separation colorspace."); + + if (ss->type == FZ_COLORSPACE_INDEXED) + { + if (ss->u.indexed.base->type == FZ_COLORSPACE_SEPARATION) + { + cc->ss = ss->u.indexed.base->u.separation.base; + cc->ss_via = ss; + fz_init_process_color_converter(ctx, cc, cc->ss, ds, is, params); + cc->convert_via = cc->convert; + cc->convert = indexed_via_separation_via_base; + } + else + { + cc->ss = ss->u.indexed.base; + cc->ss_via = ss; + fz_init_process_color_converter(ctx, cc, cc->ss, ds, is, params); + cc->convert_via = cc->convert; + cc->convert = indexed_via_base; + } + } + else if (ss->type == FZ_COLORSPACE_SEPARATION) + { + if (dseps && + fz_init_separation_copy_color_converter(ctx, cc, ss, ds, dseps, is, params)) + { + /* We can just copy separations from ss to dseps */ + cc->dseps = dseps; + cc->dst_n += fz_count_separations(ctx, dseps); + } + else + { + cc->ss = ss->u.separation.base; + cc->ss_via = ss; + fz_init_process_color_converter(ctx, cc, cc->ss, ds, is, params); + cc->convert_via = cc->convert; + cc->convert = separation_via_base; + } + } + else + { + cc->ss = ss; + fz_init_process_color_converter(ctx, cc, ss, ds, is, params); + } +} + +void +fz_drop_color_converter(fz_context *ctx, fz_color_converter *cc) +{ +#if FZ_ENABLE_ICC + if (cc->link) + { + fz_drop_icc_link(ctx, cc->link); + cc->link = NULL; + } +#endif +} + +void +fz_convert_color(fz_context *ctx, fz_colorspace *ss, const float *sv, fz_colorspace *ds, float *dv, fz_colorspace *is, fz_color_params params) +{ + fz_color_converter cc; + fz_find_color_converter(ctx, &cc, ss, ds, NULL, is, params); + cc.convert(ctx, &cc, sv, dv); + fz_drop_color_converter(ctx, &cc); +} + +/* Cached color converter using hash table. */ + +typedef struct fz_cached_color_converter +{ + fz_color_converter base; + fz_hash_table *hash; +} fz_cached_color_converter; + +static void fz_cached_color_convert(fz_context *ctx, fz_color_converter *cc_, const float *ss, float *ds) +{ + fz_cached_color_converter *cc = cc_->opaque; + if (cc->hash) + { + float *val = fz_hash_find(ctx, cc->hash, ss); + int n = cc->base.dst_n * sizeof(float); + + if (val) + { + memcpy(ds, val, n); + return; + } + + cc->base.convert(ctx, &cc->base, ss, ds); + + val = Memento_label(fz_malloc_array(ctx, cc->base.dst_n, float), "cached_color_convert"); + memcpy(val, ds, n); + fz_try(ctx) + fz_hash_insert(ctx, cc->hash, ss, val); + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_free(ctx, val); + } + } + else + { + cc->base.convert(ctx, &cc->base, ss, ds); + } +} + +void fz_init_cached_color_converter(fz_context *ctx, fz_color_converter *cc, fz_colorspace *ss, fz_colorspace *ds, fz_separations *dseps, fz_colorspace *is, fz_color_params params) +{ + int n = ss->n; + fz_cached_color_converter *cached = fz_malloc_struct(ctx, fz_cached_color_converter); + + cc->opaque = cached; + cc->convert = fz_cached_color_convert; + cc->ss = ss; + cc->ds = ds; +#if FZ_ENABLE_ICC + cc->link = NULL; +#endif + + fz_try(ctx) + { + fz_find_color_converter(ctx, &cached->base, ss, ds, dseps, is, params); + if (n * sizeof(float) <= FZ_HASH_TABLE_KEY_LENGTH) + cached->hash = fz_new_hash_table(ctx, 256, n * sizeof(float), -1, fz_free); + else + fz_warn(ctx, "colorspace has too many components to be cached"); + } + fz_catch(ctx) + { + fz_drop_color_converter(ctx, &cached->base); + fz_drop_hash_table(ctx, cached->hash); + fz_free(ctx, cached); + cc->opaque = NULL; + fz_rethrow(ctx); + } +} + +void fz_fin_cached_color_converter(fz_context *ctx, fz_color_converter *cc_) +{ + fz_cached_color_converter *cc; + if (cc_ == NULL) + return; + cc = cc_->opaque; + if (cc == NULL) + return; + cc_->opaque = NULL; + fz_drop_hash_table(ctx, cc->hash); + fz_drop_color_converter(ctx, &cc->base); + fz_free(ctx, cc); +} + +/* Pixmap color conversion */ + +static inline void +template_convert_lab(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params, int sa, int da, int spots) +{ + float srcv[FZ_MAX_COLORS]; + float dstv[FZ_MAX_COLORS]; + size_t w = src->w; + int h = src->h; + fz_colorspace *src_cs = src->colorspace; + fz_colorspace *dst_cs = dst->colorspace; + + unsigned char *s = src->samples; + unsigned char *d = dst->samples; + + int src_n = spots ? src->n : 3+sa; + int dst_c = dst->n - (spots ? dst->s : 0) - da; + int dst_n = dst->n; + + fz_color_converter cc; + int alpha = 255; + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + int k; + + fz_find_color_converter(ctx, &cc, src_cs, dst_cs, NULL, is, params); + while (h--) + { + size_t ww = w; + while (ww--) + { + if (sa) + { + alpha = s[4]; + srcv[0] = fz_div255(s[0], alpha) / 255.0f * 100; + srcv[1] = fz_div255(s[1], alpha) - 128; + srcv[2] = fz_div255(s[2], alpha) - 128; + } + else + { + srcv[0] = s[0] / 255.0f * 100; + srcv[1] = s[1] - 128; + srcv[2] = s[2] - 128; + } + s += src_n; + + cc.convert(ctx, &cc, srcv, dstv); + + if (da) + { + for (k = 0; k < dst_c; k++) + *d++ = fz_mul255(dstv[k] * 255, alpha); + /* Just fill in spots as empty */ + if (spots) + for (; k < dst_n; k++) + *d++ = 0; + *d++ = alpha; + } + else + { + for (k = 0; k < dst_c; k++) + *d++ = dstv[k] * 255; + if (spots) + for (; k < dst_n; k++) + *d++ = 0; + } + } + d += d_line_inc; + s += s_line_inc; + } + fz_drop_color_converter(ctx, &cc); +} + +static void convert_lab(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 0, 0, 0); +} + +static void convert_lab_sa(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 1, 0, 0); +} + +static void convert_lab_da(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 0, 1, 0); +} + +static void convert_lab_sa_da(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 1, 1, 0); +} + +static void convert_lab_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 0, 0, 1); +} + +static void convert_lab_sa_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 1, 0, 1); +} + +static void convert_lab_da_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 0, 1, 1); +} + +static void convert_lab_sa_da_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_convert_lab(ctx, src, dst, is, params, 1, 1, 1); +} + +static inline void +template_brute_force(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params, int sa, int da, int spots) +{ + float srcv[FZ_MAX_COLORS]; + float dstv[FZ_MAX_COLORS]; + size_t w = src->w; + int h = src->h; + fz_colorspace *src_cs = src->colorspace; + fz_colorspace *dst_cs = dst->colorspace; + + unsigned char *s = src->samples; + unsigned char *d = dst->samples; + + int src_c = src->n - (spots ? src->s : 0) - sa; + int src_n = src->n; + int dst_c = dst->n - (spots ? dst->s : 0) - da; + int dst_n = dst->n; + + fz_color_converter cc; + int alpha = 255; + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + int k; + + fz_find_color_converter(ctx, &cc, src_cs, dst_cs, NULL, is, params); + while (h--) + { + size_t ww = w; + while (ww--) + { + if (sa) + { + alpha = s[src_n]; + for (k = 0; k < src_c; k++) + srcv[k] = fz_div255(s[k], alpha) / 255.0f; + } + else + { + for (k = 0; k < src_c; k++) + srcv[k] = s[k] / 255.0f; + } + s += src_n; + + cc.convert(ctx, &cc, srcv, dstv); + + if (da) + { + for (k = 0; k < dst_c; k++) + *d++ = fz_mul255(dstv[k] * 255, alpha); + if (spots) + for (; k < dst_n; k++) + *d++ = 0; + *d++ = alpha; + } + else + { + for (k = 0; k < dst_c; k++) + *d++ = dstv[k] * 255; + if (spots) + for (; k < dst_n; k++) + *d++ = 0; + } + } + d += d_line_inc; + s += s_line_inc; + } + fz_drop_color_converter(ctx, &cc); +} + +static void brute_force(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 0, 0, 0); +} + +static void brute_force_sa(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 1, 0, 0); +} + +static void brute_force_da(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 0, 1, 0); +} + +static void brute_force_sa_da(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 1, 1, 0); +} + +static void brute_force_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 0, 0, 1); +} + +static void brute_force_sa_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 1, 0, 1); +} + +static void brute_force_da_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 0, 1, 1); +} + +static void brute_force_sa_da_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + template_brute_force(ctx, src, dst, is, params, 1, 1, 1); +} + +static void +lookup_1d(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + float srcv[FZ_MAX_COLORS]; + float dstv[FZ_MAX_COLORS]; + size_t w = src->w; + int h = src->h; + fz_colorspace *src_cs = src->colorspace; + fz_colorspace *dst_cs = dst->colorspace; + + unsigned char *s = src->samples; + unsigned char *d = dst->samples; + + int sa = src->alpha; + int da = dst->alpha; + int dst_s = dst->s; + int dst_n = dst->n; + int dst_c = dst_n - dst_s - da; + + fz_color_converter cc; + int alpha = 255; + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + int i, k; + + unsigned char lookup[FZ_MAX_COLORS * 256]; + + fz_find_color_converter(ctx, &cc, src_cs, dst_cs, NULL, is, params); + for (i = 0; i < 256; i++) + { + srcv[0] = i / 255.0f; + cc.convert(ctx, &cc, srcv, dstv); + for (k = 0; k < dst_c; k++) + lookup[i * dst_c + k] = dstv[k] * 255; + } + fz_drop_color_converter(ctx, &cc); + + while (h--) + { + size_t ww = w; + while (ww--) + { + if (sa) + { + alpha = s[1]; + i = fz_div255(s[0], alpha); + s += 2; + } + else + { + i = *s++; + } + + if (da) + { + for (k = 0; k < dst_c; k++) + *d++ = fz_mul255(lookup[i * dst_c + k], alpha); + for (; k < dst_n; k++) + *d++ = 0; + *d++ = alpha; + } + else + { + for (k = 0; k < dst_c; k++) + *d++ = lookup[i * dst_c + k]; + for (; k < dst_n; k++) + *d++ = 0; + } + } + d += d_line_inc; + s += s_line_inc; + } +} + +static void +memoize_nospots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + float srcv[FZ_MAX_COLORS]; + float dstv[FZ_MAX_COLORS]; + size_t w = src->w; + int h = src->h; + fz_colorspace *src_cs = src->colorspace; + fz_colorspace *dst_cs = dst->colorspace; + + unsigned char *s = src->samples; + unsigned char *d = dst->samples; + + int sa = src->alpha; + int src_s = src->s; + int src_n = src->n; + int src_c = src_n - src_s - sa; + int da = dst->alpha; + int dst_s = dst->s; + int dst_n = dst->n; + int dst_c = dst_n - dst_s - da; + + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + int k; + + fz_hash_table *lookup; + unsigned char *color; + unsigned char dummy = s[0] ^ 255; + unsigned char *sold = &dummy; + unsigned char *dold; + fz_color_converter cc; + int alpha = 255; + + lookup = fz_new_hash_table(ctx, 509, src_n, -1, NULL); + fz_find_color_converter(ctx, &cc, src_cs, dst_cs, NULL, is, params); + + fz_try(ctx) + { + while (h--) + { + size_t ww = w; + while (ww--) + { + if (*s == *sold && memcmp(sold, s, src_n) == 0) + { + sold = s; + memcpy(d, dold, dst_n); + } + else + { + sold = s; + dold = d; + color = fz_hash_find(ctx, lookup, s); + if (color) + { + memcpy(d, color, dst_n); + } + else + { + if (sa) + { + alpha = s[src_c]; + for (k = 0; k < src_c; k++) + srcv[k] = fz_div255(s[k], alpha) / 255.0f; + } + else + { + for (k = 0; k < src_c; k++) + srcv[k] = s[k] / 255.0f; + } + + cc.convert(ctx, &cc, srcv, dstv); + + if (da) + { + for (k = 0; k < dst_c; k++) + d[k] = fz_mul255(dstv[k] * 255, alpha); + d[k] = alpha; + } + else + { + for (k = 0; k < dst_c; k++) + d[k] = dstv[k] * 255; + } + + fz_hash_insert(ctx, lookup, s, d); + } + } + s += src_n; + d += dst_n; + } + d += d_line_inc; + s += s_line_inc; + } + } + fz_always(ctx) + { + fz_drop_color_converter(ctx, &cc); + fz_drop_hash_table(ctx, lookup); + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +static void +memoize_spots(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params) +{ + float srcv[FZ_MAX_COLORS]; + float dstv[FZ_MAX_COLORS]; + size_t w = src->w; + int h = src->h; + fz_colorspace *src_cs = src->colorspace; + fz_colorspace *dst_cs = dst->colorspace; + + unsigned char *s = src->samples; + unsigned char *d = dst->samples; + + int sa = src->alpha; + int src_s = src->s; + int src_n = src->n; + int src_c = src_n - src_s - sa; + int src_m = src_c + sa; + int da = dst->alpha; + int dst_s = dst->s; + int dst_n = dst->n; + int dst_c = dst_n - dst_s - da; + + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + int k; + + fz_hash_table *lookup; + unsigned char *color; + unsigned char sold[FZ_MAX_COLORS]; + unsigned char dold[FZ_MAX_COLORS]; + fz_color_converter cc; + int alpha = 255; + + sold[0] = s[0] ^ 255; + + lookup = fz_new_hash_table(ctx, 509, src_m, -1, NULL); + fz_find_color_converter(ctx, &cc, src_cs, dst_cs, NULL, is, params); + + fz_try(ctx) + { + while (h--) + { + size_t ww = w; + while (ww--) + { + if (*s == *sold && memcmp(sold, s, src_m) == 0) + { + memcpy(d, dold, dst_c); + if (dst_s) + memset(d + dst_c, 0, dst_s); + if (da) + d[dst_n-1] = sold[src_m]; + } + else + { + memcpy(sold, s, src_m); + if (sa) + sold[src_m] = s[src_n-1]; + color = fz_hash_find(ctx, lookup, sold); + if (color) + { + memcpy(d, color, dst_n); + } + else + { + if (sa) + { + alpha = s[src_c]; + for (k = 0; k < src_c; k++) + srcv[k] = fz_div255(s[k], alpha) / 255.0f; + } + else + { + for (k = 0; k < src_c; k++) + srcv[k] = s[k] / 255.0f; + } + + cc.convert(ctx, &cc, srcv, dstv); + + if (da) + { + for (k = 0; k < dst_c; k++) + d[k] = fz_mul255(dstv[k] * 255, alpha); + if (dst_s) + memset(d + dst_c, 0, dst_s); + dold[dst_c] = d[dst_n-1] = alpha; + } + else + { + for (k = 0; k < dst_c; k++) + d[k] = dstv[k] * 255; + if (dst_s) + memset(d + dst_c, 0, dst_s); + } + memcpy(dold, d, dst_c); + + fz_hash_insert(ctx, lookup, sold, dold); + } + } + s += src_n; + d += dst_n; + } + d += d_line_inc; + s += s_line_inc; + } + } + fz_always(ctx) + { + fz_drop_color_converter(ctx, &cc); + fz_drop_hash_table(ctx, lookup); + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +void +fz_convert_slow_pixmap_samples(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, fz_colorspace *is, fz_color_params params, int copy_spots) +{ + int sa = src->alpha; + int src_s = src->s; + int src_c = src->n - src_s - sa; + int da = dst->alpha; + int dst_s = dst->s; + size_t w = src->w; + int h = src->h; + ptrdiff_t d_line_inc = dst->stride - w * dst->n; + ptrdiff_t s_line_inc = src->stride - w * src->n; + + fz_colorspace *ss = src->colorspace; + + if ((int)w < 0 || h < 0) + return; + + assert(src->w == dst->w && src->h == dst->h); + + if (d_line_inc == 0 && s_line_inc == 0) + { + w *= h; + h = 1; + } + + if (src_s != 0 || dst_s != 0) + { + fz_warn(ctx, "Spots dropped during pixmap conversion"); + } + + /* Special case for Lab colorspace (scaling of components to float) */ + if (ss->type == FZ_COLORSPACE_LAB) + { + if (src_s == 0 && dst_s == 0) + { + if (sa) + { + if (da) + convert_lab_sa_da(ctx, src, dst, is, params); + else + convert_lab_sa(ctx, src, dst, is, params); + } + else + { + if (da) + convert_lab_da(ctx, src, dst, is, params); + else + convert_lab(ctx, src, dst, is, params); + } + } + else + { + if (sa) + { + if (da) + convert_lab_sa_da_spots(ctx, src, dst, is, params); + else + convert_lab_sa_spots(ctx, src, dst, is, params); + } + else + { + if (da) + convert_lab_da_spots(ctx, src, dst, is, params); + else + convert_lab_spots(ctx, src, dst, is, params); + } + } + } + + /* Brute-force for small images */ + else if (w*h < 256) + { + if (src_s == 0 && dst_s == 0) + { + if (sa) + { + if (da) + brute_force_sa_da(ctx, src, dst, is, params); + else + brute_force_sa(ctx, src, dst, is, params); + } + else + { + if (da) + brute_force_da(ctx, src, dst, is, params); + else + brute_force(ctx, src, dst, is, params); + } + } + else + { + if (sa) + { + if (da) + brute_force_sa_da_spots(ctx, src, dst, is, params); + else + brute_force_sa_spots(ctx, src, dst, is, params); + } + else + { + if (da) + brute_force_da_spots(ctx, src, dst, is, params); + else + brute_force_spots(ctx, src, dst, is, params); + } + } + } + + /* 1-d lookup table for single channel colorspaces */ + else if (src_c == 1) + { + lookup_1d(ctx, src, dst, is, params); + } + + /* Memoize colors using a hash table for the general case */ + else + { + if (src_s == 0 && dst_s == 0) + memoize_nospots(ctx, src, dst, is, params); + else + memoize_spots(ctx, src, dst, is, params); + } +} + +void +fz_convert_pixmap_samples(fz_context *ctx, const fz_pixmap *src, fz_pixmap *dst, + fz_colorspace *prf, + const fz_default_colorspaces *default_cs, + fz_color_params params, + int copy_spots) +{ +#if FZ_ENABLE_ICC + fz_colorspace *ss = src->colorspace; + fz_colorspace *ds = dst->colorspace; + fz_pixmap *base_idx = NULL; + fz_pixmap *base_sep = NULL; + fz_icc_link *link = NULL; + + fz_var(link); + fz_var(base_idx); + fz_var(base_sep); + + if (!ds) + { + fz_fast_any_to_alpha(ctx, src, dst, copy_spots); + return; + } + + fz_try(ctx) + { + /* Treat any alpha-only pixmap as being device gray here. */ + if (!ss) + ss = fz_device_gray(ctx); + + /* Convert indexed into base colorspace. */ + if (ss->type == FZ_COLORSPACE_INDEXED) + { + src = base_idx = fz_convert_indexed_pixmap_to_base(ctx, src); + ss = src->colorspace; + } + + /* Convert separation into base colorspace. */ + if (ss->type == FZ_COLORSPACE_SEPARATION) + { + src = base_sep = fz_convert_separation_pixmap_to_base(ctx, src); + ss = src->colorspace; + } + + /* Substitute Device colorspace with page Default colorspace: */ + if (ss->flags & FZ_COLORSPACE_IS_DEVICE) + { + switch (ss->type) + { + default: break; + case FZ_COLORSPACE_GRAY: ss = fz_default_gray(ctx, default_cs); break; + case FZ_COLORSPACE_RGB: ss = fz_default_rgb(ctx, default_cs); break; + case FZ_COLORSPACE_CMYK: ss = fz_default_cmyk(ctx, default_cs); break; + } + } + + if (!ctx->icc_enabled) + { + fz_convert_fast_pixmap_samples(ctx, src, dst, copy_spots); + } + + /* Handle identity case. */ + else if (ss == ds || (!memcmp(ss->u.icc.md5, ds->u.icc.md5, 16))) + { + fz_convert_fast_pixmap_samples(ctx, src, dst, copy_spots); + } + + /* Handle DeviceGray to CMYK as K only. See note in Section 6.3 of PDF spec 1.7. */ + else if ((ss->flags & FZ_COLORSPACE_IS_DEVICE) && + (ss->type == FZ_COLORSPACE_GRAY) && + (ds->type == FZ_COLORSPACE_CMYK)) + { + fz_convert_fast_pixmap_samples(ctx, src, dst, copy_spots); + } + + /* Use slow conversion path for indexed. */ + else if (ss->type == FZ_COLORSPACE_INDEXED) + { + fz_convert_slow_pixmap_samples(ctx, src, dst, prf, params, copy_spots); + } + + /* Use slow conversion path for separation. */ + else if (ss->type == FZ_COLORSPACE_SEPARATION) + { + fz_convert_slow_pixmap_samples(ctx, src, dst, prf, params, copy_spots); + } + + else + { + fz_try(ctx) + { + int sx = src->s + src->alpha; + int dx = dst->s + dst->alpha; + /* If there are no spots to copy, we might as well copy spots! */ + int effectively_copying_spots = copy_spots || (src->s == 0 && dst->s == 0); + /* If we have alpha, we're preserving spots and we have the same number + * of 'extra' (non process, spots+alpha) channels (i.e. sx == dx), then + * we get lcms2 to do the premultiplication handling for us. If not, + * fz_icc_transform_pixmap will have to do it by steam. */ + int premult = src->alpha && (sx == dx) && effectively_copying_spots; + link = fz_find_icc_link(ctx, ss, sx, ds, dx, prf, params, 0, effectively_copying_spots, premult); + fz_icc_transform_pixmap(ctx, link, src, dst, effectively_copying_spots); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "falling back to fast color conversion"); + fz_convert_fast_pixmap_samples(ctx, src, dst, copy_spots); + } + } + } + fz_always(ctx) + { + fz_drop_icc_link(ctx, link); + fz_drop_pixmap(ctx, base_sep); + fz_drop_pixmap(ctx, base_idx); + } + fz_catch(ctx) + fz_rethrow(ctx); +#else + fz_convert_fast_pixmap_samples(ctx, src, dst, copy_spots); +#endif +} + +void fz_colorspace_digest(fz_context *ctx, fz_colorspace *cs, unsigned char digest[16]) +{ +#if FZ_ENABLE_ICC + if (!fz_colorspace_is_icc(ctx, cs)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "must have icc profile for colorspace digest"); + memcpy(digest, cs->u.icc.md5, 16); +#else + fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "ICC support disabled"); +#endif +}
