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