001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.icc;
018
019import static org.apache.commons.imaging.common.BinaryFunctions.printCharQuad;
020import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
021import static org.apache.commons.imaging.common.BinaryFunctions.skipBytes;
022
023import java.awt.color.ICC_Profile;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.nio.ByteOrder;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030
031import org.apache.commons.imaging.common.BinaryFileParser;
032import org.apache.commons.imaging.common.bytesource.ByteSource;
033import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
034import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
035
036public class IccProfileParser extends BinaryFileParser {
037
038    private static final Logger LOGGER = Logger.getLogger(IccProfileParser.class.getName());
039
040    public IccProfileParser() {
041        this.setByteOrder(ByteOrder.BIG_ENDIAN);
042    }
043
044    public IccProfileInfo getICCProfileInfo(final ICC_Profile iccProfile) {
045        if (iccProfile == null) {
046            return null;
047        }
048
049        return getICCProfileInfo(new ByteSourceArray(iccProfile.getData()));
050    }
051
052    public IccProfileInfo getICCProfileInfo(final byte[] bytes) {
053        if (bytes == null) {
054            return null;
055        }
056
057        return getICCProfileInfo(new ByteSourceArray(bytes));
058    }
059
060    public IccProfileInfo getICCProfileInfo(final File file) {
061        if (file == null) {
062            return null;
063        }
064
065        return getICCProfileInfo(new ByteSourceFile(file));
066    }
067
068    public IccProfileInfo getICCProfileInfo(final ByteSource byteSource) {
069
070        InputStream is = null;
071
072        try {
073
074            is = byteSource.getInputStream();
075            final IccProfileInfo result = readICCProfileInfo(is);
076
077            if (result == null) {
078                return null;
079            }
080
081            is.close();
082            is = null;
083
084            for (final IccTag tag : result.getTags()) {
085                final byte[] bytes = byteSource.getBlock(tag.offset, tag.length);
086                // Debug.debug("bytes: " + bytes.length);
087                tag.setData(bytes);
088                // tag.dump("\t" + i + ": ");
089            }
090            // result.fillInTagData(byteSource);
091
092            return result;
093        } catch (final Exception e) {
094            // Debug.debug("Error: " + file.getAbsolutePath());
095            LOGGER.log(Level.SEVERE, e.getMessage(), e);
096        } finally {
097            try {
098                if (is != null) {
099                    is.close();
100                }
101            } catch (final Exception e) {
102                LOGGER.log(Level.SEVERE, e.getMessage(), e);
103            }
104
105        }
106
107        return null;
108    }
109
110    private IccProfileInfo readICCProfileInfo(InputStream is) {
111        final CachingInputStream cis = new CachingInputStream(is);
112        is = cis;
113
114        // setDebug(true);
115
116        // if (LOGGER.isLoggable(Level.FINEST))
117        // Debug.debug("length: " + length);
118
119        try {
120            final int profileSize = read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder());
121
122            // if (length != ProfileSize)
123            // {
124            // // Debug.debug("Unexpected Length data expected: " +
125            // Integer.toHexString((int) length)
126            // // + ", encoded: " + Integer.toHexString(ProfileSize));
127            // // Debug.debug("Unexpected Length data: " + length
128            // // + ", length: " + ProfileSize);
129            // // throw new Error("asd");
130            // return null;
131            // }
132
133            final int cmmTypeSignature = read4Bytes("Signature", is, "Not a Valid ICC Profile", getByteOrder());
134            if (LOGGER.isLoggable(Level.FINEST)) {
135                printCharQuad("CMMTypeSignature", cmmTypeSignature);
136            }
137
138            final int profileVersion = read4Bytes("ProfileVersion", is, "Not a Valid ICC Profile", getByteOrder());
139
140            final int profileDeviceClassSignature = read4Bytes("ProfileDeviceClassSignature", is,
141                    "Not a Valid ICC Profile", getByteOrder());
142            if (LOGGER.isLoggable(Level.FINEST)) {
143                printCharQuad("ProfileDeviceClassSignature", profileDeviceClassSignature);
144            }
145
146            final int colorSpace = read4Bytes("ColorSpace", is, "Not a Valid ICC Profile", getByteOrder());
147            if (LOGGER.isLoggable(Level.FINEST)) {
148                printCharQuad("ColorSpace", colorSpace);
149            }
150
151            final int profileConnectionSpace = read4Bytes("ProfileConnectionSpace", is, "Not a Valid ICC Profile", getByteOrder());
152            if (LOGGER.isLoggable(Level.FINEST)) {
153                printCharQuad("ProfileConnectionSpace", profileConnectionSpace);
154            }
155
156            skipBytes(is, 12, "Not a Valid ICC Profile");
157
158            final int profileFileSignature = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder());
159            if (LOGGER.isLoggable(Level.FINEST)) {
160                printCharQuad("ProfileFileSignature", profileFileSignature);
161            }
162
163            final int primaryPlatformSignature = read4Bytes("PrimaryPlatformSignature", is, "Not a Valid ICC Profile", getByteOrder());
164            if (LOGGER.isLoggable(Level.FINEST)) {
165                printCharQuad("PrimaryPlatformSignature", primaryPlatformSignature);
166            }
167
168            final int variousFlags = read4Bytes("VariousFlags", is, "Not a Valid ICC Profile", getByteOrder());
169            if (LOGGER.isLoggable(Level.FINEST)) {
170                printCharQuad("VariousFlags", profileFileSignature);
171            }
172
173            final int deviceManufacturer = read4Bytes("DeviceManufacturer", is, "Not a Valid ICC Profile", getByteOrder());
174            if (LOGGER.isLoggable(Level.FINEST)) {
175                printCharQuad("DeviceManufacturer", deviceManufacturer);
176            }
177
178            final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder());
179            if (LOGGER.isLoggable(Level.FINEST)) {
180                printCharQuad("DeviceModel", deviceModel);
181            }
182
183            skipBytes(is, 8, "Not a Valid ICC Profile");
184
185            final int renderingIntent = read4Bytes("RenderingIntent", is, "Not a Valid ICC Profile", getByteOrder());
186            if (LOGGER.isLoggable(Level.FINEST)) {
187                printCharQuad("RenderingIntent", renderingIntent);
188            }
189
190            skipBytes(is, 12, "Not a Valid ICC Profile");
191
192            final int profileCreatorSignature = read4Bytes("ProfileCreatorSignature", is, "Not a Valid ICC Profile", getByteOrder());
193            if (LOGGER.isLoggable(Level.FINEST)) {
194                printCharQuad("ProfileCreatorSignature", profileCreatorSignature);
195            }
196
197            skipBytes(is, 16, "Not a Valid ICC Profile");
198            // readByteArray("ProfileID", 16, is,
199            // "Not a Valid ICC Profile");
200            // if (LOGGER.isLoggable(Level.FINEST))
201            // System.out
202            // .println("ProfileID: '" + new String(ProfileID) + "'");
203
204            skipBytes(is, 28, "Not a Valid ICC Profile");
205
206            // this.setDebug(true);
207
208            final int tagCount = read4Bytes("TagCount", is, "Not a Valid ICC Profile", getByteOrder());
209
210            // List tags = new ArrayList();
211            final IccTag[] tags = new IccTag[tagCount];
212
213            for (int i = 0; i < tagCount; i++) {
214                final int tagSignature = read4Bytes("TagSignature[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
215                // Debug.debug("TagSignature t "
216                // + Integer.toHexString(TagSignature));
217
218                // this.printCharQuad("TagSignature", TagSignature);
219                final int offsetToData = read4Bytes("OffsetToData[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
220                final int elementSize = read4Bytes("ElementSize[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
221
222                final IccTagType fIccTagType = getIccTagType(tagSignature);
223                // if (fIccTagType == null)
224                // throw new Error("oops.");
225
226                // System.out
227                // .println("\t["
228                // + i
229                // + "]: "
230                // + ((fIccTagType == null)
231                // ? "unknown"
232                // : fIccTagType.name));
233                // Debug.debug();
234
235                final IccTag tag = new IccTag(tagSignature, offsetToData,
236                        elementSize, fIccTagType);
237                // tag.dump("\t" + i + ": ");
238                tags[i] = tag;
239                // tags .add(tag);
240            }
241
242            {
243                // read stream to end, filling cache.
244                while (is.read() >= 0) { // NOPMD we're doing nothing with the data
245                }
246            }
247
248            final byte[] data = cis.getCache();
249
250            if (data.length < profileSize) {
251                throw new IOException("Couldn't read ICC Profile.");
252            }
253
254            final IccProfileInfo result = new IccProfileInfo(data, profileSize,
255                    cmmTypeSignature, profileVersion,
256                    profileDeviceClassSignature, colorSpace,
257                    profileConnectionSpace, profileFileSignature,
258                    primaryPlatformSignature, variousFlags, deviceManufacturer,
259                    deviceModel, renderingIntent, profileCreatorSignature,
260                    null, tags);
261
262            if (LOGGER.isLoggable(Level.FINEST)) {
263                LOGGER.finest("issRGB: " + result.issRGB());
264            }
265
266            return result;
267        } catch (final Exception e) {
268            LOGGER.log(Level.SEVERE, e.getMessage(), e);
269        }
270
271        return null;
272    }
273
274    private IccTagType getIccTagType(final int quad) {
275        for (final IccTagType iccTagType : IccTagTypes.values()) {
276            if (iccTagType.getSignature() == quad) {
277                return iccTagType;
278            }
279        }
280
281        return null;
282    }
283
284    public boolean issRGB(final ICC_Profile iccProfile) throws IOException {
285        return issRGB(new ByteSourceArray(iccProfile.getData()));
286    }
287
288    public boolean issRGB(final byte[] bytes) throws IOException {
289        return issRGB(new ByteSourceArray(bytes));
290    }
291
292    public boolean issRGB(final File file) throws IOException {
293        return issRGB(new ByteSourceFile(file));
294    }
295
296    public boolean issRGB(final ByteSource byteSource) throws IOException {
297        // setDebug(true);
298
299        // long length = byteSource.getLength();
300        //
301        // if (LOGGER.isLoggable(Level.FINEST))
302        // Debug.debug("length: " + length);
303
304        try (InputStream is = byteSource.getInputStream()) {
305            read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder());
306
307            // if (length != ProfileSize)
308            // return null;
309
310            skipBytes(is, 4 * 5);
311
312            skipBytes(is, 12, "Not a Valid ICC Profile");
313
314            skipBytes(is, 4 * 3);
315
316            final int deviceManufacturer = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder());
317            if (LOGGER.isLoggable(Level.FINEST)) {
318                printCharQuad("DeviceManufacturer", deviceManufacturer);
319            }
320
321            final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder());
322            if (LOGGER.isLoggable(Level.FINEST)) {
323                printCharQuad("DeviceModel", deviceModel);
324            }
325
326            return deviceManufacturer == IccConstants.IEC && deviceModel == IccConstants.sRGB;
327        }
328    }
329
330}