    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}