Merge branch 'master' of /Users/jrmuizel/bugs/colorspace/qcms into bnew
Conflicts:
transform.c
diff --git a/Makefile b/Makefile
index 042761e..ebdb147 100644
--- a/Makefile
+++ b/Makefile
@@ -6,8 +6,8 @@
CFLAGS=-Wall $(OPT_FLAGS) $(COVERAGE_FLAGS) -Wdeclaration-after-statement -ggdb `pkg-config --cflags lcms`
LDFLAGS=`pkg-config --libs lcms` -ldl
-QCMS_SRC=iccread.c transform.c transform-sse2.c transform-sse1.c
-QCMS_OBJS=iccread.o transform.o transform-sse2.o transform-sse1.o
+QCMS_SRC=iccread.c transform.c matrix.c chain.c transform_util.c transform-sse2.c transform-sse1.c
+QCMS_OBJS=iccread.o transform.o matrix.o chain.o transform_util.o transform-sse2.o transform-sse1.o
PROGRAMS=profile-gen test test-invalid lcms-compare dump-profile div-test coverage malloc-fail invalid-coverage
diff --git a/chain.c b/chain.c
new file mode 100644
index 0000000..9c52e3f
--- /dev/null
+++ b/chain.c
@@ -0,0 +1,989 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+// qcms
+// Copyright (C) 2009 Mozilla Corporation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h> //memcpy
+#include "qcmsint.h"
+#include "transform_util.h"
+#include "matrix.h"
+
+static struct matrix build_lut_matrix(struct lutType *lut)
+{
+ struct matrix result;
+ if (lut) {
+ result.m[0][0] = s15Fixed16Number_to_float(lut->e00);
+ result.m[0][1] = s15Fixed16Number_to_float(lut->e01);
+ result.m[0][2] = s15Fixed16Number_to_float(lut->e02);
+ result.m[1][0] = s15Fixed16Number_to_float(lut->e10);
+ result.m[1][1] = s15Fixed16Number_to_float(lut->e11);
+ result.m[1][2] = s15Fixed16Number_to_float(lut->e12);
+ result.m[2][0] = s15Fixed16Number_to_float(lut->e20);
+ result.m[2][1] = s15Fixed16Number_to_float(lut->e21);
+ result.m[2][2] = s15Fixed16Number_to_float(lut->e22);
+ result.invalid = false;
+ } else {
+ memset(&result, 0, sizeof(struct matrix));
+ result.invalid = true;
+ }
+ return result;
+}
+
+static struct matrix build_mAB_matrix(struct lutmABType *lut)
+{
+ struct matrix result;
+ if (lut) {
+ result.m[0][0] = s15Fixed16Number_to_float(lut->e00);
+ result.m[0][1] = s15Fixed16Number_to_float(lut->e01);
+ result.m[0][2] = s15Fixed16Number_to_float(lut->e02);
+ result.m[1][0] = s15Fixed16Number_to_float(lut->e10);
+ result.m[1][1] = s15Fixed16Number_to_float(lut->e11);
+ result.m[1][2] = s15Fixed16Number_to_float(lut->e12);
+ result.m[2][0] = s15Fixed16Number_to_float(lut->e20);
+ result.m[2][1] = s15Fixed16Number_to_float(lut->e21);
+ result.m[2][2] = s15Fixed16Number_to_float(lut->e22);
+ result.invalid = false;
+ } else {
+ memset(&result, 0, sizeof(struct matrix));
+ result.invalid = true;
+ }
+ return result;
+}
+
+//Based on lcms cmsLab2XYZ
+#define f(t) (t <= (24.0f/116.0f)*(24.0f/116.0f)*(24.0f/116.0f)) ? ((841.0/108.0) * t + (16.0/116.0)) : pow(t,1.0/3.0)
+#define f_1(t) (t <= (24.0f/116.0f)) ? ((108.0/841.0) * (t - (16.0/116.0))) : (t * t * t)
+static void qcms_transform_module_LAB_to_XYZ(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ // lcms: D50 XYZ values
+ float WhitePointX = 0.9642f;
+ float WhitePointY = 1.0f;
+ float WhitePointZ = 0.8249f;
+ for (i = 0; i < length; i++) {
+ float device_L = *src++ * 100.0f;
+ float device_a = *src++ * 255.0f - 128.0f;
+ float device_b = *src++ * 255.0f - 128.0f;
+ float y = (device_L + 16.0f) / 116.0f;
+
+ float X = f_1((y + 0.002f * device_a)) * WhitePointX;
+ float Y = f_1(y) * WhitePointY;
+ float Z = f_1((y - 0.005f * device_b)) * WhitePointZ;
+ *dest++ = X / (1.0 + 32767.0/32768.0);
+ *dest++ = Y / (1.0 + 32767.0/32768.0);
+ *dest++ = Z / (1.0 + 32767.0/32768.0);
+ }
+}
+
+//Based on lcms cmsXYZ2Lab
+static void qcms_transform_module_XYZ_to_LAB(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ // lcms: D50 XYZ values
+ float WhitePointX = 0.9642f;
+ float WhitePointY = 1.0f;
+ float WhitePointZ = 0.8249f;
+ for (i = 0; i < length; i++) {
+ float device_x = *src++ * (1.0 + 32767.0/32768.0) / WhitePointX;
+ float device_y = *src++ * (1.0 + 32767.0/32768.0) / WhitePointY;
+ float device_z = *src++ * (1.0 + 32767.0/32768.0) / WhitePointZ;
+
+ float fx = f(device_x);
+ float fy = f(device_y);
+ float fz = f(device_z);
+
+ float L = 116.0f*fy - 16.0f;
+ float a = 500.0f*(fx - fy);
+ float b = 200.0f*(fy - fz);
+ *dest++ = L / 100.0f;
+ *dest++ = (a+128.0f) / 255.0f;
+ *dest++ = (b+128.0f) / 255.0f;
+ }
+
+}
+
+static void qcms_transform_module_clut_only(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+
+ for (i = 0; i < length; i++) {
+ float linear_r = *src++;
+ float linear_g = *src++;
+ float linear_b = *src++;
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float x_d = linear_r * (transform->grid_size-1) - x;
+ float y_d = linear_g * (transform->grid_size-1) - y;
+ float z_d = linear_b * (transform->grid_size-1) - z;
+
+ float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
+ float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
+ float r_y1 = lerp(r_x1, r_x2, y_d);
+ float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
+ float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
+ float r_y2 = lerp(r_x3, r_x4, y_d);
+ float clut_r = lerp(r_y1, r_y2, z_d);
+
+ float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
+ float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
+ float g_y1 = lerp(g_x1, g_x2, y_d);
+ float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
+ float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
+ float g_y2 = lerp(g_x3, g_x4, y_d);
+ float clut_g = lerp(g_y1, g_y2, z_d);
+
+ float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
+ float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
+ float b_y1 = lerp(b_x1, b_x2, y_d);
+ float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
+ float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
+ float b_y2 = lerp(b_x3, b_x4, y_d);
+ float clut_b = lerp(b_y1, b_y2, z_d);
+
+ *dest++ = clamp_float(clut_r);
+ *dest++ = clamp_float(clut_g);
+ *dest++ = clamp_float(clut_b);
+ }
+}
+
+static void qcms_transform_module_clut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+ for (i = 0; i < length; i++) {
+ float device_r = *src++;
+ float device_g = *src++;
+ float device_b = *src++;
+ float linear_r = lut_interp_linear_float(device_r,
+ transform->input_clut_table_r, transform->input_clut_table_length);
+ float linear_g = lut_interp_linear_float(device_g,
+ transform->input_clut_table_g, transform->input_clut_table_length);
+ float linear_b = lut_interp_linear_float(device_b,
+ transform->input_clut_table_b, transform->input_clut_table_length);
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float x_d = linear_r * (transform->grid_size-1) - x;
+ float y_d = linear_g * (transform->grid_size-1) - y;
+ float z_d = linear_b * (transform->grid_size-1) - z;
+
+ float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
+ float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
+ float r_y1 = lerp(r_x1, r_x2, y_d);
+ float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
+ float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
+ float r_y2 = lerp(r_x3, r_x4, y_d);
+ float clut_r = lerp(r_y1, r_y2, z_d);
+
+ float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
+ float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
+ float g_y1 = lerp(g_x1, g_x2, y_d);
+ float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
+ float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
+ float g_y2 = lerp(g_x3, g_x4, y_d);
+ float clut_g = lerp(g_y1, g_y2, z_d);
+
+ float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
+ float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
+ float b_y1 = lerp(b_x1, b_x2, y_d);
+ float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
+ float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
+ float b_y2 = lerp(b_x3, b_x4, y_d);
+ float clut_b = lerp(b_y1, b_y2, z_d);
+
+ float pcs_r = lut_interp_linear_float(clut_r,
+ transform->output_clut_table_r, transform->output_clut_table_length);
+ float pcs_g = lut_interp_linear_float(clut_g,
+ transform->output_clut_table_g, transform->output_clut_table_length);
+ float pcs_b = lut_interp_linear_float(clut_b,
+ transform->output_clut_table_b, transform->output_clut_table_length);
+
+ *dest++ = clamp_float(pcs_r);
+ *dest++ = clamp_float(pcs_g);
+ *dest++ = clamp_float(pcs_b);
+ }
+}
+
+/* NOT USED
+static void qcms_transform_module_tetra_clut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+ float c0_r, c1_r, c2_r, c3_r;
+ float c0_g, c1_g, c2_g, c3_g;
+ float c0_b, c1_b, c2_b, c3_b;
+ float clut_r, clut_g, clut_b;
+ float pcs_r, pcs_g, pcs_b;
+ for (i = 0; i < length; i++) {
+ float device_r = *src++;
+ float device_g = *src++;
+ float device_b = *src++;
+ float linear_r = lut_interp_linear_float(device_r,
+ transform->input_clut_table_r, transform->input_clut_table_length);
+ float linear_g = lut_interp_linear_float(device_g,
+ transform->input_clut_table_g, transform->input_clut_table_length);
+ float linear_b = lut_interp_linear_float(device_b,
+ transform->input_clut_table_b, transform->input_clut_table_length);
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float rx = linear_r * (transform->grid_size-1) - x;
+ float ry = linear_g * (transform->grid_size-1) - y;
+ float rz = linear_b * (transform->grid_size-1) - z;
+
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+ if( rx >= ry ) {
+ if (ry >= rz) { //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (rx >= rz) { //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else { //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ } else {
+ if (rx >= rz) { //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x_n, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x_n, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x_n, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (ry >= rz) { //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else { //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ }
+ }
+ }
+
+ clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz;
+ clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz;
+ clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz;
+
+ pcs_r = lut_interp_linear_float(clut_r,
+ transform->output_clut_table_r, transform->output_clut_table_length);
+ pcs_g = lut_interp_linear_float(clut_g,
+ transform->output_clut_table_g, transform->output_clut_table_length);
+ pcs_b = lut_interp_linear_float(clut_b,
+ transform->output_clut_table_b, transform->output_clut_table_length);
+ *dest++ = clamp_float(pcs_r);
+ *dest++ = clamp_float(pcs_g);
+ *dest++ = clamp_float(pcs_b);
+ }
+}
+*/
+
+static void qcms_transform_module_gamma_table(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ float out_r, out_g, out_b;
+ for (i = 0; i < length; i++) {
+ float in_r = *src++;
+ float in_g = *src++;
+ float in_b = *src++;
+
+ out_r = lut_interp_linear_float(in_r, transform->input_clut_table_r, 256);
+ out_g = lut_interp_linear_float(in_g, transform->input_clut_table_g, 256);
+ out_b = lut_interp_linear_float(in_b, transform->input_clut_table_b, 256);
+
+ *dest++ = clamp_float(out_r);
+ *dest++ = clamp_float(out_g);
+ *dest++ = clamp_float(out_b);
+ }
+}
+
+static void qcms_transform_module_gamma_lut(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ float out_r, out_g, out_b;
+ for (i = 0; i < length; i++) {
+ float in_r = *src++;
+ float in_g = *src++;
+ float in_b = *src++;
+
+ out_r = lut_interp_linear(in_r,
+ transform->output_gamma_lut_r, transform->output_gamma_lut_r_length);
+ out_g = lut_interp_linear(in_g,
+ transform->output_gamma_lut_g, transform->output_gamma_lut_g_length);
+ out_b = lut_interp_linear(in_b,
+ transform->output_gamma_lut_b, transform->output_gamma_lut_b_length);
+
+ *dest++ = clamp_float(out_r);
+ *dest++ = clamp_float(out_g);
+ *dest++ = clamp_float(out_b);
+ }
+}
+
+static void qcms_transform_module_matrix_translate(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ struct matrix mat;
+
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ mat.m[0][0] = transform->matrix.m[0][0];
+ mat.m[1][0] = transform->matrix.m[0][1];
+ mat.m[2][0] = transform->matrix.m[0][2];
+ mat.m[0][1] = transform->matrix.m[1][0];
+ mat.m[1][1] = transform->matrix.m[1][1];
+ mat.m[2][1] = transform->matrix.m[1][2];
+ mat.m[0][2] = transform->matrix.m[2][0];
+ mat.m[1][2] = transform->matrix.m[2][1];
+ mat.m[2][2] = transform->matrix.m[2][2];
+
+ for (i = 0; i < length; i++) {
+ float in_r = *src++;
+ float in_g = *src++;
+ float in_b = *src++;
+
+ float out_r = mat.m[0][0]*in_r + mat.m[1][0]*in_g + mat.m[2][0]*in_b + transform->tx;
+ float out_g = mat.m[0][1]*in_r + mat.m[1][1]*in_g + mat.m[2][1]*in_b + transform->ty;
+ float out_b = mat.m[0][2]*in_r + mat.m[1][2]*in_g + mat.m[2][2]*in_b + transform->tz;
+
+ *dest++ = clamp_float(out_r);
+ *dest++ = clamp_float(out_g);
+ *dest++ = clamp_float(out_b);
+ }
+}
+
+static void qcms_transform_module_matrix(struct qcms_modular_transform *transform, float *src, float *dest, size_t length)
+{
+ size_t i;
+ struct matrix mat;
+
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ mat.m[0][0] = transform->matrix.m[0][0];
+ mat.m[1][0] = transform->matrix.m[0][1];
+ mat.m[2][0] = transform->matrix.m[0][2];
+ mat.m[0][1] = transform->matrix.m[1][0];
+ mat.m[1][1] = transform->matrix.m[1][1];
+ mat.m[2][1] = transform->matrix.m[1][2];
+ mat.m[0][2] = transform->matrix.m[2][0];
+ mat.m[1][2] = transform->matrix.m[2][1];
+ mat.m[2][2] = transform->matrix.m[2][2];
+
+ for (i = 0; i < length; i++) {
+ float in_r = *src++;
+ float in_g = *src++;
+ float in_b = *src++;
+
+ float out_r = mat.m[0][0]*in_r + mat.m[1][0]*in_g + mat.m[2][0]*in_b;
+ float out_g = mat.m[0][1]*in_r + mat.m[1][1]*in_g + mat.m[2][1]*in_b;
+ float out_b = mat.m[0][2]*in_r + mat.m[1][2]*in_g + mat.m[2][2]*in_b;
+
+ *dest++ = clamp_float(out_r);
+ *dest++ = clamp_float(out_g);
+ *dest++ = clamp_float(out_b);
+ }
+}
+
+static struct qcms_modular_transform* qcms_modular_transform_alloc() {
+ return calloc(1, sizeof(struct qcms_modular_transform));
+}
+
+static void qcms_modular_transform_release(struct qcms_modular_transform *transform)
+{
+ struct qcms_modular_transform *next_transform;
+ while (transform != NULL) {
+ next_transform = transform->next_transform;
+ // clut may use a single block of memory.
+ // Perhaps we should remove this to simply the code.
+ if (transform->input_clut_table_r + transform->input_clut_table_length == transform->input_clut_table_g && transform->input_clut_table_g + transform->input_clut_table_length == transform->input_clut_table_b) {
+ if (transform->input_clut_table_r) free(transform->input_clut_table_r);
+ } else {
+ if (transform->input_clut_table_r) free(transform->input_clut_table_r);
+ if (transform->input_clut_table_g) free(transform->input_clut_table_g);
+ if (transform->input_clut_table_b) free(transform->input_clut_table_b);
+ }
+ if (transform->r_clut + 1 == transform->g_clut && transform->g_clut + 1 == transform->b_clut) {
+ if (transform->r_clut) free(transform->r_clut);
+ } else {
+ if (transform->r_clut) free(transform->r_clut);
+ if (transform->g_clut) free(transform->g_clut);
+ if (transform->b_clut) free(transform->b_clut);
+ }
+ if (transform->output_clut_table_r + transform->output_clut_table_length == transform->output_clut_table_g && transform->output_clut_table_g+ transform->output_clut_table_length == transform->output_clut_table_b) {
+ if (transform->output_clut_table_r) free(transform->output_clut_table_r);
+ } else {
+ if (transform->output_clut_table_r) free(transform->output_clut_table_r);
+ if (transform->output_clut_table_g) free(transform->output_clut_table_g);
+ if (transform->output_clut_table_b) free(transform->output_clut_table_b);
+ }
+ if (transform->output_gamma_lut_r) free(transform->output_gamma_lut_r);
+ if (transform->output_gamma_lut_g) free(transform->output_gamma_lut_g);
+ if (transform->output_gamma_lut_b) free(transform->output_gamma_lut_b);
+ free(transform);
+ transform = next_transform;
+ }
+}
+
+/* Set transform to be the next element in the linked list. */
+static void append_transform(struct qcms_modular_transform *transform, struct qcms_modular_transform ***next_transform)
+{
+ **next_transform = transform;
+ while (transform) {
+ *next_transform = &(transform->next_transform);
+ transform = transform->next_transform;
+ }
+}
+
+/* reverse the transformation list (used by mBA) */
+static struct qcms_modular_transform* reverse_transform(struct qcms_modular_transform *transform)
+{
+ struct qcms_modular_transform *prev_transform = NULL;
+ while (transform != NULL) {
+ struct qcms_modular_transform *next_transform = transform->next_transform;
+ transform->next_transform = prev_transform;
+ prev_transform = transform;
+ transform = next_transform;
+ }
+
+ return prev_transform;
+}
+
+#define EMPTY_TRANSFORM_LIST NULL
+static struct qcms_modular_transform* qcms_modular_transform_create_mAB(struct lutmABType *lut)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform **next_transform = &first_transform;
+ struct qcms_modular_transform *transform = NULL;
+
+ if (lut->a_curves[0] != NULL) {
+ size_t clut_length;
+ float *clut;
+
+ // If the A curve is present this also implies the
+ // presence of a CLUT.
+ if (!lut->clut_table)
+ goto fail;
+
+ // Prepare A curve.
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->input_clut_table_r = build_input_gamma_table(lut->a_curves[0]);
+ transform->input_clut_table_g = build_input_gamma_table(lut->a_curves[1]);
+ transform->input_clut_table_b = build_input_gamma_table(lut->a_curves[2]);
+ transform->transform_module_fn = qcms_transform_module_gamma_table;
+ if (lut->num_grid_points[0] != lut->num_grid_points[1] ||
+ lut->num_grid_points[1] != lut->num_grid_points[2] ) {
+ //XXX: We don't currently support clut that are not squared!
+ goto fail;
+ }
+
+ // Prepare CLUT
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ clut_length = sizeof(float)*pow(lut->num_grid_points[0], 3)*3;
+ clut = malloc(clut_length);
+ if (!clut)
+ goto fail;
+ memcpy(clut, lut->clut_table, clut_length);
+ transform->r_clut = clut + 0;
+ transform->g_clut = clut + 1;
+ transform->b_clut = clut + 2;
+ transform->grid_size = lut->num_grid_points[0];
+ transform->transform_module_fn = qcms_transform_module_clut_only;
+ }
+ if (lut->m_curves[0] != NULL) {
+ // M curve imples the presence of a Matrix
+
+ // Prepare M curve
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->input_clut_table_r = build_input_gamma_table(lut->m_curves[0]);
+ transform->input_clut_table_g = build_input_gamma_table(lut->m_curves[1]);
+ transform->input_clut_table_b = build_input_gamma_table(lut->m_curves[2]);
+ transform->transform_module_fn = qcms_transform_module_gamma_table;
+
+ // Prepare Matrix
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix = build_mAB_matrix(lut);
+ if (transform->matrix.invalid)
+ goto fail;
+ transform->tx = s15Fixed16Number_to_float(lut->e03);
+ transform->ty = s15Fixed16Number_to_float(lut->e13);
+ transform->tz = s15Fixed16Number_to_float(lut->e23);
+ transform->transform_module_fn = qcms_transform_module_matrix_translate;
+ }
+ if (lut->b_curves[0] != NULL) {
+ // Prepare B curve
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->input_clut_table_r = build_input_gamma_table(lut->b_curves[0]);
+ transform->input_clut_table_g = build_input_gamma_table(lut->b_curves[1]);
+ transform->input_clut_table_b = build_input_gamma_table(lut->b_curves[2]);
+ transform->transform_module_fn = qcms_transform_module_gamma_table;
+ } else {
+ // B curve is mandatory
+ goto fail;
+ }
+
+ if (lut->reversed) {
+ // mBA are identical to mAB except that the transformation order
+ // is reversed
+ first_transform = reverse_transform(first_transform);
+ }
+
+ return first_transform;
+fail:
+ qcms_modular_transform_release(first_transform);
+ return NULL;
+}
+
+static struct qcms_modular_transform* qcms_modular_transform_create_lut(struct lutType *lut)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform **next_transform = &first_transform;
+ struct qcms_modular_transform *transform = NULL;
+
+ size_t in_curve_len, clut_length, out_curve_len;
+ float *in_curves, *clut, *out_curves;
+
+ // Prepare Matrix
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix = build_lut_matrix(lut);
+ if (transform->matrix.invalid)
+ goto fail;
+ transform->transform_module_fn = qcms_transform_module_matrix;
+
+ // Prepare input curves
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ in_curve_len = sizeof(float)*lut->num_input_table_entries * 3;
+ in_curves = malloc(in_curve_len);
+ if (!in_curves)
+ goto fail;
+ memcpy(in_curves, lut->input_table, in_curve_len);
+ transform->input_clut_table_r = in_curves + lut->num_input_table_entries * 0;
+ transform->input_clut_table_g = in_curves + lut->num_input_table_entries * 1;
+ transform->input_clut_table_b = in_curves + lut->num_input_table_entries * 2;
+ transform->input_clut_table_length = lut->num_input_table_entries;
+
+ // Prepare table
+ clut_length = sizeof(float)*pow(lut->num_clut_grid_points, 3)*3;
+ clut = malloc(clut_length);
+ if (!clut)
+ goto fail;
+ memcpy(clut, lut->clut_table, clut_length);
+ transform->r_clut = clut + 0;
+ transform->g_clut = clut + 1;
+ transform->b_clut = clut + 2;
+ transform->grid_size = lut->num_clut_grid_points;
+
+ // Prepare output curves
+ out_curve_len = sizeof(float) * lut->num_output_table_entries * 3;
+ out_curves = malloc(out_curve_len);
+ if (!out_curves)
+ goto fail;
+ memcpy(out_curves, lut->output_table, out_curve_len);
+ transform->output_clut_table_r = out_curves + lut->num_output_table_entries * 0;
+ transform->output_clut_table_g = out_curves + lut->num_output_table_entries * 1;
+ transform->output_clut_table_b = out_curves + lut->num_output_table_entries * 2;
+ transform->output_clut_table_length = lut->num_output_table_entries;
+ transform->transform_module_fn = qcms_transform_module_clut;
+
+ return first_transform;
+fail:
+ qcms_modular_transform_release(first_transform);
+ return NULL;
+}
+
+struct qcms_modular_transform* qcms_modular_transform_create_input(qcms_profile *in)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform **next_transform = &first_transform;
+
+ if (in->A2B0) {
+ struct qcms_modular_transform *lut_transform;
+ lut_transform = qcms_modular_transform_create_lut(in->A2B0);
+ if (!lut_transform)
+ goto fail;
+ append_transform(lut_transform, &next_transform);
+ } else if (in->mAB && in->mAB->num_in_channels == 3 && in->mAB->num_out_channels == 3) {
+ struct qcms_modular_transform *mAB_transform;
+ mAB_transform = qcms_modular_transform_create_mAB(in->mAB);
+ if (!mAB_transform)
+ goto fail;
+ append_transform(mAB_transform, &next_transform);
+
+ } else {
+ struct qcms_modular_transform *transform;
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->input_clut_table_r = build_input_gamma_table(in->redTRC);
+ transform->input_clut_table_g = build_input_gamma_table(in->greenTRC);
+ transform->input_clut_table_b = build_input_gamma_table(in->blueTRC);
+ transform->transform_module_fn = qcms_transform_module_gamma_table;
+ if (!transform->input_clut_table_r || !transform->input_clut_table_g ||
+ !transform->input_clut_table_b) {
+ goto fail;
+ }
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix.m[0][0] = 1/1.999969482421875f;
+ transform->matrix.m[0][1] = 0.f;
+ transform->matrix.m[0][2] = 0.f;
+ transform->matrix.m[1][0] = 0.f;
+ transform->matrix.m[1][1] = 1/1.999969482421875f;
+ transform->matrix.m[1][2] = 0.f;
+ transform->matrix.m[2][0] = 0.f;
+ transform->matrix.m[2][1] = 0.f;
+ transform->matrix.m[2][2] = 1/1.999969482421875f;
+ transform->matrix.invalid = false;
+ transform->transform_module_fn = qcms_transform_module_matrix;
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix = build_colorant_matrix(in);
+ transform->transform_module_fn = qcms_transform_module_matrix;
+ }
+
+ return first_transform;
+fail:
+ qcms_modular_transform_release(first_transform);
+ return EMPTY_TRANSFORM_LIST;
+}
+static struct qcms_modular_transform* qcms_modular_transform_create_output(qcms_profile *out)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform **next_transform = &first_transform;
+
+ if (out->B2A0) {
+ struct qcms_modular_transform *lut_transform;
+ lut_transform = qcms_modular_transform_create_lut(out->B2A0);
+ if (!lut_transform)
+ goto fail;
+ append_transform(lut_transform, &next_transform);
+ } else if (out->mBA && out->mBA->num_in_channels == 3 && out->mBA->num_out_channels == 3) {
+ struct qcms_modular_transform *lut_transform;
+ lut_transform = qcms_modular_transform_create_mAB(out->mBA);
+ if (!lut_transform)
+ goto fail;
+ append_transform(lut_transform, &next_transform);
+ } else if (out->redTRC && out->greenTRC && out->blueTRC) {
+ struct qcms_modular_transform *transform;
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix = matrix_invert(build_colorant_matrix(out));
+ transform->transform_module_fn = qcms_transform_module_matrix;
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ transform->matrix.m[0][0] = 1.999969482421875f;
+ transform->matrix.m[0][1] = 0.f;
+ transform->matrix.m[0][2] = 0.f;
+ transform->matrix.m[1][0] = 0.f;
+ transform->matrix.m[1][1] = 1.999969482421875f;
+ transform->matrix.m[1][2] = 0.f;
+ transform->matrix.m[2][0] = 0.f;
+ transform->matrix.m[2][1] = 0.f;
+ transform->matrix.m[2][2] = 1.999969482421875f;
+ transform->matrix.invalid = false;
+ transform->transform_module_fn = qcms_transform_module_matrix;
+
+ transform = qcms_modular_transform_alloc();
+ if (!transform)
+ goto fail;
+ append_transform(transform, &next_transform);
+ build_output_lut(out->redTRC, &transform->output_gamma_lut_r,
+ &transform->output_gamma_lut_r_length);
+ build_output_lut(out->greenTRC, &transform->output_gamma_lut_g,
+ &transform->output_gamma_lut_g_length);
+ build_output_lut(out->blueTRC, &transform->output_gamma_lut_b,
+ &transform->output_gamma_lut_b_length);
+ transform->transform_module_fn = qcms_transform_module_gamma_lut;
+
+ if (!transform->output_gamma_lut_r || !transform->output_gamma_lut_g ||
+ !transform->output_gamma_lut_b) {
+ goto fail;
+ }
+ } else {
+ assert(0 && "Unsupported output profile workflow.");
+ return NULL;
+ }
+
+ return first_transform;
+fail:
+ qcms_modular_transform_release(first_transform);
+ return EMPTY_TRANSFORM_LIST;
+}
+
+/* Not Completed
+// Simplify the transformation chain to an equivalent transformation chain
+static struct qcms_modular_transform* qcms_modular_transform_reduce(struct qcms_modular_transform *transform)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform *curr_trans = transform;
+ struct qcms_modular_transform *prev_trans = NULL;
+ while (curr_trans) {
+ struct qcms_modular_transform *next_trans = curr_trans->next_transform;
+ if (curr_trans->transform_module_fn == qcms_transform_module_matrix) {
+ if (next_trans && next_trans->transform_module_fn == qcms_transform_module_matrix) {
+ curr_trans->matrix = matrix_multiply(curr_trans->matrix, next_trans->matrix);
+ goto remove_next;
+ }
+ }
+ if (curr_trans->transform_module_fn == qcms_transform_module_gamma_table) {
+ bool isLinear = true;
+ uint16_t i;
+ for (i = 0; isLinear && i < 256; i++) {
+ isLinear &= (int)(curr_trans->input_clut_table_r[i] * 255) == i;
+ isLinear &= (int)(curr_trans->input_clut_table_g[i] * 255) == i;
+ isLinear &= (int)(curr_trans->input_clut_table_b[i] * 255) == i;
+ }
+ goto remove_current;
+ }
+
+next_transform:
+ if (!next_trans) break;
+ prev_trans = curr_trans;
+ curr_trans = next_trans;
+ continue;
+remove_current:
+ if (curr_trans == transform) {
+ //Update head
+ transform = next_trans;
+ } else {
+ prev_trans->next_transform = next_trans;
+ }
+ curr_trans->next_transform = NULL;
+ qcms_modular_transform_release(curr_trans);
+ //return transform;
+ return qcms_modular_transform_reduce(transform);
+remove_next:
+ curr_trans->next_transform = next_trans->next_transform;
+ next_trans->next_transform = NULL;
+ qcms_modular_transform_release(next_trans);
+ continue;
+ }
+ return transform;
+}
+*/
+
+static struct qcms_modular_transform* qcms_modular_transform_create(qcms_profile *in, qcms_profile *out)
+{
+ struct qcms_modular_transform *first_transform = NULL;
+ struct qcms_modular_transform **next_transform = &first_transform;
+
+ if (in->color_space == RGB_SIGNATURE) {
+ struct qcms_modular_transform* rgb_to_pcs;
+ rgb_to_pcs = qcms_modular_transform_create_input(in);
+ if (!rgb_to_pcs)
+ goto fail;
+ append_transform(rgb_to_pcs, &next_transform);
+ } else {
+ assert(0 && "input color space not supported");
+ goto fail;
+ }
+
+ if (in->pcs == LAB_SIGNATURE && out->pcs == XYZ_SIGNATURE) {
+ struct qcms_modular_transform* lab_to_pcs;
+ lab_to_pcs = qcms_modular_transform_alloc();
+ if (!lab_to_pcs)
+ goto fail;
+ append_transform(lab_to_pcs, &next_transform);
+ lab_to_pcs->transform_module_fn = qcms_transform_module_LAB_to_XYZ;
+ }
+
+ // This does not improve accuracy in practice, something is wrong here.
+ //if (in->chromaticAdaption.invalid == false) {
+ // struct qcms_modular_transform* chromaticAdaption;
+ // chromaticAdaption = qcms_modular_transform_alloc();
+ // if (!chromaticAdaption)
+ // goto fail;
+ // append_transform(chromaticAdaption, &next_transform);
+ // chromaticAdaption->matrix = matrix_invert(in->chromaticAdaption);
+ // chromaticAdaption->transform_module_fn = qcms_transform_module_matrix;
+ //}
+
+ if (in->pcs == XYZ_SIGNATURE && out->pcs == LAB_SIGNATURE) {
+ struct qcms_modular_transform* pcs_to_lab;
+ pcs_to_lab = qcms_modular_transform_alloc();
+ if (!pcs_to_lab)
+ goto fail;
+ append_transform(pcs_to_lab, &next_transform);
+ pcs_to_lab->transform_module_fn = qcms_transform_module_XYZ_to_LAB;
+ }
+
+ if (out->color_space == RGB_SIGNATURE) {
+ struct qcms_modular_transform* pcs_to_rgb;
+ pcs_to_rgb = qcms_modular_transform_create_output(out);
+ if (!pcs_to_rgb)
+ goto fail;
+ append_transform(pcs_to_rgb, &next_transform);
+ } else {
+ assert(0 && "output color space not supported");
+ goto fail;
+ }
+ // Not Completed
+ //return qcms_modular_transform_reduce(first_transform);
+ return first_transform;
+fail:
+ qcms_modular_transform_release(first_transform);
+ return EMPTY_TRANSFORM_LIST;
+}
+
+static float* qcms_modular_transform_data(struct qcms_modular_transform *transform, float *src, float *dest, size_t len)
+{
+ while (transform != NULL) {
+ // Keep swaping src/dest when performing a transform to use less memory.
+ float *new_src = dest;
+ const void *transform_fn = transform->transform_module_fn;
+ if (transform_fn != qcms_transform_module_gamma_table &&
+ transform_fn != qcms_transform_module_gamma_lut &&
+ transform_fn != qcms_transform_module_clut &&
+ transform_fn != qcms_transform_module_clut_only &&
+ transform_fn != qcms_transform_module_matrix &&
+ transform_fn != qcms_transform_module_matrix_translate &&
+ transform_fn != qcms_transform_module_LAB_to_XYZ &&
+ transform_fn != qcms_transform_module_XYZ_to_LAB) {
+ assert(0 && "Unsupported transform module");
+ return NULL;
+ }
+ transform->transform_module_fn(transform,src,dest,len);
+ dest = src;
+ src = new_src;
+ transform = transform->next_transform;
+ }
+ // The results end up in the src buffer because of the switching
+ return src;
+}
+
+float* qcms_chain_transform(qcms_profile *in, qcms_profile *out, float *src, float *dest, size_t lutSize)
+{
+ struct qcms_modular_transform *transform_list = qcms_modular_transform_create(in, out);
+ if (transform_list != NULL) {
+ float *lut = qcms_modular_transform_data(transform_list, src, dest, lutSize/3);
+ qcms_modular_transform_release(transform_list);
+ return lut;
+ }
+ return NULL;
+}
diff --git a/chain.h b/chain.h
new file mode 100644
index 0000000..bdc6c88
--- /dev/null
+++ b/chain.h
@@ -0,0 +1,30 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef _QCMS_CHAIN_H
+#define _QCMS_CHAIN_H
+
+// Generates and returns a 3D LUT with lutSize^3 samples using the provided src/dest.
+float* qcms_chain_transform(qcms_profile *in, qcms_profile *out, float *src, float *dest, size_t lutSize);
+
+#endif
diff --git a/iccread.c b/iccread.c
index f8c50f4..2bb0014 100644
--- a/iccread.c
+++ b/iccread.c
@@ -1,3 +1,4 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
// qcms
// Copyright (C) 2009 Mozilla Foundation
// Copyright (C) 1998-2007 Marti Maria
@@ -23,7 +24,7 @@
#include <math.h>
#include <assert.h>
#include <stdlib.h>
-#include <string.h>
+#include <string.h> //memset
#include "qcmsint.h"
typedef uint32_t be32;
@@ -144,6 +145,8 @@
static void check_profile_version(struct mem_source *src)
{
+ //REVIEW Do we want to check for a specific version range?
+ /*
uint8_t major_revision = read_u8(src, 8 + 0);
uint8_t minor_revision = read_u8(src, 8 + 1);
uint8_t reserved1 = read_u8(src, 8 + 2);
@@ -156,6 +159,7 @@
}
if (reserved1 != 0 || reserved2 != 0)
invalid_source(src, "Invalid reserved bytes");
+ */
}
#define INPUT_DEVICE_PROFILE 0x73636e72 // 'scnr'
@@ -172,8 +176,9 @@
switch (profile->class) {
case DISPLAY_DEVICE_PROFILE:
case INPUT_DEVICE_PROFILE:
- break;
case OUTPUT_DEVICE_PROFILE:
+ case COLOR_SPACE_PROFILE:
+ break;
default:
invalid_source(mem, "Invalid Profile/Device Class signature");
}
@@ -191,6 +196,18 @@
}
}
+static void read_pcs(qcms_profile *profile, struct mem_source *mem)
+{
+ profile->pcs = read_u32(mem, 20);
+ switch (profile->pcs) {
+ case XYZ_SIGNATURE:
+ case LAB_SIGNATURE:
+ break;
+ default:
+ invalid_source(mem, "Unsupported pcs");
+ }
+}
+
struct tag
{
uint32_t signature;
@@ -240,6 +257,9 @@
if (profile->color_space != RGB_SIGNATURE)
return false;
+ if (profile->A2B0 || profile->B2A0)
+ return false;
+
rX = s15Fixed16Number_to_float(profile->redColorant.X);
rY = s15Fixed16Number_to_float(profile->redColorant.Y);
rZ = s15Fixed16Number_to_float(profile->redColorant.Z);
@@ -300,6 +320,8 @@
#define TAG_gTRC 0x67545243
#define TAG_kTRC 0x6b545243
#define TAG_A2B0 0x41324230
+#define TAG_B2A0 0x42324130
+#define TAG_CHAD 0x63686164
static struct tag *find_tag(struct tag_index index, uint32_t tag_id)
{
@@ -313,10 +335,39 @@
return tag;
}
-#define XYZ_TYPE 0x58595a20 // 'XYZ '
-#define CURVE_TYPE 0x63757276 // 'curv'
-#define LUT16_TYPE 0x6d667432 // 'mft2'
-#define LUT8_TYPE 0x6d667431 // 'mft1'
+#define XYZ_TYPE 0x58595a20 // 'XYZ '
+#define CURVE_TYPE 0x63757276 // 'curv'
+#define PARAMETRIC_CURVE_TYPE 0x70617261 // 'para'
+#define LUT16_TYPE 0x6d667432 // 'mft2'
+#define LUT8_TYPE 0x6d667431 // 'mft1'
+#define LUT_MAB_TYPE 0x6d414220 // 'mAB '
+#define LUT_MBA_TYPE 0x6d424120 // 'mBA '
+#define CHROMATIC_TYPE 0x73663332 // 'sf32'
+
+static struct matrix read_tag_s15Fixed16ArrayType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
+{
+ struct tag *tag = find_tag(index, tag_id);
+ struct matrix matrix;
+ if (tag) {
+ uint8_t i;
+ uint32_t offset = tag->offset;
+ uint32_t type = read_u32(src, offset);
+
+ // Check mandatory type signature for s16Fixed16ArrayType
+ if (type != CHROMATIC_TYPE) {
+ invalid_source(src, "unexpected type, expected 'sf32'");
+ }
+
+ for (i = 0; i < 9; i++) {
+ matrix.m[i/3][i%3] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset+8+i*4));
+ }
+ matrix.invalid = false;
+ } else {
+ matrix.invalid = true;
+ invalid_source(src, "missing sf32tag");
+ }
+ return matrix;
+}
static struct XYZNumber read_tag_XYZType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
{
@@ -337,20 +388,25 @@
return num;
}
-static struct curveType *read_tag_curveType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
+// Read the tag at a given offset rather then the tag_index.
+// This method is used when reading mAB tags where nested curveType are
+// present that are not part of the tag_index.
+static struct curveType *read_tag_curveType_At(struct mem_source *src, uint32_t offset, uint32_t *len)
{
- struct tag *tag = find_tag(index, tag_id);
+ static const size_t count_to_length[5] = {1, 3, 4, 5, 7};
struct curveType *curve = NULL;
- if (tag) {
- uint32_t offset = tag->offset;
- uint32_t type = read_u32(src, offset);
- uint32_t count = read_u32(src, offset+8);
- unsigned int i;
+ uint32_t type = read_u32(src, offset);
+ uint32_t count;
+ int i;
- if (type != CURVE_TYPE) {
- invalid_source(src, "unexpected type, expected CURV");
- return NULL;
- }
+ if (type != CURVE_TYPE && type != PARAMETRIC_CURVE_TYPE) {
+ assert(0 && "Unexpected curve type");
+ invalid_source(src, "unexpected type, expected CURV or PARA");
+ return NULL;
+ }
+
+ if (type == CURVE_TYPE) {
+ count = read_u32(src, offset+8);
#define MAX_CURVE_ENTRIES 40000 //arbitrary
if (count > MAX_CURVE_ENTRIES) {
@@ -362,17 +418,219 @@
return NULL;
curve->count = count;
+ curve->type = type;
+
for (i=0; i<count; i++) {
curve->data[i] = read_u16(src, offset + 12 + i *2);
}
+ *len = 12 + count * 2;
+ } else { //PARAMETRIC_CURVE_TYPE
+ count = read_u16(src, offset+8);
+
+ if (count > 4) {
+ assert(0 && "Parametric function type not supported.");
+ invalid_source(src, "parametric function type not supported.");
+ return NULL;
+ }
+
+ curve = malloc(sizeof(struct curveType));
+ if (!curve)
+ return NULL;
+
+ curve->count = count;
+ curve->type = type;
+
+ for (i=0; i < count_to_length[count]; i++) {
+ curve->parameter[i] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset + 12 + i*4));
+ }
+ *len = 12 + count_to_length[count] * 4;
+
+ if ((count == 1 || count == 2) && curve->parameter[1] == 0) {
+ invalid_source(src, "parametricCurve definition causes division by zero.");
+ }
+ }
+
+ return curve;
+}
+
+static struct curveType *read_tag_curveType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
+{
+ struct tag *tag = find_tag(index, tag_id);
+ struct curveType *curve = NULL;
+ if (tag) {
+ uint32_t len;
+ return read_tag_curveType_At(src, tag->offset, &len);
} else {
+ assert(0 && "Unexpected curve type");
invalid_source(src, "missing curvetag");
}
return curve;
}
-/* This function's not done yet */
+#define MAX_CLUT_SIZE 500000 // arbitrary
+#define MAX_CHANNELS 10 // arbitrary
+static void read_nested_curveType(struct mem_source *src, struct curveType *(*curveArray)[10], uint8_t num_channels, uint32_t curve_offset)
+{
+ uint32_t channel_offset = 0;
+ int i;
+ for (i = 0; i < num_channels; i++) {
+ uint32_t tag_len;
+
+ (*curveArray)[i] = read_tag_curveType_At(src, curve_offset + channel_offset, &tag_len);
+ if (!(*curveArray)[i]) {
+ invalid_source(src, "invalid nested curveType curve");
+ }
+
+ channel_offset += tag_len;
+ // 4 byte aligned
+ if ((tag_len % 4) != 0) channel_offset += 4 - (tag_len % 4);
+ }
+
+}
+
+/* See section 10.10 for specs */
+static struct lutmABType *read_tag_lutmABType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
+{
+ struct tag *tag = find_tag(index, tag_id);
+ uint32_t offset = tag->offset;
+ // XXX: m_offset != matrix_offset
+ uint32_t a_offset, b_offset, m_offset;
+ uint32_t matrix_offset;
+ uint32_t clut_offset;
+ uint32_t clut_size = 1;
+ uint8_t clut_precision;
+ uint32_t type = read_u32(src, offset);
+ uint8_t num_in_channels, num_out_channels;
+ struct lutmABType *lut;
+ int i;
+
+ if (type != LUT_MAB_TYPE && type != LUT_MBA_TYPE) {
+ return NULL;
+ }
+
+ num_in_channels = read_u8(src, offset + 8);
+ num_out_channels = read_u8(src, offset + 8);
+ if (num_in_channels > MAX_CHANNELS || num_out_channels > MAX_CHANNELS)
+ return NULL;
+
+ // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB)
+ // XXX: If we remove this restriction make sure that the number of channels
+ // is less or equal to the maximum number of mAB curves in qcmsint.h
+ // also check for clut_size overflow.
+ if (num_in_channels != 3 || num_out_channels != 3)
+ return NULL;
+
+ // XXX: Be careful with these pointers since they could point
+ // to 'dangerous' memory.
+ a_offset = read_u32(src, offset + 28);
+ clut_offset = read_u32(src, offset + 24);
+ m_offset = read_u32(src, offset + 20);
+ matrix_offset = read_u32(src, offset + 16);
+ b_offset = read_u32(src, offset + 12);
+
+ // Convert offsets relative to the tag to relative to the profile
+ if (a_offset)
+ a_offset += offset;
+ if (clut_offset)
+ clut_offset += offset;
+ if (m_offset)
+ m_offset += offset;
+ if (matrix_offset)
+ matrix_offset += offset;
+ if (b_offset)
+ b_offset += offset;
+
+ if (!src->valid)
+ return NULL;
+
+ if (clut_offset) {
+ // clut_size can not overflow since lg(256^3) = 24 bits.
+ for (i = 0; i < num_in_channels; i++) {
+ clut_size *= read_u8(src, clut_offset + i);
+ }
+ } else {
+ clut_size = 0;
+ }
+ clut_size = clut_size * num_out_channels;
+
+ if (!src->valid)
+ return NULL;
+ if (clut_size > MAX_CLUT_SIZE)
+ return NULL;
+
+ lut = malloc(sizeof(struct lutmABType) + (clut_size) * sizeof(float));
+ if (!lut)
+ return NULL;
+ memset(lut, 0, sizeof(struct lutmABType));
+ lut->clut_table = (float*)(((char*)lut) + sizeof(struct lutmABType));
+
+ for (i = 0; i < num_in_channels; i++) {
+ lut->num_grid_points[i] = read_u8(src, clut_offset + i);
+ }
+
+ // Reverse the processing of transformation elements for mBA type.
+ lut->reversed = (type == LUT_MBA_TYPE);
+
+ lut->num_in_channels = num_in_channels;
+ lut->num_out_channels = num_out_channels;
+ if (matrix_offset) {
+ lut->e00 = read_s15Fixed16Number(src, matrix_offset+4*0);
+ lut->e01 = read_s15Fixed16Number(src, matrix_offset+4*1);
+ lut->e02 = read_s15Fixed16Number(src, matrix_offset+4*2);
+ lut->e10 = read_s15Fixed16Number(src, matrix_offset+4*3);
+ lut->e11 = read_s15Fixed16Number(src, matrix_offset+4*4);
+ lut->e12 = read_s15Fixed16Number(src, matrix_offset+4*5);
+ lut->e20 = read_s15Fixed16Number(src, matrix_offset+4*6);
+ lut->e21 = read_s15Fixed16Number(src, matrix_offset+4*7);
+ lut->e22 = read_s15Fixed16Number(src, matrix_offset+4*8);
+ lut->e03 = read_s15Fixed16Number(src, matrix_offset+4*9);
+ lut->e13 = read_s15Fixed16Number(src, matrix_offset+4*10);
+ lut->e23 = read_s15Fixed16Number(src, matrix_offset+4*11);
+ }
+
+ if (a_offset) {
+ read_nested_curveType(src, &lut->a_curves, num_in_channels, a_offset);
+ }
+ if (m_offset) {
+ read_nested_curveType(src, &lut->m_curves, num_out_channels, m_offset);
+ }
+ if (b_offset) {
+ read_nested_curveType(src, &lut->b_curves, num_out_channels, b_offset);
+ } else {
+ invalid_source(src, "B curves required");
+ }
+
+ if (clut_offset) {
+ clut_precision = read_u8(src, clut_offset + 16);
+ if (clut_precision == 1) {
+ for (i = 0; i < clut_size; i++) {
+ lut->clut_table[i] = read_u8(src, clut_offset + 20 + i*1) / 255.0f;
+ }
+ } else if (clut_precision == 2) {
+ for (i = 0; i < clut_size; i++) {
+ lut->clut_table[i] = read_u16(src, clut_offset + 20 + i*2) / 65535.0f;
+ }
+ } else {
+ invalid_source(src, "Invalid clut precision");
+ }
+ }
+
+ if (!src->valid) {
+ for (i = 0; i < num_in_channels; i++) {
+ free(lut->a_curves[i]);
+ }
+ for (i = 0; i < num_out_channels; i++) {
+ free(lut->m_curves[i]);
+ free(lut->b_curves[i]);
+ }
+ free(lut);
+ return NULL;
+ }
+
+ return lut;
+}
+
static struct lutType *read_tag_lutType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
{
struct tag *tag = find_tag(index, tag_id);
@@ -381,12 +639,23 @@
uint16_t num_input_table_entries;
uint16_t num_output_table_entries;
uint8_t in_chan, grid_points, out_chan;
+ uint32_t clut_offset, output_offset;
uint32_t clut_size;
+ size_t entry_size;
struct lutType *lut;
int i;
- num_input_table_entries = read_u16(src, offset + 48);
- num_output_table_entries = read_u16(src, offset + 50);
+ /* I'm not sure why but LUT8 tables have a fixed number of entries despite
+ * having room for the following fields the fields */
+ if (type == LUT8_TYPE) {
+ num_input_table_entries = 256;
+ num_output_table_entries = 256;
+ } else if (type == LUT16_TYPE) {
+ num_input_table_entries = read_u16(src, offset + 48);
+ num_output_table_entries = read_u16(src, offset + 50);
+ } else {
+ return NULL;
+ }
in_chan = read_u8(src, offset + 8);
out_chan = read_u8(src, offset + 9);
@@ -395,18 +664,37 @@
if (!src->valid)
return NULL;
- clut_size = in_chan * grid_points * out_chan;
-#define MAX_CLUT_SIZE 10000 // arbitrary
+ clut_size = pow(grid_points, in_chan);
if (clut_size > MAX_CLUT_SIZE) {
return NULL;
}
-
- if (type != LUT16_TYPE && type != LUT8_TYPE)
+
+ if (in_chan != 3 || out_chan != 3) {
return NULL;
+ }
- lut = malloc(sizeof(struct lutType) + (clut_size + num_input_table_entries + num_output_table_entries)*sizeof(uint8_t));
- if (!lut)
+ if (type == LUT16_TYPE) {
+ entry_size = 2;
+ } else if (type == LUT8_TYPE) {
+ entry_size = 1;
+ } else {
return NULL;
+ }
+
+ lut = malloc(sizeof(struct lutType) + (num_input_table_entries * in_chan + clut_size*out_chan + num_output_table_entries * out_chan)*sizeof(float));
+ if (!lut) {
+ return NULL;
+ }
+
+ /* compute the offsets of tables */
+ //REVIEW Where is the allign code? This assertion is failing.
+ //assert((sizeof(struct lutType) & 0xf) == 0);
+ lut->input_table = (float*)(((char*)lut) + sizeof(struct lutType));
+ lut->clut_table = (float*)(((char*)(lut->input_table)) + in_chan*num_input_table_entries*sizeof(*(lut->input_table)));
+ lut->output_table = (float*)(((char*)(lut->clut_table)) + clut_size*out_chan*sizeof(*(lut->clut_table)));
+
+ lut->num_input_table_entries = num_input_table_entries;
+ lut->num_output_table_entries = num_output_table_entries;
lut->num_input_channels = read_u8(src, offset + 8);
lut->num_output_channels = read_u8(src, offset + 9);
lut->num_clut_grid_points = read_u8(src, offset + 10);
@@ -420,7 +708,36 @@
lut->e21 = read_s15Fixed16Number(src, offset+40);
lut->e22 = read_s15Fixed16Number(src, offset+44);
- //TODO: finish up
+ for (i = 0; i < lut->num_input_table_entries * in_chan; i++) {
+ if (type == LUT8_TYPE) {
+ lut->input_table[i] = read_u8(src, offset + 52 + i * entry_size) / 255.0f;
+ } else {
+ lut->input_table[i] = read_u16(src, offset + 52 + i * entry_size) / 65535.0f;
+ }
+ }
+
+ clut_offset = offset + 52 + lut->num_input_table_entries * in_chan * entry_size;
+ for (i = 0; i < clut_size * out_chan; i+=3) {
+ if (type == LUT8_TYPE) {
+ lut->clut_table[i*3+0] = read_u8(src, clut_offset + i*entry_size + 0) / 255.0f;
+ lut->clut_table[i*3+1] = read_u8(src, clut_offset + i*entry_size + 1) / 255.0f;
+ lut->clut_table[i*3+2] = read_u8(src, clut_offset + i*entry_size + 2) / 255.0f;
+ } else {
+ lut->clut_table[i+0] = read_u16(src, clut_offset + i*entry_size + 0) / 65535.0f;
+ lut->clut_table[i+1] = read_u16(src, clut_offset + i*entry_size + 2) / 65535.0f;
+ lut->clut_table[i+2] = read_u16(src, clut_offset + i*entry_size + 4) / 65535.0f;
+ }
+ }
+
+ output_offset = clut_offset + clut_size * out_chan * entry_size;
+ for (i = 0; i < lut->num_output_table_entries * out_chan; i++) {
+ if (type == LUT8_TYPE) {
+ lut->output_table[i] = read_u8(src, output_offset + i*entry_size) / 255.0f;
+ } else {
+ lut->output_table[i] = read_u16(src, output_offset + i*entry_size) / 65535.0f;
+ }
+ }
+
return lut;
}
@@ -493,6 +810,7 @@
curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*num_entries);
if (!curve)
return NULL;
+ curve->type = CURVE_TYPE;
curve->count = num_entries;
for (i = 0; i < num_entries; i++) {
curve->data[i] = table[i];
@@ -547,7 +865,7 @@
//XXX: should store the whitepoint
if (!set_rgb_colorants(profile, white_point, primaries)) {
- qcms_profile_fini(profile);
+ qcms_profile_release(profile);
return INVALID_PROFILE;
}
@@ -556,7 +874,7 @@
profile->greenTRC = curve_from_gamma(gamma);
if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) {
- qcms_profile_fini(profile);
+ qcms_profile_release(profile);
return NO_MEM_PROFILE;
}
profile->class = DISPLAY_DEVICE_PROFILE;
@@ -576,7 +894,7 @@
//XXX: should store the whitepoint
if (!set_rgb_colorants(profile, white_point, primaries)) {
- qcms_profile_fini(profile);
+ qcms_profile_release(profile);
return INVALID_PROFILE;
}
@@ -585,7 +903,7 @@
profile->greenTRC = curve_from_table(table, num_entries);
if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) {
- qcms_profile_fini(profile);
+ qcms_profile_release(profile);
return NO_MEM_PROFILE;
}
profile->class = DISPLAY_DEVICE_PROFILE;
@@ -697,6 +1015,7 @@
read_class_signature(profile, src);
read_rendering_intent(profile, src);
read_color_space(profile, src);
+ read_pcs(profile, src);
//TODO read rest of profile stuff
if (!src->valid)
@@ -706,23 +1025,48 @@
if (!src->valid || !index.tags)
goto invalid_tag_table;
- if (profile->class == DISPLAY_DEVICE_PROFILE || profile->class == INPUT_DEVICE_PROFILE) {
- if (profile->color_space == RGB_SIGNATURE) {
+ if (find_tag(index, TAG_CHAD)) {
+ profile->chromaticAdaption = read_tag_s15Fixed16ArrayType(src, index, TAG_CHAD);
+ } else {
+ profile->chromaticAdaption.invalid = true; //Signal the data is not present
+ }
- profile->redColorant = read_tag_XYZType(src, index, TAG_rXYZ);
- profile->greenColorant = read_tag_XYZType(src, index, TAG_gXYZ);
- profile->blueColorant = read_tag_XYZType(src, index, TAG_bXYZ);
+ if (profile->class == DISPLAY_DEVICE_PROFILE || profile->class == INPUT_DEVICE_PROFILE ||
+ profile->class == OUTPUT_DEVICE_PROFILE || profile->class == COLOR_SPACE_PROFILE) {
+ if (profile->color_space == RGB_SIGNATURE) {
+ if (find_tag(index, TAG_A2B0)) {
+ if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT8_TYPE ||
+ read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT16_TYPE) {
+ profile->A2B0 = read_tag_lutType(src, index, TAG_A2B0);
+ } else if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT_MAB_TYPE) {
+ profile->mAB = read_tag_lutmABType(src, index, TAG_A2B0);
+ }
+ }
+ if (find_tag(index, TAG_B2A0)) {
+ if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT8_TYPE ||
+ read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT16_TYPE) {
+ profile->B2A0 = read_tag_lutType(src, index, TAG_B2A0);
+ } else if (read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT_MBA_TYPE) {
+ profile->mBA = read_tag_lutmABType(src, index, TAG_B2A0);
+ }
+ }
+ if (find_tag(index, TAG_rXYZ)) {
+ profile->redColorant = read_tag_XYZType(src, index, TAG_rXYZ);
+ profile->greenColorant = read_tag_XYZType(src, index, TAG_gXYZ);
+ profile->blueColorant = read_tag_XYZType(src, index, TAG_bXYZ);
+ }
if (!src->valid)
goto invalid_tag_table;
- profile->redTRC = read_tag_curveType(src, index, TAG_rTRC);
- profile->greenTRC = read_tag_curveType(src, index, TAG_gTRC);
- profile->blueTRC = read_tag_curveType(src, index, TAG_bTRC);
+ if (find_tag(index, TAG_rTRC)) {
+ profile->redTRC = read_tag_curveType(src, index, TAG_rTRC);
+ profile->greenTRC = read_tag_curveType(src, index, TAG_gTRC);
+ profile->blueTRC = read_tag_curveType(src, index, TAG_bTRC);
- if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC)
- goto invalid_tag_table;
-
+ if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC)
+ goto invalid_tag_table;
+ }
} else if (profile->color_space == GRAY_SIGNATURE) {
profile->grayTRC = read_tag_curveType(src, index, TAG_kTRC);
@@ -732,8 +1076,6 @@
} else {
goto invalid_tag_table;
}
- } else if (0 && profile->class == OUTPUT_DEVICE_PROFILE) {
- profile->A2B0 = read_tag_lutType(src, index, TAG_A2B0);
} else {
goto invalid_tag_table;
}
@@ -748,7 +1090,9 @@
invalid_tag_table:
free(index.tags);
invalid_profile:
- qcms_profile_fini(profile);
+ // REVIEW Why is this fini? This should be qcms_profile_release right?
+ // We could fail after some tags have been parsed and leak.
+ qcms_profile_release(profile);
return INVALID_PROFILE;
}
@@ -763,6 +1107,25 @@
return profile->color_space;
}
+static void lut_release(struct lutType *lut)
+{
+ free(lut);
+}
+
+static void mAB_release(struct lutmABType *lut)
+{
+ uint8_t i;
+
+ for (i = 0; i < lut->num_in_channels; i++){
+ free(lut->a_curves[i]);
+ }
+ for (i = 0; i < lut->num_out_channels; i++){
+ free(lut->b_curves[i]);
+ free(lut->m_curves[i]);
+ }
+ free(lut);
+}
+
void qcms_profile_release(qcms_profile *profile)
{
if (profile->output_table_r)
@@ -771,6 +1134,18 @@
precache_release(profile->output_table_g);
if (profile->output_table_b)
precache_release(profile->output_table_b);
+
+ // Should this go into _release or _fini?
+ if (profile->A2B0)
+ lut_release(profile->A2B0);
+ if (profile->B2A0)
+ lut_release(profile->B2A0);
+
+ // Should this go into _release or _fini?
+ if (profile->mAB)
+ mAB_release(profile->mAB);
+ if (profile->mBA)
+ mAB_release(profile->mBA);
qcms_profile_fini(profile);
}
diff --git a/matrix.c b/matrix.c
new file mode 100644
index 0000000..0ce5bd6
--- /dev/null
+++ b/matrix.c
@@ -0,0 +1,136 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include <stdlib.h>
+#include "qcmsint.h"
+#include "matrix.h"
+
+struct vector matrix_eval(struct matrix mat, struct vector v)
+{
+ struct vector result;
+ result.v[0] = mat.m[0][0]*v.v[0] + mat.m[0][1]*v.v[1] + mat.m[0][2]*v.v[2];
+ result.v[1] = mat.m[1][0]*v.v[0] + mat.m[1][1]*v.v[1] + mat.m[1][2]*v.v[2];
+ result.v[2] = mat.m[2][0]*v.v[0] + mat.m[2][1]*v.v[1] + mat.m[2][2]*v.v[2];
+ return result;
+}
+
+//XXX: should probably pass by reference and we could
+//probably reuse this computation in matrix_invert
+float matrix_det(struct matrix mat)
+{
+ float det;
+ det = mat.m[0][0]*mat.m[1][1]*mat.m[2][2] +
+ mat.m[0][1]*mat.m[1][2]*mat.m[2][0] +
+ mat.m[0][2]*mat.m[1][0]*mat.m[2][1] -
+ mat.m[0][0]*mat.m[1][2]*mat.m[2][1] -
+ mat.m[0][1]*mat.m[1][0]*mat.m[2][2] -
+ mat.m[0][2]*mat.m[1][1]*mat.m[2][0];
+ return det;
+}
+
+/* from pixman and cairo and Mathematics for Game Programmers */
+/* lcms uses gauss-jordan elimination with partial pivoting which is
+ * less efficient and not as numerically stable. See Mathematics for
+ * Game Programmers. */
+struct matrix matrix_invert(struct matrix mat)
+{
+ struct matrix dest_mat;
+ int i,j;
+ static int a[3] = { 2, 2, 1 };
+ static int b[3] = { 1, 0, 0 };
+
+ /* inv (A) = 1/det (A) * adj (A) */
+ float det = matrix_det(mat);
+
+ if (det == 0) {
+ dest_mat.invalid = true;
+ } else {
+ dest_mat.invalid = false;
+ }
+
+ det = 1/det;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 3; i++) {
+ double p;
+ int ai = a[i];
+ int aj = a[j];
+ int bi = b[i];
+ int bj = b[j];
+
+ p = mat.m[ai][aj] * mat.m[bi][bj] -
+ mat.m[ai][bj] * mat.m[bi][aj];
+ if (((i + j) & 1) != 0)
+ p = -p;
+
+ dest_mat.m[j][i] = det * p;
+ }
+ }
+ return dest_mat;
+}
+
+struct matrix matrix_identity(void)
+{
+ struct matrix i;
+ i.m[0][0] = 1;
+ i.m[0][1] = 0;
+ i.m[0][2] = 0;
+ i.m[1][0] = 0;
+ i.m[1][1] = 1;
+ i.m[1][2] = 0;
+ i.m[2][0] = 0;
+ i.m[2][1] = 0;
+ i.m[2][2] = 1;
+ i.invalid = false;
+ return i;
+}
+
+struct matrix matrix_invalid(void)
+{
+ struct matrix inv = matrix_identity();
+ inv.invalid = true;
+ return inv;
+}
+
+
+/* from pixman */
+/* MAT3per... */
+struct matrix matrix_multiply(struct matrix a, struct matrix b)
+{
+ struct matrix result;
+ int dx, dy;
+ int o;
+ for (dy = 0; dy < 3; dy++) {
+ for (dx = 0; dx < 3; dx++) {
+ double v = 0;
+ for (o = 0; o < 3; o++) {
+ v += a.m[dy][o] * b.m[o][dx];
+ }
+ result.m[dy][dx] = v;
+ }
+ }
+ result.invalid = a.invalid || b.invalid;
+ return result;
+}
+
+
diff --git a/matrix.h b/matrix.h
new file mode 100644
index 0000000..5011988
--- /dev/null
+++ b/matrix.h
@@ -0,0 +1,39 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef _QCMS_MATRIX_H
+#define _QCMS_MATRIX_H
+
+struct vector {
+ float v[3];
+};
+
+struct vector matrix_eval(struct matrix mat, struct vector v);
+float matrix_det(struct matrix mat);
+struct matrix matrix_identity(void);
+struct matrix matrix_multiply(struct matrix a, struct matrix b);
+struct matrix matrix_invert(struct matrix mat);
+
+struct matrix matrix_invalid(void);
+
+#endif
diff --git a/qcmsint.h b/qcmsint.h
index efe7b15..3818f0f 100644
--- a/qcmsint.h
+++ b/qcmsint.h
@@ -1,3 +1,4 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
#include "qcms.h"
#include "qcmstypes.h"
@@ -29,6 +30,19 @@
float *input_gamma_table_g;
float *input_gamma_table_b;
+ float *input_clut_table_r;
+ float *input_clut_table_g;
+ float *input_clut_table_b;
+ uint16_t input_clut_table_length;
+ float *r_clut;
+ float *g_clut;
+ float *b_clut;
+ uint16_t grid_size;
+ float *output_clut_table_r;
+ float *output_clut_table_g;
+ float *output_clut_table_b;
+ uint16_t output_clut_table_length;
+
float *input_gamma_table_gray;
float out_gamma_r;
@@ -56,6 +70,39 @@
void (*transform_fn)(struct _qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length);
};
+struct matrix {
+ float m[3][3];
+ bool invalid;
+};
+struct qcms_modular_transform {
+ struct matrix matrix;
+ float tx, ty, tz;
+
+ float *input_clut_table_r;
+ float *input_clut_table_g;
+ float *input_clut_table_b;
+ uint16_t input_clut_table_length;
+ float *r_clut;
+ float *g_clut;
+ float *b_clut;
+ uint16_t grid_size;
+ float *output_clut_table_r;
+ float *output_clut_table_g;
+ float *output_clut_table_b;
+ uint16_t output_clut_table_length;
+
+ uint16_t *output_gamma_lut_r;
+ uint16_t *output_gamma_lut_g;
+ uint16_t *output_gamma_lut_b;
+
+ size_t output_gamma_lut_r_length;
+ size_t output_gamma_lut_g_length;
+ size_t output_gamma_lut_b_length;
+
+ void (*transform_module_fn)(struct qcms_modular_transform *transform, float *src, float *dest, size_t length);
+ struct qcms_modular_transform *next_transform;
+};
+
typedef int32_t s15Fixed16Number;
typedef uint16_t uInt16Number;
@@ -66,7 +113,9 @@
};
struct curveType {
+ uint32_t type;
uint32_t count;
+ float parameter[7];
/* Using the C99 flexible array member syntax with IBM compiler */
#if defined (__IBMC__) || defined (__IBMCPP__)
uInt16Number data[];
@@ -75,7 +124,36 @@
#endif
};
-struct lutType {
+struct lutmABType {
+ uint8_t num_in_channels;
+ uint8_t num_out_channels;
+ // 16 is the upperbound, actual is 0..num_in_channels.
+ uint8_t num_grid_points[16];
+
+ s15Fixed16Number e00;
+ s15Fixed16Number e01;
+ s15Fixed16Number e02;
+ s15Fixed16Number e03;
+ s15Fixed16Number e10;
+ s15Fixed16Number e11;
+ s15Fixed16Number e12;
+ s15Fixed16Number e13;
+ s15Fixed16Number e20;
+ s15Fixed16Number e21;
+ s15Fixed16Number e22;
+ s15Fixed16Number e23;
+
+ // reversed elements (for mBA)
+ bool reversed;
+
+ float *clut_table;
+ struct curveType *a_curves[10];
+ struct curveType *b_curves[10];
+ struct curveType *m_curves[10];
+};
+
+/* should lut8Type and lut16Type be different types? */
+struct lutType { // used by lut8Type/lut16Type (mft2) only
uint8_t num_input_channels;
uint8_t num_output_channels;
uint8_t num_clut_grid_points;
@@ -93,9 +171,9 @@
uint16_t num_input_table_entries;
uint16_t num_output_table_entries;
- uint16_t *input_table;
- uint16_t *clut_table;
- uint16_t *output_table;
+ float *input_table;
+ float *clut_table;
+ float *output_table;
};
#if 0
/* this is from an intial idea of having the struct correspond to the data in
@@ -118,10 +196,13 @@
#define RGB_SIGNATURE 0x52474220
#define GRAY_SIGNATURE 0x47524159
+#define XYZ_SIGNATURE 0x58595A20
+#define LAB_SIGNATURE 0x4C616220
struct _qcms_profile {
uint32_t class;
uint32_t color_space;
+ uint32_t pcs;
qcms_intent rendering_intent;
struct XYZNumber redColorant;
struct XYZNumber blueColorant;
@@ -131,6 +212,10 @@
struct curveType *greenTRC;
struct curveType *grayTRC;
struct lutType *A2B0;
+ struct lutType *B2A0;
+ struct lutmABType *mAB;
+ struct lutmABType *mBA;
+ struct matrix chromaticAdaption;
struct precache_output *output_table_r;
struct precache_output *output_table_g;
diff --git a/transform.c b/transform.c
index 0e7da10..72e5535 100644
--- a/transform.c
+++ b/transform.c
@@ -1,3 +1,4 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
// qcms
// Copyright (C) 2009 Mozilla Corporation
// Copyright (C) 1998-2007 Marti Maria
@@ -23,382 +24,17 @@
#include <stdlib.h>
#include <math.h>
#include <assert.h>
+#include <string.h> //memcpy
#include "qcmsint.h"
+#include "chain.h"
+#include "matrix.h"
+#include "transform_util.h"
/* for MSVC, GCC, Intel, and Sun compilers */
#if defined(_M_IX86) || defined(__i386__) || defined(__i386) || defined(_M_AMD64) || defined(__x86_64__) || defined(__x86_64)
#define X86
#endif /* _M_IX86 || __i386__ || __i386 || _M_AMD64 || __x86_64__ || __x86_64 */
-//XXX: could use a bettername
-typedef uint16_t uint16_fract_t;
-
-/* value must be a value between 0 and 1 */
-//XXX: is the above a good restriction to have?
-float lut_interp_linear(double value, uint16_t *table, int length)
-{
- int upper, lower;
- value = value * (length - 1); // scale to length of the array
- upper = ceil(value);
- lower = floor(value);
- //XXX: can we be more performant here?
- value = table[upper]*(1. - (upper - value)) + table[lower]*(upper - value);
- /* scale the value */
- return value * (1./65535.);
-}
-
-/* same as above but takes and returns a uint16_t value representing a range from 0..1 */
-uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length)
-{
- /* Start scaling input_value to the length of the array: 65535*(length-1).
- * We'll divide out the 65535 next */
- uint32_t value = (input_value * (length - 1));
- uint32_t upper = (value + 65534) / 65535; /* equivalent to ceil(value/65535) */
- uint32_t lower = value / 65535; /* equivalent to floor(value/65535) */
- /* interp is the distance from upper to value scaled to 0..65535 */
- uint32_t interp = value % 65535;
-
- value = (table[upper]*(interp) + table[lower]*(65535 - interp))/65535; // 0..65535*65535
-
- return value;
-}
-
-/* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX
- * and returns a uint8_t value representing a range from 0..1 */
-static
-uint8_t lut_interp_linear_precache_output(uint32_t input_value, uint16_t *table, int length)
-{
- /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1).
- * We'll divide out the PRECACHE_OUTPUT_MAX next */
- uint32_t value = (input_value * (length - 1));
-
- /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */
- uint32_t upper = (value + PRECACHE_OUTPUT_MAX-1) / PRECACHE_OUTPUT_MAX;
- /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */
- uint32_t lower = value / PRECACHE_OUTPUT_MAX;
- /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */
- uint32_t interp = value % PRECACHE_OUTPUT_MAX;
-
- /* the table values range from 0..65535 */
- value = (table[upper]*(interp) + table[lower]*(PRECACHE_OUTPUT_MAX - interp)); // 0..(65535*PRECACHE_OUTPUT_MAX)
-
- /* round and scale */
- value += (PRECACHE_OUTPUT_MAX*65535/255)/2;
- value /= (PRECACHE_OUTPUT_MAX*65535/255); // scale to 0..255
- return value;
-}
-
-#if 0
-/* if we use a different representation i.e. one that goes from 0 to 0x1000 we can be more efficient
- * because we can avoid the divisions and use a shifting instead */
-/* same as above but takes and returns a uint16_t value representing a range from 0..1 */
-uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length)
-{
- uint32_t value = (input_value * (length - 1));
- uint32_t upper = (value + 4095) / 4096; /* equivalent to ceil(value/4096) */
- uint32_t lower = value / 4096; /* equivalent to floor(value/4096) */
- uint32_t interp = value % 4096;
-
- value = (table[upper]*(interp) + table[lower]*(4096 - interp))/4096; // 0..4096*4096
-
- return value;
-}
-#endif
-
-void compute_curve_gamma_table_type1(float gamma_table[256], double gamma)
-{
- unsigned int i;
- for (i = 0; i < 256; i++) {
- gamma_table[i] = pow(i/255., gamma);
- }
-}
-
-void compute_curve_gamma_table_type2(float gamma_table[256], uint16_t *table, int length)
-{
- unsigned int i;
- for (i = 0; i < 256; i++) {
- gamma_table[i] = lut_interp_linear(i/255., table, length);
- }
-}
-
-void compute_curve_gamma_table_type0(float gamma_table[256])
-{
- unsigned int i;
- for (i = 0; i < 256; i++) {
- gamma_table[i] = i/255.;
- }
-}
-
-unsigned char clamp_u8(float v)
-{
- if (v > 255.)
- return 255;
- else if (v < 0)
- return 0;
- else
- return floor(v+.5);
-}
-
-struct vector {
- float v[3];
-};
-
-struct matrix {
- float m[3][3];
- bool invalid;
-};
-
-struct vector matrix_eval(struct matrix mat, struct vector v)
-{
- struct vector result;
- result.v[0] = mat.m[0][0]*v.v[0] + mat.m[0][1]*v.v[1] + mat.m[0][2]*v.v[2];
- result.v[1] = mat.m[1][0]*v.v[0] + mat.m[1][1]*v.v[1] + mat.m[1][2]*v.v[2];
- result.v[2] = mat.m[2][0]*v.v[0] + mat.m[2][1]*v.v[1] + mat.m[2][2]*v.v[2];
- return result;
-}
-
-//XXX: should probably pass by reference and we could
-//probably reuse this computation in matrix_invert
-float matrix_det(struct matrix mat)
-{
- float det;
- det = mat.m[0][0]*mat.m[1][1]*mat.m[2][2] +
- mat.m[0][1]*mat.m[1][2]*mat.m[2][0] +
- mat.m[0][2]*mat.m[1][0]*mat.m[2][1] -
- mat.m[0][0]*mat.m[1][2]*mat.m[2][1] -
- mat.m[0][1]*mat.m[1][0]*mat.m[2][2] -
- mat.m[0][2]*mat.m[1][1]*mat.m[2][0];
- return det;
-}
-
-/* from pixman and cairo and Mathematics for Game Programmers */
-/* lcms uses gauss-jordan elimination with partial pivoting which is
- * less efficient and not as numerically stable. See Mathematics for
- * Game Programmers. */
-struct matrix matrix_invert(struct matrix mat)
-{
- struct matrix dest_mat;
- int i,j;
- static int a[3] = { 2, 2, 1 };
- static int b[3] = { 1, 0, 0 };
-
- /* inv (A) = 1/det (A) * adj (A) */
- float det = matrix_det(mat);
-
- if (det == 0) {
- dest_mat.invalid = true;
- } else {
- dest_mat.invalid = false;
- }
-
- det = 1/det;
-
- for (j = 0; j < 3; j++) {
- for (i = 0; i < 3; i++) {
- double p;
- int ai = a[i];
- int aj = a[j];
- int bi = b[i];
- int bj = b[j];
-
- p = mat.m[ai][aj] * mat.m[bi][bj] -
- mat.m[ai][bj] * mat.m[bi][aj];
- if (((i + j) & 1) != 0)
- p = -p;
-
- dest_mat.m[j][i] = det * p;
- }
- }
- return dest_mat;
-}
-
-struct matrix matrix_identity(void)
-{
- struct matrix i;
- i.m[0][0] = 1;
- i.m[0][1] = 0;
- i.m[0][2] = 0;
- i.m[1][0] = 0;
- i.m[1][1] = 1;
- i.m[1][2] = 0;
- i.m[2][0] = 0;
- i.m[2][1] = 0;
- i.m[2][2] = 1;
- i.invalid = false;
- return i;
-}
-
-static struct matrix matrix_invalid(void)
-{
- struct matrix inv = matrix_identity();
- inv.invalid = true;
- return inv;
-}
-
-
-/* from pixman */
-/* MAT3per... */
-struct matrix matrix_multiply(struct matrix a, struct matrix b)
-{
- struct matrix result;
- int dx, dy;
- int o;
- for (dy = 0; dy < 3; dy++) {
- for (dx = 0; dx < 3; dx++) {
- double v = 0;
- for (o = 0; o < 3; o++) {
- v += a.m[dy][o] * b.m[o][dx];
- }
- result.m[dy][dx] = v;
- }
- }
- result.invalid = a.invalid || b.invalid;
- return result;
-}
-
-float u8Fixed8Number_to_float(uint16_t x)
-{
- // 0x0000 = 0.
- // 0x0100 = 1.
- // 0xffff = 255 + 255/256
- return x/256.;
-}
-
-float *build_input_gamma_table(struct curveType *TRC)
-{
- float *gamma_table = malloc(sizeof(float)*256);
- if (gamma_table) {
- if (TRC->count == 0) {
- compute_curve_gamma_table_type0(gamma_table);
- } else if (TRC->count == 1) {
- compute_curve_gamma_table_type1(gamma_table, u8Fixed8Number_to_float(TRC->data[0]));
- } else {
- compute_curve_gamma_table_type2(gamma_table, TRC->data, TRC->count);
- }
- }
- return gamma_table;
-}
-
-struct matrix build_colorant_matrix(qcms_profile *p)
-{
- struct matrix result;
- result.m[0][0] = s15Fixed16Number_to_float(p->redColorant.X);
- result.m[0][1] = s15Fixed16Number_to_float(p->greenColorant.X);
- result.m[0][2] = s15Fixed16Number_to_float(p->blueColorant.X);
- result.m[1][0] = s15Fixed16Number_to_float(p->redColorant.Y);
- result.m[1][1] = s15Fixed16Number_to_float(p->greenColorant.Y);
- result.m[1][2] = s15Fixed16Number_to_float(p->blueColorant.Y);
- result.m[2][0] = s15Fixed16Number_to_float(p->redColorant.Z);
- result.m[2][1] = s15Fixed16Number_to_float(p->greenColorant.Z);
- result.m[2][2] = s15Fixed16Number_to_float(p->blueColorant.Z);
- result.invalid = false;
- return result;
-}
-
-/* The following code is copied nearly directly from lcms.
- * I think it could be much better. For example, Argyll seems to have better code in
- * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way
- * to a working solution and allows for easy comparing with lcms. */
-uint16_fract_t lut_inverse_interp16(uint16_t Value, uint16_t LutTable[], int length)
-{
- int l = 1;
- int r = 0x10000;
- int x = 0, res; // 'int' Give spacing for negative values
- int NumZeroes, NumPoles;
- int cell0, cell1;
- double val2;
- double y0, y1, x0, x1;
- double a, b, f;
-
- // July/27 2001 - Expanded to handle degenerated curves with an arbitrary
- // number of elements containing 0 at the begining of the table (Zeroes)
- // and another arbitrary number of poles (FFFFh) at the end.
- // First the zero and pole extents are computed, then value is compared.
-
- NumZeroes = 0;
- while (LutTable[NumZeroes] == 0 && NumZeroes < length-1)
- NumZeroes++;
-
- // There are no zeros at the beginning and we are trying to find a zero, so
- // return anything. It seems zero would be the less destructive choice
- /* I'm not sure that this makes sense, but oh well... */
- if (NumZeroes == 0 && Value == 0)
- return 0;
-
- NumPoles = 0;
- while (LutTable[length-1- NumPoles] == 0xFFFF && NumPoles < length-1)
- NumPoles++;
-
- // Does the curve belong to this case?
- if (NumZeroes > 1 || NumPoles > 1)
- {
- int a, b;
-
- // Identify if value fall downto 0 or FFFF zone
- if (Value == 0) return 0;
- // if (Value == 0xFFFF) return 0xFFFF;
-
- // else restrict to valid zone
-
- a = ((NumZeroes-1) * 0xFFFF) / (length-1);
- b = ((length-1 - NumPoles) * 0xFFFF) / (length-1);
-
- l = a - 1;
- r = b + 1;
- }
-
-
- // Seems not a degenerated case... apply binary search
-
- while (r > l) {
-
- x = (l + r) / 2;
-
- res = (int) lut_interp_linear16((uint16_fract_t) (x-1), LutTable, length);
-
- if (res == Value) {
-
- // Found exact match.
-
- return (uint16_fract_t) (x - 1);
- }
-
- if (res > Value) r = x - 1;
- else l = x + 1;
- }
-
- // Not found, should we interpolate?
-
-
- // Get surrounding nodes
-
- val2 = (length-1) * ((double) (x - 1) / 65535.0);
-
- cell0 = (int) floor(val2);
- cell1 = (int) ceil(val2);
-
- if (cell0 == cell1) return (uint16_fract_t) x;
-
- y0 = LutTable[cell0] ;
- x0 = (65535.0 * cell0) / (length-1);
-
- y1 = LutTable[cell1] ;
- x1 = (65535.0 * cell1) / (length-1);
-
- a = (y1 - y0) / (x1 - x0);
- b = y0 - a * x0;
-
- if (fabs(a) < 0.01) return (uint16_fract_t) x;
-
- f = ((Value - b) / a);
-
- if (f < 0.0) return (uint16_fract_t) 0;
- if (f >= 65535.0) return (uint16_fract_t) 0xFFFF;
-
- return (uint16_fract_t) floor(f + 0.5);
-
-}
-
// Build a White point, primary chromas transfer matrix from RGB to CIE XYZ
// This is just an approximation, I am not handling all the non-linear
// aspects of the RGB to XYZ process, and assumming that the gamma correction
@@ -593,79 +229,6 @@
return true;
}
-/*
- The number of entries needed to invert a lookup table should not
- necessarily be the same as the original number of entries. This is
- especially true of lookup tables that have a small number of entries.
-
- For example:
- Using a table like:
- {0, 3104, 14263, 34802, 65535}
- invert_lut will produce an inverse of:
- {3, 34459, 47529, 56801, 65535}
- which has an maximum error of about 9855 (pixel difference of ~38.346)
-
- For now, we punt the decision of output size to the caller. */
-static uint16_t *invert_lut(uint16_t *table, int length, int out_length)
-{
- int i;
- /* for now we invert the lut by creating a lut of size out_length
- * and attempting to lookup a value for each entry using lut_inverse_interp16 */
- uint16_t *output = malloc(sizeof(uint16_t)*out_length);
- if (!output)
- return NULL;
-
- for (i = 0; i < out_length; i++) {
- double x = ((double) i * 65535.) / (double) (out_length - 1);
- uint16_fract_t input = floor(x + .5);
- output[i] = lut_inverse_interp16(input, table, length);
- }
- return output;
-}
-
-static uint16_t *build_linear_table(int length)
-{
- int i;
- uint16_t *output = malloc(sizeof(uint16_t)*length);
- if (!output)
- return NULL;
-
- for (i = 0; i < length; i++) {
- double x = ((double) i * 65535.) / (double) (length - 1);
- uint16_fract_t input = floor(x + .5);
- output[i] = input;
- }
- return output;
-}
-
-static uint16_t *build_pow_table(float gamma, int length)
-{
- int i;
- uint16_t *output = malloc(sizeof(uint16_t)*length);
- if (!output)
- return NULL;
-
- for (i = 0; i < length; i++) {
- uint16_fract_t result;
- double x = ((double) i) / (double) (length - 1);
- x = pow(x, gamma);
- //XXX turn this conversion into a function
- result = floor(x*65535. + .5);
- output[i] = result;
- }
- return output;
-}
-
-static float clamp_float(float a)
-{
- if (a > 1.)
- return 1.;
- else if (a < 0)
- return 0;
- else
- return a;
-}
-
#if 0
static void qcms_transform_data_rgb_out_pow(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length)
{
@@ -848,6 +411,292 @@
}
}
+// Not used
+/*
+static void qcms_transform_data_clut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length) {
+ unsigned int i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+
+ for (i = 0; i < length; i++) {
+ unsigned char in_r = *src++;
+ unsigned char in_g = *src++;
+ unsigned char in_b = *src++;
+ float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float x_d = linear_r * (transform->grid_size-1) - x;
+ float y_d = linear_g * (transform->grid_size-1) - y;
+ float z_d = linear_b * (transform->grid_size-1) - z;
+
+ float r_x1 = lerp(CLU(r_table,x,y,z), CLU(r_table,x_n,y,z), x_d);
+ float r_x2 = lerp(CLU(r_table,x,y_n,z), CLU(r_table,x_n,y_n,z), x_d);
+ float r_y1 = lerp(r_x1, r_x2, y_d);
+ float r_x3 = lerp(CLU(r_table,x,y,z_n), CLU(r_table,x_n,y,z_n), x_d);
+ float r_x4 = lerp(CLU(r_table,x,y_n,z_n), CLU(r_table,x_n,y_n,z_n), x_d);
+ float r_y2 = lerp(r_x3, r_x4, y_d);
+ float clut_r = lerp(r_y1, r_y2, z_d);
+
+ float g_x1 = lerp(CLU(g_table,x,y,z), CLU(g_table,x_n,y,z), x_d);
+ float g_x2 = lerp(CLU(g_table,x,y_n,z), CLU(g_table,x_n,y_n,z), x_d);
+ float g_y1 = lerp(g_x1, g_x2, y_d);
+ float g_x3 = lerp(CLU(g_table,x,y,z_n), CLU(g_table,x_n,y,z_n), x_d);
+ float g_x4 = lerp(CLU(g_table,x,y_n,z_n), CLU(g_table,x_n,y_n,z_n), x_d);
+ float g_y2 = lerp(g_x3, g_x4, y_d);
+ float clut_g = lerp(g_y1, g_y2, z_d);
+
+ float b_x1 = lerp(CLU(b_table,x,y,z), CLU(b_table,x_n,y,z), x_d);
+ float b_x2 = lerp(CLU(b_table,x,y_n,z), CLU(b_table,x_n,y_n,z), x_d);
+ float b_y1 = lerp(b_x1, b_x2, y_d);
+ float b_x3 = lerp(CLU(b_table,x,y,z_n), CLU(b_table,x_n,y,z_n), x_d);
+ float b_x4 = lerp(CLU(b_table,x,y_n,z_n), CLU(b_table,x_n,y_n,z_n), x_d);
+ float b_y2 = lerp(b_x3, b_x4, y_d);
+ float clut_b = lerp(b_y1, b_y2, z_d);
+
+ *dest++ = clamp_u8(clut_r*255.0f);
+ *dest++ = clamp_u8(clut_g*255.0f);
+ *dest++ = clamp_u8(clut_b*255.0f);
+ }
+}
+*/
+
+// Using lcms' tetra interpolation algorithm.
+static void qcms_transform_data_tetra_clut_rgba(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length) {
+ unsigned int i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+ float c0_r, c1_r, c2_r, c3_r;
+ float c0_g, c1_g, c2_g, c3_g;
+ float c0_b, c1_b, c2_b, c3_b;
+ float clut_r, clut_g, clut_b;
+ for (i = 0; i < length; i++) {
+ unsigned char in_r = *src++;
+ unsigned char in_g = *src++;
+ unsigned char in_b = *src++;
+ unsigned char in_a = *src++;
+ float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float rx = linear_r * (transform->grid_size-1) - x;
+ float ry = linear_g * (transform->grid_size-1) - y;
+ float rz = linear_b * (transform->grid_size-1) - z;
+
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+
+ if( rx >= ry ) {
+ if (ry >= rz) { //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (rx >= rz) { //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else { //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ } else {
+ if (rx >= rz) { //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (ry >= rz) { //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else { //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ }
+
+ clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz;
+ clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz;
+ clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz;
+
+ *dest++ = clamp_u8(clut_r*255.0f);
+ *dest++ = clamp_u8(clut_g*255.0f);
+ *dest++ = clamp_u8(clut_b*255.0f);
+ *dest++ = in_a;
+ }
+}
+
+// Using lcms' tetra interpolation code.
+static void qcms_transform_data_tetra_clut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length) {
+ unsigned int i;
+ int xy_len = 1;
+ int x_len = transform->grid_size;
+ int len = x_len * x_len;
+ float* r_table = transform->r_clut;
+ float* g_table = transform->g_clut;
+ float* b_table = transform->b_clut;
+ float c0_r, c1_r, c2_r, c3_r;
+ float c0_g, c1_g, c2_g, c3_g;
+ float c0_b, c1_b, c2_b, c3_b;
+ float clut_r, clut_g, clut_b;
+ for (i = 0; i < length; i++) {
+ unsigned char in_r = *src++;
+ unsigned char in_g = *src++;
+ unsigned char in_b = *src++;
+ float linear_r = in_r/255.0f, linear_g=in_g/255.0f, linear_b = in_b/255.0f;
+
+ int x = floor(linear_r * (transform->grid_size-1));
+ int y = floor(linear_g * (transform->grid_size-1));
+ int z = floor(linear_b * (transform->grid_size-1));
+ int x_n = ceil(linear_r * (transform->grid_size-1));
+ int y_n = ceil(linear_g * (transform->grid_size-1));
+ int z_n = ceil(linear_b * (transform->grid_size-1));
+ float rx = linear_r * (transform->grid_size-1) - x;
+ float ry = linear_g * (transform->grid_size-1) - y;
+ float rz = linear_b * (transform->grid_size-1) - z;
+
+ c0_r = CLU(r_table, x, y, z);
+ c0_g = CLU(g_table, x, y, z);
+ c0_b = CLU(b_table, x, y, z);
+
+ if( rx >= ry ) {
+ if (ry >= rz) { //rx >= ry && ry >= rz
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x_n, y, z);
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x_n, y, z);
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x_n, y, z);
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (rx >= rz) { //rx >= rz && rz >= ry
+ c1_r = CLU(r_table, x_n, y, z) - c0_r;
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x_n, y, z);
+ c1_g = CLU(g_table, x_n, y, z) - c0_g;
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x_n, y, z);
+ c1_b = CLU(b_table, x_n, y, z) - c0_b;
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x_n, y, z);
+ } else { //rz > rx && rx >= ry
+ c1_r = CLU(r_table, x_n, y, z_n) - CLU(r_table, x, y, z_n);
+ c2_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y, z_n) - CLU(g_table, x, y, z_n);
+ c2_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y, z_n) - CLU(b_table, x, y, z_n);
+ c2_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ } else {
+ if (rx >= rz) { //ry > rx && rx >= rz
+ c1_r = CLU(r_table, x_n, y_n, z) - CLU(r_table, x, y_n, z);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x_n, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z) - CLU(g_table, x, y_n, z);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x_n, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z) - CLU(b_table, x, y_n, z);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x_n, y_n, z);
+ } else {
+ if (ry >= rz) { //ry >= rz && rz > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z) - c0_r;
+ c3_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y_n, z);
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z) - c0_g;
+ c3_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y_n, z);
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z) - c0_b;
+ c3_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y_n, z);
+ } else { //rz > ry && ry > rx
+ c1_r = CLU(r_table, x_n, y_n, z_n) - CLU(r_table, x, y_n, z_n);
+ c2_r = CLU(r_table, x, y_n, z_n) - CLU(r_table, x, y, z_n);
+ c3_r = CLU(r_table, x, y, z_n) - c0_r;
+ c1_g = CLU(g_table, x_n, y_n, z_n) - CLU(g_table, x, y_n, z_n);
+ c2_g = CLU(g_table, x, y_n, z_n) - CLU(g_table, x, y, z_n);
+ c3_g = CLU(g_table, x, y, z_n) - c0_g;
+ c1_b = CLU(b_table, x_n, y_n, z_n) - CLU(b_table, x, y_n, z_n);
+ c2_b = CLU(b_table, x, y_n, z_n) - CLU(b_table, x, y, z_n);
+ c3_b = CLU(b_table, x, y, z_n) - c0_b;
+ }
+ }
+ }
+
+ clut_r = c0_r + c1_r*rx + c2_r*ry + c3_r*rz;
+ clut_g = c0_g + c1_g*rx + c2_g*ry + c3_g*rz;
+ clut_b = c0_b + c1_b*rx + c2_b*ry + c3_b*rz;
+
+ *dest++ = clamp_u8(clut_r*255.0f);
+ *dest++ = clamp_u8(clut_g*255.0f);
+ *dest++ = clamp_u8(clut_b*255.0f);
+ }
+}
+
static void qcms_transform_data_rgb_out_lut(qcms_transform *transform, unsigned char *src, unsigned char *dest, size_t length)
{
unsigned int i;
@@ -870,9 +719,12 @@
out_linear_g = clamp_float(out_linear_g);
out_linear_b = clamp_float(out_linear_b);
- out_device_r = lut_interp_linear(out_linear_r, transform->output_gamma_lut_r, transform->output_gamma_lut_r_length);
- out_device_g = lut_interp_linear(out_linear_g, transform->output_gamma_lut_g, transform->output_gamma_lut_g_length);
- out_device_b = lut_interp_linear(out_linear_b, transform->output_gamma_lut_b, transform->output_gamma_lut_b_length);
+ out_device_r = lut_interp_linear(out_linear_r,
+ transform->output_gamma_lut_r, transform->output_gamma_lut_r_length);
+ out_device_g = lut_interp_linear(out_linear_g,
+ transform->output_gamma_lut_g, transform->output_gamma_lut_g_length);
+ out_device_b = lut_interp_linear(out_linear_b,
+ transform->output_gamma_lut_b, transform->output_gamma_lut_b_length);
*dest++ = clamp_u8(out_device_r*255);
*dest++ = clamp_u8(out_device_g*255);
@@ -903,9 +755,12 @@
out_linear_g = clamp_float(out_linear_g);
out_linear_b = clamp_float(out_linear_b);
- out_device_r = lut_interp_linear(out_linear_r, transform->output_gamma_lut_r, transform->output_gamma_lut_r_length);
- out_device_g = lut_interp_linear(out_linear_g, transform->output_gamma_lut_g, transform->output_gamma_lut_g_length);
- out_device_b = lut_interp_linear(out_linear_b, transform->output_gamma_lut_b, transform->output_gamma_lut_b_length);
+ out_device_r = lut_interp_linear(out_linear_r,
+ transform->output_gamma_lut_r, transform->output_gamma_lut_r_length);
+ out_device_g = lut_interp_linear(out_linear_g,
+ transform->output_gamma_lut_g, transform->output_gamma_lut_g_length);
+ out_device_b = lut_interp_linear(out_linear_b,
+ transform->output_gamma_lut_b, transform->output_gamma_lut_b_length);
*dest++ = clamp_u8(out_device_r*255);
*dest++ = clamp_u8(out_device_g*255);
@@ -1030,55 +885,6 @@
transform_free(t);
}
-static void compute_precache_pow(uint8_t *output, float gamma)
-{
- uint32_t v = 0;
- for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
- //XXX: don't do integer/float conversion... and round?
- output[v] = 255. * pow(v/(double)PRECACHE_OUTPUT_MAX, gamma);
- }
-}
-
-void compute_precache_lut(uint8_t *output, uint16_t *table, int length)
-{
- uint32_t v = 0;
- for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
- output[v] = lut_interp_linear_precache_output(v, table, length);
- }
-}
-
-void compute_precache_linear(uint8_t *output)
-{
- uint32_t v = 0;
- for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
- //XXX: round?
- output[v] = v / (PRECACHE_OUTPUT_SIZE/256);
- }
-}
-
-qcms_bool compute_precache(struct curveType *trc, uint8_t *output)
-{
- if (trc->count == 0) {
- compute_precache_linear(output);
- } else if (trc->count == 1) {
- compute_precache_pow(output, 1./u8Fixed8Number_to_float(trc->data[0]));
- } else {
- uint16_t *inverted;
- int inverted_size = trc->count;
- //XXX: the choice of a minimum of 256 here is not backed by any theory, measurement or data, however it is what lcms uses.
- // the maximum number we would need is 65535 because that's the accuracy used for computing the precache table
- if (inverted_size < 256)
- inverted_size = 256;
-
- inverted = invert_lut(trc->data, trc->count, inverted_size);
- if (!inverted)
- return false;
- compute_precache_lut(output, inverted, inverted_size);
- free(inverted);
- }
- return true;
-}
-
#ifdef X86
// Determine if we can build with SSE2 (this was partly copied from jmorecfg.h in
// mozilla/jpeg)
@@ -1160,25 +966,26 @@
}
#endif
-void build_output_lut(struct curveType *trc,
- uint16_t **output_gamma_lut, size_t *output_gamma_lut_length)
-{
- if (trc->count == 0) {
- *output_gamma_lut = build_linear_table(4096);
- *output_gamma_lut_length = 4096;
- } else if (trc->count == 1) {
- float gamma = 1./u8Fixed8Number_to_float(trc->data[0]);
- *output_gamma_lut = build_pow_table(gamma, 4096);
- *output_gamma_lut_length = 4096;
- } else {
- //XXX: the choice of a minimum of 256 here is not backed by any theory, measurement or data, however it is what lcms uses.
- *output_gamma_lut_length = trc->count;
- if (*output_gamma_lut_length < 256)
- *output_gamma_lut_length = 256;
+static const struct matrix bradford_matrix = {{ { 0.8951f, 0.2664f,-0.1614f},
+ {-0.7502f, 1.7135f, 0.0367f},
+ { 0.0389f,-0.0685f, 1.0296f}},
+ false};
- *output_gamma_lut = invert_lut(trc->data, trc->count, *output_gamma_lut_length);
- }
+static const struct matrix bradford_matrix_inv = {{ { 0.9869929f,-0.1470543f, 0.1599627f},
+ { 0.4323053f, 0.5183603f, 0.0492912f},
+ {-0.0085287f, 0.0400428f, 0.9684867f}},
+ false};
+// See ICCv4 E.3
+struct matrix compute_whitepoint_adaption(float X, float Y, float Z) {
+ float p = (0.96422f*bradford_matrix.m[0][0] + 1.000f*bradford_matrix.m[1][0] + 0.82521f*bradford_matrix.m[2][0]) /
+ (X*bradford_matrix.m[0][0] + Y*bradford_matrix.m[1][0] + Z*bradford_matrix.m[2][0] );
+ float y = (0.96422f*bradford_matrix.m[0][1] + 1.000f*bradford_matrix.m[1][1] + 0.82521f*bradford_matrix.m[2][1]) /
+ (X*bradford_matrix.m[0][1] + Y*bradford_matrix.m[1][1] + Z*bradford_matrix.m[2][1] );
+ float b = (0.96422f*bradford_matrix.m[0][2] + 1.000f*bradford_matrix.m[1][2] + 0.82521f*bradford_matrix.m[2][2]) /
+ (X*bradford_matrix.m[0][2] + Y*bradford_matrix.m[1][2] + Z*bradford_matrix.m[2][2] );
+ struct matrix white_adaption = {{ {p,0,0}, {0,y,0}, {0,0,b}}, false};
+ return matrix_multiply( bradford_matrix_inv, matrix_multiply(white_adaption, bradford_matrix) );
}
void qcms_profile_precache_output_transform(qcms_profile *profile)
@@ -1187,6 +994,18 @@
if (profile->color_space != RGB_SIGNATURE)
return;
+ /* don't precache since we will use the B2A LUT */
+ if (profile->B2A0)
+ return;
+
+ /* don't precache since we will use the mBA LUT */
+ if (profile->mBA)
+ return;
+
+ /* don't precache if we do not have the TRC curves */
+ if (!profile->redTRC || !profile->greenTRC || !profile->blueTRC)
+ return;
+
if (!profile->output_table_r) {
profile->output_table_r = precache_create();
if (profile->output_table_r &&
@@ -1213,11 +1032,67 @@
}
}
+/* Replace the current transformation with a LUT transformation using a given number of sample points */
+qcms_transform* qcms_transform_precacheLUT_float(qcms_transform *transform, qcms_profile *in, qcms_profile *out,
+ int samples, qcms_data_type in_type)
+{
+ /* The range between which 2 consecutive sample points can be used to interpolate */
+ uint16_t x,y,z;
+ uint32_t l;
+ uint32_t lutSize = 3 * samples * samples * samples;
+ float* src = NULL;
+ float* dest = NULL;
+ float* lut = NULL;
+
+ src = malloc(lutSize*sizeof(float));
+ dest = malloc(lutSize*sizeof(float));
+
+ if (src && dest) {
+ /* Prepare a list of points we want to sample */
+ l = 0;
+ for (x = 0; x < samples; x++) {
+ for (y = 0; y < samples; y++) {
+ for (z = 0; z < samples; z++) {
+ src[l++] = x / (float)(samples-1);
+ src[l++] = y / (float)(samples-1);
+ src[l++] = z / (float)(samples-1);
+ }
+ }
+ }
+
+ lut = qcms_chain_transform(in, out, src, dest, lutSize);
+ if (lut) {
+ transform->r_clut = &lut[0];
+ transform->g_clut = &lut[1];
+ transform->b_clut = &lut[2];
+ transform->grid_size = samples;
+ if (in_type == QCMS_DATA_RGBA_8) {
+ transform->transform_fn = qcms_transform_data_tetra_clut_rgba;
+ } else {
+ transform->transform_fn = qcms_transform_data_tetra_clut;
+ }
+ }
+ }
+
+
+ //XXX: qcms_modular_transform_data may return either the src or dest buffer. If so it must not be free-ed
+ if (src && lut != src) {
+ free(src);
+ } else if (dest && lut != src) {
+ free(dest);
+ }
+
+ if (lut == NULL) {
+ return NULL;
+ }
+ return transform;
+}
+
#define NO_MEM_TRANSFORM NULL
qcms_transform* qcms_transform_create(
qcms_profile *in, qcms_data_type in_type,
- qcms_profile* out, qcms_data_type out_type,
+ qcms_profile *out, qcms_data_type out_type,
qcms_intent intent)
{
bool precache = false;
@@ -1239,11 +1114,30 @@
precache = true;
}
+ if (in->A2B0 || out->B2A0 || in->mAB || out->mAB) {
+ // Precache the transformation to a CLUT 33x33x33 in size.
+ // 33 is used by many profiles and works well in pratice.
+ // This evenly divides 256 into blocks of 8x8x8.
+ // TODO For transforming small data sets of about 200x200 or less
+ // precaching should be avoided.
+ qcms_transform *result = qcms_transform_precacheLUT_float(transform, in, out, 33, in_type);
+ if (!result) {
+ assert(0 && "precacheLUT failed");
+ transform_free(transform);
+ return NULL;
+ }
+ return result;
+ }
+
if (precache) {
transform->output_table_r = precache_reference(out->output_table_r);
transform->output_table_g = precache_reference(out->output_table_g);
transform->output_table_b = precache_reference(out->output_table_b);
} else {
+ if (!out->redTRC || !out->greenTRC || !out->blueTRC) {
+ qcms_transform_release(transform);
+ return NO_MEM_TRANSFORM;
+ }
build_output_lut(out->redTRC, &transform->output_gamma_lut_r, &transform->output_gamma_lut_r_length);
build_output_lut(out->greenTRC, &transform->output_gamma_lut_g, &transform->output_gamma_lut_g_length);
build_output_lut(out->blueTRC, &transform->output_gamma_lut_b, &transform->output_gamma_lut_b_length);
@@ -1254,15 +1148,15 @@
}
if (in->color_space == RGB_SIGNATURE) {
- struct matrix in_matrix, out_matrix, result;
+ struct matrix in_matrix, out_matrix, result;
- if (in_type != QCMS_DATA_RGB_8 &&
+ if (in_type != QCMS_DATA_RGB_8 &&
in_type != QCMS_DATA_RGBA_8){
- assert(0 && "input type");
- transform_free(transform);
- return NULL;
- }
- if (precache) {
+ assert(0 && "input type");
+ transform_free(transform);
+ return NULL;
+ }
+ if (precache) {
#ifdef X86
if (sse_version_available() >= 2) {
if (in_type == QCMS_DATA_RGB_8)
@@ -1282,82 +1176,82 @@
#endif
} else
#endif
- {
- if (in_type == QCMS_DATA_RGB_8)
- transform->transform_fn = qcms_transform_data_rgb_out_lut_precache;
- else
- transform->transform_fn = qcms_transform_data_rgba_out_lut_precache;
- }
- } else {
- if (in_type == QCMS_DATA_RGB_8)
- transform->transform_fn = qcms_transform_data_rgb_out_lut;
- else
- transform->transform_fn = qcms_transform_data_rgba_out_lut;
- }
+ {
+ if (in_type == QCMS_DATA_RGB_8)
+ transform->transform_fn = qcms_transform_data_rgb_out_lut_precache;
+ else
+ transform->transform_fn = qcms_transform_data_rgba_out_lut_precache;
+ }
+ } else {
+ if (in_type == QCMS_DATA_RGB_8)
+ transform->transform_fn = qcms_transform_data_rgb_out_lut;
+ else
+ transform->transform_fn = qcms_transform_data_rgba_out_lut;
+ }
- //XXX: avoid duplicating tables if we can
- transform->input_gamma_table_r = build_input_gamma_table(in->redTRC);
- transform->input_gamma_table_g = build_input_gamma_table(in->greenTRC);
- transform->input_gamma_table_b = build_input_gamma_table(in->blueTRC);
+ //XXX: avoid duplicating tables if we can
+ transform->input_gamma_table_r = build_input_gamma_table(in->redTRC);
+ transform->input_gamma_table_g = build_input_gamma_table(in->greenTRC);
+ transform->input_gamma_table_b = build_input_gamma_table(in->blueTRC);
+ if (!transform->input_gamma_table_r || !transform->input_gamma_table_g || !transform->input_gamma_table_b) {
+ qcms_transform_release(transform);
+ return NO_MEM_TRANSFORM;
+ }
- if (!transform->input_gamma_table_r || !transform->input_gamma_table_g || !transform->input_gamma_table_b) {
- qcms_transform_release(transform);
- return NO_MEM_TRANSFORM;
- }
- /* build combined colorant matrix */
- in_matrix = build_colorant_matrix(in);
- out_matrix = build_colorant_matrix(out);
- out_matrix = matrix_invert(out_matrix);
- if (out_matrix.invalid) {
- qcms_transform_release(transform);
- return NULL;
- }
- result = matrix_multiply(out_matrix, in_matrix);
+ /* build combined colorant matrix */
+ in_matrix = build_colorant_matrix(in);
+ out_matrix = build_colorant_matrix(out);
+ out_matrix = matrix_invert(out_matrix);
+ if (out_matrix.invalid) {
+ qcms_transform_release(transform);
+ return NULL;
+ }
+ result = matrix_multiply(out_matrix, in_matrix);
- /* store the results in column major mode
- * this makes doing the multiplication with sse easier */
- transform->matrix[0][0] = result.m[0][0];
- transform->matrix[1][0] = result.m[0][1];
- transform->matrix[2][0] = result.m[0][2];
- transform->matrix[0][1] = result.m[1][0];
- transform->matrix[1][1] = result.m[1][1];
- transform->matrix[2][1] = result.m[1][2];
- transform->matrix[0][2] = result.m[2][0];
- transform->matrix[1][2] = result.m[2][1];
- transform->matrix[2][2] = result.m[2][2];
+ /* store the results in column major mode
+ * this makes doing the multiplication with sse easier */
+ transform->matrix[0][0] = result.m[0][0];
+ transform->matrix[1][0] = result.m[0][1];
+ transform->matrix[2][0] = result.m[0][2];
+ transform->matrix[0][1] = result.m[1][0];
+ transform->matrix[1][1] = result.m[1][1];
+ transform->matrix[2][1] = result.m[1][2];
+ transform->matrix[0][2] = result.m[2][0];
+ transform->matrix[1][2] = result.m[2][1];
+ transform->matrix[2][2] = result.m[2][2];
- } else if (in->color_space == GRAY_SIGNATURE) {
- if (in_type != QCMS_DATA_GRAY_8 &&
- in_type != QCMS_DATA_GRAYA_8){
- assert(0 && "input type");
- transform_free(transform);
- return NULL;
- }
+ } else if (in->color_space == GRAY_SIGNATURE) {
+ if (in_type != QCMS_DATA_GRAY_8 &&
+ in_type != QCMS_DATA_GRAYA_8){
+ assert(0 && "input type");
+ transform_free(transform);
+ return NULL;
+ }
- transform->input_gamma_table_gray = build_input_gamma_table(in->grayTRC);
- if (!transform->input_gamma_table_gray) {
- qcms_transform_release(transform);
- return NO_MEM_TRANSFORM;
- }
+ transform->input_gamma_table_gray = build_input_gamma_table(in->grayTRC);
+ if (!transform->input_gamma_table_gray) {
+ qcms_transform_release(transform);
+ return NO_MEM_TRANSFORM;
+ }
- if (precache) {
- if (in_type == QCMS_DATA_GRAY_8) {
- transform->transform_fn = qcms_transform_data_gray_out_precache;
- } else {
- transform->transform_fn = qcms_transform_data_graya_out_precache;
- }
- } else {
- if (in_type == QCMS_DATA_GRAY_8) {
- transform->transform_fn = qcms_transform_data_gray_out_lut;
- } else {
- transform->transform_fn = qcms_transform_data_graya_out_lut;
- }
- }
+ if (precache) {
+ if (in_type == QCMS_DATA_GRAY_8) {
+ transform->transform_fn = qcms_transform_data_gray_out_precache;
+ } else {
+ transform->transform_fn = qcms_transform_data_graya_out_precache;
+ }
+ } else {
+ if (in_type == QCMS_DATA_GRAY_8) {
+ transform->transform_fn = qcms_transform_data_gray_out_lut;
+ } else {
+ transform->transform_fn = qcms_transform_data_graya_out_lut;
+ }
+ }
} else {
assert(0 && "unexpected colorspace");
- qcms_transform_release(transform);
- return NO_MEM_TRANSFORM;
+ transform_free(transform);
+ return NULL;
}
return transform;
}
diff --git a/transform_util.c b/transform_util.c
new file mode 100644
index 0000000..bcc1a3e
--- /dev/null
+++ b/transform_util.c
@@ -0,0 +1,539 @@
+#define _ISOC99_SOURCE /* for INFINITY */
+
+#include <math.h>
+#include <assert.h>
+#include <string.h> //memcpy
+#include "qcmsint.h"
+#include "transform_util.h"
+#include "matrix.h"
+
+#if !defined(INFINITY)
+#define INFINITY HUGE_VAL
+#endif
+
+#define PARAMETRIC_CURVE_TYPE 0x70617261 //'para'
+
+/* value must be a value between 0 and 1 */
+//XXX: is the above a good restriction to have?
+float lut_interp_linear(double value, uint16_t *table, int length)
+{
+ int upper, lower;
+ value = value * (length - 1); // scale to length of the array
+ upper = ceil(value);
+ lower = floor(value);
+ //XXX: can we be more performant here?
+ value = table[upper]*(1. - (upper - value)) + table[lower]*(upper - value);
+ /* scale the value */
+ return value * (1./65535.);
+}
+
+/* same as above but takes and returns a uint16_t value representing a range from 0..1 */
+uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length)
+{
+ /* Start scaling input_value to the length of the array: 65535*(length-1).
+ * We'll divide out the 65535 next */
+ uint32_t value = (input_value * (length - 1));
+ uint32_t upper = (value + 65534) / 65535; /* equivalent to ceil(value/65535) */
+ uint32_t lower = value / 65535; /* equivalent to floor(value/65535) */
+ /* interp is the distance from upper to value scaled to 0..65535 */
+ uint32_t interp = value % 65535;
+
+ value = (table[upper]*(interp) + table[lower]*(65535 - interp))/65535; // 0..65535*65535
+
+ return value;
+}
+
+/* same as above but takes an input_value from 0..PRECACHE_OUTPUT_MAX
+ * and returns a uint8_t value representing a range from 0..1 */
+static
+uint8_t lut_interp_linear_precache_output(uint32_t input_value, uint16_t *table, int length)
+{
+ /* Start scaling input_value to the length of the array: PRECACHE_OUTPUT_MAX*(length-1).
+ * We'll divide out the PRECACHE_OUTPUT_MAX next */
+ uint32_t value = (input_value * (length - 1));
+
+ /* equivalent to ceil(value/PRECACHE_OUTPUT_MAX) */
+ uint32_t upper = (value + PRECACHE_OUTPUT_MAX-1) / PRECACHE_OUTPUT_MAX;
+ /* equivalent to floor(value/PRECACHE_OUTPUT_MAX) */
+ uint32_t lower = value / PRECACHE_OUTPUT_MAX;
+ /* interp is the distance from upper to value scaled to 0..PRECACHE_OUTPUT_MAX */
+ uint32_t interp = value % PRECACHE_OUTPUT_MAX;
+
+ /* the table values range from 0..65535 */
+ value = (table[upper]*(interp) + table[lower]*(PRECACHE_OUTPUT_MAX - interp)); // 0..(65535*PRECACHE_OUTPUT_MAX)
+
+ /* round and scale */
+ value += (PRECACHE_OUTPUT_MAX*65535/255)/2;
+ value /= (PRECACHE_OUTPUT_MAX*65535/255); // scale to 0..255
+ return value;
+}
+
+/* value must be a value between 0 and 1 */
+//XXX: is the above a good restriction to have?
+float lut_interp_linear_float(float value, float *table, int length)
+{
+ int upper, lower;
+ value = value * (length - 1);
+ upper = ceil(value);
+ lower = floor(value);
+ //XXX: can we be more performant here?
+ value = table[upper]*(1. - (upper - value)) + table[lower]*(upper - value);
+ /* scale the value */
+ return value;
+}
+
+#if 0
+/* if we use a different representation i.e. one that goes from 0 to 0x1000 we can be more efficient
+ * because we can avoid the divisions and use a shifting instead */
+/* same as above but takes and returns a uint16_t value representing a range from 0..1 */
+uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length)
+{
+ uint32_t value = (input_value * (length - 1));
+ uint32_t upper = (value + 4095) / 4096; /* equivalent to ceil(value/4096) */
+ uint32_t lower = value / 4096; /* equivalent to floor(value/4096) */
+ uint32_t interp = value % 4096;
+
+ value = (table[upper]*(interp) + table[lower]*(4096 - interp))/4096; // 0..4096*4096
+
+ return value;
+}
+#endif
+
+void compute_curve_gamma_table_type1(float gamma_table[256], double gamma)
+{
+ unsigned int i;
+ for (i = 0; i < 256; i++) {
+ gamma_table[i] = pow(i/255., gamma);
+ }
+}
+
+void compute_curve_gamma_table_type2(float gamma_table[256], uint16_t *table, int length)
+{
+ unsigned int i;
+ for (i = 0; i < 256; i++) {
+ gamma_table[i] = lut_interp_linear(i/255., table, length);
+ }
+}
+
+void compute_curve_gamma_table_type_parametric(float gamma_table[256], float parameter[7], int count)
+{
+ size_t X;
+ float interval;
+ float a, b, c, e, f;
+ float y = parameter[0];
+ if (count == 0) {
+ a = 1;
+ b = 0;
+ c = 0;
+ e = 0;
+ f = 0;
+ interval = -INFINITY;
+ } else if(count == 1) {
+ a = parameter[1];
+ b = parameter[2];
+ c = 0;
+ e = 0;
+ f = 0;
+ interval = -1 * parameter[2] / parameter[1];
+ } else if(count == 2) {
+ a = parameter[1];
+ b = parameter[2];
+ c = 0;
+ e = parameter[3];
+ f = parameter[3];
+ interval = -1 * parameter[2] / parameter[1];
+ } else if(count == 3) {
+ a = parameter[1];
+ b = parameter[2];
+ c = parameter[3];
+ e = -c;
+ f = 0;
+ interval = parameter[4];
+ } else if(count == 4) {
+ a = parameter[1];
+ b = parameter[2];
+ c = parameter[3];
+ e = parameter[5] - c;
+ f = parameter[6];
+ interval = parameter[4];
+ } else {
+ assert(0 && "invalid parametric function type.");
+ a = 1;
+ b = 0;
+ c = 0;
+ e = 0;
+ f = 0;
+ interval = -INFINITY;
+ }
+ for (X = 0; X < 256; X++) {
+ if (X >= interval) {
+ // XXX The equations are not exactly as definied in the spec but are
+ // algebraic equivilent.
+ // TODO Should division by 255 be for the whole expression.
+ gamma_table[X] = pow(a * X / 255. + b, y) + c + e;
+ } else {
+ gamma_table[X] = c * X / 255. + f;
+ }
+ }
+}
+
+void compute_curve_gamma_table_type0(float gamma_table[256])
+{
+ unsigned int i;
+ for (i = 0; i < 256; i++) {
+ gamma_table[i] = i/255.;
+ }
+}
+
+
+float clamp_float(float a)
+{
+ if (a > 1.)
+ return 1.;
+ else if (a < 0)
+ return 0;
+ else
+ return a;
+}
+
+unsigned char clamp_u8(float v)
+{
+ if (v > 255.)
+ return 255;
+ else if (v < 0)
+ return 0;
+ else
+ return floor(v+.5);
+}
+
+float u8Fixed8Number_to_float(uint16_t x)
+{
+ // 0x0000 = 0.
+ // 0x0100 = 1.
+ // 0xffff = 255 + 255/256
+ return x/256.;
+}
+
+float *build_input_gamma_table(struct curveType *TRC)
+{
+ float *gamma_table;
+
+ if (!TRC) return NULL;
+ gamma_table = malloc(sizeof(float)*256);
+ if (gamma_table) {
+ if (TRC->type == PARAMETRIC_CURVE_TYPE) {
+ compute_curve_gamma_table_type_parametric(gamma_table, TRC->parameter, TRC->count);
+ } else {
+ if (TRC->count == 0) {
+ compute_curve_gamma_table_type0(gamma_table);
+ } else if (TRC->count == 1) {
+ compute_curve_gamma_table_type1(gamma_table, u8Fixed8Number_to_float(TRC->data[0]));
+ } else {
+ compute_curve_gamma_table_type2(gamma_table, TRC->data, TRC->count);
+ }
+ }
+ }
+ return gamma_table;
+}
+
+struct matrix build_colorant_matrix(qcms_profile *p)
+{
+ struct matrix result;
+ result.m[0][0] = s15Fixed16Number_to_float(p->redColorant.X);
+ result.m[0][1] = s15Fixed16Number_to_float(p->greenColorant.X);
+ result.m[0][2] = s15Fixed16Number_to_float(p->blueColorant.X);
+ result.m[1][0] = s15Fixed16Number_to_float(p->redColorant.Y);
+ result.m[1][1] = s15Fixed16Number_to_float(p->greenColorant.Y);
+ result.m[1][2] = s15Fixed16Number_to_float(p->blueColorant.Y);
+ result.m[2][0] = s15Fixed16Number_to_float(p->redColorant.Z);
+ result.m[2][1] = s15Fixed16Number_to_float(p->greenColorant.Z);
+ result.m[2][2] = s15Fixed16Number_to_float(p->blueColorant.Z);
+ result.invalid = false;
+ return result;
+}
+
+/* The following code is copied nearly directly from lcms.
+ * I think it could be much better. For example, Argyll seems to have better code in
+ * icmTable_lookup_bwd and icmTable_setup_bwd. However, for now this is a quick way
+ * to a working solution and allows for easy comparing with lcms. */
+uint16_fract_t lut_inverse_interp16(uint16_t Value, uint16_t LutTable[], int length)
+{
+ int l = 1;
+ int r = 0x10000;
+ int x = 0, res; // 'int' Give spacing for negative values
+ int NumZeroes, NumPoles;
+ int cell0, cell1;
+ double val2;
+ double y0, y1, x0, x1;
+ double a, b, f;
+
+ // July/27 2001 - Expanded to handle degenerated curves with an arbitrary
+ // number of elements containing 0 at the begining of the table (Zeroes)
+ // and another arbitrary number of poles (FFFFh) at the end.
+ // First the zero and pole extents are computed, then value is compared.
+
+ NumZeroes = 0;
+ while (LutTable[NumZeroes] == 0 && NumZeroes < length-1)
+ NumZeroes++;
+
+ // There are no zeros at the beginning and we are trying to find a zero, so
+ // return anything. It seems zero would be the less destructive choice
+ /* I'm not sure that this makes sense, but oh well... */
+ if (NumZeroes == 0 && Value == 0)
+ return 0;
+
+ NumPoles = 0;
+ while (LutTable[length-1- NumPoles] == 0xFFFF && NumPoles < length-1)
+ NumPoles++;
+
+ // Does the curve belong to this case?
+ if (NumZeroes > 1 || NumPoles > 1)
+ {
+ int a, b;
+
+ // Identify if value fall downto 0 or FFFF zone
+ if (Value == 0) return 0;
+ // if (Value == 0xFFFF) return 0xFFFF;
+
+ // else restrict to valid zone
+
+ a = ((NumZeroes-1) * 0xFFFF) / (length-1);
+ b = ((length-1 - NumPoles) * 0xFFFF) / (length-1);
+
+ l = a - 1;
+ r = b + 1;
+ }
+
+
+ // Seems not a degenerated case... apply binary search
+
+ while (r > l) {
+
+ x = (l + r) / 2;
+
+ res = (int) lut_interp_linear16((uint16_fract_t) (x-1), LutTable, length);
+
+ if (res == Value) {
+
+ // Found exact match.
+
+ return (uint16_fract_t) (x - 1);
+ }
+
+ if (res > Value) r = x - 1;
+ else l = x + 1;
+ }
+
+ // Not found, should we interpolate?
+
+
+ // Get surrounding nodes
+
+ val2 = (length-1) * ((double) (x - 1) / 65535.0);
+
+ cell0 = (int) floor(val2);
+ cell1 = (int) ceil(val2);
+
+ if (cell0 == cell1) return (uint16_fract_t) x;
+
+ y0 = LutTable[cell0] ;
+ x0 = (65535.0 * cell0) / (length-1);
+
+ y1 = LutTable[cell1] ;
+ x1 = (65535.0 * cell1) / (length-1);
+
+ a = (y1 - y0) / (x1 - x0);
+ b = y0 - a * x0;
+
+ if (fabs(a) < 0.01) return (uint16_fract_t) x;
+
+ f = ((Value - b) / a);
+
+ if (f < 0.0) return (uint16_fract_t) 0;
+ if (f >= 65535.0) return (uint16_fract_t) 0xFFFF;
+
+ return (uint16_fract_t) floor(f + 0.5);
+
+}
+
+/*
+ The number of entries needed to invert a lookup table should not
+ necessarily be the same as the original number of entries. This is
+ especially true of lookup tables that have a small number of entries.
+
+ For example:
+ Using a table like:
+ {0, 3104, 14263, 34802, 65535}
+ invert_lut will produce an inverse of:
+ {3, 34459, 47529, 56801, 65535}
+ which has an maximum error of about 9855 (pixel difference of ~38.346)
+
+ For now, we punt the decision of output size to the caller. */
+static uint16_t *invert_lut(uint16_t *table, int length, int out_length)
+{
+ int i;
+ /* for now we invert the lut by creating a lut of size out_length
+ * and attempting to lookup a value for each entry using lut_inverse_interp16 */
+ uint16_t *output = malloc(sizeof(uint16_t)*out_length);
+ if (!output)
+ return NULL;
+
+ for (i = 0; i < out_length; i++) {
+ double x = ((double) i * 65535.) / (double) (out_length - 1);
+ uint16_fract_t input = floor(x + .5);
+ output[i] = lut_inverse_interp16(input, table, length);
+ }
+ return output;
+}
+
+static void compute_precache_pow(uint8_t *output, float gamma)
+{
+ uint32_t v = 0;
+ for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
+ //XXX: don't do integer/float conversion... and round?
+ output[v] = 255. * pow(v/(double)PRECACHE_OUTPUT_MAX, gamma);
+ }
+}
+
+void compute_precache_lut(uint8_t *output, uint16_t *table, int length)
+{
+ uint32_t v = 0;
+ for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
+ output[v] = lut_interp_linear_precache_output(v, table, length);
+ }
+}
+
+void compute_precache_linear(uint8_t *output)
+{
+ uint32_t v = 0;
+ for (v = 0; v < PRECACHE_OUTPUT_SIZE; v++) {
+ //XXX: round?
+ output[v] = v / (PRECACHE_OUTPUT_SIZE/256);
+ }
+}
+
+qcms_bool compute_precache(struct curveType *trc, uint8_t *output)
+{
+
+ if (trc->type == PARAMETRIC_CURVE_TYPE) {
+ float gamma_table[256];
+ uint16_t gamma_table_uint[256];
+ uint16_t i;
+ uint16_t *inverted;
+ int inverted_size = 256;
+
+ compute_curve_gamma_table_type_parametric(gamma_table, trc->parameter, trc->count);
+ for(i = 0; i < 256; i++) {
+ gamma_table_uint[i] = (uint16_t)(gamma_table[i] * 65535);
+ }
+
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, howeve r it is what lcms uses.
+ // the maximum number we would need is 65535 because that's the
+ // accuracy used for computing the pre cache table
+ if (inverted_size < 256)
+ inverted_size = 256;
+
+ inverted = invert_lut(gamma_table_uint, 256, inverted_size);
+ if (!inverted)
+ return false;
+ compute_precache_lut(output, inverted, inverted_size);
+ free(inverted);
+ } else {
+ if (trc->count == 0) {
+ compute_precache_linear(output);
+ } else if (trc->count == 1) {
+ compute_precache_pow(output, 1./u8Fixed8Number_to_float(trc->data[0]));
+ } else {
+ uint16_t *inverted;
+ int inverted_size = trc->count;
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, howeve r it is what lcms uses.
+ // the maximum number we would need is 65535 because that's the
+ // accuracy used for computing the pre cache table
+ if (inverted_size < 256)
+ inverted_size = 256;
+
+ inverted = invert_lut(trc->data, trc->count, inverted_size);
+ if (!inverted)
+ return false;
+ compute_precache_lut(output, inverted, inverted_size);
+ free(inverted);
+ }
+ }
+ return true;
+}
+
+
+static uint16_t *build_linear_table(int length)
+{
+ int i;
+ uint16_t *output = malloc(sizeof(uint16_t)*length);
+ if (!output)
+ return NULL;
+
+ for (i = 0; i < length; i++) {
+ double x = ((double) i * 65535.) / (double) (length - 1);
+ uint16_fract_t input = floor(x + .5);
+ output[i] = input;
+ }
+ return output;
+}
+
+static uint16_t *build_pow_table(float gamma, int length)
+{
+ int i;
+ uint16_t *output = malloc(sizeof(uint16_t)*length);
+ if (!output)
+ return NULL;
+
+ for (i = 0; i < length; i++) {
+ uint16_fract_t result;
+ double x = ((double) i) / (double) (length - 1);
+ x = pow(x, gamma); //XXX turn this conversion into a function
+ result = floor(x*65535. + .5);
+ output[i] = result;
+ }
+ return output;
+}
+
+void build_output_lut(struct curveType *trc,
+ uint16_t **output_gamma_lut, size_t *output_gamma_lut_length)
+{
+ if (trc->type == PARAMETRIC_CURVE_TYPE) {
+ float gamma_table[256];
+ uint16_t i;
+ uint16_t *output = malloc(sizeof(uint16_t)*256);
+
+ if (!output) {
+ *output_gamma_lut = NULL;
+ return;
+ }
+
+ compute_curve_gamma_table_type_parametric(gamma_table, trc->parameter, trc->count);
+ *output_gamma_lut_length = 256;
+ for(i = 0; i < 256; i++) {
+ output[i] = (uint16_t)(gamma_table[i] * 65535);
+ }
+ *output_gamma_lut = output;
+ } else {
+ if (trc->count == 0) {
+ *output_gamma_lut = build_linear_table(4096);
+ *output_gamma_lut_length = 4096;
+ } else if (trc->count == 1) {
+ float gamma = 1./u8Fixed8Number_to_float(trc->data[0]);
+ *output_gamma_lut = build_pow_table(gamma, 4096);
+ *output_gamma_lut_length = 4096;
+ } else {
+ //XXX: the choice of a minimum of 256 here is not backed by any theory,
+ // measurement or data, however it is what lcms uses.
+ *output_gamma_lut_length = trc->count;
+ if (*output_gamma_lut_length < 256)
+ *output_gamma_lut_length = 256;
+
+ *output_gamma_lut = invert_lut(trc->data, trc->count, *output_gamma_lut_length);
+ }
+ }
+
+}
+
diff --git a/transform_util.h b/transform_util.h
new file mode 100644
index 0000000..8f358a8
--- /dev/null
+++ b/transform_util.h
@@ -0,0 +1,59 @@
+/* vim: set ts=8 sw=8 noexpandtab: */
+// qcms
+// Copyright (C) 2009 Mozilla Foundation
+// Copyright (C) 1998-2007 Marti Maria
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef _QCMS_TRANSFORM_UTIL_H
+#define _QCMS_TRANSFORM_UTIL_H
+
+#include <stdlib.h>
+
+#define CLU(table,x,y,z) table[(x*len + y*x_len + z*xy_len)*3]
+
+//XXX: could use a bettername
+typedef uint16_t uint16_fract_t;
+
+float lut_interp_linear(double value, uint16_t *table, int length);
+float lut_interp_linear_float(float value, float *table, int length);
+uint16_t lut_interp_linear16(uint16_t input_value, uint16_t *table, int length);
+
+
+static inline float lerp(float a, float b, float t)
+{
+ return a*(1.f-t) + b*t;
+}
+
+unsigned char clamp_u8(float v);
+float clamp_float(float a);
+
+float u8Fixed8Number_to_float(uint16_t x);
+
+
+float *build_input_gamma_table(struct curveType *TRC);
+struct matrix build_colorant_matrix(qcms_profile *p);
+void build_output_lut(struct curveType *trc,
+ uint16_t **output_gamma_lut, size_t *output_gamma_lut_length);
+
+struct matrix matrix_invert(struct matrix mat);
+qcms_bool compute_precache(struct curveType *trc, uint8_t *output);
+
+
+#endif