    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}