001/* ICC_Profile.java -- color space profiling
002   Copyright (C) 2000, 2002, 2004 Free Software Foundation
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt.color;
040
041import gnu.java.awt.color.ProfileHeader;
042import gnu.java.awt.color.TagEntry;
043
044import java.io.FileInputStream;
045import java.io.FileOutputStream;
046import java.io.IOException;
047import java.io.InputStream;
048import java.io.ObjectInputStream;
049import java.io.ObjectOutputStream;
050import java.io.ObjectStreamException;
051import java.io.OutputStream;
052import java.io.Serializable;
053import java.io.UnsupportedEncodingException;
054import java.nio.ByteBuffer;
055import java.util.Enumeration;
056import 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 */
085public 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