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;
+}