source: josm/trunk/src/com/drew/metadata/icc/IccReader.java@ 15218

Last change on this file since 15218 was 15218, checked in by Don-vip, 7 years ago

see #17848 - add ICC/Photoshop metadata support, otherwise IPTC does not work

  • Property svn:eol-style set to native
File size: 8.6 KB
RevLine 
[15218]1/*
2 * Copyright 2002-2019 Drew Noakes and contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * More information about this project is available at:
17 *
18 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/metadata-extractor
20 */
21package com.drew.metadata.icc;
22
23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
24import com.drew.imaging.jpeg.JpegSegmentType;
25import com.drew.lang.ByteArrayReader;
26import com.drew.lang.DateUtil;
27import com.drew.lang.RandomAccessReader;
28import com.drew.lang.annotations.NotNull;
29import com.drew.lang.annotations.Nullable;
30import com.drew.metadata.Directory;
31import com.drew.metadata.Metadata;
32import com.drew.metadata.MetadataReader;
33
34import java.io.IOException;
35import java.util.Collections;
36
37/**
38 * Reads an ICC profile.
39 * <p>
40 * More information about ICC:
41 * <ul>
42 * <li>http://en.wikipedia.org/wiki/ICC_profile</li>
43 * <li>http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/ICC_Profile.html</li>
44 * <li>https://developer.apple.com/library/mac/samplecode/ImageApp/Listings/ICC_h.html</li>
45 * </ul>
46 *
47 * @author Yuri Binev
48 * @author Drew Noakes https://drewnoakes.com
49 */
50public class IccReader implements JpegSegmentMetadataReader, MetadataReader
51{
52 public static final String JPEG_SEGMENT_PREAMBLE = "ICC_PROFILE";
53
54 @NotNull
55 public Iterable<JpegSegmentType> getSegmentTypes()
56 {
57 return Collections.singletonList(JpegSegmentType.APP2);
58 }
59
60 public void readJpegSegments(@NotNull Iterable<byte[]> segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
61 {
62 final int preambleLength = JPEG_SEGMENT_PREAMBLE.length();
63
64 // ICC data can be spread across multiple JPEG segments.
65 // We concat them together in this buffer for later processing.
66 byte[] buffer = null;
67
68 for (byte[] segmentBytes : segments) {
69 // Skip any segments that do not contain the required preamble
70 if (segmentBytes.length < preambleLength || !JPEG_SEGMENT_PREAMBLE.equalsIgnoreCase(new String(segmentBytes, 0, preambleLength)))
71 continue;
72
73 // NOTE we ignore three bytes here -- are they useful for anything?
74
75 // Grow the buffer
76 if (buffer == null) {
77 buffer = new byte[segmentBytes.length - 14];
78 // skip the first 14 bytes
79 System.arraycopy(segmentBytes, 14, buffer, 0, segmentBytes.length - 14);
80 } else {
81 byte[] newBuffer = new byte[buffer.length + segmentBytes.length - 14];
82 System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
83 System.arraycopy(segmentBytes, 14, newBuffer, buffer.length, segmentBytes.length - 14);
84 buffer = newBuffer;
85 }
86 }
87
88 if (buffer != null)
89 extract(new ByteArrayReader(buffer), metadata);
90 }
91
92 public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata)
93 {
94 extract(reader, metadata, null);
95 }
96
97 public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory)
98 {
99 // TODO review whether the 'tagPtr' values below really do require RandomAccessReader or whether SequentialReader may be used instead
100
101 IccDirectory directory = new IccDirectory();
102
103 if (parentDirectory != null)
104 directory.setParent(parentDirectory);
105
106 try {
107 int profileByteCount = reader.getInt32(IccDirectory.TAG_PROFILE_BYTE_COUNT);
108 directory.setInt(IccDirectory.TAG_PROFILE_BYTE_COUNT, profileByteCount);
109
110 // For these tags, the int value of the tag is in fact it's offset within the buffer.
111 set4ByteString(directory, IccDirectory.TAG_CMM_TYPE, reader);
112 setInt32(directory, IccDirectory.TAG_PROFILE_VERSION, reader);
113 set4ByteString(directory, IccDirectory.TAG_PROFILE_CLASS, reader);
114 set4ByteString(directory, IccDirectory.TAG_COLOR_SPACE, reader);
115 set4ByteString(directory, IccDirectory.TAG_PROFILE_CONNECTION_SPACE, reader);
116 setDate(directory, IccDirectory.TAG_PROFILE_DATETIME, reader);
117 set4ByteString(directory, IccDirectory.TAG_SIGNATURE, reader);
118 set4ByteString(directory, IccDirectory.TAG_PLATFORM, reader);
119 setInt32(directory, IccDirectory.TAG_CMM_FLAGS, reader);
120 set4ByteString(directory, IccDirectory.TAG_DEVICE_MAKE, reader);
121
122 int temp = reader.getInt32(IccDirectory.TAG_DEVICE_MODEL);
123 if (temp != 0) {
124 if (temp <= 0x20202020) {
125 directory.setInt(IccDirectory.TAG_DEVICE_MODEL, temp);
126 } else {
127 directory.setString(IccDirectory.TAG_DEVICE_MODEL, getStringFromInt32(temp));
128 }
129 }
130
131 setInt32(directory, IccDirectory.TAG_RENDERING_INTENT, reader);
132 setInt64(directory, IccDirectory.TAG_DEVICE_ATTR, reader);
133
134 float[] xyz = new float[]{
135 reader.getS15Fixed16(IccDirectory.TAG_XYZ_VALUES),
136 reader.getS15Fixed16(IccDirectory.TAG_XYZ_VALUES + 4),
137 reader.getS15Fixed16(IccDirectory.TAG_XYZ_VALUES + 8)
138 };
139 directory.setObject(IccDirectory.TAG_XYZ_VALUES, xyz);
140
141 // Process 'ICC tags'
142 int tagCount = reader.getInt32(IccDirectory.TAG_TAG_COUNT);
143 directory.setInt(IccDirectory.TAG_TAG_COUNT, tagCount);
144
145 for (int i = 0; i < tagCount; i++) {
146 int pos = IccDirectory.TAG_TAG_COUNT + 4 + i * 12;
147 int tagType = reader.getInt32(pos);
148 int tagPtr = reader.getInt32(pos + 4);
149 int tagLen = reader.getInt32(pos + 8);
150 byte[] b = reader.getBytes(tagPtr, tagLen);
151 directory.setByteArray(tagType, b);
152 }
153 } catch (IOException ex) {
154 directory.addError("Exception reading ICC profile: " + ex.getMessage());
155 }
156
157 metadata.addDirectory(directory);
158 }
159
160 private void set4ByteString(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException
161 {
162 int i = reader.getInt32(tagType);
163 if (i != 0)
164 directory.setString(tagType, getStringFromInt32(i));
165 }
166
167 private void setInt32(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException
168 {
169 int i = reader.getInt32(tagType);
170 if (i != 0)
171 directory.setInt(tagType, i);
172 }
173
174 @SuppressWarnings({"SameParameterValue"})
175 private void setInt64(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException
176 {
177 long l = reader.getInt64(tagType);
178 if (l != 0)
179 directory.setLong(tagType, l);
180 }
181
182 @SuppressWarnings({"SameParameterValue", "MagicConstant"})
183 private void setDate(@NotNull final IccDirectory directory, final int tagType, @NotNull RandomAccessReader reader) throws IOException
184 {
185 final int y = reader.getUInt16(tagType);
186 final int m = reader.getUInt16(tagType + 2);
187 final int d = reader.getUInt16(tagType + 4);
188 final int h = reader.getUInt16(tagType + 6);
189 final int M = reader.getUInt16(tagType + 8);
190 final int s = reader.getUInt16(tagType + 10);
191
192 if (DateUtil.isValidDate(y, m - 1, d) && DateUtil.isValidTime(h, M, s))
193 {
194 String dateString = String.format("%04d:%02d:%02d %02d:%02d:%02d", y, m, d, h, M, s);
195 directory.setString(tagType, dateString);
196 }
197 else
198 {
199 directory.addError(String.format(
200 "ICC data describes an invalid date/time: year=%d month=%d day=%d hour=%d minute=%d second=%d",
201 y, m, d, h, M, s));
202 }
203 }
204
205 @NotNull
206 public static String getStringFromInt32(int d)
207 {
208 // MSB
209 byte[] b = new byte[] {
210 (byte) ((d & 0xFF000000) >> 24),
211 (byte) ((d & 0x00FF0000) >> 16),
212 (byte) ((d & 0x0000FF00) >> 8),
213 (byte) ((d & 0x000000FF))
214 };
215 return new String(b);
216 }
217}
Note: See TracBrowser for help on using the repository browser.