Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/color-icc-create.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/color-icc-create.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,641 @@ +#include "mupdf/fitz.h" +#include "icc34.h" + +#include <string.h> + +#define SAVEICCPROFILE 0 +#define ICC_HEADER_SIZE 128 +#define ICC_TAG_SIZE 12 +#define ICC_NUMBER_COMMON_TAGS 2 +#define ICC_XYZPT_SIZE 12 +#define ICC_DATATYPE_SIZE 8 +#define D50_X 0.9642f +#define D50_Y 1.0f +#define D50_Z 0.8249f +static const char copy_right[] = "Copyright Artifex Software 2020"; +#if SAVEICCPROFILE +unsigned int icc_debug_index = 0; +#endif + +typedef struct +{ + icTagSignature sig; + icUInt32Number offset; + icUInt32Number size; + unsigned char byte_padding; +} fz_icc_tag; + +#if SAVEICCPROFILE +static void +save_profile(fz_context *ctx, fz_buffer *buf, const char *name) +{ + char full_file_name[50]; + fz_snprintf(full_file_name, sizeof full_file_name, "profile%d-%s.icc", icc_debug_index, name); + fz_save_buffer(ctx, buf, full_file_name); + icc_debug_index++; +} +#endif + +static void +fz_append_byte_n(fz_context *ctx, fz_buffer *buf, int c, int n) +{ + int k; + for (k = 0; k < n; k++) + fz_append_byte(ctx, buf, c); +} + +static int +get_padding(int x) +{ + return (4 - x % 4) % 4; +} + +static void +setdatetime(fz_context *ctx, icDateTimeNumber *datetime) +{ + datetime->day = 0; + datetime->hours = 0; + datetime->minutes = 0; + datetime->month = 0; + datetime->seconds = 0; + datetime->year = 0; +} + +static void +add_gammadata(fz_context *ctx, fz_buffer *buf, unsigned short gamma, icTagTypeSignature curveType) +{ + fz_append_int32_be(ctx, buf, curveType); + fz_append_byte_n(ctx, buf, 0, 4); + + /* one entry for gamma */ + fz_append_int32_be(ctx, buf, 1); + + /* The encode (8frac8) gamma, with padding */ + fz_append_int16_be(ctx, buf, gamma); + + /* pad two bytes */ + fz_append_byte_n(ctx, buf, 0, 2); +} + +static unsigned short +float2u8Fixed8(fz_context *ctx, float number_in) +{ + return (unsigned short)(number_in * 256); +} + +static void +add_xyzdata(fz_context *ctx, fz_buffer *buf, icS15Fixed16Number temp_XYZ[]) +{ + int j; + + fz_append_int32_be(ctx, buf, icSigXYZType); + fz_append_byte_n(ctx, buf, 0, 4); + + for (j = 0; j < 3; j++) + fz_append_int32_be(ctx, buf, temp_XYZ[j]); +} + +static icS15Fixed16Number +double2XYZtype(fz_context *ctx, float number_in) +{ + short s; + unsigned short m; + + if (number_in < 0) + number_in = 0; + s = (short)number_in; + m = (unsigned short)((number_in - s) * 65536); + return (icS15Fixed16Number) ((s << 16) | m); +} + +static void +get_D50(fz_context *ctx, icS15Fixed16Number XYZ[]) +{ + XYZ[0] = double2XYZtype(ctx, D50_X); + XYZ[1] = double2XYZtype(ctx, D50_Y); + XYZ[2] = double2XYZtype(ctx, D50_Z); +} + +static void +get_XYZ_doubletr(fz_context *ctx, icS15Fixed16Number XYZ[], float vector[]) +{ + XYZ[0] = double2XYZtype(ctx, vector[0]); + XYZ[1] = double2XYZtype(ctx, vector[1]); + XYZ[2] = double2XYZtype(ctx, vector[2]); +} + +static void +add_desc_tag(fz_context *ctx, fz_buffer *buf, const char text[], fz_icc_tag tag_list[], int curr_tag) +{ + size_t len = strlen(text); + + fz_append_int32_be(ctx, buf, icSigTextDescriptionType); + fz_append_byte_n(ctx, buf, 0, 4); + fz_append_int32_be(ctx, buf, (int)len + 1); + fz_append_string(ctx, buf, text); + /* 1 + 4 + 4 + 2 + 1 + 67 */ + fz_append_byte_n(ctx, buf, 0, 79); + fz_append_byte_n(ctx, buf, 0, tag_list[curr_tag].byte_padding); +} + +static void +add_text_tag(fz_context *ctx, fz_buffer *buf, const char text[], fz_icc_tag tag_list[], int curr_tag) +{ + fz_append_int32_be(ctx, buf, icSigTextType); + fz_append_byte_n(ctx, buf, 0, 4); + fz_append_string(ctx, buf, text); + fz_append_byte(ctx, buf, 0); + fz_append_byte_n(ctx, buf, 0, tag_list[curr_tag].byte_padding); +} + +static void +add_common_tag_data(fz_context *ctx, fz_buffer *buf, fz_icc_tag tag_list[], const char *desc_name) +{ + add_desc_tag(ctx, buf, desc_name, tag_list, 0); + add_text_tag(ctx, buf, copy_right, tag_list, 1); +} + +static void +init_common_tags(fz_context *ctx, fz_icc_tag tag_list[], int num_tags, int *last_tag, const char *desc_name) +{ + int curr_tag, temp_size; + + if (*last_tag < 0) + curr_tag = 0; + else + curr_tag = (*last_tag) + 1; + + tag_list[curr_tag].offset = ICC_HEADER_SIZE + num_tags * ICC_TAG_SIZE + 4; + tag_list[curr_tag].sig = icSigProfileDescriptionTag; + + /* temp_size = DATATYPE_SIZE + 4 (zeros) + 4 (len) + strlen(desc_name) + 1 (null) + 4 + 4 + 2 + 1 + 67 + bytepad; */ + temp_size = (int)strlen(desc_name) + 91; + + tag_list[curr_tag].byte_padding = get_padding(temp_size); + tag_list[curr_tag].size = temp_size + tag_list[curr_tag].byte_padding; + curr_tag++; + tag_list[curr_tag].offset = tag_list[curr_tag - 1].offset + tag_list[curr_tag - 1].size; + tag_list[curr_tag].sig = icSigCopyrightTag; + + /* temp_size = DATATYPE_SIZE + 4 (zeros) + strlen(copy_right) + 1 (null); */ + temp_size = (int)strlen(copy_right) + 9; + tag_list[curr_tag].byte_padding = get_padding(temp_size); + tag_list[curr_tag].size = temp_size + tag_list[curr_tag].byte_padding; + *last_tag = curr_tag; +} + +static void +copy_header(fz_context *ctx, fz_buffer *buffer, icHeader *header) +{ + fz_append_int32_be(ctx, buffer, header->size); + fz_append_byte_n(ctx, buffer, 0, 4); + fz_append_int32_be(ctx, buffer, header->version); + fz_append_int32_be(ctx, buffer, header->deviceClass); + fz_append_int32_be(ctx, buffer, header->colorSpace); + fz_append_int32_be(ctx, buffer, header->pcs); + fz_append_byte_n(ctx, buffer, 0, 12); + fz_append_int32_be(ctx, buffer, header->magic); + fz_append_int32_be(ctx, buffer, header->platform); + fz_append_byte_n(ctx, buffer, 0, 24); + fz_append_int32_be(ctx, buffer, header->illuminant.X); + fz_append_int32_be(ctx, buffer, header->illuminant.Y); + fz_append_int32_be(ctx, buffer, header->illuminant.Z); + fz_append_byte_n(ctx, buffer, 0, 48); +} + +static void +setheader_common(fz_context *ctx, icHeader *header) +{ + header->cmmId = 0; + header->version = 0x02200000; + setdatetime(ctx, &(header->date)); + header->magic = icMagicNumber; + header->platform = icSigMacintosh; + header->flags = 0; + header->manufacturer = 0; + header->model = 0; + header->attributes[0] = 0; + header->attributes[1] = 0; + header->renderingIntent = 3; + header->illuminant.X = double2XYZtype(ctx, (float) 0.9642); + header->illuminant.Y = double2XYZtype(ctx, (float) 1.0); + header->illuminant.Z = double2XYZtype(ctx, (float) 0.8249); + header->creator = 0; + memset(header->reserved, 0, 44); +} + +static void +copy_tagtable(fz_context *ctx, fz_buffer *buf, fz_icc_tag *tag_list, int num_tags) +{ + int k; + + fz_append_int32_be(ctx, buf, num_tags); + for (k = 0; k < num_tags; k++) + { + fz_append_int32_be(ctx, buf, tag_list[k].sig); + fz_append_int32_be(ctx, buf, tag_list[k].offset); + fz_append_int32_be(ctx, buf, tag_list[k].size); + } +} + +static void +init_tag(fz_context *ctx, fz_icc_tag tag_list[], int *last_tag, icTagSignature tagsig, int datasize) +{ + int curr_tag = (*last_tag) + 1; + + tag_list[curr_tag].offset = tag_list[curr_tag - 1].offset + tag_list[curr_tag - 1].size; + tag_list[curr_tag].sig = tagsig; + tag_list[curr_tag].byte_padding = get_padding(ICC_DATATYPE_SIZE + datasize); + tag_list[curr_tag].size = ICC_DATATYPE_SIZE + datasize + tag_list[curr_tag].byte_padding; + *last_tag = curr_tag; +} + +static void +matrixmult(fz_context *ctx, float leftmatrix[], int nlrow, int nlcol, float rightmatrix[], int nrrow, int nrcol, float result[]) +{ + float *curr_row; + int k, l, j, ncols, nrows; + float sum; + + nrows = nlrow; + ncols = nrcol; + if (nlcol == nrrow) + { + for (k = 0; k < nrows; k++) + { + curr_row = &(leftmatrix[k*nlcol]); + for (l = 0; l < ncols; l++) + { + sum = 0.0; + for (j = 0; j < nlcol; j++) + sum = sum + curr_row[j] * rightmatrix[j*nrcol + l]; + result[k*ncols + l] = sum; + } + } + } +} + +static void +apply_adaption(fz_context *ctx, float matrix[], float in[], float out[]) +{ + out[0] = matrix[0] * in[0] + matrix[1] * in[1] + matrix[2] * in[2]; + out[1] = matrix[3] * in[0] + matrix[4] * in[1] + matrix[5] * in[2]; + out[2] = matrix[6] * in[0] + matrix[7] * in[1] + matrix[8] * in[2]; +} + +/* + Compute the CAT02 transformation to get us from the Cal White point to the + D50 white point +*/ +static void +gsicc_create_compute_cam(fz_context *ctx, float white_src[], float *cam) +{ + float cat02matrix[] = { 0.7328f, 0.4296f, -0.1624f, -0.7036f, 1.6975f, 0.0061f, 0.003f, 0.0136f, 0.9834f }; + float cat02matrixinv[] = { 1.0961f, -0.2789f, 0.1827f, 0.4544f, 0.4735f, 0.0721f, -0.0096f, -0.0057f, 1.0153f }; + float vonkries_diag[9]; + float temp_matrix[9]; + float lms_wp_src[3], lms_wp_des[3]; + int k; + float d50[3] = { D50_X, D50_Y, D50_Z }; + + matrixmult(ctx, cat02matrix, 3, 3, white_src, 3, 1, lms_wp_src); + matrixmult(ctx, cat02matrix, 3, 3, d50, 3, 1, lms_wp_des); + memset(&(vonkries_diag[0]), 0, sizeof(float) * 9); + + for (k = 0; k < 3; k++) + { + if (lms_wp_src[k] > 0) + vonkries_diag[k * 3 + k] = lms_wp_des[k] / lms_wp_src[k]; + else + vonkries_diag[k * 3 + k] = 1; + } + matrixmult(ctx, &(vonkries_diag[0]), 3, 3, cat02matrix, 3, 3, temp_matrix); + matrixmult(ctx, &(cat02matrixinv[0]), 3, 3, temp_matrix, 3, 3, cam); +} + +/* The algorithm used by the following few routines comes from + * LCMS2. We use this for converting the XYZ primaries + whitepoint + * supplied to fz_new_icc_data_from_cal into the correct form for + * writing into the profile. + * + * Prior to this code, we were, in some cases (such as with + * the color data from pal8v4.bmp) ending up with a profile where + * the whitepoint did not match properly. + */ + +// Determinant lower than that are assumed zero (used on matrix invert) +#define MATRIX_DET_TOLERANCE 0.0001 + +static int +matrix_invert(double *out, const double *in) +{ + double det, c0, c1, c2, absdet; + + c0 = in[4]*in[8] - in[5]*in[7]; + c1 = -in[3]*in[8] + in[5]*in[6]; + c2 = in[3]*in[7] - in[4]*in[6]; + + det = in[0]*c0 + in[1]*c1 + in[2]*c2; + absdet = det; + if (absdet < 0) + absdet = -absdet; + if (absdet < MATRIX_DET_TOLERANCE) + return 1; // singular matrix; can't invert + + out[0] = c0/det; + out[1] = (in[2]*in[7] - in[1]*in[8])/det; + out[2] = (in[1]*in[5] - in[2]*in[4])/det; + out[3] = c1/det; + out[4] = (in[0]*in[8] - in[2]*in[6])/det; + out[5] = (in[2]*in[3] - in[0]*in[5])/det; + out[6] = c2/det; + out[7] = (in[1]*in[6] - in[0]*in[7])/det; + out[8] = (in[0]*in[4] - in[1]*in[3])/det; + + return 0; +} + +static void +transform_vector(double *out, const double *mat, const double *v) +{ + out[0] = mat[0]*v[0] + mat[1]*v[1] + mat[2]*v[2]; + out[1] = mat[3]*v[0] + mat[4]*v[1] + mat[5]*v[2]; + out[2] = mat[6]*v[0] + mat[7]*v[1] + mat[8]*v[2]; +} + +static void +matrix_compose(double *out, const double *a, const double *b) +{ + out[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; + out[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; + out[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8]; + out[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6]; + out[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7]; + out[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8]; + out[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6]; + out[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7]; + out[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8]; +} + +static int +adaptation_matrix(double *out, const double *fromXYZ, const double *toXYZ) +{ + // Bradford matrix + static const double LamRigg[9] = { + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296 + }; + double chad_inv[9]; + double fromRGB[3]; + double toRGB[3]; + double cone[9]; + double tmp[9]; + + /* This should never fail, because it's the same operation + * every time! Could save the work here... */ + if (matrix_invert(chad_inv, LamRigg)) + return 1; + + transform_vector(fromRGB, LamRigg, fromXYZ); + transform_vector(toRGB, LamRigg, toXYZ); + + // Build matrix + cone[0] = toRGB[0]/fromRGB[0]; + cone[1] = 0.0; + cone[2] = 0.0; + cone[3] = 0.0; + cone[4] = toRGB[1]/fromRGB[1]; + cone[5] = 0.0; + cone[6] = 0.0; + cone[7] = 0.0; + cone[8] = toRGB[2]/fromRGB[2]; + + // Normalize + matrix_compose(tmp, cone, LamRigg); + matrix_compose(out, chad_inv, tmp); + + return 0; +} + +static int +build_rgb2XYZ_transfer_matrix(double *out, double *xyYwhite, const double *xyYprimaries) +{ + double whitepoint[3], coef[3]; + double xn, yn; + double xr, yr; + double xg, yg; + double xb, yb; + double primaries[9]; + double result[9]; + double mat[9]; + double bradford[9]; + static double d50XYZ[3] = { 0.9642, 1.0, 0.8249 }; + + xn = xyYwhite[0]; + yn = xyYwhite[1]; + xr = xyYprimaries[0]; + yr = xyYprimaries[1]; + xg = xyYprimaries[3]; + yg = xyYprimaries[4]; + xb = xyYprimaries[6]; + yb = xyYprimaries[7]; + + // Build Primaries matrix + primaries[0] = xr; + primaries[1] = xg; + primaries[2] = xb; + primaries[3] = yr; + primaries[4] = yg; + primaries[5] = yb; + primaries[6] = (1-xr-yr); + primaries[7] = (1-xg-yg); + primaries[8] = (1-xb-yb); + + // Result = Primaries ^ (-1) inverse matrix + if (matrix_invert(&result[0], &primaries[0])) + return 1; + + /* Convert whitepoint from xyY to XYZ. Isn't this where + * we came in? I think we've effectively normalised during + * this process. */ + whitepoint[0] = xn/yn; + whitepoint[1] = 1; + whitepoint[2] = (1-xn-yn)/yn; + + // Across inverse primaries ... + transform_vector(coef, result, whitepoint); + + // Give us the Coefs, then I build transformation matrix + mat[0] = coef[0]*xr; + mat[1] = coef[1]*xg; + mat[2] = coef[2]*xb; + mat[3] = coef[0]*yr; + mat[4] = coef[1]*yg; + mat[5] = coef[2]*yb; + mat[6] = coef[0]*(1.0-xr-yr); + mat[7] = coef[1]*(1.0-xg-yg); + mat[8] = coef[2]*(1.0-xb-yb); + + if (adaptation_matrix(bradford, whitepoint, d50XYZ)) + return 1; + + matrix_compose(out, bradford, mat); + + return 0; +} + + +fz_buffer * +fz_new_icc_data_from_cal(fz_context *ctx, + float wp[3], + float bp[3], + float *gamma, + float matrix[9], + int n) +{ + fz_icc_tag *tag_list; + icProfile iccprofile; + icHeader *header = &(iccprofile.header); + fz_buffer *profile = NULL; + size_t profile_size; + int k; + int num_tags; + unsigned short encode_gamma; + int last_tag; + icS15Fixed16Number temp_XYZ[3]; + icTagSignature TRC_Tags[3] = { icSigRedTRCTag, icSigGreenTRCTag, icSigBlueTRCTag }; + int trc_tag_size; + float cat02[9]; + float black_adapt[3]; + const char *desc_name; + + /* common */ + setheader_common(ctx, header); + header->pcs = icSigXYZData; + profile_size = ICC_HEADER_SIZE; + header->deviceClass = icSigInputClass; + + if (n == 3) + { + desc_name = "CalRGB"; + header->colorSpace = icSigRgbData; + num_tags = 10; /* common (2) + rXYZ, gXYZ, bXYZ, rTRC, gTRC, bTRC, bkpt, wtpt */ + } + else + { + desc_name = "CalGray"; + header->colorSpace = icSigGrayData; + num_tags = 5; /* common (2) + GrayTRC, bkpt, wtpt */ + TRC_Tags[0] = icSigGrayTRCTag; + } + + tag_list = Memento_label(fz_malloc(ctx, sizeof(fz_icc_tag) * num_tags), "icc_tag_list"); + + /* precompute sizes and offsets */ + profile_size += ICC_TAG_SIZE * num_tags; + profile_size += 4; /* number of tags.... */ + last_tag = -1; + init_common_tags(ctx, tag_list, num_tags, &last_tag, desc_name); + if (n == 3) + { + init_tag(ctx, tag_list, &last_tag, icSigRedColorantTag, ICC_XYZPT_SIZE); + init_tag(ctx, tag_list, &last_tag, icSigGreenColorantTag, ICC_XYZPT_SIZE); + init_tag(ctx, tag_list, &last_tag, icSigBlueColorantTag, ICC_XYZPT_SIZE); + } + init_tag(ctx, tag_list, &last_tag, icSigMediaWhitePointTag, ICC_XYZPT_SIZE); + init_tag(ctx, tag_list, &last_tag, icSigMediaBlackPointTag, ICC_XYZPT_SIZE); + + /* 4 for count, 2 for gamma, Extra 2 bytes for 4 byte alignment requirement */ + trc_tag_size = 8; + for (k = 0; k < n; k++) + init_tag(ctx, tag_list, &last_tag, TRC_Tags[k], trc_tag_size); + for (k = 0; k < num_tags; k++) + profile_size += tag_list[k].size; + + fz_var(profile); + + /* Allocate buffer */ + fz_try(ctx) + { + profile = fz_new_buffer(ctx, profile_size); + + /* Header */ + header->size = (icUInt32Number)profile_size; + copy_header(ctx, profile, header); + + /* Tag table */ + copy_tagtable(ctx, profile, tag_list, num_tags); + + /* Common tags */ + add_common_tag_data(ctx, profile, tag_list, desc_name); + + /* Get the cat02 matrix */ + gsicc_create_compute_cam(ctx, wp, cat02); + + /* The matrix */ + if (n == 3) + { + /* Convert whitepoint from XYZ to xyY */ + double xyz = wp[0] + wp[1] + wp[2]; + double whitexyY[3] = { wp[0] / xyz, wp[1] / xyz, 1.0 }; + /* Convert primaries from XYZ to xyY */ + double matrix012 = matrix[0] + matrix[1] + matrix[2]; + double matrix345 = matrix[3] + matrix[4] + matrix[5]; + double matrix678 = matrix[6] + matrix[7] + matrix[8]; + double primariesxyY[9] = { + matrix[0] / matrix012, + matrix[1] / matrix012, + matrix[1], + matrix[3] / matrix345, + matrix[4] / matrix345, + matrix[5], + matrix[6] / matrix678, + matrix[7] / matrix678, + matrix[8] + }; + double primaries[9]; + + if (build_rgb2XYZ_transfer_matrix(primaries, whitexyY, primariesxyY)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "CalRGB profile creation failed; bad values"); + + for (k = 0; k < 3; k++) + { + float primary[3] = { primaries[k+0], primaries[k+3], primaries[k+6] }; + get_XYZ_doubletr(ctx, temp_XYZ, primary); + add_xyzdata(ctx, profile, temp_XYZ); + } + } + + /* White and black points. WP is D50 */ + get_D50(ctx, temp_XYZ); + add_xyzdata(ctx, profile, temp_XYZ); + + /* Black point. Apply cat02*/ + apply_adaption(ctx, cat02, bp, &(black_adapt[0])); + get_XYZ_doubletr(ctx, temp_XYZ, &(black_adapt[0])); + add_xyzdata(ctx, profile, temp_XYZ); + + /* Gamma */ + for (k = 0; k < n; k++) + { + encode_gamma = float2u8Fixed8(ctx, gamma[k]); + add_gammadata(ctx, profile, encode_gamma, icSigCurveType); + } + } + fz_always(ctx) + fz_free(ctx, tag_list); + fz_catch(ctx) + { + fz_drop_buffer(ctx, profile); + fz_rethrow(ctx); + } + +#if SAVEICCPROFILE + if (n == 3) + save_profile(ctx, profile, "calRGB"); + else + save_profile(ctx, profile, "calGray"); +#endif + return profile; +}
