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