1 /* ICC_Profile.java -- color space profiling
2    Copyright (C) 2000, 2002, 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 
39 package java.awt.color;
40 
41 import gnu.java.awt.color.ProfileHeader;
42 import gnu.java.awt.color.TagEntry;
43 
44 import java.io.FileInputStream;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.ObjectInputStream;
49 import java.io.ObjectOutputStream;
50 import java.io.ObjectStreamException;
51 import java.io.OutputStream;
52 import java.io.Serializable;
53 import java.io.UnsupportedEncodingException;
54 import java.nio.ByteBuffer;
55 import java.util.Enumeration;
56 import java.util.Hashtable;
57 
58 /**
59  * ICC Profile - represents an ICC Color profile.
60  * The ICC profile format is a standard file format which maps the transform
61  * from a device color space to a standard Profile Color Space (PCS), which
62  * can either be CIE L*a*b or CIE XYZ.
63  * (With the exception of device link profiles which map from one device space
64  * to another)
65  *
66  * ICC profiles calibrated to specific input/output devices are used when color
67  * fidelity is of importance.
68  *
69  * An instance of ICC_Profile can be created using the getInstance() methods,
70  * either using one of the predefined color spaces enumerated in ColorSpace,
71  * or from an ICC profile file, or from an input stream.
72  *
73  * An ICC_ColorSpace object can then be created to transform color values
74  * through the profile.
75  *
76  * The ICC_Profile class implements the version 2 format specified by
77  * International Color Consortium Specification ICC.1:1998-09,
78  * and its addendum ICC.1A:1999-04, April 1999
79  * (available at www.color.org)
80  *
81  * @author Sven de Marothy
82  * @author Rolf W. Rasmussen (rolfwr@ii.uib.no)
83  * @since 1.2
84  */
85 public class ICC_Profile implements Serializable
86 {
87   /**
88    * Compatible with JDK 1.2+.
89    */
90   private static final long serialVersionUID = -3938515861990936766L;
91 
92   /**
93    * ICC Profile classes
94    */
95   public static final int CLASS_INPUT = 0;
96   public static final int CLASS_DISPLAY = 1;
97   public static final int CLASS_OUTPUT = 2;
98   public static final int CLASS_DEVICELINK = 3;
99   public static final int CLASS_COLORSPACECONVERSION = 4;
100   public static final int CLASS_ABSTRACT = 5;
101   public static final int CLASS_NAMEDCOLOR = 6;
102 
103   /**
104    * ICC Profile class signatures
105    */
106   public static final int icSigInputClass = 0x73636e72; // 'scnr'
107   public static final int icSigDisplayClass = 0x6d6e7472; // 'mntr'
108   public static final int icSigOutputClass = 0x70727472; // 'prtr'
109   public static final int icSigLinkClass = 0x6c696e6b; // 'link'
110   public static final int icSigColorSpaceClass = 0x73706163; // 'spac'
111   public static final int icSigAbstractClass = 0x61627374; // 'abst'
112   public static final int icSigNamedColorClass = 0x6e6d636c; // 'nmcl'
113 
114   /**
115    * Color space signatures
116    */
117   public static final int icSigXYZData = 0x58595A20; // 'XYZ '
118   public static final int icSigLabData = 0x4C616220; // 'Lab '
119   public static final int icSigLuvData = 0x4C757620; // 'Luv '
120   public static final int icSigYCbCrData = 0x59436272; // 'YCbr'
121   public static final int icSigYxyData = 0x59787920; // 'Yxy '
122   public static final int icSigRgbData = 0x52474220; // 'RGB '
123   public static final int icSigGrayData = 0x47524159; // 'GRAY'
124   public static final int icSigHsvData = 0x48535620; // 'HSV '
125   public static final int icSigHlsData = 0x484C5320; // 'HLS '
126   public static final int icSigCmykData = 0x434D594B; // 'CMYK'
127   public static final int icSigCmyData = 0x434D5920; // 'CMY '
128   public static final int icSigSpace2CLR = 0x32434C52; // '2CLR'
129   public static final int icSigSpace3CLR = 0x33434C52; // '3CLR'
130   public static final int icSigSpace4CLR = 0x34434C52; // '4CLR'
131   public static final int icSigSpace5CLR = 0x35434C52; // '5CLR'
132   public static final int icSigSpace6CLR = 0x36434C52; // '6CLR'
133   public static final int icSigSpace7CLR = 0x37434C52; // '7CLR'
134   public static final int icSigSpace8CLR = 0x38434C52; // '8CLR'
135   public static final int icSigSpace9CLR = 0x39434C52; // '9CLR'
136   public static final int icSigSpaceACLR = 0x41434C52; // 'ACLR'
137   public static final int icSigSpaceBCLR = 0x42434C52; // 'BCLR'
138   public static final int icSigSpaceCCLR = 0x43434C52; // 'CCLR'
139   public static final int icSigSpaceDCLR = 0x44434C52; // 'DCLR'
140   public static final int icSigSpaceECLR = 0x45434C52; // 'ECLR'
141   public static final int icSigSpaceFCLR = 0x46434C52; // 'FCLR'
142 
143   /**
144    * Rendering intents
145    */
146   public static final int icPerceptual = 0;
147   public static final int icRelativeColorimetric = 1;
148   public static final int icSaturation = 2;
149   public static final int icAbsoluteColorimetric = 3;
150 
151   /**
152    * Tag signatures
153    */
154   public static final int icSigAToB0Tag = 0x41324230; // 'A2B0'
155   public static final int icSigAToB1Tag = 0x41324231; // 'A2B1'
156   public static final int icSigAToB2Tag = 0x41324232; // 'A2B2'
157   public static final int icSigBlueColorantTag = 0x6258595A; // 'bXYZ'
158   public static final int icSigBlueTRCTag = 0x62545243; // 'bTRC'
159   public static final int icSigBToA0Tag = 0x42324130; // 'B2A0'
160   public static final int icSigBToA1Tag = 0x42324131; // 'B2A1'
161   public static final int icSigBToA2Tag = 0x42324132; // 'B2A2'
162   public static final int icSigCalibrationDateTimeTag = 0x63616C74; // 'calt'
163   public static final int icSigCharTargetTag = 0x74617267; // 'targ'
164   public static final int icSigCopyrightTag = 0x63707274; // 'cprt'
165   public static final int icSigCrdInfoTag = 0x63726469; // 'crdi'
166   public static final int icSigDeviceMfgDescTag = 0x646D6E64; // 'dmnd'
167   public static final int icSigDeviceModelDescTag = 0x646D6464; // 'dmdd'
168   public static final int icSigDeviceSettingsTag = 0x64657673; // 'devs'
169   public static final int icSigGamutTag = 0x67616D74; // 'gamt'
170   public static final int icSigGrayTRCTag = 0x6b545243; // 'kTRC'
171   public static final int icSigGreenColorantTag = 0x6758595A; // 'gXYZ'
172   public static final int icSigGreenTRCTag = 0x67545243; // 'gTRC'
173   public static final int icSigLuminanceTag = 0x6C756d69; // 'lumi'
174   public static final int icSigMeasurementTag = 0x6D656173; // 'meas'
175   public static final int icSigMediaBlackPointTag = 0x626B7074; // 'bkpt'
176   public static final int icSigMediaWhitePointTag = 0x77747074; // 'wtpt'
177   public static final int icSigNamedColor2Tag = 0x6E636C32; // 'ncl2'
178   public static final int icSigOutputResponseTag = 0x72657370; // 'resp'
179   public static final int icSigPreview0Tag = 0x70726530; // 'pre0'
180   public static final int icSigPreview1Tag = 0x70726531; // 'pre1'
181   public static final int icSigPreview2Tag = 0x70726532; // 'pre2'
182   public static final int icSigProfileDescriptionTag = 0x64657363; // 'desc'
183   public static final int icSigProfileSequenceDescTag = 0x70736571; // 'pseq'
184   public static final int icSigPs2CRD0Tag = 0x70736430; // 'psd0'
185   public static final int icSigPs2CRD1Tag = 0x70736431; // 'psd1'
186   public static final int icSigPs2CRD2Tag = 0x70736432; // 'psd2'
187   public static final int icSigPs2CRD3Tag = 0x70736433; // 'psd3'
188   public static final int icSigPs2CSATag = 0x70733273; // 'ps2s'
189   public static final int icSigPs2RenderingIntentTag = 0x70733269; // 'ps2i'
190   public static final int icSigRedColorantTag = 0x7258595A; // 'rXYZ'
191   public static final int icSigRedTRCTag = 0x72545243; // 'rTRC'
192   public static final int icSigScreeningDescTag = 0x73637264; // 'scrd'
193   public static final int icSigScreeningTag = 0x7363726E; // 'scrn'
194   public static final int icSigTechnologyTag = 0x74656368; // 'tech'
195   public static final int icSigUcrBgTag = 0x62666420; // 'bfd '
196   public static final int icSigViewingCondDescTag = 0x76756564; // 'vued'
197   public static final int icSigViewingConditionsTag = 0x76696577; // 'view'
198   public static final int icSigChromaticityTag = 0x6368726D; // 'chrm'
199 
200   /**
201    * Non-ICC tag 'head' for use in retrieving the header with getData()
202    */
203   public static final int icSigHead = 0x68656164;
204 
205   /**
206    * Header offsets
207    */
208   public static final int icHdrSize = 0;
209   public static final int icHdrCmmId = 4;
210   public static final int icHdrVersion = 8;
211   public static final int icHdrDeviceClass = 12;
212   public static final int icHdrColorSpace = 16;
213   public static final int icHdrPcs = 20;
214   public static final int icHdrDate = 24;
215   public static final int icHdrMagic = 36;
216   public static final int icHdrPlatform = 40;
217   public static final int icHdrFlags = 44;
218   public static final int icHdrManufacturer = 48;
219   public static final int icHdrModel = 52;
220   public static final int icHdrAttributes = 56;
221   public static final int icHdrRenderingIntent = 64;
222   public static final int icHdrIlluminant = 68;
223   public static final int icHdrCreator = 80;
224 
225   /**
226    *
227    */
228   public static final int icTagType = 0;
229   public static final int icTagReserved = 4;
230   public static final int icCurveCount = 8;
231   public static final int icCurveData = 12;
232   public static final int icXYZNumberX = 8;
233 
234   /**
235    * offset of the Tag table
236    */
237   private static final int tagTableOffset = 128;
238 
239   /**
240    * @serial
241    */
242   private static final int iccProfileSerializedDataVersion = 1;
243 
244   /**
245    * Constants related to generating profiles for
246    * built-in colorspace profiles
247    */
248   /**
249    * Copyright notice to stick into built-in-profile files.
250    */
251   private static final String copyrightNotice = "Generated by GNU Classpath.";
252 
253   /**
254    * Resolution of the TRC to use for predefined profiles.
255    * 1024 should suffice.
256    */
257   private static final int TRC_POINTS = 1024;
258 
259   /**
260    * CIE 1931 D50 white point (in Lab coordinates)
261    */
262   private static final float[] D50 = { 0.96422f, 1.00f, 0.82521f };
263 
264   /**
265    * Color space profile ID
266    * Set to the predefined profile class (e.g. CS_sRGB) if a predefined
267    * color space is used, set to -1 otherwise.
268    * (or if the profile has been modified)
269    */
270   private transient int profileID;
271 
272   /**
273    * The profile header data
274    */
275   private transient ProfileHeader header;
276 
277   /**
278    * A hashtable containing the profile tags as TagEntry objects
279    */
280   private transient Hashtable tagTable;
281 
282   /**
283    * Contructor for predefined colorspaces
284    */
ICC_Profile(int profileID)285   ICC_Profile(int profileID)
286   {
287     header = null;
288     tagTable = null;
289     createProfile(profileID);
290   }
291 
292   /**
293    * Constructs an ICC_Profile from a header and a table of loaded tags.
294    */
ICC_Profile(ProfileHeader h, Hashtable tags)295   ICC_Profile(ProfileHeader h, Hashtable tags) throws IllegalArgumentException
296   {
297     header = h;
298     tagTable = tags;
299     profileID = -1; // Not a predefined color space
300   }
301 
302   /**
303    * Constructs an ICC_Profile from a byte array of data.
304    */
ICC_Profile(byte[] data)305   ICC_Profile(byte[] data) throws IllegalArgumentException
306   {
307     // get header and verify it
308     header = new ProfileHeader(data);
309     header.verifyHeader(data.length);
310     tagTable = createTagTable(data);
311     profileID = -1; // Not a predefined color space
312   }
313 
314   /**
315    * Free up the used memory.
316    */
finalize()317   protected void finalize()
318   {
319   }
320 
321   /**
322    * Returns an ICC_Profile instance from a byte array of profile data.
323    *
324    * An instance of the specialized classes ICC_ProfileRGB or ICC_ProfileGray
325    * may be returned if appropriate.
326    *
327    * @param data - the profile data
328    * @return An ICC_Profile object
329    *
330    * @throws IllegalArgumentException if the profile data is an invalid
331    * v2 profile.
332    */
getInstance(byte[] data)333   public static ICC_Profile getInstance(byte[] data)
334   {
335     ProfileHeader header = new ProfileHeader(data);
336 
337     // verify it as a correct ICC header, including size
338     header.verifyHeader(data.length);
339 
340     Hashtable tags = createTagTable(data);
341 
342     if (isRGBProfile(header, tags))
343       return new ICC_ProfileRGB(data);
344     if (isGrayProfile(header, tags))
345       return new ICC_ProfileGray(data);
346 
347     return new ICC_Profile(header, tags);
348   }
349 
350   /**
351    * Returns an predefined ICC_Profile instance.
352    *
353    * This will construct an ICC_Profile instance from one of the predefined
354    * color spaces in the ColorSpace class. (e.g. CS_sRGB, CS_GRAY, etc)
355    *
356    * An instance of the specialized classes ICC_ProfileRGB or ICC_ProfileGray
357    * may be returned if appropriate.
358    *
359    * @return An ICC_Profile object
360    */
getInstance(int cspace)361   public static ICC_Profile getInstance(int cspace)
362   {
363     if (cspace == ColorSpace.CS_sRGB || cspace == ColorSpace.CS_LINEAR_RGB)
364       return new ICC_ProfileRGB(cspace);
365     if (cspace == ColorSpace.CS_GRAY)
366       return new ICC_ProfileGray(cspace);
367     return new ICC_Profile(cspace);
368   }
369 
370   /**
371    * Returns an ICC_Profile instance from an ICC Profile file.
372    *
373    * An instance of the specialized classes ICC_ProfileRGB or ICC_ProfileGray
374    * may be returned if appropriate.
375    *
376    * @param filename - the file name of the profile file.
377    * @return An ICC_Profile object
378    *
379    * @throws IllegalArgumentException if the profile data is an invalid
380    * v2 profile.
381    * @throws IOException if the file could not be read.
382    */
getInstance(String filename)383   public static ICC_Profile getInstance(String filename)
384                                  throws IOException
385   {
386     return getInstance(new FileInputStream(filename));
387   }
388 
389   /**
390    * Returns an ICC_Profile instance from an InputStream.
391    *
392    * This method can be used for reading ICC profiles embedded in files
393    * which support this. (JPEG and SVG for instance).
394    *
395    * The stream is treated in the following way: The profile header
396    * (128 bytes) is read first, and the header is validated. If the profile
397    * header is valid, it will then attempt to read the rest of the profile
398    * from the stream. The stream is not closed after reading.
399    *
400    * An instance of the specialized classes ICC_ProfileRGB or ICC_ProfileGray
401    * may be returned if appropriate.
402    *
403    * @param in - the input stream to read the profile from.
404    * @return An ICC_Profile object
405    *
406    * @throws IllegalArgumentException if the profile data is an invalid
407    * v2 profile.
408    * @throws IOException if the stream could not be read.
409    */
getInstance(InputStream in)410   public static ICC_Profile getInstance(InputStream in)
411                                  throws IOException
412   {
413     // read the header
414     byte[] headerData = new byte[ProfileHeader.HEADERSIZE];
415     if (in.read(headerData) != ProfileHeader.HEADERSIZE)
416       throw new IllegalArgumentException("Invalid profile header");
417 
418     ProfileHeader header = new ProfileHeader(headerData);
419 
420     // verify it as a correct ICC header, but do not verify the
421     // size as we are reading from a stream.
422     header.verifyHeader(-1);
423 
424     // get the size
425     byte[] data = new byte[header.getSize()];
426     System.arraycopy(headerData, 0, data, 0, ProfileHeader.HEADERSIZE);
427 
428     // read the rest
429     int totalBytes = header.getSize() - ProfileHeader.HEADERSIZE;
430     int bytesLeft = totalBytes;
431     while (bytesLeft > 0)
432       {
433         int read = in.read(data,
434                            ProfileHeader.HEADERSIZE + (totalBytes - bytesLeft),
435                            bytesLeft);
436         bytesLeft -= read;
437       }
438 
439     return getInstance(data);
440   }
441 
442   /**
443    * Returns the major version number
444    */
getMajorVersion()445   public int getMajorVersion()
446   {
447     return header.getMajorVersion();
448   }
449 
450   /**
451    * Returns the minor version number.
452    *
453    * Only the least-significant byte contains data, in BCD form:
454    * the least-significant nibble is the BCD bug fix revision,
455    * the most-significant nibble is the BCD minor revision number.
456    *
457    * (E.g. For a v2.1.0 profile this will return <code>0x10</code>)
458    */
getMinorVersion()459   public int getMinorVersion()
460   {
461     return header.getMinorVersion();
462   }
463 
464   /**
465    * Returns the device class of this profile,
466    *
467    * (E.g. CLASS_INPUT for a scanner profile,
468    * CLASS_OUTPUT for a printer)
469    */
getProfileClass()470   public int getProfileClass()
471   {
472     return header.getProfileClass();
473   }
474 
475   /**
476    * Returns the color space of this profile, in terms
477    * of the color space constants defined in ColorSpace.
478    * (For example, it may be a ColorSpace.TYPE_RGB)
479    */
getColorSpaceType()480   public int getColorSpaceType()
481   {
482     return header.getColorSpace();
483   }
484 
485   /**
486    * Returns the color space of this profile's Profile Connection Space (OCS)
487    *
488    * In terms of the color space constants defined in ColorSpace.
489    * This may be TYPE_XYZ or TYPE_Lab
490    */
getPCSType()491   public int getPCSType()
492   {
493     return header.getProfileColorSpace();
494   }
495 
496   /**
497    * Writes the profile data to an ICC profile file.
498    * @param filename - The name of the file to write
499    * @throws IOException if the write failed.
500    */
write(String filename)501   public void write(String filename) throws IOException
502   {
503     FileOutputStream out = new FileOutputStream(filename);
504     write(out);
505     out.flush();
506     out.close();
507   }
508 
509   /**
510    * Writes the profile data in ICC profile file-format to a stream.
511    * This is useful for embedding ICC profiles in file formats which
512    * support this (such as JPEG and SVG).
513    *
514    * The stream is not closed after writing.
515    * @param out - The outputstream to which the profile data should be written
516    * @throws IOException if the write failed.
517    */
write(OutputStream out)518   public void write(OutputStream out) throws IOException
519   {
520     out.write(getData());
521   }
522 
523   /**
524    * Returns the data corresponding to this ICC_Profile as a byte array.
525    *
526    * @return The data in a byte array,
527    * where the first element corresponds to first byte of the profile file.
528    */
getData()529   public byte[] getData()
530   {
531     int size = getSize();
532     byte[] data = new byte[size];
533 
534     // Header
535     System.arraycopy(header.getData(size), 0, data, 0, ProfileHeader.HEADERSIZE);
536     // # of tags
537     byte[] tt = getTagTable();
538     System.arraycopy(tt, 0, data, ProfileHeader.HEADERSIZE, tt.length);
539 
540     Enumeration e = tagTable.elements();
541     while (e.hasMoreElements())
542       {
543         TagEntry tag = (TagEntry) e.nextElement();
544         System.arraycopy(tag.getData(), 0,
545                          data, tag.getOffset(), tag.getSize());
546       }
547     return data;
548   }
549 
550   /**
551    * Returns the ICC profile tag data
552    * The non ICC-tag icSigHead is also permitted to request the header data.
553    *
554    * @param tagSignature The ICC signature of the requested tag
555    * @return A byte array containing the tag data
556    */
getData(int tagSignature)557   public byte[] getData(int tagSignature)
558   {
559     if (tagSignature == icSigHead)
560       return header.getData(getSize());
561 
562     TagEntry t = (TagEntry) tagTable.get(TagEntry.tagHashKey(tagSignature));
563     if (t == null)
564       return null;
565     return t.getData();
566   }
567 
568   /**
569    * Sets the ICC profile tag data.
570    *
571    * Note that an ICC profile can only contain one tag of each type, if
572    * a tag already exists with the given signature, it is replaced.
573    *
574    * @param tagSignature - The signature of the tag to set
575    * @param data - A byte array containing the tag data
576    */
setData(int tagSignature, byte[] data)577   public void setData(int tagSignature, byte[] data)
578   {
579     profileID = -1; // Not a predefined color space if modified.
580 
581     if (tagSignature == icSigHead)
582       header = new ProfileHeader(data);
583     else
584       {
585         TagEntry t = new TagEntry(tagSignature, data);
586         tagTable.put(t.hashKey(), t);
587       }
588   }
589 
590   /**
591    * Get the number of components in the profile's device color space.
592    */
getNumComponents()593   public int getNumComponents()
594   {
595     int[] lookup =
596                    {
597                      ColorSpace.TYPE_RGB, 3, ColorSpace.TYPE_CMY, 3,
598                      ColorSpace.TYPE_CMYK, 4, ColorSpace.TYPE_GRAY, 1,
599                      ColorSpace.TYPE_YCbCr, 3, ColorSpace.TYPE_XYZ, 3,
600                      ColorSpace.TYPE_Lab, 3, ColorSpace.TYPE_HSV, 3,
601                      ColorSpace.TYPE_2CLR, 2, ColorSpace.TYPE_Luv, 3,
602                      ColorSpace.TYPE_Yxy, 3, ColorSpace.TYPE_HLS, 3,
603                      ColorSpace.TYPE_3CLR, 3, ColorSpace.TYPE_4CLR, 4,
604                      ColorSpace.TYPE_5CLR, 5, ColorSpace.TYPE_6CLR, 6,
605                      ColorSpace.TYPE_7CLR, 7, ColorSpace.TYPE_8CLR, 8,
606                      ColorSpace.TYPE_9CLR, 9, ColorSpace.TYPE_ACLR, 10,
607                      ColorSpace.TYPE_BCLR, 11, ColorSpace.TYPE_CCLR, 12,
608                      ColorSpace.TYPE_DCLR, 13, ColorSpace.TYPE_ECLR, 14,
609                      ColorSpace.TYPE_FCLR, 15
610                    };
611     for (int i = 0; i < lookup.length; i += 2)
612       if (header.getColorSpace() == lookup[i])
613         return lookup[i + 1];
614     return 3; // should never happen.
615   }
616 
617   /**
618    * After deserializing we must determine if the class we want
619    * is really one of the more specialized ICC_ProfileRGB or
620    * ICC_ProfileGray classes.
621    */
readResolve()622   protected Object readResolve() throws ObjectStreamException
623   {
624     if (isRGBProfile(header, tagTable))
625       return new ICC_ProfileRGB(getData());
626     if (isGrayProfile(header, tagTable))
627       return new ICC_ProfileGray(getData());
628     return this;
629   }
630 
631   /**
632    * Deserializes an instance
633    */
readObject(ObjectInputStream s)634   private void readObject(ObjectInputStream s)
635                    throws IOException, ClassNotFoundException
636   {
637     s.defaultReadObject();
638     String predef = (String) s.readObject();
639     byte[] data = (byte[]) s.readObject();
640 
641     if (data != null)
642       {
643         header = new ProfileHeader(data);
644         tagTable = createTagTable(data);
645         profileID = -1; // Not a predefined color space
646       }
647 
648     if (predef != null)
649       {
650         predef = predef.intern();
651         if (predef.equals("CS_sRGB"))
652           createProfile(ColorSpace.CS_sRGB);
653         if (predef.equals("CS_LINEAR_RGB"))
654           createProfile(ColorSpace.CS_LINEAR_RGB);
655         if (predef.equals("CS_CIEXYZ"))
656           createProfile(ColorSpace.CS_CIEXYZ);
657         if (predef.equals("CS_GRAY"))
658           createProfile(ColorSpace.CS_GRAY);
659         if (predef.equals("CS_PYCC"))
660           createProfile(ColorSpace.CS_PYCC);
661       }
662   }
663 
664   /**
665    * Serializes an instance
666    * The format is a String and a byte array,
667    * The string is non-null if the instance is one of the built-in profiles.
668    * Otherwise the byte array is non-null and represents the profile data.
669    */
writeObject(ObjectOutputStream s)670   private void writeObject(ObjectOutputStream s) throws IOException
671   {
672     s.defaultWriteObject();
673     if (profileID == ColorSpace.CS_sRGB)
674       s.writeObject("CS_sRGB");
675     else if (profileID == ColorSpace.CS_LINEAR_RGB)
676       s.writeObject("CS_LINEAR_RGB");
677     else if (profileID == ColorSpace.CS_CIEXYZ)
678       s.writeObject("CS_CIEXYZ");
679     else if (profileID == ColorSpace.CS_GRAY)
680       s.writeObject("CS_GRAY");
681     else if (profileID == ColorSpace.CS_PYCC)
682       s.writeObject("CS_PYCC");
683     else
684       {
685         s.writeObject(null); // null string
686         s.writeObject(getData()); // data
687         return;
688       }
689     s.writeObject(null); // null data
690   }
691 
692   /**
693    * Sorts a ICC profile byte array into TagEntry objects stored in
694    * a hash table.
695    */
createTagTable(byte[] data)696   private static Hashtable createTagTable(byte[] data)
697                                    throws IllegalArgumentException
698   {
699     ByteBuffer buf = ByteBuffer.wrap(data);
700     int nTags = buf.getInt(tagTableOffset);
701 
702     Hashtable tagTable = new Hashtable();
703     for (int i = 0; i < nTags; i++)
704       {
705         TagEntry te = new TagEntry(buf.getInt(tagTableOffset
706                                               + i * TagEntry.entrySize + 4),
707                                    buf.getInt(tagTableOffset
708                                               + i * TagEntry.entrySize + 8),
709                                    buf.getInt(tagTableOffset
710                                               + i * TagEntry.entrySize + 12),
711                                    data);
712 
713         if (tagTable.put(te.hashKey(), te) != null)
714           throw new IllegalArgumentException("Duplicate tag in profile:" + te);
715       }
716     return tagTable;
717   }
718 
719   /**
720    * Returns the total size of the padded, stored data
721    * Note: Tags must be stored on 4-byte aligned offsets.
722    */
getSize()723   private int getSize()
724   {
725     int totalSize = ProfileHeader.HEADERSIZE; // size of header
726 
727     int tagTableSize = 4 + tagTable.size() * TagEntry.entrySize; // size of tag table
728     if ((tagTableSize & 0x0003) != 0)
729       tagTableSize += 4 - (tagTableSize & 0x0003); // pad
730     totalSize += tagTableSize;
731 
732     Enumeration e = tagTable.elements();
733     while (e.hasMoreElements())
734       { // tag data
735         int tagSize = ((TagEntry) e.nextElement()).getSize();
736         if ((tagSize & 0x0003) != 0)
737           tagSize += 4 - (tagSize & 0x0003); // pad
738         totalSize += tagSize;
739       }
740     return totalSize;
741   }
742 
743   /**
744    * Generates the tag index table
745    */
getTagTable()746   private byte[] getTagTable()
747   {
748     int tagTableSize = 4 + tagTable.size() * TagEntry.entrySize;
749     if ((tagTableSize & 0x0003) != 0)
750       tagTableSize += 4 - (tagTableSize & 0x0003); // pad
751 
752     int offset = 4;
753     int tagOffset = ProfileHeader.HEADERSIZE + tagTableSize;
754     ByteBuffer buf = ByteBuffer.allocate(tagTableSize);
755     buf.putInt(tagTable.size()); // number of tags
756 
757     Enumeration e = tagTable.elements();
758     while (e.hasMoreElements())
759       {
760         TagEntry tag = (TagEntry) e.nextElement();
761         buf.putInt(offset, tag.getSignature());
762         buf.putInt(offset + 4, tagOffset);
763         buf.putInt(offset + 8, tag.getSize());
764         tag.setOffset(tagOffset);
765         int tagSize = tag.getSize();
766         if ((tagSize & 0x0003) != 0)
767           tagSize += 4 - (tagSize & 0x0003); // pad
768         tagOffset += tagSize;
769         offset += 12;
770       }
771     return buf.array();
772   }
773 
774   /**
775    * Returns if the criteria for an ICC_ProfileRGB are met.
776    * This means:
777    * Color space is TYPE_RGB
778    * (r,g,b)ColorantTags included
779    * (r,g,b)TRCTags included
780    * mediaWhitePointTag included
781    */
isRGBProfile(ProfileHeader header, Hashtable tags)782   private static boolean isRGBProfile(ProfileHeader header, Hashtable tags)
783   {
784     if (header.getColorSpace() != ColorSpace.TYPE_RGB)
785       return false;
786     if (tags.get(TagEntry.tagHashKey(icSigRedColorantTag)) == null)
787       return false;
788     if (tags.get(TagEntry.tagHashKey(icSigGreenColorantTag)) == null)
789       return false;
790     if (tags.get(TagEntry.tagHashKey(icSigBlueColorantTag)) == null)
791       return false;
792     if (tags.get(TagEntry.tagHashKey(icSigRedTRCTag)) == null)
793       return false;
794     if (tags.get(TagEntry.tagHashKey(icSigGreenTRCTag)) == null)
795       return false;
796     if (tags.get(TagEntry.tagHashKey(icSigBlueTRCTag)) == null)
797       return false;
798     return (tags.get(TagEntry.tagHashKey(icSigMediaWhitePointTag)) != null);
799   }
800 
801   /**
802    * Returns if the criteria for an ICC_ProfileGray are met.
803    * This means:
804    * Colorspace is TYPE_GRAY
805    * grayTRCTag included
806    * mediaWhitePointTag included
807    */
isGrayProfile(ProfileHeader header, Hashtable tags)808   private static boolean isGrayProfile(ProfileHeader header, Hashtable tags)
809   {
810     if (header.getColorSpace() != ColorSpace.TYPE_GRAY)
811       return false;
812     if (tags.get(TagEntry.tagHashKey(icSigGrayTRCTag)) == null)
813       return false;
814     return (tags.get(TagEntry.tagHashKey(icSigMediaWhitePointTag)) != null);
815   }
816 
817   /**
818    * Returns curve data for a 'curv'-type tag
819    * If it's a gamma curve, a single entry will be returned with the
820    * gamma value (including 1.0 for linear response)
821    * Otherwise the TRC table is returned.
822    *
823    * (Package private - used by ICC_ProfileRGB and ICC_ProfileGray)
824    */
getCurve(int signature)825   short[] getCurve(int signature)
826   {
827     byte[] data = getData(signature);
828     short[] curve;
829 
830     // can't find tag?
831     if (data == null)
832       return null;
833 
834     // not an curve type tag?
835     ByteBuffer buf = ByteBuffer.wrap(data);
836     if (buf.getInt(0) != 0x63757276) // 'curv' type
837       return null;
838     int count = buf.getInt(8);
839     if (count == 0)
840       {
841         curve = new short[1];
842         curve[0] = 0x0100; // 1.00 in u8fixed8
843         return curve;
844       }
845     if (count == 1)
846       {
847         curve = new short[1];
848         curve[0] = buf.getShort(12); // other u8fixed8 gamma
849         return curve;
850       }
851     curve = new short[count];
852     for (int i = 0; i < count; i++)
853       curve[i] = buf.getShort(12 + i * 2);
854     return curve;
855   }
856 
857   /**
858    * Returns XYZ tristimulus values for an 'XYZ ' type tag
859    * @return the XYZ values, or null if the tag was not an 'XYZ ' type tag.
860    *
861    * (Package private - used by ICC_ProfileXYZ and ICC_ProfileGray)
862    */
getXYZData(int signature)863   float[] getXYZData(int signature)
864   {
865     byte[] data = getData(signature);
866 
867     // can't find tag?
868     if (data == null)
869       return null;
870 
871     // not an XYZData type tag?
872     ByteBuffer buf = ByteBuffer.wrap(data);
873     if (buf.getInt(0) != icSigXYZData) // 'XYZ ' type
874       return null;
875 
876     float[] point = new float[3];
877 
878     // get the X,Y,Z tristimulus values
879     point[0] = ((float) buf.getInt(8)) / 65536f;
880     point[1] = ((float) buf.getInt(12)) / 65536f;
881     point[2] = ((float) buf.getInt(16)) / 65536f;
882     return point;
883   }
884 
885   /**
886    * Returns the profile ID if it's a predefined profile
887    * Or -1 for a profile loaded from an ICC profile
888    *
889    * (Package private - used by ICC_ColorSpace)
890    */
isPredefined()891   int isPredefined()
892   {
893     return profileID;
894   }
895 
896   /**
897    * Creates a tag of XYZ-value type.
898    */
makeXYZData(float[] values)899   private byte[] makeXYZData(float[] values)
900   {
901     ByteBuffer buf = ByteBuffer.allocate(20);
902     buf.putInt(0, icSigXYZData); // 'XYZ '
903     buf.putInt(4, 0);
904     buf.putInt(8, (int) (values[0] * 65536.0));
905     buf.putInt(12, (int) (values[1] * 65536.0));
906     buf.putInt(16, (int) (values[2] * 65536.0));
907     return buf.array();
908   }
909 
910   /**
911    * Creates a tag of text type
912    */
makeTextTag(String text)913   private byte[] makeTextTag(String text)
914   {
915     int length = text.length();
916     ByteBuffer buf = ByteBuffer.allocate(8 + length + 1);
917     byte[] data;
918     try
919       {
920         data = text.getBytes("US-ASCII");
921       }
922     catch (UnsupportedEncodingException e)
923       {
924         data = new byte[length]; // shouldn't happen
925       }
926 
927     buf.putInt(0, (int) 0x74657874); // 'text'
928     buf.putInt(4, 0);
929     for (int i = 0; i < length; i++)
930       buf.put(8 + i, data[i]);
931     buf.put(8 + length, (byte) 0); // null-terminate
932     return buf.array();
933   }
934 
935   /**
936    * Creates a tag of textDescriptionType
937    */
makeDescTag(String text)938   private byte[] makeDescTag(String text)
939   {
940     int length = text.length();
941     ByteBuffer buf = ByteBuffer.allocate(90 + length + 1);
942     buf.putInt(0, (int) 0x64657363); // 'desc'
943     buf.putInt(4, 0); // reserved
944     buf.putInt(8, length + 1); // ASCII length, including null termination
945     byte[] data;
946 
947     try
948       {
949         data = text.getBytes("US-ASCII");
950       }
951     catch (UnsupportedEncodingException e)
952       {
953         data = new byte[length]; // shouldn't happen
954       }
955 
956     for (int i = 0; i < length; i++)
957       buf.put(12 + i, data[i]);
958     buf.put(12 + length, (byte) 0); // null-terminate
959 
960     for (int i = 0; i < 39; i++)
961       buf.putShort(13 + length + (i * 2), (short) 0); // 78 bytes we can ignore
962 
963     return buf.array();
964   }
965 
966   /**
967    * Creates a tag of TRC type (linear curve)
968    */
makeTRC()969   private byte[] makeTRC()
970   {
971     ByteBuffer buf = ByteBuffer.allocate(12);
972     buf.putInt(0, 0x63757276); // 'curv' type
973     buf.putInt(4, 0); // reserved
974     buf.putInt(8, 0);
975     return buf.array();
976   }
977 
978   /**
979    * Creates a tag of TRC type (single gamma value)
980    */
makeTRC(float gamma)981   private byte[] makeTRC(float gamma)
982   {
983     short gammaValue = (short) (gamma * 256f);
984     ByteBuffer buf = ByteBuffer.allocate(14);
985     buf.putInt(0, 0x63757276); // 'curv' type
986     buf.putInt(4, 0); // reserved
987     buf.putInt(8, 1);
988     buf.putShort(12, gammaValue); // 1.00 in u8fixed8
989     return buf.array();
990   }
991 
992   /**
993    * Creates a tag of TRC type (TRC curve points)
994    */
makeTRC(float[] trc)995   private byte[] makeTRC(float[] trc)
996   {
997     ByteBuffer buf = ByteBuffer.allocate(12 + 2 * trc.length);
998     buf.putInt(0, 0x63757276); // 'curv' type
999     buf.putInt(4, 0); // reserved
1000     buf.putInt(8, trc.length); // number of points
1001 
1002     // put the curve values
1003     for (int i = 0; i < trc.length; i++)
1004       buf.putShort(12 + i * 2, (short) (trc[i] * 65535f));
1005 
1006     return buf.array();
1007   }
1008 
1009   /**
1010    * Creates an identity color lookup table.
1011    */
makeIdentityClut()1012   private byte[] makeIdentityClut()
1013   {
1014     final int nIn = 3;
1015     final int nOut = 3;
1016     final int nInEntries = 256;
1017     final int nOutEntries = 256;
1018     final int gridpoints = 16;
1019 
1020     // gridpoints**nIn
1021     final int clutSize = 2 * nOut * gridpoints * gridpoints * gridpoints;
1022     final int totalSize = clutSize + 2 * nInEntries * nIn
1023                           + 2 * nOutEntries * nOut + 52;
1024 
1025     ByteBuffer buf = ByteBuffer.allocate(totalSize);
1026     buf.putInt(0, 0x6D667432); // 'mft2'
1027     buf.putInt(4, 0); // reserved
1028     buf.put(8, (byte) nIn); // number input channels
1029     buf.put(9, (byte) nOut); // number output channels
1030     buf.put(10, (byte) gridpoints); // number gridpoints
1031     buf.put(11, (byte) 0); // padding
1032 
1033     // identity matrix
1034     buf.putInt(12, 65536); // = 1 in s15.16 fixed point
1035     buf.putInt(16, 0);
1036     buf.putInt(20, 0);
1037     buf.putInt(24, 0);
1038     buf.putInt(28, 65536);
1039     buf.putInt(32, 0);
1040     buf.putInt(36, 0);
1041     buf.putInt(40, 0);
1042     buf.putInt(44, 65536);
1043 
1044     buf.putShort(48, (short) nInEntries); // input table entries
1045     buf.putShort(50, (short) nOutEntries); // output table entries
1046 
1047     // write the linear input channels, unsigned 16.16 fixed point,
1048     // from 0.0 to FF.FF
1049     for (int channel = 0; channel < 3; channel++)
1050       for (int i = 0; i < nInEntries; i++)
1051         {
1052           short n = (short) ((i << 8) | i); // assumes 256 entries
1053           buf.putShort(52 + (channel * nInEntries + i) * 2, n);
1054         }
1055     int clutOffset = 52 + nInEntries * nIn * 2;
1056 
1057     for (int x = 0; x < gridpoints; x++)
1058       for (int y = 0; y < gridpoints; y++)
1059         for (int z = 0; z < gridpoints; z++)
1060           {
1061             int offset = clutOffset + z * 2 * nOut + y * gridpoints * 2 * nOut
1062                          + x * gridpoints * gridpoints * 2 * nOut;
1063             double xf = ((double) x) / ((double) gridpoints - 1.0);
1064             double yf = ((double) y) / ((double) gridpoints - 1.0);
1065             double zf = ((double) z) / ((double) gridpoints - 1.0);
1066             buf.putShort(offset, (short) (xf * 65535.0));
1067             buf.putShort(offset + 2, (short) (yf * 65535.0));
1068             buf.putShort(offset + 4, (short) (zf * 65535.0));
1069           }
1070 
1071     for (int channel = 0; channel < 3; channel++)
1072       for (int i = 0; i < nOutEntries; i++)
1073         {
1074           short n = (short) ((i << 8) | i); // assumes 256 entries
1075           buf.putShort(clutOffset + clutSize + (channel * nOutEntries + i) * 2,
1076                        n);
1077         }
1078 
1079     return buf.array();
1080   }
1081 
1082   /**
1083    * Creates profile data corresponding to the built-in colorspaces.
1084    */
createProfile(int colorSpace)1085   private void createProfile(int colorSpace) throws IllegalArgumentException
1086   {
1087     this.profileID = colorSpace;
1088     header = new ProfileHeader();
1089     tagTable = new Hashtable();
1090 
1091     switch (colorSpace)
1092       {
1093       case ColorSpace.CS_sRGB:
1094         createRGBProfile();
1095         return;
1096       case ColorSpace.CS_LINEAR_RGB:
1097         createLinearRGBProfile();
1098         return;
1099       case ColorSpace.CS_CIEXYZ:
1100         createCIEProfile();
1101         return;
1102       case ColorSpace.CS_GRAY:
1103         createGrayProfile();
1104         return;
1105       case ColorSpace.CS_PYCC:
1106         createPyccProfile();
1107         return;
1108       default:
1109         throw new IllegalArgumentException("Not a predefined color space!");
1110       }
1111   }
1112 
1113   /**
1114    * Creates an ICC_Profile representing the sRGB color space
1115    */
createRGBProfile()1116   private void createRGBProfile()
1117   {
1118     header.setColorSpace( ColorSpace.TYPE_RGB );
1119     header.setProfileColorSpace( ColorSpace.TYPE_XYZ );
1120     ICC_ColorSpace cs = new ICC_ColorSpace(this);
1121 
1122     float[] r = { 1f, 0f, 0f };
1123     float[] g = { 0f, 1f, 0f };
1124     float[] b = { 0f, 0f, 1f };
1125     float[] black = { 0f, 0f, 0f };
1126 
1127     // CIE 1931 D50 white point (in Lab coordinates)
1128     float[] white = D50;
1129 
1130     // Get tristimulus values (matrix elements)
1131     r = cs.toCIEXYZ(r);
1132     g = cs.toCIEXYZ(g);
1133     b = cs.toCIEXYZ(b);
1134 
1135     // Generate the sRGB TRC curve, this is the linear->nonlinear
1136     // RGB transform.
1137     cs = new ICC_ColorSpace(getInstance(ICC_ColorSpace.CS_LINEAR_RGB));
1138     float[] points = new float[TRC_POINTS];
1139     float[] in = new float[3];
1140     for (int i = 0; i < TRC_POINTS; i++)
1141       {
1142         in[0] = in[1] = in[2] = ((float) i) / ((float) TRC_POINTS - 1);
1143         in = cs.fromRGB(in);
1144         // Note this value is the same for all components.
1145         points[i] = in[0];
1146       }
1147 
1148     setData(icSigRedColorantTag, makeXYZData(r));
1149     setData(icSigGreenColorantTag, makeXYZData(g));
1150     setData(icSigBlueColorantTag, makeXYZData(b));
1151     setData(icSigMediaWhitePointTag, makeXYZData(white));
1152     setData(icSigMediaBlackPointTag, makeXYZData(black));
1153     setData(icSigRedTRCTag, makeTRC(points));
1154     setData(icSigGreenTRCTag, makeTRC(points));
1155     setData(icSigBlueTRCTag, makeTRC(points));
1156     setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1157     setData(icSigProfileDescriptionTag, makeDescTag("Generic sRGB"));
1158     this.profileID = ColorSpace.CS_sRGB;
1159   }
1160 
1161   /**
1162    * Creates an linear sRGB profile
1163    */
createLinearRGBProfile()1164   private void createLinearRGBProfile()
1165   {
1166     header.setColorSpace(ColorSpace.TYPE_RGB);
1167     header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1168     ICC_ColorSpace cs = new ICC_ColorSpace(this);
1169 
1170     float[] r = { 1f, 0f, 0f };
1171     float[] g = { 0f, 1f, 0f };
1172     float[] b = { 0f, 0f, 1f };
1173     float[] black = { 0f, 0f, 0f };
1174 
1175     float[] white = D50;
1176 
1177     // Get tristimulus values (matrix elements)
1178     r = cs.toCIEXYZ(r);
1179     g = cs.toCIEXYZ(g);
1180     b = cs.toCIEXYZ(b);
1181 
1182     setData(icSigRedColorantTag, makeXYZData(r));
1183     setData(icSigGreenColorantTag, makeXYZData(g));
1184     setData(icSigBlueColorantTag, makeXYZData(b));
1185 
1186     setData(icSigMediaWhitePointTag, makeXYZData(white));
1187     setData(icSigMediaBlackPointTag, makeXYZData(black));
1188 
1189     setData(icSigRedTRCTag, makeTRC());
1190     setData(icSigGreenTRCTag, makeTRC());
1191     setData(icSigBlueTRCTag, makeTRC());
1192     setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1193     setData(icSigProfileDescriptionTag, makeDescTag("Linear RGB"));
1194     this.profileID = ColorSpace.CS_LINEAR_RGB;
1195   }
1196 
1197   /**
1198    * Creates an CIE XYZ identity profile
1199    */
createCIEProfile()1200   private void createCIEProfile()
1201   {
1202     header.setColorSpace( ColorSpace.TYPE_XYZ );
1203     header.setProfileColorSpace( ColorSpace.TYPE_XYZ );
1204     header.setProfileClass( CLASS_COLORSPACECONVERSION );
1205     ICC_ColorSpace cs = new ICC_ColorSpace(this);
1206 
1207     float[] white = D50;
1208 
1209     setData(icSigMediaWhitePointTag, makeXYZData(white));
1210     setData(icSigAToB0Tag, makeIdentityClut());
1211     setData(icSigBToA0Tag, makeIdentityClut());
1212     setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1213     setData(icSigProfileDescriptionTag, makeDescTag("CIE XYZ identity profile"));
1214     this.profileID = ColorSpace.CS_CIEXYZ;
1215   }
1216 
1217   /**
1218    * Creates a linear gray ICC_Profile
1219    */
createGrayProfile()1220   private void createGrayProfile()
1221   {
1222     header.setColorSpace(ColorSpace.TYPE_GRAY);
1223     header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1224 
1225     // CIE 1931 D50 white point (in Lab coordinates)
1226     float[] white = D50;
1227 
1228     setData(icSigMediaWhitePointTag, makeXYZData(white));
1229     setData(icSigGrayTRCTag, makeTRC(1.0f));
1230     setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1231     setData(icSigProfileDescriptionTag, makeDescTag("Linear grayscale"));
1232     this.profileID = ColorSpace.CS_GRAY;
1233   }
1234 
1235   /**
1236    * XXX Implement me
1237    */
createPyccProfile()1238   private void createPyccProfile()
1239   {
1240     header.setColorSpace(ColorSpace.TYPE_3CLR);
1241     header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1242 
1243     // Create CLUTs here. :-)
1244 
1245     setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1246     setData(icSigProfileDescriptionTag, makeDescTag("Photo YCC"));
1247     this.profileID = ColorSpace.CS_PYCC;
1248   }
1249 } // class ICC_Profile
1250