source: josm/trunk/src/com/drew/metadata/icc/IccDescriptor.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: 13.4 KB
Line 
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 */
21
22package com.drew.metadata.icc;
23
24import com.drew.lang.ByteArrayReader;
25import com.drew.lang.RandomAccessReader;
26import com.drew.lang.annotations.NotNull;
27import com.drew.lang.annotations.Nullable;
28import com.drew.metadata.TagDescriptor;
29
30import java.io.IOException;
31import java.io.UnsupportedEncodingException;
32import java.text.DecimalFormat;
33
34import static com.drew.metadata.icc.IccDirectory.*;
35
36/**
37 * @author Yuri Binev
38 * @author Drew Noakes https://drewnoakes.com
39 */
40@SuppressWarnings("WeakerAccess")
41public class IccDescriptor extends TagDescriptor<IccDirectory>
42{
43 public IccDescriptor(@NotNull IccDirectory directory)
44 {
45 super(directory);
46 }
47
48 @Override
49 public String getDescription(int tagType)
50 {
51 switch (tagType) {
52 case TAG_PROFILE_VERSION:
53 return getProfileVersionDescription();
54 case TAG_PROFILE_CLASS:
55 return getProfileClassDescription();
56 case TAG_PLATFORM:
57 return getPlatformDescription();
58 case TAG_RENDERING_INTENT:
59 return getRenderingIntentDescription();
60 }
61
62 if (tagType > 0x20202020 && tagType < 0x7a7a7a7a)
63 return getTagDataString(tagType);
64
65 return super.getDescription(tagType);
66 }
67
68 private static final int ICC_TAG_TYPE_TEXT = 0x74657874;
69 private static final int ICC_TAG_TYPE_DESC = 0x64657363;
70 private static final int ICC_TAG_TYPE_SIG = 0x73696720;
71 private static final int ICC_TAG_TYPE_MEAS = 0x6D656173;
72 private static final int ICC_TAG_TYPE_XYZ_ARRAY = 0x58595A20;
73 private static final int ICC_TAG_TYPE_MLUC = 0x6d6c7563;
74 private static final int ICC_TAG_TYPE_CURV = 0x63757276;
75
76 @Nullable
77 private String getTagDataString(int tagType)
78 {
79 try {
80 byte[] bytes = _directory.getByteArray(tagType);
81 if (bytes == null)
82 return _directory.getString(tagType);
83 RandomAccessReader reader = new ByteArrayReader(bytes);
84 int iccTagType = reader.getInt32(0);
85 switch (iccTagType) {
86 case ICC_TAG_TYPE_TEXT:
87 try {
88 return new String(bytes, 8, bytes.length - 8 - 1, "ASCII");
89 } catch (UnsupportedEncodingException ex) {
90 return new String(bytes, 8, bytes.length - 8 - 1);
91 }
92 case ICC_TAG_TYPE_DESC:
93 int stringLength = reader.getInt32(8);
94 return new String(bytes, 12, stringLength - 1);
95 case ICC_TAG_TYPE_SIG:
96 return IccReader.getStringFromInt32(reader.getInt32(8));
97 case ICC_TAG_TYPE_MEAS: {
98 int observerType = reader.getInt32(8);
99 float x = reader.getS15Fixed16(12);
100 float y = reader.getS15Fixed16(16);
101 float z = reader.getS15Fixed16(20);
102 int geometryType = reader.getInt32(24);
103 float flare = reader.getS15Fixed16(28);
104 int illuminantType = reader.getInt32(32);
105 String observerString;
106 switch (observerType) {
107 case 0:
108 observerString = "Unknown";
109 break;
110 case 1:
111 observerString = "1931 2\u00B0";
112 break;
113 case 2:
114 observerString = "1964 10\u00B0";
115 break;
116 default:
117 observerString = String.format("Unknown %d", observerType);
118 }
119 String geometryString;
120 switch (geometryType) {
121 case 0:
122 geometryString = "Unknown";
123 break;
124 case 1:
125 geometryString = "0/45 or 45/0";
126 break;
127 case 2:
128 geometryString = "0/d or d/0";
129 break;
130 default:
131 geometryString = String.format("Unknown %d", observerType);
132 }
133 String illuminantString;
134 switch (illuminantType) {
135 case 0:
136 illuminantString = "unknown";
137 break;
138 case 1:
139 illuminantString = "D50";
140 break;
141 case 2:
142 illuminantString = "D65";
143 break;
144 case 3:
145 illuminantString = "D93";
146 break;
147 case 4:
148 illuminantString = "F2";
149 break;
150 case 5:
151 illuminantString = "D55";
152 break;
153 case 6:
154 illuminantString = "A";
155 break;
156 case 7:
157 illuminantString = "Equi-Power (E)";
158 break;
159 case 8:
160 illuminantString = "F8";
161 break;
162 default:
163 illuminantString = String.format("Unknown %d", illuminantType);
164 break;
165 }
166 DecimalFormat format = new DecimalFormat("0.###");
167 return String.format("%s Observer, Backing (%s, %s, %s), Geometry %s, Flare %d%%, Illuminant %s",
168 observerString, format.format(x), format.format(y), format.format(z), geometryString, Math.round(flare * 100), illuminantString);
169 }
170 case ICC_TAG_TYPE_XYZ_ARRAY: {
171 StringBuilder res = new StringBuilder();
172 DecimalFormat format = new DecimalFormat("0.####");
173 int count = (bytes.length - 8) / 12;
174 for (int i = 0; i < count; i++) {
175 float x = reader.getS15Fixed16(8 + i * 12);
176 float y = reader.getS15Fixed16(8 + i * 12 + 4);
177 float z = reader.getS15Fixed16(8 + i * 12 + 8);
178 if (i > 0)
179 res.append(", ");
180 res.append("(").append(format.format(x)).append(", ").append(format.format(y)).append(", ").append(format.format(z)).append(")");
181 }
182 return res.toString();
183 }
184 case ICC_TAG_TYPE_MLUC: {
185 int int1 = reader.getInt32(8);
186 StringBuilder res = new StringBuilder();
187 res.append(int1);
188 //int int2 = reader.getInt32(12);
189 //System.err.format("int1: %d, int2: %d\n", int1, int2);
190 for (int i = 0; i < int1; i++) {
191 String str = IccReader.getStringFromInt32(reader.getInt32(16 + i * 12));
192 int len = reader.getInt32(16 + i * 12 + 4);
193 int ofs = reader.getInt32(16 + i * 12 + 8);
194 String name;
195 try {
196 name = new String(bytes, ofs, len, "UTF-16BE");
197 } catch (UnsupportedEncodingException ex) {
198 name = new String(bytes, ofs, len);
199 }
200 res.append(" ").append(str).append("(").append(name).append(")");
201 //System.err.format("% 3d: %s, len: %d, ofs: %d, \"%s\"\n", i, str, len,ofs,name);
202 }
203 return res.toString();
204 }
205 case ICC_TAG_TYPE_CURV: {
206 int num = reader.getInt32(8);
207 StringBuilder res = new StringBuilder();
208 for (int i = 0; i < num; i++) {
209 if (i != 0)
210 res.append(", ");
211 res.append(formatDoubleAsString(((float)reader.getUInt16(12 + i * 2)) / 65535.0, 7, false));
212 //res+=String.format("%1.7g",Math.round(((float)iccReader.getInt16(b,12+i*2))/0.065535)/1E7);
213 }
214 return res.toString();
215 }
216 default:
217 return String.format("%s (0x%08X): %d bytes", IccReader.getStringFromInt32(iccTagType), iccTagType, bytes.length);
218 }
219 } catch (IOException e) {
220 // TODO decode these values during IccReader.extract so we can report any errors at that time
221 // It is convention to return null if a description cannot be formulated.
222 // If an error is to be reported, it should be done during the extraction process.
223 return null;
224 }
225 }
226
227 @NotNull
228 public static String formatDoubleAsString(double value, int precision, boolean zeroes)
229 {
230 if (precision < 1)
231 return "" + Math.round(value);
232 long intPart = Math.abs((long)value);
233 long rest = (int)Math.round((Math.abs(value) - intPart) * Math.pow(10, precision));
234 long restKept = rest;
235 String res = "";
236 byte cour;
237 for (int i = precision; i > 0; i--) {
238 cour = (byte)(Math.abs(rest % 10));
239 rest /= 10;
240 if (res.length() > 0 || zeroes || cour != 0 || i == 1)
241 res = cour + res;
242 }
243 intPart += rest;
244 boolean isNegative = ((value < 0) && (intPart != 0 || restKept != 0));
245 return (isNegative ? "-" : "") + intPart + "." + res;
246 }
247
248 @Nullable
249 private String getRenderingIntentDescription()
250 {
251 return getIndexedDescription(TAG_RENDERING_INTENT,
252 "Perceptual",
253 "Media-Relative Colorimetric",
254 "Saturation",
255 "ICC-Absolute Colorimetric");
256 }
257
258 @Nullable
259 private String getPlatformDescription()
260 {
261 String str = _directory.getString(TAG_PLATFORM);
262 if (str==null)
263 return null;
264 // Because Java doesn't allow switching on string values, create an integer from the first four chars
265 // and switch on that instead.
266 int i;
267 try {
268 i = getInt32FromString(str);
269 } catch (IOException e) {
270 return str;
271 }
272 switch (i) {
273 case 0x4150504C: // "APPL"
274 return "Apple Computer, Inc.";
275 case 0x4D534654: // "MSFT"
276 return "Microsoft Corporation";
277 case 0x53474920:
278 return "Silicon Graphics, Inc.";
279 case 0x53554E57:
280 return "Sun Microsystems, Inc.";
281 case 0x54474E54:
282 return "Taligent, Inc.";
283 default:
284 return String.format("Unknown (%s)", str);
285 }
286 }
287
288 @Nullable
289 private String getProfileClassDescription()
290 {
291 String str = _directory.getString(TAG_PROFILE_CLASS);
292 if (str==null)
293 return null;
294 // Because Java doesn't allow switching on string values, create an integer from the first four chars
295 // and switch on that instead.
296 int i;
297 try {
298 i = getInt32FromString(str);
299 } catch (IOException e) {
300 return str;
301 }
302 switch (i) {
303 case 0x73636E72:
304 return "Input Device";
305 case 0x6D6E7472: // mntr
306 return "Display Device";
307 case 0x70727472:
308 return "Output Device";
309 case 0x6C696E6B:
310 return "DeviceLink";
311 case 0x73706163:
312 return "ColorSpace Conversion";
313 case 0x61627374:
314 return "Abstract";
315 case 0x6E6D636C:
316 return "Named Color";
317 default:
318 return String.format("Unknown (%s)", str);
319 }
320 }
321
322 @Nullable
323 private String getProfileVersionDescription()
324 {
325 Integer value = _directory.getInteger(TAG_PROFILE_VERSION);
326
327 if (value == null)
328 return null;
329
330 int m = (value & 0xFF000000) >> 24;
331 int r = (value & 0x00F00000) >> 20;
332 int R = (value & 0x000F0000) >> 16;
333
334 return String.format("%d.%d.%d", m, r, R);
335 }
336
337 private static int getInt32FromString(@NotNull String string) throws IOException
338 {
339 byte[] bytes = string.getBytes();
340 return new ByteArrayReader(bytes).getInt32(0);
341 }
342}
Note: See TracBrowser for help on using the repository browser.