001    /* ICC_Profile.java -- color space profiling
002       Copyright (C) 2000, 2002, 2004 Free Software Foundation
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt.color;
040    
041    import gnu.java.awt.color.ProfileHeader;
042    import gnu.java.awt.color.TagEntry;
043    
044    import java.io.FileInputStream;
045    import java.io.FileOutputStream;
046    import java.io.IOException;
047    import java.io.InputStream;
048    import java.io.ObjectInputStream;
049    import java.io.ObjectOutputStream;
050    import java.io.ObjectStreamException;
051    import java.io.OutputStream;
052    import java.io.Serializable;
053    import java.io.UnsupportedEncodingException;
054    import java.nio.ByteBuffer;
055    import java.util.Enumeration;
056    import java.util.Hashtable;
057    
058    /**
059     * ICC Profile - represents an ICC Color profile.
060     * The ICC profile format is a standard file format which maps the transform
061     * from a device color space to a standard Profile Color Space (PCS), which
062     * can either be CIE L*a*b or CIE XYZ.
063     * (With the exception of device link profiles which map from one device space
064     * to another)
065     *
066     * ICC profiles calibrated to specific input/output devices are used when color
067     * fidelity is of importance.
068     *
069     * An instance of ICC_Profile can be created using the getInstance() methods,
070     * either using one of the predefined color spaces enumerated in ColorSpace,
071     * or from an ICC profile file, or from an input stream.
072     *
073     * An ICC_ColorSpace object can then be created to transform color values
074     * through the profile.
075     *
076     * The ICC_Profile class implements the version 2 format specified by
077     * International Color Consortium Specification ICC.1:1998-09,
078     * and its addendum ICC.1A:1999-04, April 1999
079     * (available at www.color.org)
080     *
081     * @author Sven de Marothy
082     * @author Rolf W. Rasmussen (rolfwr@ii.uib.no)
083     * @since 1.2
084     */
085    public class ICC_Profile implements Serializable
086    {
087      /**
088       * Compatible with JDK 1.2+.
089       */
090      private static final long serialVersionUID = -3938515861990936766L;
091    
092      /**
093       * ICC Profile classes
094       */
095      public static final int CLASS_INPUT = 0;
096      public static final int CLASS_DISPLAY = 1;
097      public static final int CLASS_OUTPUT = 2;
098      public static final int CLASS_DEVICELINK = 3;
099      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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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        if (in.read(data, ProfileHeader.HEADERSIZE,
430                    header.getSize() - ProfileHeader.HEADERSIZE) != header.getSize()
431            - ProfileHeader.HEADERSIZE)
432          throw new IOException("Incorrect profile size");
433    
434        return getInstance(data);
435      }
436    
437      /**
438       * Returns the major version number
439       */
440      public int getMajorVersion()
441      {
442        return header.getMajorVersion();
443      }
444    
445      /**
446       * Returns the minor version number.
447       *
448       * Only the least-significant byte contains data, in BCD form:
449       * the least-significant nibble is the BCD bug fix revision,
450       * the most-significant nibble is the BCD minor revision number.
451       *
452       * (E.g. For a v2.1.0 profile this will return <code>0x10</code>)
453       */
454      public int getMinorVersion()
455      {
456        return header.getMinorVersion();
457      }
458    
459      /**
460       * Returns the device class of this profile,
461       *
462       * (E.g. CLASS_INPUT for a scanner profile,
463       * CLASS_OUTPUT for a printer)
464       */
465      public int getProfileClass()
466      {
467        return header.getProfileClass();
468      }
469    
470      /**
471       * Returns the color space of this profile, in terms
472       * of the color space constants defined in ColorSpace.
473       * (For example, it may be a ColorSpace.TYPE_RGB)
474       */
475      public int getColorSpaceType()
476      {
477        return header.getColorSpace();
478      }
479    
480      /**
481       * Returns the color space of this profile's Profile Connection Space (OCS)
482       *
483       * In terms of the color space constants defined in ColorSpace.
484       * This may be TYPE_XYZ or TYPE_Lab
485       */
486      public int getPCSType()
487      {
488        return header.getProfileColorSpace();
489      }
490    
491      /**
492       * Writes the profile data to an ICC profile file.
493       * @param filename - The name of the file to write
494       * @throws IOException if the write failed.
495       */
496      public void write(String filename) throws IOException
497      {
498        FileOutputStream out = new FileOutputStream(filename);
499        write(out);
500        out.flush();
501        out.close();
502      }
503    
504      /**
505       * Writes the profile data in ICC profile file-format to a stream.
506       * This is useful for embedding ICC profiles in file formats which
507       * support this (such as JPEG and SVG).
508       *
509       * The stream is not closed after writing.
510       * @param out - The outputstream to which the profile data should be written
511       * @throws IOException if the write failed.
512       */
513      public void write(OutputStream out) throws IOException
514      {
515        out.write(getData());
516      }
517    
518      /**
519       * Returns the data corresponding to this ICC_Profile as a byte array.
520       *
521       * @return The data in a byte array,
522       * where the first element corresponds to first byte of the profile file.
523       */
524      public byte[] getData()
525      {
526        int size = getSize();
527        byte[] data = new byte[size];
528    
529        // Header
530        System.arraycopy(header.getData(size), 0, data, 0, ProfileHeader.HEADERSIZE);
531        // # of tags
532        byte[] tt = getTagTable();
533        System.arraycopy(tt, 0, data, ProfileHeader.HEADERSIZE, tt.length);
534    
535        Enumeration e = tagTable.elements();
536        while (e.hasMoreElements())
537          {
538            TagEntry tag = (TagEntry) e.nextElement();
539            System.arraycopy(tag.getData(), 0, 
540                             data, tag.getOffset(), tag.getSize());
541          }
542        return data;
543      }
544    
545      /**
546       * Returns the ICC profile tag data
547       * The non ICC-tag icSigHead is also permitted to request the header data.
548       *
549       * @param tagSignature The ICC signature of the requested tag
550       * @return A byte array containing the tag data
551       */
552      public byte[] getData(int tagSignature)
553      {
554        if (tagSignature == icSigHead)
555          return header.getData(getSize());
556    
557        TagEntry t = (TagEntry) tagTable.get(TagEntry.tagHashKey(tagSignature));
558        if (t == null)
559          return null;
560        return t.getData();
561      }
562    
563      /**
564       * Sets the ICC profile tag data.
565       *
566       * Note that an ICC profile can only contain one tag of each type, if
567       * a tag already exists with the given signature, it is replaced.
568       *
569       * @param tagSignature - The signature of the tag to set
570       * @param data - A byte array containing the tag data
571       */
572      public void setData(int tagSignature, byte[] data)
573      {
574        profileID = -1; // Not a predefined color space if modified.
575    
576        if (tagSignature == icSigHead)
577          header = new ProfileHeader(data);
578        else
579          {
580            TagEntry t = new TagEntry(tagSignature, data);
581            tagTable.put(t.hashKey(), t);
582          }
583      }
584    
585      /**
586       * Get the number of components in the profile's device color space.
587       */
588      public int getNumComponents()
589      {
590        int[] lookup = 
591                       {
592                         ColorSpace.TYPE_RGB, 3, ColorSpace.TYPE_CMY, 3,
593                         ColorSpace.TYPE_CMYK, 4, ColorSpace.TYPE_GRAY, 1,
594                         ColorSpace.TYPE_YCbCr, 3, ColorSpace.TYPE_XYZ, 3,
595                         ColorSpace.TYPE_Lab, 3, ColorSpace.TYPE_HSV, 3,
596                         ColorSpace.TYPE_2CLR, 2, ColorSpace.TYPE_Luv, 3,
597                         ColorSpace.TYPE_Yxy, 3, ColorSpace.TYPE_HLS, 3,
598                         ColorSpace.TYPE_3CLR, 3, ColorSpace.TYPE_4CLR, 4,
599                         ColorSpace.TYPE_5CLR, 5, ColorSpace.TYPE_6CLR, 6,
600                         ColorSpace.TYPE_7CLR, 7, ColorSpace.TYPE_8CLR, 8,
601                         ColorSpace.TYPE_9CLR, 9, ColorSpace.TYPE_ACLR, 10,
602                         ColorSpace.TYPE_BCLR, 11, ColorSpace.TYPE_CCLR, 12,
603                         ColorSpace.TYPE_DCLR, 13, ColorSpace.TYPE_ECLR, 14,
604                         ColorSpace.TYPE_FCLR, 15
605                       };
606        for (int i = 0; i < lookup.length; i += 2)
607          if (header.getColorSpace() == lookup[i])
608            return lookup[i + 1];
609        return 3; // should never happen.
610      }
611    
612      /**
613       * After deserializing we must determine if the class we want
614       * is really one of the more specialized ICC_ProfileRGB or
615       * ICC_ProfileGray classes.
616       */
617      protected Object readResolve() throws ObjectStreamException
618      {
619        if (isRGBProfile(header, tagTable))
620          return new ICC_ProfileRGB(getData());
621        if (isGrayProfile(header, tagTable))
622          return new ICC_ProfileGray(getData());
623        return this;
624      }
625    
626      /**
627       * Deserializes an instance
628       */
629      private void readObject(ObjectInputStream s)
630                       throws IOException, ClassNotFoundException
631      {
632        s.defaultReadObject();
633        String predef = (String) s.readObject();
634        byte[] data = (byte[]) s.readObject();
635    
636        if (data != null)
637          {
638            header = new ProfileHeader(data);
639            tagTable = createTagTable(data);
640            profileID = -1; // Not a predefined color space
641          }
642    
643        if (predef != null)
644          {
645            predef = predef.intern();
646            if (predef.equals("CS_sRGB"))
647              createProfile(ColorSpace.CS_sRGB);
648            if (predef.equals("CS_LINEAR_RGB"))
649              createProfile(ColorSpace.CS_LINEAR_RGB);
650            if (predef.equals("CS_CIEXYZ"))
651              createProfile(ColorSpace.CS_CIEXYZ);
652            if (predef.equals("CS_GRAY"))
653              createProfile(ColorSpace.CS_GRAY);
654            if (predef.equals("CS_PYCC"))
655              createProfile(ColorSpace.CS_PYCC);
656          }
657      }
658    
659      /**
660       * Serializes an instance
661       * The format is a String and a byte array,
662       * The string is non-null if the instance is one of the built-in profiles.
663       * Otherwise the byte array is non-null and represents the profile data.
664       */
665      private void writeObject(ObjectOutputStream s) throws IOException
666      {
667        s.defaultWriteObject();
668        if (profileID == ColorSpace.CS_sRGB)
669          s.writeObject("CS_sRGB");
670        else if (profileID == ColorSpace.CS_LINEAR_RGB)
671          s.writeObject("CS_LINEAR_RGB");
672        else if (profileID == ColorSpace.CS_CIEXYZ)
673          s.writeObject("CS_CIEXYZ");
674        else if (profileID == ColorSpace.CS_GRAY)
675          s.writeObject("CS_GRAY");
676        else if (profileID == ColorSpace.CS_PYCC)
677          s.writeObject("CS_PYCC");
678        else
679          {
680            s.writeObject(null); // null string
681            s.writeObject(getData()); // data
682            return;
683          }
684        s.writeObject(null); // null data
685      }
686    
687      /**
688       * Sorts a ICC profile byte array into TagEntry objects stored in
689       * a hash table.
690       */
691      private static Hashtable createTagTable(byte[] data)
692                                       throws IllegalArgumentException
693      {
694        ByteBuffer buf = ByteBuffer.wrap(data);
695        int nTags = buf.getInt(tagTableOffset);
696    
697        Hashtable tagTable = new Hashtable();
698        for (int i = 0; i < nTags; i++)
699          {
700            TagEntry te = new TagEntry(buf.getInt(tagTableOffset
701                                                  + i * TagEntry.entrySize + 4),
702                                       buf.getInt(tagTableOffset
703                                                  + i * TagEntry.entrySize + 8),
704                                       buf.getInt(tagTableOffset
705                                                  + i * TagEntry.entrySize + 12),
706                                       data);
707    
708            if (tagTable.put(te.hashKey(), te) != null)
709              throw new IllegalArgumentException("Duplicate tag in profile:" + te);
710          }
711        return tagTable;
712      }
713    
714      /**
715       * Returns the total size of the padded, stored data
716       * Note: Tags must be stored on 4-byte aligned offsets.
717       */
718      private int getSize()
719      {
720        int totalSize = ProfileHeader.HEADERSIZE; // size of header
721    
722        int tagTableSize = 4 + tagTable.size() * TagEntry.entrySize; // size of tag table   
723        if ((tagTableSize & 0x0003) != 0)
724          tagTableSize += 4 - (tagTableSize & 0x0003); // pad
725        totalSize += tagTableSize;
726    
727        Enumeration e = tagTable.elements();
728        while (e.hasMoreElements())
729          { // tag data
730            int tagSize = ((TagEntry) e.nextElement()).getSize();
731            if ((tagSize & 0x0003) != 0)
732              tagSize += 4 - (tagSize & 0x0003); // pad
733            totalSize += tagSize;
734          }
735        return totalSize;
736      }
737    
738      /**
739       * Generates the tag index table
740       */
741      private byte[] getTagTable()
742      {
743        int tagTableSize = 4 + tagTable.size() * TagEntry.entrySize;
744        if ((tagTableSize & 0x0003) != 0)
745          tagTableSize += 4 - (tagTableSize & 0x0003); // pad 
746    
747        int offset = 4;
748        int tagOffset = ProfileHeader.HEADERSIZE + tagTableSize;
749        ByteBuffer buf = ByteBuffer.allocate(tagTableSize);
750        buf.putInt(tagTable.size()); // number of tags
751    
752        Enumeration e = tagTable.elements();
753        while (e.hasMoreElements())
754          {
755            TagEntry tag = (TagEntry) e.nextElement();
756            buf.putInt(offset, tag.getSignature());
757            buf.putInt(offset + 4, tagOffset);
758            buf.putInt(offset + 8, tag.getSize());
759            tag.setOffset(tagOffset);
760            int tagSize = tag.getSize();
761            if ((tagSize & 0x0003) != 0)
762              tagSize += 4 - (tagSize & 0x0003); // pad     
763            tagOffset += tagSize;
764            offset += 12;
765          }
766        return buf.array();
767      }
768    
769      /**
770       * Returns if the criteria for an ICC_ProfileRGB are met.
771       * This means:
772       * Color space is TYPE_RGB
773       * (r,g,b)ColorantTags included
774       * (r,g,b)TRCTags included
775       * mediaWhitePointTag included
776       */
777      private static boolean isRGBProfile(ProfileHeader header, Hashtable tags)
778      {
779        if (header.getColorSpace() != ColorSpace.TYPE_RGB)
780          return false;
781        if (tags.get(TagEntry.tagHashKey(icSigRedColorantTag)) == null)
782          return false;
783        if (tags.get(TagEntry.tagHashKey(icSigGreenColorantTag)) == null)
784          return false;
785        if (tags.get(TagEntry.tagHashKey(icSigBlueColorantTag)) == null)
786          return false;
787        if (tags.get(TagEntry.tagHashKey(icSigRedTRCTag)) == null)
788          return false;
789        if (tags.get(TagEntry.tagHashKey(icSigGreenTRCTag)) == null)
790          return false;
791        if (tags.get(TagEntry.tagHashKey(icSigBlueTRCTag)) == null)
792          return false;
793        return (tags.get(TagEntry.tagHashKey(icSigMediaWhitePointTag)) != null);
794      }
795    
796      /**
797       * Returns if the criteria for an ICC_ProfileGray are met.
798       * This means:
799       * Colorspace is TYPE_GRAY
800       * grayTRCTag included
801       * mediaWhitePointTag included
802       */
803      private static boolean isGrayProfile(ProfileHeader header, Hashtable tags)
804      {
805        if (header.getColorSpace() != ColorSpace.TYPE_GRAY)
806          return false;
807        if (tags.get(TagEntry.tagHashKey(icSigGrayTRCTag)) == null)
808          return false;
809        return (tags.get(TagEntry.tagHashKey(icSigMediaWhitePointTag)) != null);
810      }
811    
812      /**
813       * Returns curve data for a 'curv'-type tag
814       * If it's a gamma curve, a single entry will be returned with the
815       * gamma value (including 1.0 for linear response)
816       * Otherwise the TRC table is returned.
817       *
818       * (Package private - used by ICC_ProfileRGB and ICC_ProfileGray)
819       */
820      short[] getCurve(int signature)
821      {
822        byte[] data = getData(signature);
823        short[] curve;
824    
825        // can't find tag?
826        if (data == null)
827          return null;
828    
829        // not an curve type tag?
830        ByteBuffer buf = ByteBuffer.wrap(data);
831        if (buf.getInt(0) != 0x63757276) // 'curv' type
832          return null;
833        int count = buf.getInt(8);
834        if (count == 0)
835          {
836            curve = new short[1];
837            curve[0] = 0x0100; // 1.00 in u8fixed8
838            return curve;
839          }
840        if (count == 1)
841          {
842            curve = new short[1];
843            curve[0] = buf.getShort(12); // other u8fixed8 gamma
844            return curve;
845          }
846        curve = new short[count];
847        for (int i = 0; i < count; i++)
848          curve[i] = buf.getShort(12 + i * 2);
849        return curve;
850      }
851    
852      /**
853       * Returns XYZ tristimulus values for an 'XYZ ' type tag
854       * @return the XYZ values, or null if the tag was not an 'XYZ ' type tag.
855       *
856       * (Package private - used by ICC_ProfileXYZ and ICC_ProfileGray)
857       */
858      float[] getXYZData(int signature)
859      {
860        byte[] data = getData(signature);
861    
862        // can't find tag?
863        if (data == null)
864          return null;
865    
866        // not an XYZData type tag?
867        ByteBuffer buf = ByteBuffer.wrap(data);
868        if (buf.getInt(0) != icSigXYZData) // 'XYZ ' type
869          return null;
870    
871        float[] point = new float[3];
872    
873        // get the X,Y,Z tristimulus values
874        point[0] = ((float) buf.getInt(8)) / 65536f;
875        point[1] = ((float) buf.getInt(12)) / 65536f;
876        point[2] = ((float) buf.getInt(16)) / 65536f;
877        return point;
878      }
879    
880      /**
881       * Returns the profile ID if it's a predefined profile
882       * Or -1 for a profile loaded from an ICC profile
883       *
884       * (Package private - used by ICC_ColorSpace)
885       */
886      int isPredefined()
887      {
888        return profileID;
889      }
890    
891      /**
892       * Creates a tag of XYZ-value type.
893       */
894      private byte[] makeXYZData(float[] values)
895      {
896        ByteBuffer buf = ByteBuffer.allocate(20);
897        buf.putInt(0, icSigXYZData); // 'XYZ '
898        buf.putInt(4, 0);
899        buf.putInt(8, (int) (values[0] * 65536.0));
900        buf.putInt(12, (int) (values[1] * 65536.0));
901        buf.putInt(16, (int) (values[2] * 65536.0));
902        return buf.array();
903      }
904    
905      /**
906       * Creates a tag of text type
907       */
908      private byte[] makeTextTag(String text)
909      {
910        int length = text.length();
911        ByteBuffer buf = ByteBuffer.allocate(8 + length + 1);
912        byte[] data;
913        try
914          {
915            data = text.getBytes("US-ASCII");
916          }
917        catch (UnsupportedEncodingException e)
918          {
919            data = new byte[length]; // shouldn't happen
920          }
921    
922        buf.putInt(0, (int) 0x74657874); // 'text'
923        buf.putInt(4, 0);
924        for (int i = 0; i < length; i++)
925          buf.put(8 + i, data[i]);
926        buf.put(8 + length, (byte) 0); // null-terminate
927        return buf.array();
928      }
929    
930      /**
931       * Creates a tag of textDescriptionType
932       */
933      private byte[] makeDescTag(String text)
934      {
935        int length = text.length();
936        ByteBuffer buf = ByteBuffer.allocate(90 + length + 1);
937        buf.putInt(0, (int) 0x64657363); // 'desc'
938        buf.putInt(4, 0); // reserved 
939        buf.putInt(8, length + 1); // ASCII length, including null termination
940        byte[] data;
941    
942        try
943          {
944            data = text.getBytes("US-ASCII");
945          }
946        catch (UnsupportedEncodingException e)
947          {
948            data = new byte[length]; // shouldn't happen
949          }
950    
951        for (int i = 0; i < length; i++)
952          buf.put(12 + i, data[i]);
953        buf.put(12 + length, (byte) 0); // null-terminate
954    
955        for (int i = 0; i < 39; i++)
956          buf.putShort(13 + length + (i * 2), (short) 0); // 78 bytes we can ignore
957    
958        return buf.array();
959      }
960    
961      /**
962       * Creates a tag of TRC type (linear curve)
963       */
964      private byte[] makeTRC()
965      {
966        ByteBuffer buf = ByteBuffer.allocate(12);
967        buf.putInt(0, 0x63757276); // 'curv' type
968        buf.putInt(4, 0); // reserved
969        buf.putInt(8, 0);
970        return buf.array();
971      }
972    
973      /**
974       * Creates a tag of TRC type (single gamma value)
975       */
976      private byte[] makeTRC(float gamma)
977      {
978        short gammaValue = (short) (gamma * 256f);
979        ByteBuffer buf = ByteBuffer.allocate(14);
980        buf.putInt(0, 0x63757276); // 'curv' type
981        buf.putInt(4, 0); // reserved
982        buf.putInt(8, 1);
983        buf.putShort(12, gammaValue); // 1.00 in u8fixed8
984        return buf.array();
985      }
986    
987      /**
988       * Creates a tag of TRC type (TRC curve points)
989       */
990      private byte[] makeTRC(float[] trc)
991      {
992        ByteBuffer buf = ByteBuffer.allocate(12 + 2 * trc.length);
993        buf.putInt(0, 0x63757276); // 'curv' type
994        buf.putInt(4, 0); // reserved
995        buf.putInt(8, trc.length); // number of points
996    
997        // put the curve values 
998        for (int i = 0; i < trc.length; i++)
999          buf.putShort(12 + i * 2, (short) (trc[i] * 65535f));
1000    
1001        return buf.array();
1002      }
1003    
1004      /**
1005       * Creates an identity color lookup table.
1006       */
1007      private byte[] makeIdentityClut()
1008      {
1009        final int nIn = 3;
1010        final int nOut = 3;
1011        final int nInEntries = 256;
1012        final int nOutEntries = 256;
1013        final int gridpoints = 16;
1014    
1015        // gridpoints**nIn
1016        final int clutSize = 2 * nOut * gridpoints * gridpoints * gridpoints;
1017        final int totalSize = clutSize + 2 * nInEntries * nIn
1018                              + 2 * nOutEntries * nOut + 52;
1019    
1020        ByteBuffer buf = ByteBuffer.allocate(totalSize);
1021        buf.putInt(0, 0x6D667432); // 'mft2'
1022        buf.putInt(4, 0); // reserved
1023        buf.put(8, (byte) nIn); // number input channels
1024        buf.put(9, (byte) nOut); // number output channels
1025        buf.put(10, (byte) gridpoints); // number gridpoints
1026        buf.put(11, (byte) 0); // padding
1027    
1028        // identity matrix
1029        buf.putInt(12, 65536); // = 1 in s15.16 fixed point
1030        buf.putInt(16, 0);
1031        buf.putInt(20, 0);
1032        buf.putInt(24, 0);
1033        buf.putInt(28, 65536);
1034        buf.putInt(32, 0);
1035        buf.putInt(36, 0);
1036        buf.putInt(40, 0);
1037        buf.putInt(44, 65536);
1038    
1039        buf.putShort(48, (short) nInEntries); // input table entries
1040        buf.putShort(50, (short) nOutEntries); // output table entries
1041    
1042        // write the linear input channels, unsigned 16.16 fixed point,
1043        // from 0.0 to FF.FF
1044        for (int channel = 0; channel < 3; channel++)
1045          for (int i = 0; i < nInEntries; i++)
1046            {
1047              short n = (short) ((i << 8) | i); // assumes 256 entries
1048              buf.putShort(52 + (channel * nInEntries + i) * 2, n);
1049            }
1050        int clutOffset = 52 + nInEntries * nIn * 2;
1051    
1052        for (int x = 0; x < gridpoints; x++)
1053          for (int y = 0; y < gridpoints; y++)
1054            for (int z = 0; z < gridpoints; z++)
1055              {
1056                int offset = clutOffset + z * 2 * nOut + y * gridpoints * 2 * nOut
1057                             + x * gridpoints * gridpoints * 2 * nOut;
1058                double xf = ((double) x) / ((double) gridpoints - 1.0);
1059                double yf = ((double) y) / ((double) gridpoints - 1.0);
1060                double zf = ((double) z) / ((double) gridpoints - 1.0);
1061                buf.putShort(offset, (short) (xf * 65535.0));
1062                buf.putShort(offset + 2, (short) (yf * 65535.0));
1063                buf.putShort(offset + 4, (short) (zf * 65535.0));
1064              }
1065    
1066        for (int channel = 0; channel < 3; channel++)
1067          for (int i = 0; i < nOutEntries; i++)
1068            {
1069              short n = (short) ((i << 8) | i); // assumes 256 entries
1070              buf.putShort(clutOffset + clutSize + (channel * nOutEntries + i) * 2,
1071                           n);
1072            }
1073    
1074        return buf.array();
1075      }
1076    
1077      /**
1078       * Creates profile data corresponding to the built-in colorspaces.
1079       */
1080      private void createProfile(int colorSpace) throws IllegalArgumentException
1081      {
1082        this.profileID = colorSpace;
1083        header = new ProfileHeader();
1084        tagTable = new Hashtable();
1085    
1086        switch (colorSpace)
1087          {
1088          case ColorSpace.CS_sRGB:
1089            createRGBProfile();
1090            return;
1091          case ColorSpace.CS_LINEAR_RGB:
1092            createLinearRGBProfile();
1093            return;
1094          case ColorSpace.CS_CIEXYZ:
1095            createCIEProfile();
1096            return;
1097          case ColorSpace.CS_GRAY:
1098            createGrayProfile();
1099            return;
1100          case ColorSpace.CS_PYCC:
1101            createPyccProfile();
1102            return;
1103          default:
1104            throw new IllegalArgumentException("Not a predefined color space!");
1105          }
1106      }
1107    
1108      /**
1109       * Creates an ICC_Profile representing the sRGB color space
1110       */
1111      private void createRGBProfile()
1112      {
1113        header.setColorSpace( ColorSpace.TYPE_RGB );
1114        header.setProfileColorSpace( ColorSpace.TYPE_XYZ );
1115        ICC_ColorSpace cs = new ICC_ColorSpace(this);
1116    
1117        float[] r = { 1f, 0f, 0f };
1118        float[] g = { 0f, 1f, 0f };
1119        float[] b = { 0f, 0f, 1f };
1120        float[] black = { 0f, 0f, 0f };
1121    
1122        // CIE 1931 D50 white point (in Lab coordinates)
1123        float[] white = D50;
1124    
1125        // Get tristimulus values (matrix elements)
1126        r = cs.toCIEXYZ(r);
1127        g = cs.toCIEXYZ(g);
1128        b = cs.toCIEXYZ(b);
1129    
1130        // Generate the sRGB TRC curve, this is the linear->nonlinear
1131        // RGB transform.
1132        cs = new ICC_ColorSpace(getInstance(ICC_ColorSpace.CS_LINEAR_RGB));
1133        float[] points = new float[TRC_POINTS];
1134        float[] in = new float[3];
1135        for (int i = 0; i < TRC_POINTS; i++)
1136          {
1137            in[0] = in[1] = in[2] = ((float) i) / ((float) TRC_POINTS - 1);
1138            in = cs.fromRGB(in);
1139            // Note this value is the same for all components.
1140            points[i] = in[0];
1141          }
1142    
1143        setData(icSigRedColorantTag, makeXYZData(r));
1144        setData(icSigGreenColorantTag, makeXYZData(g));
1145        setData(icSigBlueColorantTag, makeXYZData(b));
1146        setData(icSigMediaWhitePointTag, makeXYZData(white));
1147        setData(icSigMediaBlackPointTag, makeXYZData(black));
1148        setData(icSigRedTRCTag, makeTRC(points));
1149        setData(icSigGreenTRCTag, makeTRC(points));
1150        setData(icSigBlueTRCTag, makeTRC(points));
1151        setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1152        setData(icSigProfileDescriptionTag, makeDescTag("Generic sRGB"));
1153        this.profileID = ColorSpace.CS_sRGB;
1154      }
1155    
1156      /**
1157       * Creates an linear sRGB profile
1158       */
1159      private void createLinearRGBProfile()
1160      {
1161        header.setColorSpace(ColorSpace.TYPE_RGB);
1162        header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1163        ICC_ColorSpace cs = new ICC_ColorSpace(this);
1164    
1165        float[] r = { 1f, 0f, 0f };
1166        float[] g = { 0f, 1f, 0f };
1167        float[] b = { 0f, 0f, 1f };
1168        float[] black = { 0f, 0f, 0f };
1169    
1170        float[] white = D50;
1171    
1172        // Get tristimulus values (matrix elements)
1173        r = cs.toCIEXYZ(r);
1174        g = cs.toCIEXYZ(g);
1175        b = cs.toCIEXYZ(b);
1176    
1177        setData(icSigRedColorantTag, makeXYZData(r));
1178        setData(icSigGreenColorantTag, makeXYZData(g));
1179        setData(icSigBlueColorantTag, makeXYZData(b));
1180    
1181        setData(icSigMediaWhitePointTag, makeXYZData(white));
1182        setData(icSigMediaBlackPointTag, makeXYZData(black));
1183    
1184        setData(icSigRedTRCTag, makeTRC());
1185        setData(icSigGreenTRCTag, makeTRC());
1186        setData(icSigBlueTRCTag, makeTRC());
1187        setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1188        setData(icSigProfileDescriptionTag, makeDescTag("Linear RGB"));
1189        this.profileID = ColorSpace.CS_LINEAR_RGB;
1190      }
1191    
1192      /**
1193       * Creates an CIE XYZ identity profile
1194       */
1195      private void createCIEProfile()
1196      {
1197        header.setColorSpace( ColorSpace.TYPE_XYZ );
1198        header.setProfileColorSpace( ColorSpace.TYPE_XYZ );
1199        header.setProfileClass( CLASS_COLORSPACECONVERSION );
1200        ICC_ColorSpace cs = new ICC_ColorSpace(this);
1201    
1202        float[] white = D50;
1203    
1204        setData(icSigMediaWhitePointTag, makeXYZData(white));
1205        setData(icSigAToB0Tag, makeIdentityClut());
1206        setData(icSigBToA0Tag, makeIdentityClut());
1207        setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1208        setData(icSigProfileDescriptionTag, makeDescTag("CIE XYZ identity profile"));
1209        this.profileID = ColorSpace.CS_CIEXYZ;
1210      }
1211    
1212      /**
1213       * Creates a linear gray ICC_Profile
1214       */
1215      private void createGrayProfile()
1216      {
1217        header.setColorSpace(ColorSpace.TYPE_GRAY);
1218        header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1219    
1220        // CIE 1931 D50 white point (in Lab coordinates)
1221        float[] white = D50;
1222    
1223        setData(icSigMediaWhitePointTag, makeXYZData(white));
1224        setData(icSigGrayTRCTag, makeTRC(1.0f));
1225        setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1226        setData(icSigProfileDescriptionTag, makeDescTag("Linear grayscale"));
1227        this.profileID = ColorSpace.CS_GRAY;
1228      }
1229    
1230      /**
1231       * XXX Implement me
1232       */
1233      private void createPyccProfile()
1234      {
1235        header.setColorSpace(ColorSpace.TYPE_3CLR);
1236        header.setProfileColorSpace(ColorSpace.TYPE_XYZ);
1237    
1238        // Create CLUTs here. :-)
1239    
1240        setData(icSigCopyrightTag, makeTextTag(copyrightNotice));
1241        setData(icSigProfileDescriptionTag, makeDescTag("Photo YCC"));
1242        this.profileID = ColorSpace.CS_PYCC;
1243      }
1244    } // class ICC_Profile