source: josm/trunk/src/com/drew/metadata/photoshop/PhotoshopDescriptor.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: 17.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 */
21package com.drew.metadata.photoshop;
22
23import com.drew.lang.ByteArrayReader;
24import com.drew.lang.Charsets;
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.text.DecimalFormat;
32import java.util.ArrayList;
33
34import static com.drew.metadata.photoshop.PhotoshopDirectory.*;
35
36/**
37 * @author Drew Noakes https://drewnoakes.com
38 * @author Yuri Binev
39 * @author Payton Garland
40 */
41@SuppressWarnings("WeakerAccess")
42public class PhotoshopDescriptor extends TagDescriptor<PhotoshopDirectory>
43{
44 public PhotoshopDescriptor(@NotNull PhotoshopDirectory directory)
45 {
46 super(directory);
47 }
48
49 @Override
50 public String getDescription(int tagType)
51 {
52 switch (tagType) {
53 case TAG_THUMBNAIL:
54 case TAG_THUMBNAIL_OLD:
55 return getThumbnailDescription(tagType);
56 case TAG_URL:
57 case TAG_XML:
58 return getSimpleString(tagType);
59 case TAG_IPTC:
60 return getBinaryDataString(tagType);
61 case TAG_SLICES:
62 return getSlicesDescription();
63 case TAG_VERSION:
64 return getVersionDescription();
65 case TAG_COPYRIGHT:
66 return getBooleanString(tagType);
67 case TAG_RESOLUTION_INFO:
68 return getResolutionInfoDescription();
69 case TAG_GLOBAL_ANGLE:
70 case TAG_GLOBAL_ALTITUDE:
71 case TAG_URL_LIST:
72 case TAG_SEED_NUMBER:
73 return get32BitNumberString(tagType);
74 case TAG_JPEG_QUALITY:
75 return getJpegQualityString();
76 case TAG_PRINT_SCALE:
77 return getPrintScaleDescription();
78 case TAG_PIXEL_ASPECT_RATIO:
79 return getPixelAspectRatioString();
80 case TAG_CLIPPING_PATH_NAME:
81 return getClippingPathNameString(tagType);
82 default:
83 if (tagType >= 0x07D0 && tagType <= 0x0BB6)
84 return getPathString(tagType);
85 return super.getDescription(tagType);
86 }
87 }
88
89 @Nullable
90 public String getJpegQualityString()
91 {
92 try {
93 byte[] b = _directory.getByteArray(TAG_JPEG_QUALITY);
94
95 if (b == null)
96 return _directory.getString(TAG_JPEG_QUALITY);
97
98 RandomAccessReader reader = new ByteArrayReader(b);
99 int q = reader.getUInt16(0); // & 0xFFFF;
100 int f = reader.getUInt16(2); // & 0xFFFF;
101 int s = reader.getUInt16(4);
102
103 int q1 = q <= 0xFFFF && q >= 0xFFFD
104 ? q - 0xFFFC
105 : q <= 8
106 ? q + 4
107 : q;
108
109 String quality;
110 switch (q) {
111 case 0xFFFD:
112 case 0xFFFE:
113 case 0xFFFF:
114 case 0:
115 quality = "Low";
116 break;
117 case 1:
118 case 2:
119 case 3:
120 quality = "Medium";
121 break;
122 case 4:
123 case 5:
124 quality = "High";
125 break;
126 case 6:
127 case 7:
128 case 8:
129 quality = "Maximum";
130 break;
131 default:
132 quality = "Unknown";
133 }
134
135 String format;
136 switch (f) {
137 case 0x0000:
138 format = "Standard";
139 break;
140 case 0x0001:
141 format = "Optimised";
142 break;
143 case 0x0101:
144 format = "Progressive";
145 break;
146 default:
147 format = String.format("Unknown 0x%04X", f);
148 }
149
150 String scans = s >= 1 && s <= 3
151 ? String.format("%d", s + 2)
152 : String.format("Unknown 0x%04X", s);
153
154 return String.format("%d (%s), %s format, %s scans", q1, quality, format, scans);
155 } catch (IOException e) {
156 return null;
157 }
158 }
159
160 @Nullable
161 public String getPixelAspectRatioString()
162 {
163 try {
164 byte[] bytes = _directory.getByteArray(TAG_PIXEL_ASPECT_RATIO);
165 if (bytes == null)
166 return null;
167 RandomAccessReader reader = new ByteArrayReader(bytes);
168 double d = reader.getDouble64(4);
169 return Double.toString(d);
170 } catch (Exception e) {
171 return null;
172 }
173 }
174
175 @Nullable
176 public String getPrintScaleDescription()
177 {
178 try {
179 byte bytes[] = _directory.getByteArray(TAG_PRINT_SCALE);
180 if (bytes == null)
181 return null;
182 RandomAccessReader reader = new ByteArrayReader(bytes);
183 int style = reader.getInt32(0);
184 float locX = reader.getFloat32(2);
185 float locY = reader.getFloat32(6);
186 float scale = reader.getFloat32(10);
187 switch (style) {
188 case 0:
189 return "Centered, Scale " + scale;
190 case 1:
191 return "Size to fit";
192 case 2:
193 return String.format("User defined, X:%s Y:%s, Scale:%s", locX, locY, scale);
194 default:
195 return String.format("Unknown %04X, X:%s Y:%s, Scale:%s", style, locX, locY, scale);
196 }
197 } catch (Exception e) {
198 return null;
199 }
200 }
201
202 @Nullable
203 public String getResolutionInfoDescription()
204 {
205 try {
206 byte[] bytes = _directory.getByteArray(TAG_RESOLUTION_INFO);
207 if (bytes == null)
208 return null;
209 RandomAccessReader reader = new ByteArrayReader(bytes);
210 float resX = reader.getS15Fixed16(0);
211 float resY = reader.getS15Fixed16(8); // is this the correct offset? it's only reading 4 bytes each time
212 DecimalFormat format = new DecimalFormat("0.##");
213 return format.format(resX) + "x" + format.format(resY) + " DPI";
214 } catch (Exception e) {
215 return null;
216 }
217 }
218
219 @Nullable
220 public String getVersionDescription()
221 {
222 try {
223 final byte[] bytes = _directory.getByteArray(TAG_VERSION);
224 if (bytes == null)
225 return null;
226 RandomAccessReader reader = new ByteArrayReader(bytes);
227 int pos = 0;
228 int ver = reader.getInt32(0);
229 pos += 4;
230 pos++;
231 int readerLength = reader.getInt32(5);
232 pos += 4;
233 String readerStr = reader.getString(9, readerLength * 2, "UTF-16");
234 pos += readerLength * 2;
235 int writerLength = reader.getInt32(pos);
236 pos += 4;
237 String writerStr = reader.getString(pos, writerLength * 2, "UTF-16");
238 pos += writerLength * 2;
239 int fileVersion = reader.getInt32(pos);
240 return String.format("%d (%s, %s) %d", ver, readerStr, writerStr, fileVersion);
241 } catch (IOException e) {
242 return null;
243 }
244 }
245
246 @Nullable
247 public String getSlicesDescription()
248 {
249 try {
250 final byte bytes[] = _directory.getByteArray(TAG_SLICES);
251 if (bytes == null)
252 return null;
253 RandomAccessReader reader = new ByteArrayReader(bytes);
254 int nameLength = reader.getInt32(20);
255 String name = reader.getString(24, nameLength * 2, "UTF-16");
256 int pos = 24 + nameLength * 2;
257 int sliceCount = reader.getInt32(pos);
258 return String.format("%s (%d,%d,%d,%d) %d Slices",
259 name, reader.getInt32(4), reader.getInt32(8), reader.getInt32(12), reader.getInt32(16), sliceCount);
260 } catch (IOException e) {
261 return null;
262 }
263 }
264
265 @Nullable
266 public String getThumbnailDescription(int tagType)
267 {
268 try {
269 byte[] v = _directory.getByteArray(tagType);
270 if (v == null)
271 return null;
272 RandomAccessReader reader = new ByteArrayReader(v);
273 int format = reader.getInt32(0);
274 int width = reader.getInt32(4);
275 int height = reader.getInt32(8);
276 //skip WidthBytes
277 int totalSize = reader.getInt32(16);
278 int compSize = reader.getInt32(20);
279 int bpp = reader.getInt32(24);
280 //skip Number of planes
281 return String.format("%s, %dx%d, Decomp %d bytes, %d bpp, %d bytes",
282 format == 1 ? "JpegRGB" : "RawRGB",
283 width, height, totalSize, bpp, compSize);
284 } catch (IOException e) {
285 return null;
286 }
287 }
288
289 @Nullable
290 private String getBooleanString(int tag)
291 {
292 final byte[] bytes = _directory.getByteArray(tag);
293 if (bytes == null || bytes.length == 0)
294 return null;
295 return bytes[0] == 0 ? "No" : "Yes";
296 }
297
298 @Nullable
299 private String get32BitNumberString(int tag)
300 {
301 byte[] bytes = _directory.getByteArray(tag);
302 if (bytes == null)
303 return null;
304 RandomAccessReader reader = new ByteArrayReader(bytes);
305 try {
306 return String.format("%d", reader.getInt32(0));
307 } catch (IOException e) {
308 return null;
309 }
310 }
311
312 @Nullable
313 private String getSimpleString(int tagType)
314 {
315 final byte[] bytes = _directory.getByteArray(tagType);
316 if (bytes == null)
317 return null;
318 return new String(bytes);
319 }
320
321 @Nullable
322 private String getBinaryDataString(int tagType)
323 {
324 final byte[] bytes = _directory.getByteArray(tagType);
325 if (bytes == null)
326 return null;
327 return String.format("%d bytes binary data", bytes.length);
328 }
329
330 @Nullable
331 public String getClippingPathNameString(int tagType)
332 {
333 try {
334 byte[] bytes = _directory.getByteArray(tagType);
335 if (bytes == null)
336 return null;
337 RandomAccessReader reader = new ByteArrayReader(bytes);
338 int length = reader.getByte(0);
339 return new String(reader.getBytes(1, length), "UTF-8");
340 } catch (Exception e) {
341 return null;
342 }
343 }
344
345 @Nullable
346 public String getPathString(int tagType)
347 {
348 try {
349 byte[] bytes = _directory.getByteArray(tagType);
350 if (bytes == null)
351 return null;
352 RandomAccessReader reader = new ByteArrayReader(bytes);
353 int length = (int) (reader.getLength() - reader.getByte((int)reader.getLength() - 1) - 1) / 26;
354
355 String fillRecord = null;
356
357 // Possible subpaths
358 Subpath cSubpath = new Subpath();
359 Subpath oSubpath = new Subpath();
360
361 ArrayList<Subpath> paths = new ArrayList<Subpath>();
362
363 // Loop through each path resource block segment (26-bytes)
364 for (int i = 0; i < length; i++) {
365 // Spacer takes into account which block is currently being worked on while accessing byte array
366 int recordSpacer = 26 * i;
367 int selector = reader.getInt16(recordSpacer);
368
369 /*
370 * Subpath resource blocks come in 26-byte segments with 9 possible selectors - some selectors
371 * are formatted different from others
372 *
373 * 0 = Closed subpath length record
374 * 1 = Closed subpath Bezier knot, linked
375 * 2 = Closed subpath Bezier knot, unlinked
376 * 3 = Open subpath length record
377 * 4 = Open subpath Bezier knot, linked
378 * 5 = Open subpath Bezier knot, unlinked
379 * 6 = Subpath fill rule record
380 * 7 = Clipboard record
381 * 8 = Initial fill rule record
382 *
383 * Source: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
384 */
385 switch (selector) {
386 case 0:
387 // Insert previous Paths if there are any
388 if (cSubpath.size() != 0) {
389 paths.add(cSubpath);
390 }
391
392 // Make path size accordingly
393 cSubpath = new Subpath("Closed Subpath");
394 break;
395 case 1:
396 case 2:
397 {
398 Knot knot;
399 if (selector == 1)
400 knot = new Knot("Linked");
401 else
402 knot = new Knot("Unlinked");
403 // Insert each point into cSubpath - points are 32-bit signed, fixed point numbers and have 8-bits before the point
404 for (int j = 0; j < 6; j++) {
405 knot.setPoint(j, reader.getInt8((j * 4) + 2 + recordSpacer) + (reader.getInt24((j * 4) + 3 + recordSpacer) / Math.pow(2.0, 24.0)));
406 }
407 cSubpath.add(knot);
408 break;
409 }
410 case 3:
411 // Insert previous Paths if there are any
412 if (oSubpath.size() != 0) {
413 paths.add(oSubpath);
414 }
415
416 // Make path size accordingly
417 oSubpath = new Subpath("Open Subpath");
418 break;
419 case 4:
420 case 5:
421 {
422 Knot knot;
423 if (selector == 4)
424 knot = new Knot("Linked");
425 else
426 knot = new Knot("Unlinked");
427 // Insert each point into oSubpath - points are 32-bit signed, fixed point numbers and have 8-bits before the point
428 for (int j = 0; j < 6; j++) {
429 knot.setPoint(j, reader.getInt8((j * 4) + 2 + recordSpacer) + (reader.getInt24((j * 4) + 3 + recordSpacer) / Math.pow(2.0, 24.0)));
430 }
431 oSubpath.add(knot);
432 break;
433 }
434 case 6:
435 break;
436 case 7:
437 // TODO: Clipboard record
438// for (int j = 0; j < 24; j++) {
439// clipboardRecord[j] = bytes[j + 2 + recordSpacer];
440// }
441 break;
442 case 8:
443 if (reader.getInt16(2 + recordSpacer) == 1)
444 fillRecord = "with all pixels";
445 else
446 fillRecord = "without all pixels";
447 break;
448 }
449 }
450
451 // Add any more paths that were not added already
452 if (cSubpath.size() != 0)
453 paths.add(cSubpath);
454 if (oSubpath.size() != 0)
455 paths.add(oSubpath);
456
457 // Extract name (previously appended to end of byte array)
458 int nameLength = reader.getByte((int)reader.getLength() - 1);
459 String name = reader.getString((int)reader.getLength() - nameLength - 1, nameLength, Charsets.ASCII);
460
461 // Build description
462 StringBuilder str = new StringBuilder();
463
464 str.append('"').append(name).append('"')
465 .append(" having ");
466
467 if (fillRecord != null)
468 str.append("initial fill rule \"").append(fillRecord).append("\" and ");
469
470 str.append(paths.size()).append(paths.size() == 1 ? " subpath:" : " subpaths:");
471
472 for (Subpath path : paths) {
473 str.append("\n- ").append(path.getType()).append(" with ").append(paths.size()).append(paths.size() == 1 ? " knot:" : " knots:");
474
475 for (Knot knot : path.getKnots()) {
476 str.append("\n - ").append(knot.getType());
477 str.append(" (").append(knot.getPoint(0)).append(",").append(knot.getPoint(1)).append(")");
478 str.append(" (").append(knot.getPoint(2)).append(",").append(knot.getPoint(3)).append(")");
479 str.append(" (").append(knot.getPoint(4)).append(",").append(knot.getPoint(5)).append(")");
480 }
481 }
482
483 return str.toString();
484 } catch (Exception e) {
485 return null;
486 }
487 }
488}
Note: See TracBrowser for help on using the repository browser.