com.drew.metadata.photoshop.PhotoshopReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metadata-extractor Show documentation
Show all versions of metadata-extractor Show documentation
Java library for extracting EXIF, IPTC, XMP, ICC and other metadata from image and video files.
/*
* Copyright 2002-2015 Drew Noakes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.photoshop;
import com.drew.imaging.ImageProcessingException;
import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
import com.drew.imaging.jpeg.JpegSegmentType;
import com.drew.lang.*;
import com.drew.lang.annotations.NotNull;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.icc.IccReader;
import com.drew.metadata.iptc.IptcReader;
import com.drew.metadata.xmp.XmpReader;
import java.util.Arrays;
/**
* Reads metadata created by Photoshop and stored in the APPD segment of JPEG files.
* Note that IPTC data may be stored within this segment, in which case this reader will
* create both a {@link PhotoshopDirectory} and a {@link com.drew.metadata.iptc.IptcDirectory}.
*
* @author Yuri Binev
* @author Drew Noakes https://drewnoakes.com
*/
public class PhotoshopReader implements JpegSegmentMetadataReader
{
@NotNull
private static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0";
@NotNull
public Iterable getSegmentTypes()
{
return Arrays.asList(JpegSegmentType.APPD);
}
public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
{
final int preambleLength = JPEG_SEGMENT_PREAMBLE.length();
for (byte[] segmentBytes : segments) {
// Ensure data starts with the necessary preamble
if (segmentBytes.length < preambleLength + 1 || !JPEG_SEGMENT_PREAMBLE.equals(new String(segmentBytes, 0, preambleLength)))
continue;
extract(
new SequentialByteArrayReader(segmentBytes, preambleLength + 1),
segmentBytes.length - preambleLength - 1,
metadata);
}
}
public void extract(@NotNull final SequentialReader reader, int length, @NotNull final Metadata metadata)
{
PhotoshopDirectory directory = new PhotoshopDirectory();
metadata.addDirectory(directory);
// Data contains a sequence of Image Resource Blocks (IRBs):
//
// 4 bytes - Signature "8BIM"
// 2 bytes - Resource identifier
// String - Pascal string, padded to make length even
// 4 bytes - Size of resource data which follows
// Data - The resource data, padded to make size even
//
// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
int pos = 0;
while (pos < length) {
try {
// 4 bytes for the signature. Should always be "8BIM".
String signature = reader.getString(4);
if (!signature.equals("8BIM"))
throw new ImageProcessingException("Expecting 8BIM marker");
pos += 4;
// 2 bytes for the resource identifier (tag type).
int tagType = reader.getUInt16(); // segment type
pos += 2;
// A variable number of bytes holding a pascal string (two leading bytes for length).
short descriptionLength = reader.getUInt8();
pos += 1;
// Some basic bounds checking
if (descriptionLength < 0 || descriptionLength + pos > length)
throw new ImageProcessingException("Invalid string length");
// We don't use the string value here
reader.skip(descriptionLength);
pos += descriptionLength;
// The number of bytes is padded with a trailing zero, if needed, to make the size even.
if (pos % 2 != 0) {
reader.skip(1);
pos++;
}
// 4 bytes for the size of the resource data that follows.
int byteCount = reader.getInt32();
pos += 4;
// The resource data.
byte[] tagBytes = reader.getBytes(byteCount);
pos += byteCount;
// The number of bytes is padded with a trailing zero, if needed, to make the size even.
if (pos % 2 != 0) {
reader.skip(1);
pos++;
}
if (tagType == PhotoshopDirectory.TAG_IPTC)
new IptcReader().extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.length);
else if (tagType == PhotoshopDirectory.TAG_ICC_PROFILE_BYTES)
new IccReader().extract(new ByteArrayReader(tagBytes), metadata);
else if (tagType == PhotoshopDirectory.TAG_EXIF_DATA_1 || tagType == PhotoshopDirectory.TAG_EXIF_DATA_3)
new ExifReader().extract(new ByteArrayReader(tagBytes), metadata);
else if (tagType == PhotoshopDirectory.TAG_XMP_DATA)
new XmpReader().extract(tagBytes, metadata);
else
directory.setByteArray(tagType, tagBytes);
if (tagType >= 0x0fa0 && tagType <= 0x1387)
PhotoshopDirectory._tagNameMap.put(tagType, String.format("Plug-in %d Data", tagType - 0x0fa0 + 1));
} catch (Exception ex) {
directory.addError(ex.getMessage());
return;
}
}
}
}