1 |
771 |
jeremybenn |
/* 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 |
|
|
*/
|
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
|