Source code: gnu/java/awt/color/ColorLookUpTable.java
1 /* ColorLookUpTable.java -- ICC v2 CLUT
2 Copyright (C) 2004 Free Software Foundation
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package gnu.java.awt.color;
39
40 import java.awt.color.ColorSpace;
41 import java.awt.color.ICC_Profile;
42 import java.nio.ByteBuffer;
43
44
45 /**
46 * ColorLookUpTable handles color lookups through a color lookup table,
47 * as defined in the ICC specification.
48 * Both 'mft2' and 'mft1' (8 and 16-bit) type CLUTs are handled.
49 *
50 * This will have to be updated later for ICC 4.0.0
51 *
52 * @author Sven de Marothy
53 */
54 public class ColorLookUpTable
55 {
56 /**
57 * CIE 1931 D50 white point (in Lab coordinates)
58 */
59 private static float[] D50 = { 0.96422f, 1.00f, 0.82521f };
60
61 /**
62 * Number of input/output channels
63 */
64 int nIn;
65
66 /**
67 * Number of input/output channels
68 */
69 int nOut;
70 int nInTableEntries; // Number of input table entries
71 int nOutTableEntries; // Number of output table entries
72 int gridpoints; // Number of gridpoints
73 int nClut; // This is nOut*(gridpoints**nIn)
74 double[][] inTable; // 1D input table ([channel][table])
75 short[][] outTable; // 1D input table ([channel][table])
76 double[] clut; // The color lookup table
77 float[][] inMatrix; // input matrix (XYZ only)
78 boolean useMatrix; // Whether to use the matrix or not.
79 int[] multiplier;
80 int[] offsets; // Hypercube offsets
81 boolean inputLab; // Set if the CLUT input CS is Lab
82 boolean outputLab; // Set if the CLUT output CS is Lab
83
84 /**
85 * Constructor
86 * Requires a profile file to get the CLUT from and the tag of the
87 * CLUT to create. (icSigXToYZTag where X,Y = [A | B], Z = [0,1,2])
88 */
89 public ColorLookUpTable(ICC_Profile profile, int tag)
90 {
91 useMatrix = false;
92
93 switch (tag)
94 {
95 case ICC_Profile.icSigAToB0Tag:
96 case ICC_Profile.icSigAToB1Tag:
97 case ICC_Profile.icSigAToB2Tag:
98 if (profile.getColorSpaceType() == ColorSpace.TYPE_XYZ)
99 useMatrix = true;
100 inputLab = false;
101 outputLab = (profile.getPCSType() == ColorSpace.TYPE_Lab);
102 break;
103 case ICC_Profile.icSigBToA0Tag:
104 case ICC_Profile.icSigBToA1Tag:
105 case ICC_Profile.icSigBToA2Tag:
106 if (profile.getPCSType() == ColorSpace.TYPE_XYZ)
107 useMatrix = true;
108 inputLab = (profile.getPCSType() == ColorSpace.TYPE_Lab);
109 outputLab = false;
110 break;
111 default:
112 throw new IllegalArgumentException("Not a clut-type tag.");
113 }
114
115 byte[] data = profile.getData(tag);
116 if (data == null)
117 throw new IllegalArgumentException("Unsuitable profile, does not contain a CLUT.");
118
119 // check 'mft'
120 if (data[0] != 0x6d || data[1] != 0x66 || data[2] != 0x74)
121 throw new IllegalArgumentException("Unsuitable profile, invalid CLUT data.");
122
123 if (data[3] == 0x32)
124 readClut16(data);
125 else if (data[3] == 0x31)
126 readClut8(data);
127 else
128 throw new IllegalArgumentException("Unknown/invalid CLUT type.");
129 }
130
131 /**
132 * Loads a 16-bit CLUT into our data structures
133 */
134 private void readClut16(byte[] data)
135 {
136 ByteBuffer buf = ByteBuffer.wrap(data);
137
138 nIn = data[8] & (0xFF);
139 nOut = data[9] & (0xFF);
140 nInTableEntries = buf.getShort(48);
141 nOutTableEntries = buf.getShort(50);
142 gridpoints = data[10] & (0xFF);
143
144 inMatrix = new float[3][3];
145 for (int i = 0; i < 3; i++)
146 for (int j = 0; j < 3; j++)
147 inMatrix[i][j] = ((float) (buf.getInt(12 + (i * 3 + j) * 4))) / 65536.0f;
148
149 inTable = new double[nIn][nInTableEntries];
150 for (int channel = 0; channel < nIn; channel++)
151 for (int i = 0; i < nInTableEntries; i++)
152 inTable[channel][i] = (double) ((int) buf.getShort(52
153 + (channel * nInTableEntries
154 + i) * 2)
155 & (0xFFFF)) / 65536.0;
156
157 nClut = nOut;
158 multiplier = new int[nIn];
159 multiplier[nIn - 1] = nOut;
160 for (int i = 0; i < nIn; i++)
161 {
162 nClut *= gridpoints;
163 if (i > 0)
164 multiplier[nIn - i - 1] = multiplier[nIn - i] * gridpoints;
165 }
166
167 int clutOffset = 52 + nIn * nInTableEntries * 2;
168 clut = new double[nClut];
169 for (int i = 0; i < nClut; i++)
170 clut[i] = (double) ((int) buf.getShort(clutOffset + i * 2) & (0xFFFF)) / 65536.0;
171
172 outTable = new short[nOut][nOutTableEntries];
173 for (int channel = 0; channel < nOut; channel++)
174 for (int i = 0; i < nOutTableEntries; i++)
175 outTable[channel][i] = buf.getShort(clutOffset
176 + (nClut
177 + channel * nOutTableEntries + i) * 2);
178
179 // calculate the hypercube corner offsets
180 offsets = new int[(1 << nIn)];
181 offsets[0] = 0;
182 for (int j = 0; j < nIn; j++)
183 {
184 int factor = 1 << j;
185 for (int i = 0; i < factor; i++)
186 offsets[factor + i] = offsets[i] + multiplier[j];
187 }
188 }
189
190 /**
191 * Loads a 8-bit CLUT into our data structures.
192 */
193 private void readClut8(byte[] data)
194 {
195 ByteBuffer buf = ByteBuffer.wrap(data);
196
197 nIn = (data[8] & (0xFF));
198 nOut = (data[9] & (0xFF));
199 nInTableEntries = 256; // always 256
200 nOutTableEntries = 256; // always 256
201 gridpoints = (data[10] & (0xFF));
202
203 inMatrix = new float[3][3];
204 for (int i = 0; i < 3; i++)
205 for (int j = 0; j < 3; j++)
206 inMatrix[i][j] = ((float) (buf.getInt(12 + (i * 3 + j) * 4))) / 65536.0f;
207
208 inTable = new double[nIn][nInTableEntries];
209 for (int channel = 0; channel < nIn; channel++)
210 for (int i = 0; i < nInTableEntries; i++)
211 inTable[channel][i] = (double) ((int) buf.get(48
212 + (channel * nInTableEntries
213 + i)) & (0xFF)) / 255.0;
214
215 nClut = nOut;
216 multiplier = new int[nIn];
217 multiplier[nIn - 1] = nOut;
218 for (int i = 0; i < nIn; i++)
219 {
220 nClut *= gridpoints;
221 if (i > 0)
222 multiplier[nIn - i - 1] = multiplier[nIn - i] * gridpoints;
223 }
224
225 int clutOffset = 48 + nIn * nInTableEntries;
226 clut = new double[nClut];
227 for (int i = 0; i < nClut; i++)
228 clut[i] = (double) ((int) buf.get(clutOffset + i) & (0xFF)) / 255.0;
229
230 outTable = new short[nOut][nOutTableEntries];
231 for (int channel = 0; channel < nOut; channel++)
232 for (int i = 0; i < nOutTableEntries; i++)
233 outTable[channel][i] = (short) (buf.get(clutOffset + nClut
234 + channel * nOutTableEntries
235 + i) * 257);
236
237 // calculate the hypercube corner offsets
238 offsets = new int[(1 << nIn)];
239 offsets[0] = 0;
240 for (int j = 0; j < nIn; j++)
241 {
242 int factor = 1 << j;
243 for (int i = 0; i < factor; i++)
244 offsets[factor + i] = offsets[i] + multiplier[j];
245 }
246 }
247
248 /**
249 * Performs a lookup through the Color LookUp Table.
250 * If the CLUT tag type is AtoB the conversion will be from the device
251 * color space to the PCS, BtoA type goes in the opposite direction.
252 *
253 * For convenience, the PCS values for input or output will always be
254 * CIE XYZ (D50), if the actual PCS is Lab, the values will be converted.
255 *
256 * N-dimensional linear interpolation is used.
257 */
258 float[] lookup(float[] in)
259 {
260 float[] in2 = new float[in.length];
261 if (useMatrix)
262 {
263 for (int i = 0; i < 3; i++)
264 in2[i] = in[0] * inMatrix[i][0] + in[1] * inMatrix[i][1]
265 + in[2] * inMatrix[i][2];
266 }
267 else if (inputLab)
268 in2 = XYZtoLab(in);
269 else
270 System.arraycopy(in, 0, in2, 0, in.length);
271
272 // input table
273 for (int i = 0; i < nIn; i++)
274 {
275 int index = (int) Math.floor(in2[i] * (double) (nInTableEntries - 1)); // floor in
276
277 // clip values.
278 if (index >= nInTableEntries - 1)
279 in2[i] = (float) inTable[i][nInTableEntries - 1];
280 else if (index < 0)
281 in2[i] = (float) inTable[i][0];
282 else
283 {
284 // linear interpolation
285 double alpha = in2[i] * ((double) nInTableEntries - 1.0) - index;
286 in2[i] = (float) (inTable[i][index] * (1 - alpha)
287 + inTable[i][index + 1] * alpha);
288 }
289 }
290
291 // CLUT lookup
292 double[] output2 = new double[nOut];
293 double[] weights = new double[(1 << nIn)];
294 double[] clutalpha = new double[nIn]; // interpolation values
295 int offset = 0; // = gp
296 for (int i = 0; i < nIn; i++)
297 {
298 int index = (int) Math.floor(in2[i] * ((double) gridpoints - 1.0));
299 double alpha = in2[i] * ((double) gridpoints - 1.0) - (double) index;
300
301 // clip values.
302 if (index >= gridpoints - 1)
303 {
304 index = gridpoints - 1;
305 alpha = 1.0;
306 }
307 else if (index < 0)
308 index = 0;
309 clutalpha[i] = alpha;
310 offset += index * multiplier[i];
311 }
312
313 // Calculate interpolation weights
314 weights[0] = 1.0;
315 for (int j = 0; j < nIn; j++)
316 {
317 int factor = 1 << j;
318 for (int i = 0; i < factor; i++)
319 {
320 weights[factor + i] = weights[i] * clutalpha[j];
321 weights[i] *= (1.0 - clutalpha[j]);
322 }
323 }
324
325 for (int i = 0; i < nOut; i++)
326 output2[i] = weights[0] * clut[offset + i];
327
328 for (int i = 1; i < (1 << nIn); i++)
329 {
330 int offset2 = offset + offsets[i];
331 for (int f = 0; f < nOut; f++)
332 output2[f] += weights[i] * clut[offset2 + f];
333 }
334
335 // output table
336 float[] output = new float[nOut];
337 for (int i = 0; i < nOut; i++)
338 {
339 int index = (int) Math.floor(output2[i] * ((double) nOutTableEntries
340 - 1.0));
341
342 // clip values.
343 if (index >= nOutTableEntries - 1)
344 output[i] = outTable[i][nOutTableEntries - 1];
345 else if (index < 0)
346 output[i] = outTable[i][0];
347 else
348 {
349 // linear interpolation
350 double a = output2[i] * ((double) nOutTableEntries - 1.0)
351 - (double) index;
352 output[i] = (float) ((double) ((int) outTable[i][index] & (0xFFFF)) * (1
353 - a)
354 + (double) ((int) outTable[i][index + 1] & (0xFFFF)) * a) / 65536f;
355 }
356 }
357
358 if (outputLab)
359 return LabtoXYZ(output);
360 return output;
361 }
362
363 /**
364 * Converts CIE Lab coordinates to (D50) XYZ ones.
365 */
366 private float[] LabtoXYZ(float[] in)
367 {
368 // Convert from byte-packed format to a
369 // more convenient one (actual Lab values)
370 // (See ICC spec for details)
371 // factor is 100 * 65536 / 65280
372 in[0] = (float) (100.392156862745 * in[0]);
373 in[1] = (in[1] * 256.0f) - 128.0f;
374 in[2] = (in[2] * 256.0f) - 128.0f;
375
376 float[] out = new float[3];
377
378 out[1] = (in[0] + 16.0f) / 116.0f;
379 out[0] = in[1] / 500.0f + out[1];
380 out[2] = out[1] - in[2] / 200.0f;
381
382 for (int i = 0; i < 3; i++)
383 {
384 double exp = out[i] * out[i] * out[i];
385 if (exp <= 0.008856)
386 out[i] = (out[i] - 16.0f / 116.0f) / 7.787f;
387 else
388 out[i] = (float) exp;
389 out[i] = D50[i] * out[i];
390 }
391 return out;
392 }
393
394 /**
395 * Converts CIE XYZ coordinates to Lab ones.
396 */
397 private float[] XYZtoLab(float[] in)
398 {
399 float[] temp = new float[3];
400
401 for (int i = 0; i < 3; i++)
402 {
403 temp[i] = in[i] / D50[i];
404
405 if (temp[i] <= 0.008856f)
406 temp[i] = (7.7870689f * temp[i]) + (16f / 116.0f);
407 else
408 temp[i] = (float) Math.exp((1.0 / 3.0) * Math.log(temp[i]));
409 }
410
411 float[] out = new float[3];
412 out[0] = (116.0f * temp[1]) - 16f;
413 out[1] = 500.0f * (temp[0] - temp[1]);
414 out[2] = 200.0f * (temp[1] - temp[2]);
415
416 // Normalize to packed format
417 out[0] = (float) (out[0] / 100.392156862745);
418 out[1] = (out[1] + 128f) / 256f;
419 out[2] = (out[2] + 128f) / 256f;
420 for (int i = 0; i < 3; i++)
421 {
422 if (out[i] < 0f)
423 out[i] = 0f;
424 if (out[i] > 1f)
425 out[i] = 1f;
426 }
427 return out;
428 }
429 }