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}