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 extends Entry> 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