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.nio.ByteOrder; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031import org.apache.commons.imaging.ImagingException; 032import org.apache.commons.imaging.bytesource.ByteSource; 033import org.apache.commons.imaging.common.Allocator; 034import org.apache.commons.imaging.common.BinaryFileParser; 035import org.apache.commons.io.IOUtils; 036 037public class IccProfileParser extends BinaryFileParser { 038 039 private static final Logger LOGGER = Logger.getLogger(IccProfileParser.class.getName()); 040 041 public IccProfileParser() { 042 super(ByteOrder.BIG_ENDIAN); 043 } 044 045 public IccProfileInfo getIccProfileInfo(final byte[] bytes) throws IOException { 046 if (bytes == null) { 047 return null; 048 } 049 return getIccProfileInfo(ByteSource.array(bytes)); 050 } 051 052 public IccProfileInfo getIccProfileInfo(final ByteSource byteSource) throws IOException { 053 // TODO Throw instead of logging? 054 final IccProfileInfo result; 055 try (InputStream is = byteSource.getInputStream()) { 056 result = readIccProfileInfo(is); 057 } 058 // 059 for (final IccTag tag : result.getTags()) { 060 final byte[] bytes = byteSource.getByteArray(tag.offset, tag.length); 061 // Debug.debug("bytes: " + bytes.length); 062 tag.setData(bytes); 063 // tag.dump("\t" + i + ": "); 064 } 065 // result.fillInTagData(byteSource); 066 return result; 067 } 068 069 public IccProfileInfo getIccProfileInfo(final File file) throws IOException { 070 if (file == null) { 071 return null; 072 } 073 074 return getIccProfileInfo(ByteSource.file(file)); 075 } 076 077 public IccProfileInfo getIccProfileInfo(final ICC_Profile iccProfile) throws IOException { 078 if (iccProfile == null) { 079 return null; 080 } 081 082 return getIccProfileInfo(ByteSource.array(iccProfile.getData())); 083 } 084 085 private IccTagType getIccTagType(final int quad) { 086 for (final IccTagType iccTagType : IccTagTypes.values()) { 087 if (iccTagType.getSignature() == quad) { 088 return iccTagType; 089 } 090 } 091 092 return null; 093 } 094 095 public boolean isSrgb(final byte[] bytes) throws IOException { 096 return isSrgb(ByteSource.array(bytes)); 097 } 098 099 public boolean isSrgb(final ByteSource byteSource) throws IOException { 100 // setDebug(true); 101 102 // long length = byteSource.getLength(); 103 // 104 // if (LOGGER.isLoggable(Level.FINEST)) 105 // Debug.debug("length: " + length); 106 107 try (InputStream is = byteSource.getInputStream()) { 108 read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder()); 109 110 // if (length != ProfileSize) 111 // return null; 112 113 skipBytes(is, 4 * 5); 114 115 skipBytes(is, 12, "Not a Valid ICC Profile"); 116 117 skipBytes(is, 4 * 3); 118 119 final int deviceManufacturer = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder()); 120 if (LOGGER.isLoggable(Level.FINEST)) { 121 logCharQuad("DeviceManufacturer", deviceManufacturer); 122 } 123 124 final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder()); 125 if (LOGGER.isLoggable(Level.FINEST)) { 126 logCharQuad("DeviceModel", deviceModel); 127 } 128 129 return deviceManufacturer == IccConstants.IEC && deviceModel == IccConstants.sRGB; 130 } 131 } 132 133 public boolean isSrgb(final File file) throws IOException { 134 return isSrgb(ByteSource.file(file)); 135 } 136 137 public boolean isSrgb(final ICC_Profile iccProfile) throws IOException { 138 return isSrgb(ByteSource.array(iccProfile.getData())); 139 } 140 141 private IccProfileInfo readIccProfileInfo(InputStream is) throws IOException { 142 final CachingInputStream cis = new CachingInputStream(is); 143 is = cis; 144 145 // setDebug(true); 146 147 // if (LOGGER.isLoggable(Level.FINEST)) 148 // Debug.debug("length: " + length); 149 150 final int profileSize = read4Bytes("ProfileSize", is, "Not a Valid ICC Profile", getByteOrder()); 151 152 // if (length != ProfileSize) 153 // { 154 // // Debug.debug("Unexpected Length data expected: " + 155 // Integer.toHexString((int) length) 156 // // + ", encoded: " + Integer.toHexString(ProfileSize)); 157 // // Debug.debug("Unexpected Length data: " + length 158 // // + ", length: " + ProfileSize); 159 // // throw new Error("asd"); 160 // return null; 161 // } 162 163 final int cmmTypeSignature = read4Bytes("Signature", is, "Not a Valid ICC Profile", getByteOrder()); 164 if (LOGGER.isLoggable(Level.FINEST)) { 165 logCharQuad("CMMTypeSignature", cmmTypeSignature); 166 } 167 168 final int profileVersion = read4Bytes("ProfileVersion", is, "Not a Valid ICC Profile", getByteOrder()); 169 170 final int profileDeviceClassSignature = read4Bytes("ProfileDeviceClassSignature", is, "Not a Valid ICC Profile", getByteOrder()); 171 if (LOGGER.isLoggable(Level.FINEST)) { 172 logCharQuad("ProfileDeviceClassSignature", profileDeviceClassSignature); 173 } 174 175 final int colorSpace = read4Bytes("ColorSpace", is, "Not a Valid ICC Profile", getByteOrder()); 176 if (LOGGER.isLoggable(Level.FINEST)) { 177 logCharQuad("ColorSpace", colorSpace); 178 } 179 180 final int profileConnectionSpace = read4Bytes("ProfileConnectionSpace", is, "Not a Valid ICC Profile", getByteOrder()); 181 if (LOGGER.isLoggable(Level.FINEST)) { 182 logCharQuad("ProfileConnectionSpace", profileConnectionSpace); 183 } 184 185 skipBytes(is, 12, "Not a Valid ICC Profile"); 186 187 final int profileFileSignature = read4Bytes("ProfileFileSignature", is, "Not a Valid ICC Profile", getByteOrder()); 188 if (LOGGER.isLoggable(Level.FINEST)) { 189 logCharQuad("ProfileFileSignature", profileFileSignature); 190 } 191 192 final int primaryPlatformSignature = read4Bytes("PrimaryPlatformSignature", is, "Not a Valid ICC Profile", getByteOrder()); 193 if (LOGGER.isLoggable(Level.FINEST)) { 194 logCharQuad("PrimaryPlatformSignature", primaryPlatformSignature); 195 } 196 197 final int variousFlags = read4Bytes("VariousFlags", is, "Not a Valid ICC Profile", getByteOrder()); 198 if (LOGGER.isLoggable(Level.FINEST)) { 199 logCharQuad("VariousFlags", profileFileSignature); 200 } 201 202 final int deviceManufacturer = read4Bytes("DeviceManufacturer", is, "Not a Valid ICC Profile", getByteOrder()); 203 if (LOGGER.isLoggable(Level.FINEST)) { 204 logCharQuad("DeviceManufacturer", deviceManufacturer); 205 } 206 207 final int deviceModel = read4Bytes("DeviceModel", is, "Not a Valid ICC Profile", getByteOrder()); 208 if (LOGGER.isLoggable(Level.FINEST)) { 209 logCharQuad("DeviceModel", deviceModel); 210 } 211 212 skipBytes(is, 8, "Not a Valid ICC Profile"); 213 214 final int renderingIntent = read4Bytes("RenderingIntent", is, "Not a Valid ICC Profile", getByteOrder()); 215 if (LOGGER.isLoggable(Level.FINEST)) { 216 logCharQuad("RenderingIntent", renderingIntent); 217 } 218 219 skipBytes(is, 12, "Not a Valid ICC Profile"); 220 221 final int profileCreatorSignature = read4Bytes("ProfileCreatorSignature", is, "Not a Valid ICC Profile", getByteOrder()); 222 if (LOGGER.isLoggable(Level.FINEST)) { 223 logCharQuad("ProfileCreatorSignature", profileCreatorSignature); 224 } 225 226 skipBytes(is, 16, "Not a Valid ICC Profile"); 227 // readByteArray("ProfileID", 16, is, 228 // "Not a Valid ICC Profile"); 229 // if (LOGGER.isLoggable(Level.FINEST)) 230 // System.out 231 // .println("ProfileID: '" + new String(ProfileID) + "'"); 232 233 skipBytes(is, 28, "Not a Valid ICC Profile"); 234 235 // this.setDebug(true); 236 237 final int tagCount = read4Bytes("TagCount", is, "Not a Valid ICC Profile", getByteOrder()); 238 239 // List tags = new ArrayList(); 240 final IccTag[] tags = Allocator.array(tagCount, IccTag[]::new, IccTag.SHALLOW_SIZE); 241 242 for (int i = 0; i < tagCount; i++) { 243 final int tagSignature = read4Bytes("TagSignature[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder()); 244 // Debug.debug("TagSignature t " 245 // + Integer.toHexString(TagSignature)); 246 247 // this.printCharQuad("TagSignature", TagSignature); 248 final int offsetToData = read4Bytes("OffsetToData[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder()); 249 final int elementSize = read4Bytes("ElementSize[" + i + "]", is, "Not a Valid ICC Profile", getByteOrder()); 250 251 final IccTagType fIccTagType = getIccTagType(tagSignature); 252 // if (fIccTagType == null) 253 // throw new Error("oops."); 254 255 // System.out 256 // .println("\t[" 257 // + i 258 // + "]: " 259 // + ((fIccTagType == null) 260 // ? "unknown" 261 // : fIccTagType.name)); 262 // Debug.debug(); 263 264 final IccTag tag = new IccTag(tagSignature, offsetToData, elementSize, fIccTagType); 265 // tag.dump("\t" + i + ": "); 266 tags[i] = tag; 267 // tags .add(tag); 268 } 269 270 // read stream to end, filling cache. 271 IOUtils.consume(is); 272 273 final byte[] data = cis.getCache(); 274 275 if (data.length < profileSize) { 276 throw new ImagingException("Couldn't read ICC Profile."); 277 } 278 279 final IccProfileInfo result = new IccProfileInfo(data, profileSize, cmmTypeSignature, profileVersion, profileDeviceClassSignature, colorSpace, 280 profileConnectionSpace, profileFileSignature, primaryPlatformSignature, variousFlags, deviceManufacturer, deviceModel, renderingIntent, 281 profileCreatorSignature, null, tags); 282 283 if (LOGGER.isLoggable(Level.FINEST)) { 284 LOGGER.finest("issRGB: " + result.isSrgb()); 285 } 286 287 return result; 288 } 289 290}