View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.icc;
18  
19  import static org.apache.commons.imaging.common.BinaryFunctions.logCharQuad;
20  import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
21  import static org.apache.commons.imaging.common.BinaryFunctions.skipBytes;
22  
23  import java.awt.color.ICC_Profile;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import org.apache.commons.imaging.ImagingException;
31  import org.apache.commons.imaging.bytesource.ByteSource;
32  import org.apache.commons.imaging.common.Allocator;
33  import org.apache.commons.imaging.common.BinaryFileParser;
34  import org.apache.commons.io.IOUtils;
35  
36  public class IccProfileParser extends BinaryFileParser {
37  
38      private static final Logger LOGGER = Logger.getLogger(IccProfileParser.class.getName());
39  
40      /**
41       * Constructs a new instance with the default, big-endian, byte order.
42       */
43      public IccProfileParser() {
44          // empty
45      }
46  
47      public IccProfileInfo getIccProfileInfo(final byte[] bytes) throws IOException {
48          if (bytes == null) {
49              return null;
50          }
51          return getIccProfileInfo(ByteSource.array(bytes));
52      }
53  
54      public IccProfileInfo getIccProfileInfo(final ByteSource byteSource) throws IOException {
55          // TODO Throw instead of logging?
56          final IccProfileInfo result;
57          try (InputStream is = byteSource.getInputStream()) {
58              result = readIccProfileInfo(is);
59          }
60          //
61          for (final IccTag tag : result.getTags()) {
62              final byte[] bytes = byteSource.getByteArray(tag.offset, tag.length);
63              // Debug.debug("bytes: " + bytes.length);
64              tag.setData(bytes);
65              // tag.dump("\t" + i + ": ");
66          }
67          // result.fillInTagData(byteSource);
68          return result;
69      }
70  
71      public IccProfileInfo getIccProfileInfo(final File file) throws IOException {
72          if (file == null) {
73              return null;
74          }
75  
76          return getIccProfileInfo(ByteSource.file(file));
77      }
78  
79      public IccProfileInfo getIccProfileInfo(final ICC_Profile iccProfile) throws IOException {
80          if (iccProfile == null) {
81              return null;
82          }
83  
84          return getIccProfileInfo(ByteSource.array(iccProfile.getData()));
85      }
86  
87      private IccTagType getIccTagType(final int quad) {
88          for (final IccTagType iccTagType : IccTagTypes.values()) {
89              if (iccTagType.getSignature() == quad) {
90                  return iccTagType;
91              }
92          }
93  
94          return null;
95      }
96  
97      public boolean isSrgb(final byte[] bytes) throws IOException {
98          return isSrgb(ByteSource.array(bytes));
99      }
100 
101     public boolean isSrgb(final ByteSource byteSource) throws IOException {
102         // setDebug(true);
103 
104         // long length = byteSource.getLength();
105         //
106         // if (LOGGER.isLoggable(Level.FINEST))
107         // Debug.debug("length: " + length);
108 
109         try (InputStream is = byteSource.getInputStream()) {
110             read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder());
111 
112             // if (length != ProfileSize)
113             // return null;
114 
115             skipBytes(is, 4 * 5);
116 
117             skipBytes(is, 12, "Not a Valid ICC Profile");
118 
119             skipBytes(is, 4 * 3);
120 
121             final int deviceManufacturer = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder());
122             if (LOGGER.isLoggable(Level.FINEST)) {
123                 logCharQuad("DeviceManufacturer", deviceManufacturer);
124             }
125 
126             final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder());
127             if (LOGGER.isLoggable(Level.FINEST)) {
128                 logCharQuad("DeviceModel", deviceModel);
129             }
130 
131             return deviceManufacturer == IccConstants.IEC && deviceModel == IccConstants.sRGB;
132         }
133     }
134 
135     public boolean isSrgb(final File file) throws IOException {
136         return isSrgb(ByteSource.file(file));
137     }
138 
139     public boolean isSrgb(final ICC_Profile iccProfile) throws IOException {
140         return isSrgb(ByteSource.array(iccProfile.getData()));
141     }
142 
143     private IccProfileInfo readIccProfileInfo(InputStream is) throws IOException {
144         final CachingInputStream cis = new CachingInputStream(is);
145         is = cis;
146 
147         // setDebug(true);
148 
149         // if (LOGGER.isLoggable(Level.FINEST))
150         // Debug.debug("length: " + length);
151 
152         final int profileSize = read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder());
153 
154         // if (length != ProfileSize)
155         // {
156         // // Debug.debug("Unexpected Length data expected: " +
157         // Integer.toHexString((int) length)
158         // // + ", encoded: " + Integer.toHexString(ProfileSize));
159         // // Debug.debug("Unexpected Length data: " + length
160         // // + ", length: " + ProfileSize);
161         // // throw new Error("asd");
162         // return null;
163         // }
164 
165         final int cmmTypeSignature = read4Bytes("Signature", is, "Not a Valid ICC Profile", getByteOrder());
166         if (LOGGER.isLoggable(Level.FINEST)) {
167             logCharQuad("CMMTypeSignature", cmmTypeSignature);
168         }
169 
170         final int profileVersion = read4Bytes("ProfileVersion", is, "Not a Valid ICC Profile", getByteOrder());
171 
172         final int profileDeviceClassSignature = read4Bytes("ProfileDeviceClassSignature", is, "Not a Valid ICC Profile", getByteOrder());
173         if (LOGGER.isLoggable(Level.FINEST)) {
174             logCharQuad("ProfileDeviceClassSignature", profileDeviceClassSignature);
175         }
176 
177         final int colorSpace = read4Bytes("ColorSpace", is, "Not a Valid ICC Profile", getByteOrder());
178         if (LOGGER.isLoggable(Level.FINEST)) {
179             logCharQuad("ColorSpace", colorSpace);
180         }
181 
182         final int profileConnectionSpace = read4Bytes("ProfileConnectionSpace", is, "Not a Valid ICC Profile", getByteOrder());
183         if (LOGGER.isLoggable(Level.FINEST)) {
184             logCharQuad("ProfileConnectionSpace", profileConnectionSpace);
185         }
186 
187         skipBytes(is, 12, "Not a Valid ICC Profile");
188 
189         final int profileFileSignature = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder());
190         if (LOGGER.isLoggable(Level.FINEST)) {
191             logCharQuad("ProfileFileSignature", profileFileSignature);
192         }
193 
194         final int primaryPlatformSignature = read4Bytes("PrimaryPlatformSignature", is, "Not a Valid ICC Profile", getByteOrder());
195         if (LOGGER.isLoggable(Level.FINEST)) {
196             logCharQuad("PrimaryPlatformSignature", primaryPlatformSignature);
197         }
198 
199         final int variousFlags = read4Bytes("VariousFlags", is, "Not a Valid ICC Profile", getByteOrder());
200         if (LOGGER.isLoggable(Level.FINEST)) {
201             logCharQuad("VariousFlags", profileFileSignature);
202         }
203 
204         final int deviceManufacturer = read4Bytes("DeviceManufacturer", is, "Not a Valid ICC Profile", getByteOrder());
205         if (LOGGER.isLoggable(Level.FINEST)) {
206             logCharQuad("DeviceManufacturer", deviceManufacturer);
207         }
208 
209         final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder());
210         if (LOGGER.isLoggable(Level.FINEST)) {
211             logCharQuad("DeviceModel", deviceModel);
212         }
213 
214         skipBytes(is, 8, "Not a Valid ICC Profile");
215 
216         final int renderingIntent = read4Bytes("RenderingIntent", is, "Not a Valid ICC Profile", getByteOrder());
217         if (LOGGER.isLoggable(Level.FINEST)) {
218             logCharQuad("RenderingIntent", renderingIntent);
219         }
220 
221         skipBytes(is, 12, "Not a Valid ICC Profile");
222 
223         final int profileCreatorSignature = read4Bytes("ProfileCreatorSignature", is, "Not a Valid ICC Profile", getByteOrder());
224         if (LOGGER.isLoggable(Level.FINEST)) {
225             logCharQuad("ProfileCreatorSignature", profileCreatorSignature);
226         }
227 
228         skipBytes(is, 16, "Not a Valid ICC Profile");
229         // readByteArray("ProfileID", 16, is,
230         // "Not a Valid ICC Profile");
231         // if (LOGGER.isLoggable(Level.FINEST))
232         // System.out
233         // .println("ProfileID: '" + new String(ProfileID) + "'");
234 
235         skipBytes(is, 28, "Not a Valid ICC Profile");
236 
237         // this.setDebug(true);
238 
239         final int tagCount = read4Bytes("TagCount", is, "Not a Valid ICC Profile", getByteOrder());
240 
241         // List tags = new ArrayList();
242         final IccTag[] tags = Allocator.array(tagCount, IccTag[]::new, IccTag.SHALLOW_SIZE);
243 
244         for (int i = 0; i < tagCount; i++) {
245             final int tagSignature = read4Bytes("TagSignature[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
246             // Debug.debug("TagSignature t "
247             // + Integer.toHexString(TagSignature));
248 
249             // this.printCharQuad("TagSignature", TagSignature);
250             final int offsetToData = read4Bytes("OffsetToData[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
251             final int elementSize = read4Bytes("ElementSize[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
252 
253             final IccTagType fIccTagType = getIccTagType(tagSignature);
254             // if (fIccTagType == null)
255             // throw new Error("oops.");
256 
257             // System.out
258             // .println("\t["
259             // + i
260             // + "]: "
261             // + ((fIccTagType == null)
262             // ? "unknown"
263             // : fIccTagType.name));
264             // Debug.debug();
265 
266             final IccTag tag = new IccTag(tagSignature, offsetToData, elementSize, fIccTagType);
267             // tag.dump("\t" + i + ": ");
268             tags[i] = tag;
269             // tags .add(tag);
270         }
271 
272         // read stream to end, filling cache.
273         IOUtils.consume(is);
274 
275         final byte[] data = cis.getCache();
276 
277         if (data.length < profileSize) {
278             throw new ImagingException("Couldn't read ICC Profile.");
279         }
280 
281         final IccProfileInfo result = new IccProfileInfo(data, profileSize, cmmTypeSignature, profileVersion, profileDeviceClassSignature, colorSpace,
282                 profileConnectionSpace, profileFileSignature, primaryPlatformSignature, variousFlags, deviceManufacturer, deviceModel, renderingIntent,
283                 profileCreatorSignature, null, tags);
284 
285         if (LOGGER.isLoggable(Level.FINEST)) {
286             LOGGER.finest("issRGB: " + result.isSrgb());
287         }
288 
289         return result;
290     }
291 
292 }