source: josm/trunk/src/com/drew/metadata/photoshop/PhotoshopReader.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: 7.9 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 java.util.Arrays;
24import java.util.Collections;
25
26import com.drew.imaging.ImageProcessingException;
27import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
28import com.drew.imaging.jpeg.JpegSegmentType;
29import com.drew.lang.ByteArrayReader;
30import com.drew.lang.SequentialByteArrayReader;
31import com.drew.lang.SequentialReader;
32import com.drew.lang.annotations.NotNull;
33import com.drew.lang.annotations.Nullable;
34import com.drew.metadata.Directory;
35import com.drew.metadata.Metadata;
36import com.drew.metadata.exif.ExifReader;
37import com.drew.metadata.icc.IccReader;
38import com.drew.metadata.iptc.IptcReader;
39//import com.drew.metadata.xmp.XmpReader;
40
41/**
42 * Reads metadata created by Photoshop and stored in the APPD segment of JPEG files.
43 * Note that IPTC data may be stored within this segment, in which case this reader will
44 * create both a {@link PhotoshopDirectory} and a {@link com.drew.metadata.iptc.IptcDirectory}.
45 *
46 * @author Drew Noakes https://drewnoakes.com
47 * @author Yuri Binev
48 * @author Payton Garland
49 */
50public class PhotoshopReader implements JpegSegmentMetadataReader
51{
52 @NotNull
53 private static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0";
54
55 @NotNull
56 public Iterable<JpegSegmentType> getSegmentTypes()
57 {
58 return Collections.singletonList(JpegSegmentType.APPD);
59 }
60
61 public void readJpegSegments(@NotNull Iterable<byte[]> segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
62 {
63 final int preambleLength = JPEG_SEGMENT_PREAMBLE.length();
64
65 for (byte[] segmentBytes : segments) {
66 // Ensure data starts with the necessary preamble
67 if (segmentBytes.length < preambleLength + 1 || !JPEG_SEGMENT_PREAMBLE.equals(new String(segmentBytes, 0, preambleLength)))
68 continue;
69
70 extract(
71 new SequentialByteArrayReader(segmentBytes, preambleLength + 1),
72 segmentBytes.length - preambleLength - 1,
73 metadata);
74 }
75 }
76
77 public void extract(@NotNull final SequentialReader reader, int length, @NotNull final Metadata metadata)
78 {
79 extract(reader, length, metadata, null);
80 }
81
82 public void extract(@NotNull final SequentialReader reader, int length, @NotNull final Metadata metadata, @Nullable final Directory parentDirectory)
83 {
84 PhotoshopDirectory directory = new PhotoshopDirectory();
85 metadata.addDirectory(directory);
86
87 if (parentDirectory != null)
88 directory.setParent(parentDirectory);
89
90 // Data contains a sequence of Image Resource Blocks (IRBs):
91 //
92 // 4 bytes - Signature; mostly "8BIM" but "PHUT", "AgHg" and "DCSR" are also found
93 // 2 bytes - Resource identifier
94 // String - Pascal string, padded to make length even
95 // 4 bytes - Size of resource data which follows
96 // Data - The resource data, padded to make size even
97 //
98 // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
99
100 int pos = 0;
101 int clippingPathCount = 0;
102 while (pos < length) {
103 try {
104 // 4 bytes for the signature ("8BIM", "PHUT", etc.)
105 String signature = reader.getString(4);
106 pos += 4;
107
108 // 2 bytes for the resource identifier (tag type).
109 int tagType = reader.getUInt16(); // segment type
110 pos += 2;
111
112 // A variable number of bytes holding a pascal string (two leading bytes for length).
113 short descriptionLength = reader.getUInt8();
114 pos += 1;
115 // Some basic bounds checking
116 if (descriptionLength < 0 || descriptionLength + pos > length)
117 throw new ImageProcessingException("Invalid string length");
118
119 // Get name (important for paths)
120 StringBuilder description = new StringBuilder();
121 descriptionLength += pos;
122 // Loop through each byte and append to string
123 while (pos < descriptionLength) {
124 description.append((char)reader.getUInt8());
125 pos ++;
126 }
127
128 // The number of bytes is padded with a trailing zero, if needed, to make the size even.
129 if (pos % 2 != 0) {
130 reader.skip(1);
131 pos++;
132 }
133
134 // 4 bytes for the size of the resource data that follows.
135 int byteCount = reader.getInt32();
136 pos += 4;
137 // The resource data.
138 byte[] tagBytes = reader.getBytes(byteCount);
139 pos += byteCount;
140 // The number of bytes is padded with a trailing zero, if needed, to make the size even.
141 if (pos % 2 != 0) {
142 reader.skip(1);
143 pos++;
144 }
145
146 if (signature.equals("8BIM")) {
147 if (tagType == PhotoshopDirectory.TAG_IPTC)
148 new IptcReader().extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.length, directory);
149 else if (tagType == PhotoshopDirectory.TAG_ICC_PROFILE_BYTES)
150 new IccReader().extract(new ByteArrayReader(tagBytes), metadata, directory);
151 else if (tagType == PhotoshopDirectory.TAG_EXIF_DATA_1 || tagType == PhotoshopDirectory.TAG_EXIF_DATA_3)
152 new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, 0, directory);
153 //else if (tagType == PhotoshopDirectory.TAG_XMP_DATA)
154 // new XmpReader().extract(tagBytes, metadata, directory);
155 else if (tagType >= 0x07D0 && tagType <= 0x0BB6) {
156 clippingPathCount++;
157 tagBytes = Arrays.copyOf(tagBytes, tagBytes.length + description.length() + 1);
158 // Append description(name) to end of byte array with 1 byte before the description representing the length
159 for (int i = tagBytes.length - description.length() - 1; i < tagBytes.length; i++) {
160 if (i % (tagBytes.length - description.length() - 1 + description.length()) == 0)
161 tagBytes[i] = (byte)description.length();
162 else
163 tagBytes[i] = (byte)description.charAt(i - (tagBytes.length - description.length() - 1));
164 }
165 PhotoshopDirectory._tagNameMap.put(0x07CF + clippingPathCount, "Path Info " + clippingPathCount);
166 directory.setByteArray(0x07CF + clippingPathCount, tagBytes);
167 }
168 else
169 directory.setByteArray(tagType, tagBytes);
170
171 if (tagType >= 0x0fa0 && tagType <= 0x1387)
172 PhotoshopDirectory._tagNameMap.put(tagType, String.format("Plug-in %d Data", tagType - 0x0fa0 + 1));
173 }
174 } catch (Exception ex) {
175 directory.addError(ex.getMessage());
176 return;
177 }
178 }
179 }
180}
Note: See TracBrowser for help on using the repository browser.