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        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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
891      int isPredefined()
892      {
893        return profileID;
894      }
895    
896      /**
897       * Creates a tag of XYZ-value type.
898       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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