diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java new file mode 100644 index 00000000..325fbdad --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java @@ -0,0 +1,15 @@ +package com.twelvemonkeys.imageio.metadata; + +import javax.imageio.stream.ImageOutputStream; +import java.io.IOException; + +/** + * MetadataWriter. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: MetadataWriter.java,v 1.0 28/05/15 harald.kuhr Exp$ + */ +public abstract class MetadataWriter { + abstract public boolean write(Directory directory, ImageOutputStream stream) throws IOException; +} diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index 381c0913..dfe40b72 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -71,6 +71,8 @@ final class EXIFEntry extends AbstractEntry { return "IPTC"; case TIFF.TAG_PHOTOSHOP: return "Adobe"; + case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA: + return "ImageSourceData"; case TIFF.TAG_ICC_PROFILE: return "ICCProfile"; @@ -189,7 +191,7 @@ final class EXIFEntry extends AbstractEntry { case EXIF.TAG_WHITE_BALANCE: return "WhiteBalance"; case EXIF.TAG_DIGITAL_ZOOM_RATIO: - return "DigitalZoomRation"; + return "DigitalZoomRatio"; case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM: return "FocalLengthIn35mmFilm"; case EXIF.TAG_SCENE_CAPTURE_TYPE: diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java index 1983ac04..558a8d63 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java @@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif; import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.MetadataWriter; import com.twelvemonkeys.lang.Validate; import javax.imageio.IIOException; @@ -48,7 +49,7 @@ import java.util.*; * @author last modified by $Author: haraldk$ * @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$ */ -public class EXIFWriter { +public final class EXIFWriter extends MetadataWriter { static final int WORD_LENGTH = 2; static final int LONGWORD_LENGTH = 4; @@ -58,6 +59,7 @@ public class EXIFWriter { return write(new IFD(entries), stream); } + @Override public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException { Validate.notNull(directory); Validate.notNull(stream); diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index d169e690..daea665f 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -184,6 +184,18 @@ public interface TIFF { */ int TAG_PHOTOSHOP = 34377; + /** + * Photoshop layer and mask information (byte order follows TIFF container). + * Layer and mask information found in a typical layered Photoshop file. + * Starts with a character string of "Adobe Photoshop Document Data Block" + * (or "Adobe Photoshop Document Data V0002" for PSB) + * including the null termination character. + * @see com.twelvemonkeys.imageio.metadata.psd.PSD + */ + int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724; + + int TAG_PHOTOSHOP_ANNOTATIONS = 50255; + /** * ICC Color Profile. * @see java.awt.color.ICC_Profile diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java index a149aa80..adee2645 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java @@ -36,110 +36,115 @@ package com.twelvemonkeys.imageio.metadata.iptc; * @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$ */ public interface IPTC { - static final int ENVELOPE_RECORD = 1 << 8; - static final int APPLICATION_RECORD = 2 << 8; + int ENVELOPE_RECORD = 1 << 8; + int APPLICATION_RECORD = 2 << 8; - static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; + /** 1:05: Destination */ + int TAG_DESTINATION = ENVELOPE_RECORD | 5; + /** 1:50: Product ID */ + int TAG_PRODUCT_ID = ENVELOPE_RECORD | 50; + /** 1:90: Coded Character Set */ + int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; /** 2:00 Record Version (mandatory) */ - public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 + int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 /** 2:03 Object Type Reference */ - public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; + int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; /** 2:04 Object Attribute Reference (repeatable) */ - public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; + int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; /** 2:05 Object Name */ - public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 + int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 /** 2:07 Edit Status */ - public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; + int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; /** 2:08 Editorial Update */ - public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; + int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; /** 2:10 Urgency */ - public static final int TAG_URGENCY = APPLICATION_RECORD | 10; + int TAG_URGENCY = APPLICATION_RECORD | 10; /** 2:12 Subect Reference (repeatable) */ - public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; + int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; /** 2:15 Category */ - public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f + int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f /** 2:20 Supplemental Category (repeatable) */ - public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; + int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; /** 2:22 Fixture Identifier */ - public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; + int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; /** 2:25 Keywords (repeatable) */ - public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25; + int TAG_KEYWORDS = APPLICATION_RECORD | 25; /** 2:26 Content Locataion Code (repeatable) */ - public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; + int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; /** 2:27 Content Locataion Name (repeatable) */ - public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; + int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; /** 2:30 Release Date */ - public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; + int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; /** 2:35 Release Time */ - public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; + int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; /** 2:37 Expiration Date */ - public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; + int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; /** 2:38 Expiration Time */ - public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; + int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; /** 2:40 Special Instructions */ - public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 + int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */ - public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; + int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */ - public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; + int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; /** 2:47 Reference Date (mandatory if 2:45 present) */ - public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; + int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; /** 2:50 Reference Number (mandatory if 2:45 present) */ - public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; + int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; /** 2:55 Date Created */ - public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 + int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 /** 2:60 Time Created */ - public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60; + int TAG_TIME_CREATED = APPLICATION_RECORD | 60; /** 2:62 Digital Creation Date */ - public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; + int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; /** 2:63 Digital Creation Date */ - public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; + int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; /** 2:65 Originating Program */ - public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; + int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; /** 2:70 Program Version (only valid if 2:65 present) */ - public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; + int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; /** 2:75 Object Cycle (a: morning, p: evening, b: both) */ - public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; + int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; /** 2:80 By-line (repeatable) */ - public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 + int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 /** 2:85 By-line Title (repeatable) */ - public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 + int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 /** 2:90 City */ - public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a + int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a /** 2:92 Sub-location */ - public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; + int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; /** 2:95 Province/State */ - public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f + int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f /** 2:100 Country/Primary Location Code */ - public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; + int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; /** 2:101 Country/Primary Location Name */ - public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 + int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 /** 2:103 Original Transmission Reference */ - public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 + int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 /** 2:105 Headline */ - public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 + int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 /** 2:110 Credit */ - public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e + int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e /** 2:115 Source */ - public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 + int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 /** 2:116 Copyright Notice */ - public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 + int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 /** 2:118 Contact */ - public static final int TAG_CONTACT = APPLICATION_RECORD | 118; + int TAG_CONTACT = APPLICATION_RECORD | 118; /** 2:120 Catption/Abstract */ - public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 + int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 /** 2:122 Writer/Editor (repeatable) */ - public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a + int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a /** 2:125 Rasterized Caption (binary data) */ - public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; + int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; /** 2:130 Image Type */ - public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; + int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; /** 2:131 Image Orientation */ - public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; + int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; /** 2:135 Language Identifier */ - public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; + int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; // TODO: 2:150-2:154 Audio @@ -150,9 +155,28 @@ public interface IPTC { * * @see JobMinder Homepage */ - static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199; + int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199; // TODO: Other custom fields in 155-200 range? // TODO: 2:200-2:202 Object Preview Data + + final class Tags { + static boolean isArray(final short tagId) { + switch (tagId) { + case IPTC.TAG_DESTINATION: + case IPTC.TAG_PRODUCT_ID: + case IPTC.TAG_SUBJECT_REFERENCE: + case IPTC.TAG_SUPPLEMENTAL_CATEGORIES: + case IPTC.TAG_KEYWORDS: + case IPTC.TAG_CONTENT_LOCATION_CODE: + case IPTC.TAG_CONTENT_LOCATION_NAME: + case IPTC.TAG_BY_LINE: + return true; + + default: + return false; + } + } + } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java index 133f07bc..54a644ca 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java @@ -42,6 +42,7 @@ import java.util.Collection; */ final class IPTCDirectory extends AbstractDirectory { IPTCDirectory(final Collection entries) { + // TODO: Normalize multiple entries with same key to single entry w/array super(entries); } } \ No newline at end of file diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java index 6bca3f91..956b6ac1 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry { switch ((Integer) getIdentifier()) { case IPTC.TAG_RECORD_VERSION: return "RecordVersion"; + case IPTC.TAG_KEYWORDS: + return "Keywords"; + case IPTC.TAG_SPECIAL_INSTRUCTIONS: + return "Instructions"; + case IPTC.TAG_DIGITAL_CREATION_DATE: + return "DigitalCreationDate"; + case IPTC.TAG_DIGITAL_CREATION_TIME: + return "DigitalCreationTime"; + case IPTC.TAG_DATE_CREATED: + return "DateCreated"; + case IPTC.TAG_TIME_CREATED: + return "TimeCreated"; + case IPTC.TAG_BY_LINE_TITLE: + return "ByLineTitle"; + case IPTC.TAG_CITY: + return "City"; + case IPTC.TAG_SUB_LOCATION: + return "SubLocation"; + case IPTC.TAG_PROVINCE_OR_STATE: + return "StateProvince"; + case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE: + return "CountryCode"; + case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION: + return "Country"; case IPTC.TAG_SOURCE: return "Source"; case IPTC.TAG_CAPTION: diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java index a9fad969..34a8f1a5 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java @@ -43,8 +43,9 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; /** * IPTCReader @@ -60,61 +61,88 @@ public final class IPTCReader extends MetadataReader { private int encoding = ENCODING_UNSPECIFIED; - @Override public Directory read(final ImageInputStream input) throws IOException { Validate.notNull(input, "input"); - List entries = new ArrayList(); + Map entries = new LinkedHashMap<>(); // 0x1c identifies start of a tag while (input.read() == 0x1c) { short tagId = input.readShort(); int tagByteCount = input.readUnsignedShort(); - Entry entry = readEntry(input, tagId, tagByteCount); + + boolean array = IPTC.Tags.isArray(tagId); + Entry entry = readEntry(input, tagId, tagByteCount, array, array ? entries.get(tagId) : null); if (entry != null) { - entries.add(entry); + entries.put(tagId, entry); } } - return new IPTCDirectory(entries); + return new IPTCDirectory(entries.values()); } - private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException { - Object value = null; + private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) { + Object[] oldValue = oldEntry != null ? (Object[]) oldEntry.getValue() : null; + Object[] value; + + if (newValue instanceof String) { + if (oldValue == null) { + value = new String[] {(String) newValue}; + } + else { + String[] array = (String[]) oldValue; + value = Arrays.copyOf(array, array.length + 1); + value[value.length - 1] = newValue; + } + } + else { + if (oldValue == null) { + value = new Object[] {newValue}; + } + else { + value = Arrays.copyOf(oldValue, oldValue.length + 1); + value [value .length - 1] = newValue; + } + } + + return new IPTCEntry(tagId, value); + } + + private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength, final boolean array, final Entry oldEntry) throws IOException { + Object value; switch (pTagId) { case IPTC.TAG_CODED_CHARACTER_SET: // TODO: Mapping from ISO 646 to Java supported character sets? - // TODO: Move somewhere else? encoding = parseEncoding(pInput, pLength); return null; case IPTC.TAG_RECORD_VERSION: + // TODO: Assert length == 2? // A single unsigned short value value = pInput.readUnsignedShort(); break; default: - // Skip non-Application fields, as they are typically not human readable - if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { - pInput.skipBytes(pLength); - return null; + // TODO: Create Tags.getType(tag), to allow for more flexible types + if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) { + // Treat Application records as Strings + if (pLength < 1) { + value = null; + } + else { + value = parseString(pInput, pLength); + } + } + else { + // Non-Application fields, typically not human readable + byte[] data = new byte[pLength]; + pInput.readFully(data); + value = data; } - - // fall through } - // If we don't have a value, treat it as a string - if (value == null) { - if (pLength < 1) { - value = null; - } - else { - value = parseString(pInput, pLength); - } - } - - return new IPTCEntry(pTagId, value); + return array ? mergeEntries(pTagId, value, oldEntry) : new IPTCEntry(pTagId, value); } private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException { @@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader { } // Fall back to use ISO-8859-1 - // This will not fail, but may may create wrong fallback-characters + // This will not fail, but may create wrong fallback-characters return StringUtil.decode(data, 0, data.length, "ISO8859_1"); } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java new file mode 100644 index 00000000..88a1c6b5 --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java @@ -0,0 +1,61 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.MetadataWriter; + +import javax.imageio.stream.ImageOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * IPTCWriter. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: IPTCWriter.java,v 1.0 28/05/15 harald.kuhr Exp$ + */ +public final class IPTCWriter extends MetadataWriter { + @Override + public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException { + notNull(directory, "directory"); + notNull(stream, "stream"); + + for (Entry entry : directory) { + int tag = (Integer) entry.getIdentifier(); + Object value = entry.getValue(); + + if (IPTC.Tags.isArray((short) tag)) { + Object[] values = (Object[]) value; + + for (Object v : values) { + stream.write(0x1c); + stream.writeShort(tag); + writeValue(stream, v); + } + } + else { + stream.write(0x1c); + stream.writeShort(tag); + writeValue(stream, value); + } + } + + return false; + } + + private void writeValue(final ImageOutputStream stream, final Object value) throws IOException { + if (value instanceof String) { + byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8); + stream.writeShort(data.length); + stream.write(data); + } + else if (value instanceof Integer) { + // TODO: Need to know types from tag + stream.writeShort(2); + stream.writeShort((Integer) value); + } + } +} diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java index b89d4eb4..f07a4453 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java @@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry { field.setAccessible(true); if (field.get(null).equals(getIdentifier())) { - return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true); + String fieldName = StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true); + return name != null ? fieldName + ": " + name : fieldName; } } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java index f6e67a46..a8904a50 100755 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java @@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader { public Directory read(final ImageInputStream input) throws IOException { Validate.notNull(input, "input"); - List entries = new ArrayList(); + List entries = new ArrayList<>(); while (true) { try { diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java index 189d6c28..24c36fa7 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java @@ -79,7 +79,7 @@ public abstract class MetadataReaderAbstractTest { assertNotNull(directory); } - protected final Matcher hasValue(final Object value) { + protected static Matcher hasValue(final Object value) { return new EntryHasValue(value); } diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java new file mode 100644 index 00000000..845ebc97 --- /dev/null +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.metadata; + +import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.spi.IIORegistry; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; + +/** + * ReaderAbstractTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: ReaderAbstractTest.java,v 1.0 04.01.12 09:40 haraldk Exp$ + */ +public abstract class MetadataWriterAbstractTest { + static { + IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); + ImageIO.setUseCache(false); + } + + protected final URL getResource(final String name) throws IOException { + return getClass().getResource(name); + } + + protected final ImageInputStream getDataAsIIS() throws IOException { + return ImageIO.createImageInputStream(getData()); + } + + protected abstract InputStream getData() throws IOException; + + protected abstract MetadataWriter createWriter(); + + @Test(expected = IllegalArgumentException.class) + public void testWriteNullDirectory() throws IOException { + createWriter().write(null, new MemoryCacheImageOutputStream(new ByteArrayOutputStream())); + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteNullStream() throws IOException { + createWriter().write(new AbstractDirectory(new ArrayList()) { + }, null); + } +} diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java index a425a7c7..f682fd00 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java @@ -28,24 +28,17 @@ package com.twelvemonkeys.imageio.metadata.exif; -import com.twelvemonkeys.imageio.metadata.AbstractDirectory; -import com.twelvemonkeys.imageio.metadata.AbstractEntry; -import com.twelvemonkeys.imageio.metadata.Directory; -import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.*; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; -import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; import com.twelvemonkeys.io.FastByteArrayOutputStream; import org.junit.Test; import javax.imageio.ImageIO; -import javax.imageio.spi.IIORegistry; -import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStreamImpl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collections; @@ -61,37 +54,25 @@ import static org.junit.Assert.assertNotNull; * @author last modified by $Author: haraldk$ * @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$ */ -public class EXIFWriterTest { - static { - IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi()); - ImageIO.setUseCache(false); - } +public class EXIFWriterTest extends MetadataWriterAbstractTest { - protected final URL getResource(final String name) throws IOException { - return getClass().getResource(name); - } - - protected final ImageInputStream getDataAsIIS() throws IOException { - return ImageIO.createImageInputStream(getData()); - } - - // @Override + @Override protected InputStream getData() throws IOException { return getResource("/exif/exif-jpeg-segment.bin").openStream(); } -// @Override protected EXIFReader createReader() { return new EXIFReader(); } + @Override protected EXIFWriter createWriter() { return new EXIFWriter(); } @Test public void testWriteReadSimple() throws IOException { - ArrayList entries = new ArrayList(); + ArrayList entries = new ArrayList<>(); entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT)); entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); @@ -139,7 +120,7 @@ public class EXIFWriterTest { @Test public void testWriteMotorola() throws IOException { - ArrayList entries = new ArrayList(); + ArrayList entries = new ArrayList<>(); entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG)); Directory directory = new AbstractDirectory(entries) {}; @@ -174,7 +155,7 @@ public class EXIFWriterTest { @Test public void testWriteIntel() throws IOException { - ArrayList entries = new ArrayList(); + ArrayList entries = new ArrayList<>(); entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG)); Directory directory = new AbstractDirectory(entries) {}; @@ -254,7 +235,7 @@ public class EXIFWriterTest { @Test public void testComputeIFDSize() throws IOException { - ArrayList entries = new ArrayList(); + ArrayList entries = new ArrayList<>(); entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT)); entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java new file mode 100644 index 00000000..dd620eae --- /dev/null +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java @@ -0,0 +1,72 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.MetadataWriter; +import com.twelvemonkeys.imageio.metadata.MetadataWriterAbstractTest; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import org.junit.Test; + +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNotNull; + +/** + * IPTCWriterTest. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: IPTCWriterTest.java,v 1.0 05/06/15 harald.kuhr Exp$ + */ +public class IPTCWriterTest extends MetadataWriterAbstractTest { + @Override + protected InputStream getData() throws IOException { + return getResource("/iptc/iptc-jpeg-segment.bin").openStream(); + } + + @Override + protected MetadataWriter createWriter() { + return new IPTCWriter(); + } + + private IPTCReader createReader() { + return new IPTCReader(); + } + + @Test + public void testRewriteExisting() throws IOException { + IPTCReader reader = createReader(); + Directory iptc = reader.read(getDataAsIIS()); + assumeNotNull(iptc); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes); + createWriter().write(iptc, stream); + stream.close(); + + Directory written = reader.read(new ByteArrayImageInputStream(bytes.toByteArray())); + assertEquals(iptc, written); + } + + @Test + public void testWrite() throws IOException { + List entries = new ArrayList<>(); + entries.add(new IPTCEntry(IPTC.TAG_KEYWORDS, new String[] {"Uno", "Due", "Tre"})); + + Directory iptc = new IPTCDirectory(entries); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes); + createWriter().write(iptc, stream); + stream.close(); + + Directory written = createReader().read(new ByteArrayImageInputStream(bytes.toByteArray())); + System.err.println("written: " + written); + assertEquals(iptc, written); + } +} \ No newline at end of file