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 {