    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}