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.logCharQuad;
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.ImagingException;
032import org.apache.commons.imaging.bytesource.ByteSource;
033import org.apache.commons.imaging.common.Allocator;
034import org.apache.commons.imaging.common.BinaryFileParser;
035import org.apache.commons.io.IOUtils;
036
037public class IccProfileParser extends BinaryFileParser {
038
039    private static final Logger LOGGER = Logger.getLogger(IccProfileParser.class.getName());
040
041    public IccProfileParser() {
042        super(ByteOrder.BIG_ENDIAN);
043    }
044
045    public IccProfileInfo getIccProfileInfo(final byte[] bytes) throws IOException {
046        if (bytes == null) {
047            return null;
048        }
049        return getIccProfileInfo(ByteSource.array(bytes));
050    }
051
052    public IccProfileInfo getIccProfileInfo(final ByteSource byteSource) throws IOException {
053        // TODO Throw instead of logging?
054        final IccProfileInfo result;
055        try (InputStream is = byteSource.getInputStream()) {
056            result = readIccProfileInfo(is);
057        }
058        //
059        for (final IccTag tag : result.getTags()) {
060            final byte[] bytes = byteSource.getByteArray(tag.offset, tag.length);
061            // Debug.debug("bytes: " + bytes.length);
062            tag.setData(bytes);
063            // tag.dump("\t" + i + ": ");
064        }
065        // result.fillInTagData(byteSource);
066        return result;
067    }
068
069    public IccProfileInfo getIccProfileInfo(final File file) throws IOException {
070        if (file == null) {
071            return null;
072        }
073
074        return getIccProfileInfo(ByteSource.file(file));
075    }
076
077    public IccProfileInfo getIccProfileInfo(final ICC_Profile iccProfile) throws IOException {
078        if (iccProfile == null) {
079            return null;
080        }
081
082        return getIccProfileInfo(ByteSource.array(iccProfile.getData()));
083    }
084
085    private IccTagType getIccTagType(final int quad) {
086        for (final IccTagType iccTagType : IccTagTypes.values()) {
087            if (iccTagType.getSignature() == quad) {
088                return iccTagType;
089            }
090        }
091
092        return null;
093    }
094
095    public boolean isSrgb(final byte[] bytes) throws IOException {
096        return isSrgb(ByteSource.array(bytes));
097    }
098
099    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}