Merge branch 'master' into v4

Conflicts:
	Makefile
	iccread.c
	qcmsint.h
	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/iccread.c b/iccread.c
index cfe3d13..f1790f3 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;
@@ -49,6 +50,7 @@
 	return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24);
 	//return __builtin_bswap32(v);
 #else
+sdfsdf;asdf;asdf;asdfdas
 	return v;
 #endif
 }
@@ -144,6 +146,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 +160,7 @@
 	}
 	if (reserved1 != 0 || reserved2 != 0)
 		invalid_source(src, "Invalid reserved bytes");
+	*/
 }
 
 #define INPUT_DEVICE_PROFILE   0x73636e72 // 'scnr'
@@ -172,8 +177,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 +197,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 +258,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 +321,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 +336,37 @@
 	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);
+
+		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 +387,22 @@
 	return num;
 }
 
-static struct curveType *read_tag_curveType(struct mem_source *src, struct tag_index index, uint32_t tag_id)
+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 +414,241 @@
 			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
+/* 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;
+	uint32_t channel_offset;
+	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
+	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) {
+		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;
+	// REVIEW Should we put an upper bound on the malloc size to prevent mallicious profile from
+	//        doing big memory allocations?
+	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) {
+		channel_offset = 0;
+		for (i = 0; i < num_in_channels; i++) {
+			uint32_t tag_len;
+
+			lut->a_curves[i] = read_tag_curveType_At(src, a_offset + channel_offset, &tag_len);
+			if (!lut->a_curves[i]) {
+				invalid_source(src, "invalid A curves");
+			}
+
+			channel_offset += tag_len;
+			// 4 byte aligned
+			if ((tag_len % 4) != 0) channel_offset += 4 - (tag_len % 4);
+		}
+	}
+	if (m_offset) {
+		channel_offset = 0;
+		for (i = 0; i < num_out_channels; i++) {
+			uint32_t tag_len;
+
+			lut->m_curves[i] = read_tag_curveType_At(src, m_offset + channel_offset, &tag_len);
+			if (!lut->m_curves[i]) {
+				invalid_source(src, "invalid M curves");
+			}
+
+			channel_offset += tag_len;
+			// 4 byte aligned
+			if ((tag_len % 4) != 0) channel_offset += 4 - (tag_len % 4);
+		}
+	}
+	if (b_offset) {
+		channel_offset = 0;
+		for (i = 0; i < num_out_channels; i++) {
+			uint32_t tag_len;
+
+			lut->b_curves[i] = read_tag_curveType_At(src, b_offset + channel_offset, &tag_len);
+			if (!lut->b_curves[i]) {
+				invalid_source(src, "invalid B curves");
+			}
+
+			channel_offset += tag_len;
+			// 4 byte aligned
+			if ((tag_len % 4) != 0) channel_offset += 4 - (tag_len % 4);
+		}
+	} 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) {
+		// Cleanup
+		for (i = 0; i < num_in_channels; i++) {
+			if (lut->a_curves[i]) {
+				free(lut->a_curves[i]);
+			}
+		}
+		for (i = 0; i < num_out_channels; i++) {
+			if (lut->m_curves[i]) {
+				free(lut->m_curves[i]);
+			}
+			if (lut->b_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 +657,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 +682,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 +726,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;
 }
 
@@ -524,6 +859,8 @@
 
 static void qcms_profile_fini(qcms_profile *profile)
 {
+	// REVIEW: This is called when some of these values are NULL.
+	// Is free()-ing NULL safe? I was told it was not safe on all platforms.
 	free(profile->redTRC);
 	free(profile->blueTRC);
 	free(profile->greenTRC);
@@ -697,6 +1034,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 +1044,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 +1095,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,6 +1109,8 @@
 invalid_tag_table:
 	free(index.tags);
 invalid_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_fini(profile);
 	return INVALID_PROFILE;
 }
@@ -763,6 +1126,23 @@
 	return profile->color_space;
 }
 
+static void lut_release(struct lutType *lut)
+{
+	free(lut->input_table);
+	free(lut->clut_table);
+	free(lut->output_table);
+	free(lut);
+}
+
+static void mAB_release(struct lutmABType *lut)
+{
+	free(lut->clut_table);
+	free(lut->a_curves);
+	free(lut->b_curves);
+	free(lut->m_curves);
+	free(lut);
+}
+
 void qcms_profile_release(qcms_profile *profile)
 {
 	if (profile->output_table_r)
@@ -771,6 +1151,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/qcmsint.h b/qcmsint.h
index 217b651..0de134b 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 d218aa1..cded207 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;
@@ -1229,7 +1104,7 @@
 	if (out_type != QCMS_DATA_RGB_8 &&
                 out_type != QCMS_DATA_RGBA_8) {
             assert(0 && "output type");
-	    free(transform);
+	    transform_free(transform);
             return NULL;
         }
 
@@ -1239,11 +1114,25 @@
 		precache = true;
 	}
 
+	if (in->A2B0 || out->B2A0 || in->mAB || out->mAB) {
+		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 +1143,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");
-		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,80 +1171,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");
-		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");
+		transform_free(transform);
+		return NULL;
 	}
 	return transform;
 }