diff --git a/Source/com/drew/lang/SequentialReader.java b/Source/com/drew/lang/SequentialReader.java
index f9a3f5a15..a5edc084e 100644
--- a/Source/com/drew/lang/SequentialReader.java
+++ b/Source/com/drew/lang/SequentialReader.java
@@ -362,6 +362,9 @@ public StringValue getNullTerminatedStringValue(int maxLengthBytes, Charset char
/**
* Returns the sequence of bytes punctuated by a \0
value.
+ * It will place the cursor after the first occurrence of \0
.
+ *
+ * Use getNullTerminatedStringAndSkipToNextPosition
if you want the cursor to move moved at the end of maxLengthBytes
.
*
* @param maxLengthBytes The maximum number of bytes to read. If a \0
byte is not reached within this limit,
* the returned array will be maxLengthBytes
long.
@@ -386,4 +389,19 @@ public byte[] getNullTerminatedBytes(int maxLengthBytes) throws IOException
System.arraycopy(buffer, 0, bytes, 0, length);
return bytes;
}
+
+ /**
+ * Read until the null terminated byte and automatically move the end of the requested position.
+ * @param maxLengthBytes
+ * @param charset
+ * @return
+ * @throws IOException
+ */
+ public StringValue getNullTerminatedStringAndSkipToNextPosition(int maxLengthBytes, Charset charset) throws IOException {
+ byte[] bytes = this.getNullTerminatedBytes(maxLengthBytes);
+ if (bytes.length < maxLengthBytes - 1) {
+ this.trySkip(maxLengthBytes - bytes.length - 1);
+ }
+ return new StringValue(bytes, charset);
+ }
}
diff --git a/Source/com/drew/lang/StreamReader.java b/Source/com/drew/lang/StreamReader.java
index 78761d866..3b98beae8 100644
--- a/Source/com/drew/lang/StreamReader.java
+++ b/Source/com/drew/lang/StreamReader.java
@@ -22,10 +22,12 @@
package com.drew.lang;
import com.drew.lang.annotations.NotNull;
+import com.drew.metadata.StringValue;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.Charset;
/**
*
@@ -141,4 +143,5 @@ private long skipInternal(long n) throws IOException
_pos += skippedTotal;
return skippedTotal;
}
+
}
diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java
index 39857b440..64a8316b7 100644
--- a/Source/com/drew/metadata/exif/ExifTiffHandler.java
+++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java
@@ -345,6 +345,26 @@ public boolean customProcessTag(final int tagOffset,
}
}
+ if (_currentDirectory instanceof NikonType2MakernoteDirectory) {
+ if (tagId == NikonType2MakernoteDirectory.TAG_PICTURE_CONTROL || tagId == NikonType2MakernoteDirectory.TAG_PICTURE_CONTROL_2) {
+ if (byteCount == 58) {
+ byte[] bytes = reader.getBytes(tagOffset, byteCount);
+ NikonPictureControl1Directory directory = NikonPictureControl1Directory.read(bytes);
+ directory.setParent(_currentDirectory);
+ _metadata.addDirectory(directory);
+ return true;
+ } else if (byteCount == 68) {
+ byte[] bytes = reader.getBytes(tagOffset, byteCount);
+ NikonPictureControl2Directory directory = NikonPictureControl2Directory.read(bytes);
+ directory.setParent(_currentDirectory);
+ _metadata.addDirectory(directory);
+ return true;
+ } else if (byteCount == 74) {
+ //TODO:
+ }
+ }
+ }
+
return false;
}
diff --git a/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Descriptor.java b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Descriptor.java
new file mode 100644
index 000000000..7a890a7b3
--- /dev/null
+++ b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Descriptor.java
@@ -0,0 +1,94 @@
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.metadata.TagDescriptor;
+
+import static com.drew.metadata.exif.makernotes.NikonPictureControl1Directory.TAG_FILTER_EFFECT;
+import static com.drew.metadata.exif.makernotes.NikonPictureControl1Directory.TAG_PICTURE_CONTROL_ADJUST;
+import static com.drew.metadata.exif.makernotes.NikonPictureControl1Directory.TAG_TONING_EFFECT;
+
+public final class NikonPictureControl1Descriptor extends TagDescriptor {
+ public NikonPictureControl1Descriptor(NikonPictureControl1Directory directory) {
+ super(directory);
+ }
+
+ @Override
+ public String getDescription(int tagType) {
+ switch (tagType) {
+ case TAG_PICTURE_CONTROL_ADJUST:
+ return getPictureControlAdjustDescription();
+ case TAG_FILTER_EFFECT:
+ return getFilterEffectDescription();
+ case TAG_TONING_EFFECT:
+ return getToningEffectDescription();
+ default:
+ return super.getDescription(tagType);
+ }
+ }
+
+ public String getPictureControlAdjustDescription() {
+ return getIndexedDescription(
+ TAG_PICTURE_CONTROL_ADJUST,
+ "Default Settings",
+ "Quick Adjust",
+ "Full Control"
+ );
+ }
+
+ public String getFilterEffectDescription() {
+ byte[] value = _directory.getByteArray(TAG_FILTER_EFFECT);
+ if (value == null) {
+ return null;
+ }
+
+ switch (value[0]) {
+ case (byte) 0x80:
+ return "Off";
+ case (byte) 0x81:
+ return "Yellow";
+ case (byte) 0x82:
+ return "Orange";
+ case (byte) 0x83:
+ return "Red";
+ case (byte) 0x84:
+ return "Green";
+ case (byte) 0xFF:
+ return "N/A";
+ default:
+ return super.getDescription(TAG_FILTER_EFFECT);
+ }
+ }
+
+ public String getToningEffectDescription() {
+ byte[] value = _directory.getByteArray(TAG_TONING_EFFECT);
+ if (value == null) {
+ return null;
+ }
+
+ switch (value[0]) {
+ case (byte) 0x80:
+ return "B&W";
+ case (byte) 0x81:
+ return "Sepia";
+ case (byte) 0x82:
+ return "Cyanotype";
+ case (byte) 0x83:
+ return "Red";
+ case (byte) 0x84:
+ return "Yellow";
+ case (byte) 0x85:
+ return "Green";
+ case (byte) 0x86:
+ return "Blue-green";
+ case (byte) 0x87:
+ return "Blue";
+ case (byte) 0x88:
+ return "Purple-blue";
+ case (byte) 0x89:
+ return "Red-purple";
+ case (byte) 0xFF:
+ return "N/A";
+ default:
+ return super.getDescription(TAG_TONING_EFFECT);
+ }
+ }
+}
diff --git a/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Directory.java b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Directory.java
new file mode 100644
index 000000000..8151cd590
--- /dev/null
+++ b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl1Directory.java
@@ -0,0 +1,88 @@
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.Charsets;
+import com.drew.lang.SequentialByteArrayReader;
+import com.drew.metadata.Directory;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+public final class NikonPictureControl1Directory extends Directory {
+ public static final int TAG_PICTURE_CONTROL_VERSION = 0;
+ public static final int TAG_PICTURE_CONTROL_NAME = 4;
+ public static final int TAG_PICTURE_CONTROL_BASE = 24;
+ public static final int TAG_PICTURE_CONTROL_ADJUST = 48;
+ public static final int TAG_PICTURE_CONTROL_QUICK_ADJUST = 49;
+ public static final int TAG_SHARPNESS = 50;
+ public static final int TAG_CONTRAST = 51;
+ public static final int TAG_BRIGHTNESS = 52;
+ public static final int TAG_SATURATION = 53;
+ public static final int TAG_HUE_ADJUSTMENT = 54;
+ public static final int TAG_FILTER_EFFECT = 55;
+ public static final int TAG_TONING_EFFECT = 56;
+ public static final int TAG_TONING_SATURATION = 57;
+
+ private static final HashMap TAG_NAME_MAP = new HashMap<>();
+
+ static {
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_VERSION, "Picture Control Version");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_NAME, "Picture Control Name");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_BASE, "Picture Control Base");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_ADJUST, "Picture Control Adjust");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_QUICK_ADJUST, "Picture Control Quick Adjust");
+ TAG_NAME_MAP.put(TAG_SHARPNESS, "Sharpness");
+ TAG_NAME_MAP.put(TAG_CONTRAST, "Contrast");
+ TAG_NAME_MAP.put(TAG_BRIGHTNESS, "Brightness");
+ TAG_NAME_MAP.put(TAG_SATURATION, "Saturation");
+ TAG_NAME_MAP.put(TAG_HUE_ADJUSTMENT, "Hue Adjustment");
+ TAG_NAME_MAP.put(TAG_FILTER_EFFECT, "Filter Effect");
+ TAG_NAME_MAP.put(TAG_TONING_EFFECT, "Toning Effect");
+ TAG_NAME_MAP.put(TAG_TONING_SATURATION, "Toning Saturation");
+ }
+
+ public NikonPictureControl1Directory() {
+ setDescriptor(new NikonPictureControl1Descriptor(this));
+ }
+
+ @Override
+ public String getName() {
+ return "Nikon PictureControl 1";
+ }
+
+ @Override
+ protected HashMap getTagNameMap() {
+ return TAG_NAME_MAP;
+ }
+
+ public static NikonPictureControl1Directory read(byte[] bytes) throws IOException {
+ int EXPECTED_LENGTH = 58;
+
+ if (bytes.length != EXPECTED_LENGTH) {
+ throw new IllegalArgumentException("Must have " + EXPECTED_LENGTH + " bytes.");
+ }
+
+ SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes);
+
+ NikonPictureControl1Directory directory = new NikonPictureControl1Directory();
+
+ directory.setString(TAG_PICTURE_CONTROL_VERSION, reader.getNullTerminatedStringAndSkipToNextPosition(4, Charsets.UTF_8).toString());
+ directory.setString(TAG_PICTURE_CONTROL_NAME, reader.getNullTerminatedStringAndSkipToNextPosition(20, Charsets.UTF_8).toString());
+ directory.setString(TAG_PICTURE_CONTROL_BASE, reader.getNullTerminatedStringAndSkipToNextPosition(20, Charsets.UTF_8).toString());
+
+ reader.skip(4);
+ directory.setObject(TAG_PICTURE_CONTROL_ADJUST, reader.getByte());
+ directory.setObject(TAG_PICTURE_CONTROL_QUICK_ADJUST, reader.getByte());
+ directory.setObject(TAG_SHARPNESS, reader.getByte());
+ directory.setObject(TAG_CONTRAST, reader.getByte());
+ directory.setObject(TAG_BRIGHTNESS, reader.getByte());
+ directory.setObject(TAG_SATURATION, reader.getByte());
+ directory.setObject(TAG_HUE_ADJUSTMENT, reader.getByte());
+ directory.setObject(TAG_FILTER_EFFECT, reader.getByte());
+ directory.setObject(TAG_TONING_EFFECT, reader.getByte());
+ directory.setObject(TAG_TONING_SATURATION, reader.getByte());
+
+ assert (reader.getPosition() == EXPECTED_LENGTH);
+
+ return directory;
+ }
+}
diff --git a/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Descriptor.java b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Descriptor.java
new file mode 100644
index 000000000..37d498b0d
--- /dev/null
+++ b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Descriptor.java
@@ -0,0 +1,94 @@
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.metadata.TagDescriptor;
+
+import static com.drew.metadata.exif.makernotes.NikonPictureControl2Directory.TAG_FILTER_EFFECT;
+import static com.drew.metadata.exif.makernotes.NikonPictureControl2Directory.TAG_PICTURE_CONTROL_ADJUST;
+import static com.drew.metadata.exif.makernotes.NikonPictureControl2Directory.TAG_TONING_EFFECT;
+
+public final class NikonPictureControl2Descriptor extends TagDescriptor {
+ public NikonPictureControl2Descriptor(NikonPictureControl2Directory directory) {
+ super(directory);
+ }
+
+ @Override
+ public String getDescription(int tagType) {
+ switch (tagType) {
+ case TAG_PICTURE_CONTROL_ADJUST:
+ return getPictureControlAdjustDescription();
+ case TAG_FILTER_EFFECT:
+ return getFilterEffectDescription();
+ case TAG_TONING_EFFECT:
+ return getToningEffectDescription();
+ default:
+ return super.getDescription(tagType);
+ }
+ }
+
+ public String getPictureControlAdjustDescription() {
+ return getIndexedDescription(
+ TAG_PICTURE_CONTROL_ADJUST,
+ "Default Settings",
+ "Quick Adjust",
+ "Full Control"
+ );
+ }
+
+ public String getFilterEffectDescription() {
+ byte[] value = _directory.getByteArray(TAG_FILTER_EFFECT);
+ if (value == null) {
+ return null;
+ }
+
+ switch (value[0]) {
+ case (byte) 0x80:
+ return "Off";
+ case (byte) 0x81:
+ return "Yellow";
+ case (byte) 0x82:
+ return "Orange";
+ case (byte) 0x83:
+ return "Red";
+ case (byte) 0x84:
+ return "Green";
+ case (byte) 0xFF:
+ return "N/A";
+ default:
+ return super.getDescription(TAG_FILTER_EFFECT);
+ }
+ }
+
+ public String getToningEffectDescription() {
+ byte[] value = _directory.getByteArray(TAG_TONING_EFFECT);
+ if (value == null) {
+ return null;
+ }
+
+ switch (value[0]) {
+ case (byte) 0x80:
+ return "B&W";
+ case (byte) 0x81:
+ return "Sepia";
+ case (byte) 0x82:
+ return "Cyanotype";
+ case (byte) 0x83:
+ return "Red";
+ case (byte) 0x84:
+ return "Yellow";
+ case (byte) 0x85:
+ return "Green";
+ case (byte) 0x86:
+ return "Blue-green";
+ case (byte) 0x87:
+ return "Blue";
+ case (byte) 0x88:
+ return "Purple-blue";
+ case (byte) 0x89:
+ return "Red-purple";
+ case (byte) 0xFF:
+ return "N/A";
+ default:
+ return super.getDescription(TAG_TONING_EFFECT);
+ }
+ }
+}
diff --git a/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Directory.java b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Directory.java
new file mode 100644
index 000000000..65bd89890
--- /dev/null
+++ b/Source/com/drew/metadata/exif/makernotes/NikonPictureControl2Directory.java
@@ -0,0 +1,99 @@
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.Charsets;
+import com.drew.lang.SequentialByteArrayReader;
+import com.drew.metadata.Directory;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+public final class NikonPictureControl2Directory extends Directory {
+ public static final int TAG_PICTURE_CONTROL_VERSION = 0;
+ public static final int TAG_PICTURE_CONTROL_NAME = 4;
+ public static final int TAG_PICTURE_CONTROL_BASE = 24;
+ public static final int TAG_PICTURE_CONTROL_ADJUST = 48;
+ public static final int TAG_PICTURE_CONTROL_QUICK_ADJUST = 49;
+ public static final int TAG_SHARPNESS = 51;
+ public static final int TAG_CLARITY = 53;
+ public static final int TAG_CONTRAST = 55;
+ public static final int TAG_BRIGHTNESS = 57;
+ public static final int TAG_SATURATION = 59;
+ public static final int TAG_HUE = 61;
+ public static final int TAG_FILTER_EFFECT = 63;
+ public static final int TAG_TONING_EFFECT = 64;
+ public static final int TAG_TONING_SATURATION = 65;
+
+ private static final HashMap TAG_NAME_MAP = new HashMap<>();
+
+ static {
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_VERSION, "Picture Control Version");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_NAME, "Picture Control Name");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_BASE, "Picture Control Base");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_ADJUST, "Picture Control Adjust");
+ TAG_NAME_MAP.put(TAG_PICTURE_CONTROL_QUICK_ADJUST, "Picture Control Quick Adjust");
+ TAG_NAME_MAP.put(TAG_SHARPNESS, "Sharpness");
+ TAG_NAME_MAP.put(TAG_CLARITY, "Clarity");
+ TAG_NAME_MAP.put(TAG_CONTRAST, "Contrast");
+ TAG_NAME_MAP.put(TAG_BRIGHTNESS, "Brightness");
+ TAG_NAME_MAP.put(TAG_SATURATION, "Saturation");
+ TAG_NAME_MAP.put(TAG_HUE, "Hue");
+ TAG_NAME_MAP.put(TAG_FILTER_EFFECT, "Filter Effect");
+ TAG_NAME_MAP.put(TAG_TONING_EFFECT, "Toning Effect");
+ TAG_NAME_MAP.put(TAG_TONING_SATURATION, "Toning Saturation");
+ }
+
+ public NikonPictureControl2Directory() {
+ setDescriptor(new NikonPictureControl2Descriptor(this));
+ }
+
+ @Override
+ public String getName() {
+ return "Nikon PictureControl 2";
+ }
+
+ @Override
+ protected HashMap getTagNameMap() {
+ return TAG_NAME_MAP;
+ }
+
+ public static NikonPictureControl2Directory read(byte[] bytes) throws IOException {
+ int EXPECTED_LENGTH = 68;
+
+ if (bytes.length != EXPECTED_LENGTH) {
+ throw new IllegalArgumentException("Must have " + EXPECTED_LENGTH + " bytes.");
+ }
+
+ SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes);
+
+ NikonPictureControl2Directory directory = new NikonPictureControl2Directory();
+
+ directory.setString(TAG_PICTURE_CONTROL_VERSION, reader.getNullTerminatedStringAndSkipToNextPosition(4, Charsets.UTF_8).toString());
+ directory.setString(TAG_PICTURE_CONTROL_NAME, reader.getNullTerminatedStringAndSkipToNextPosition(20, Charsets.UTF_8).toString());
+ directory.setString(TAG_PICTURE_CONTROL_BASE, reader.getNullTerminatedStringAndSkipToNextPosition(20, Charsets.UTF_8).toString());
+
+ reader.skip(4);
+ directory.setObject(TAG_PICTURE_CONTROL_ADJUST, reader.getByte());
+ directory.setObject(TAG_PICTURE_CONTROL_QUICK_ADJUST, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_SHARPNESS, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_CLARITY, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_CONTRAST, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_BRIGHTNESS, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_SATURATION, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_HUE, reader.getByte());
+ reader.skip(1);
+ directory.setObject(TAG_FILTER_EFFECT, reader.getByte());
+ directory.setObject(TAG_TONING_EFFECT, reader.getByte());
+ directory.setObject(TAG_TONING_SATURATION, reader.getByte());
+ reader.skip(2);
+
+ assert (reader.getPosition() == EXPECTED_LENGTH);
+
+ return directory;
+ }
+}
diff --git a/Tests/com/drew/lang/SequentialAccessTestBase.java b/Tests/com/drew/lang/SequentialAccessTestBase.java
index 92ef65b40..c1dae75ab 100644
--- a/Tests/com/drew/lang/SequentialAccessTestBase.java
+++ b/Tests/com/drew/lang/SequentialAccessTestBase.java
@@ -240,6 +240,42 @@ public void testGetNullTerminatedString() throws IOException
assertEquals("AB", createReader(new byte[]{0x41, 0x42, 0, 0x43}).getNullTerminatedString(10, Charsets.UTF_8));
}
+ @Test
+ public void testGetNullTerminatedStringCursorPositionTest() throws IOException {
+ byte NULL = 0x00;
+ byte[] bytes = new byte[]{0x41, 0x42, NULL, NULL, NULL, 0x43, 0x44, NULL, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}; //AB\0\0\0CD\0ABCDEF
+ SequentialReader reader = createReader(bytes);
+
+ /*
+ tried to read first five values
+ */
+ assertEquals("AB", reader.getNullTerminatedString(5, Charsets.UTF_8).toString());
+
+ /*
+ the cursor is after B (third) position
+ */
+ assertEquals(reader.getPosition(), 3);
+ reader.skip(2);
+
+ assertEquals("CD", reader.getNullTerminatedString(3, Charsets.UTF_8).toString());
+
+ assertEquals(reader.getPosition(), 8);
+ //no need to skip to next position. since there's only one \0 character after "CD"
+
+ assertEquals("ABCDEF", reader.getNullTerminatedString(6, Charsets.UTF_8).toString());
+ }
+
+ @Test
+ public void testGetNullTerminatedStringAndSkipToNextPosition() throws IOException {
+ byte NULL = 0x00;
+ byte[] bytes = new byte[]{0x41, 0x42, NULL, NULL, NULL, 0x43, 0x44, NULL, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};
+ SequentialReader reader = createReader(bytes);
+
+ assertEquals("AB", reader.getNullTerminatedStringAndSkipToNextPosition(5, Charsets.UTF_8).toString());
+ assertEquals("CD", reader.getNullTerminatedStringAndSkipToNextPosition(3, Charsets.UTF_8).toString());
+ assertEquals("ABCDEF", reader.getNullTerminatedStringAndSkipToNextPosition(6, Charsets.UTF_8).toString());
+ }
+
@Test
public void testGetString() throws IOException
{