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