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 */ ColorLookUpTable(ICC_Profile profile, int tag)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 */ readClut16(byte[] data)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 */ readClut8(byte[] data)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 */ lookup(float[] in)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 */ LabtoXYZ(float[] in)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 */ XYZtoLab(float[] in)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 } 430