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            final byte[] profileId = null;
198            skipBytes(is, 16, "Not a Valid ICC Profile");
199            // readByteArray("ProfileID", 16, is,
200            // "Not a Valid ICC Profile");
201            // if (LOGGER.isLoggable(Level.FINEST))
202            // System.out
203            // .println("ProfileID: '" + new String(ProfileID) + "'");
204
205            skipBytes(is, 28, "Not a Valid ICC Profile");
206
207            // this.setDebug(true);
208
209            final int tagCount = read4Bytes("TagCount", is, "Not a Valid ICC Profile", getByteOrder());
210
211            // List tags = new ArrayList();
212            final IccTag[] tags = new IccTag[tagCount];
213
214            for (int i = 0; i < tagCount; i++) {
215                final int tagSignature = read4Bytes("TagSignature[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
216                // Debug.debug("TagSignature t "
217                // + Integer.toHexString(TagSignature));
218
219                // this.printCharQuad("TagSignature", TagSignature);
220                final int offsetToData = read4Bytes("OffsetToData[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
221                final int elementSize = read4Bytes("ElementSize[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder());
222
223                final IccTagType fIccTagType = getIccTagType(tagSignature);
224                // if (fIccTagType == null)
225                // throw new Error("oops.");
226
227                // System.out
228                // .println("\t["
229                // + i
230                // + "]: "
231                // + ((fIccTagType == null)
232                // ? "unknown"
233                // : fIccTagType.name));
234                // Debug.debug();
235
236                final IccTag tag = new IccTag(tagSignature, offsetToData,
237                        elementSize, fIccTagType);
238                // tag.dump("\t" + i + ": ");
239                tags[i] = tag;
240                // tags .add(tag);
241            }
242
243            {
244                // read stream to end, filling cache.
245                while (is.read() >= 0) { // NOPMD we're doing nothing with the data
246                }
247            }
248
249            final byte[] data = cis.getCache();
250
251            if (data.length < profileSize) {
252                throw new IOException("Couldn't read ICC Profile.");
253            }
254
255            final IccProfileInfo result = new IccProfileInfo(data, profileSize,
256                    cmmTypeSignature, profileVersion,
257                    profileDeviceClassSignature, colorSpace,
258                    profileConnectionSpace, profileFileSignature,
259                    primaryPlatformSignature, variousFlags, deviceManufacturer,
260                    deviceModel, renderingIntent, profileCreatorSignature,
261                    profileId, tags);
262
263            if (LOGGER.isLoggable(Level.FINEST)) {
264                LOGGER.finest("issRGB: " + result.issRGB());
265            }
266
267            return result;
268        } catch (final Exception e) {
269            LOGGER.log(Level.SEVERE, e.getMessage(), e);
270        }
271
272        return null;
273    }
274
275    private IccTagType getIccTagType(final int quad) {
276        for (final IccTagType iccTagType : IccTagTypes.values()) {
277            if (iccTagType.getSignature() == quad) {
278                return iccTagType;
279            }
280        }
281
282        return null;
283    }
284
285    public boolean issRGB(final ICC_Profile iccProfile) throws IOException {
286        return issRGB(new ByteSourceArray(iccProfile.getData()));
287    }
288
289    public boolean issRGB(final byte[] bytes) throws IOException {
290        return issRGB(new ByteSourceArray(bytes));
291    }
292
293    public boolean issRGB(final File file) throws IOException {
294        return issRGB(new ByteSourceFile(file));
295    }
296
297    public boolean issRGB(final ByteSource byteSource) throws IOException {
298        // setDebug(true);
299
300        // long length = byteSource.getLength();
301        //
302        // if (LOGGER.isLoggable(Level.FINEST))
303        // Debug.debug("length: " + length);
304
305        try (InputStream is = byteSource.getInputStream()) {
306            read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder());
307
308            // if (length != ProfileSize)
309            // return null;
310
311            skipBytes(is, 4 * 5);
312
313            skipBytes(is, 12, "Not a Valid ICC Profile");
314
315            skipBytes(is, 4 * 3);
316
317            final int deviceManufacturer = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder());
318            if (LOGGER.isLoggable(Level.FINEST)) {
319                printCharQuad("DeviceManufacturer", deviceManufacturer);
320            }
321
322            final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder());
323            if (LOGGER.isLoggable(Level.FINEST)) {
324                printCharQuad("DeviceModel", deviceModel);
325            }
326
327            final boolean result = deviceManufacturer == IccConstants.IEC && deviceModel == IccConstants.sRGB;
328            return result;
329        }
330    }
331
332}