Source for gnu.java.awt.color.ColorLookUpTable

   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: }