mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-04 11:26:44 -04:00
Mainly new code standard.
A few changes that should have been committed earlier.. :-/
This commit is contained in:
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AbstractEntry
|
||||
@@ -69,6 +70,45 @@ public abstract class AbstractEntry implements Entry {
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
if (valueCount() > 1) {
|
||||
if (valueCount() < 16) {
|
||||
Class<?> type = mValue.getClass().getComponentType();
|
||||
|
||||
if (type.isPrimitive()) {
|
||||
if (type.equals(boolean.class)) {
|
||||
return Arrays.toString((boolean[]) mValue);
|
||||
}
|
||||
else if (type.equals(byte.class)) {
|
||||
return Arrays.toString((byte[]) mValue);
|
||||
}
|
||||
else if (type.equals(char.class)) {
|
||||
return new String((char[]) mValue);
|
||||
}
|
||||
else if (type.equals(double.class)) {
|
||||
return Arrays.toString((double[]) mValue);
|
||||
}
|
||||
else if (type.equals(float.class)) {
|
||||
return Arrays.toString((float[]) mValue);
|
||||
}
|
||||
else if (type.equals(int.class)) {
|
||||
return Arrays.toString((int[]) mValue);
|
||||
}
|
||||
else if (type.equals(long.class)) {
|
||||
return Arrays.toString((long[]) mValue);
|
||||
}
|
||||
else if (type.equals(short.class)) {
|
||||
return Arrays.toString((short[]) mValue);
|
||||
}
|
||||
// Fall through should never happen
|
||||
}
|
||||
else {
|
||||
return Arrays.toString((Object[]) mValue);
|
||||
}
|
||||
}
|
||||
|
||||
return String.valueOf(mValue) + " (" + valueCount() + ")";
|
||||
}
|
||||
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
|
||||
|
@@ -43,16 +43,35 @@ final class EXIFEntry extends AbstractEntry {
|
||||
EXIFEntry(final int pIdentifier, final Object pValue, final short pType) {
|
||||
super(pIdentifier, pValue);
|
||||
|
||||
if (pType < 1 || pType > TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType));
|
||||
}
|
||||
// if (pType < 1 || pType > TIFF.TYPE_NAMES.length) {
|
||||
// throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType));
|
||||
// }
|
||||
|
||||
mType = pType;
|
||||
}
|
||||
|
||||
public short getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case TIFF.TAG_EXIF_IFD:
|
||||
return "EXIF";
|
||||
case TIFF.TAG_XMP:
|
||||
return "XMP";
|
||||
case TIFF.TAG_IPTC:
|
||||
return "IPTC";
|
||||
case TIFF.TAG_PHOTOSHOP:
|
||||
return "Adobe";
|
||||
case TIFF.TAG_ICC_PROFILE:
|
||||
return "ICC Profile";
|
||||
|
||||
case TIFF.TAG_IMAGE_WIDTH:
|
||||
return "ImageWidth";
|
||||
case TIFF.TAG_IMAGE_HEIGHT:
|
||||
return "ImageHeight";
|
||||
case TIFF.TAG_COMPRESSION:
|
||||
return "Compression";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
@@ -82,6 +101,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "PixelXDimension";
|
||||
case EXIF.TAG_PIXEL_Y_DIMENSION:
|
||||
return "PixelYDimension";
|
||||
|
||||
// TODO: More field names
|
||||
}
|
||||
|
||||
|
@@ -34,10 +34,15 @@ import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -48,6 +53,7 @@ import java.util.List;
|
||||
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
||||
*/
|
||||
public final class EXIFReader extends MetadataReader {
|
||||
static final Collection<Integer> KNOWN_IFDS = Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD);
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||
@@ -56,10 +62,15 @@ public final class EXIFReader extends MetadataReader {
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||
pInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
// TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
|
||||
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
|
||||
int magic = pInput.readUnsignedShort();
|
||||
if (magic != TIFF.TIFF_MAGIC) {
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
@@ -72,7 +83,6 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException {
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
int entryCount = pInput.readUnsignedShort();
|
||||
|
||||
@@ -82,6 +92,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
long nextOffset = pInput.readUnsignedInt();
|
||||
|
||||
// Read linked IFDs
|
||||
if (nextOffset != 0) {
|
||||
EXIFDirectory next = readDirectory(pInput, nextOffset);
|
||||
|
||||
@@ -90,50 +101,158 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data?
|
||||
readSubdirectories(pInput, entries,
|
||||
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD
|
||||
// , TIFF.TAG_IPTC, TIFF.TAG_XMP
|
||||
// , TIFF.TAG_ICC_PROFILE
|
||||
// , TIFF.TAG_PHOTOSHOP
|
||||
// ,TIFF.TAG_MODI_OLE_PROPERTY_SET
|
||||
)
|
||||
);
|
||||
|
||||
return new EXIFDirectory(entries);
|
||||
}
|
||||
|
||||
// private Directory readForeignMetadata(final MetadataReader reader, final byte[] bytes) throws IOException {
|
||||
// return reader.read(ImageIO.createImageInputStream(new ByteArrayInputStream(bytes)));
|
||||
// }
|
||||
|
||||
// TODO: Might be better to leave this for client code, as it's tempting go really overboard and support any possible embedded format..
|
||||
private void readSubdirectories(ImageInputStream input, List<Entry> entries, List<Integer> subIFDs) throws IOException {
|
||||
if (subIFDs == null || subIFDs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
|
||||
EXIFEntry entry = (EXIFEntry) entries.get(i);
|
||||
int tagId = (Integer) entry.getIdentifier();
|
||||
|
||||
if (subIFDs.contains(tagId)) {
|
||||
try {
|
||||
Object directory;
|
||||
|
||||
/*
|
||||
if (tagId == TIFF.TAG_IPTC) {
|
||||
directory = readForeignMetadata(new IPTCReader(), (byte[]) entry.getValue());
|
||||
}
|
||||
else if (tagId == TIFF.TAG_XMP) {
|
||||
directory = readForeignMetadata(new XMPReader(), (byte[]) entry.getValue());
|
||||
}
|
||||
else if (tagId == TIFF.TAG_PHOTOSHOP) {
|
||||
// TODO: This is waaay too fragile.. Might need registry-based meta data parsers?
|
||||
try {
|
||||
Class cl = Class.forName("com.twelvemonkeys.imageio.plugins.psd.PSDImageResource");
|
||||
Method method = cl.getMethod("read", ImageInputStream.class);
|
||||
method.setAccessible(true);
|
||||
directory = method.invoke(null, ImageIO.createImageInputStream(new ByteArrayInputStream((byte[]) entry.getValue())));
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (tagId == TIFF.TAG_ICC_PROFILE) {
|
||||
directory = ICC_Profile.getInstance((byte[]) entry.getValue());
|
||||
}
|
||||
else if (tagId == TIFF.TAG_MODI_OLE_PROPERTY_SET) {
|
||||
// TODO: Encapsulate in something more useful?
|
||||
directory = new CompoundDocument(new ByteArrayInputStream((byte[]) entry.getValue())).getRootEntry();
|
||||
}
|
||||
else*/ if (KNOWN_IFDS.contains(tagId)) {
|
||||
directory = readDirectory(input, getPointerOffset(entry));
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, directory, entry.getType()));
|
||||
}
|
||||
catch (IIOException e) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getPointerOffset(final Entry entry) throws IIOException {
|
||||
long offset;
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Byte) {
|
||||
offset = ((Byte) value & 0xff);
|
||||
}
|
||||
else if (value instanceof Short) {
|
||||
offset = ((Short) value & 0xffff);
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
offset = ((Integer) value & 0xffffffffL);
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
offset = (Long) value;
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException {
|
||||
// TODO: BigTiff entries are different
|
||||
int tagId = pInput.readUnsignedShort();
|
||||
|
||||
short type = pInput.readShort();
|
||||
int count = pInput.readInt(); // Number of values
|
||||
|
||||
Object value;
|
||||
if (count < 0) {
|
||||
throw new IIOException(String.format("Illegal count %d for tag %s type %s @%08x", count, tagId, type, pInput.getStreamPosition()));
|
||||
}
|
||||
|
||||
Object value;
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
if (type < 0 || type > 13) {
|
||||
// Invalid tag, this is just for debugging
|
||||
System.err.printf("offset: %08x%n", pInput.getStreamPosition() - 8l);
|
||||
System.err.println("tagId: " + tagId);
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
|
||||
if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) {
|
||||
// Parse sub IFDs
|
||||
long offset = pInput.readUnsignedInt();
|
||||
pInput.mark();
|
||||
pInput.seek(pInput.getStreamPosition() - 8);
|
||||
|
||||
try {
|
||||
value = readDirectory(pInput, offset);
|
||||
byte[] bytes = new byte[8 + Math.max(20, valueLength)];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
System.err.print("data: " + HexDump.dump(bytes));
|
||||
System.err.println(bytes.length < valueLength ? "..." : "");
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
value = readValueInLine(pInput, type, count);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
}
|
||||
else {
|
||||
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
value = readValue(pInput, valueOffset, type, count);
|
||||
}
|
||||
// TODO: For BigTiff allow size <= 8
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
value = readValueInLine(pInput, type, count);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
}
|
||||
else {
|
||||
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
value = readValueAt(pInput, valueOffset, type, count);
|
||||
}
|
||||
|
||||
return new EXIFEntry(tagId, value, type);
|
||||
}
|
||||
|
||||
private Object readValue(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
|
||||
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(pOffset);
|
||||
return readValueInLine(pInput, pType, pCount);
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
@@ -141,53 +260,82 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
return readValueDirect(pInput, pType, pCount);
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
|
||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
|
||||
|
||||
long pos = pInput.getStreamPosition();
|
||||
|
||||
switch (pType) {
|
||||
case 2:
|
||||
// TODO: This might be UTF-8 or ISO-8859-1, even though spec says ASCII
|
||||
case 2: // ASCII
|
||||
// TODO: This might be UTF-8 or ISO-8859-x, even though spec says ASCII
|
||||
byte[] ascii = new byte[pCount];
|
||||
pInput.readFully(ascii);
|
||||
return StringUtil.decode(ascii, 0, ascii.length, "UTF-8"); // UTF-8 is ASCII compatible
|
||||
case 1:
|
||||
case 1: // BYTE
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
case 6:
|
||||
// else fall through
|
||||
case 6: // SBYTE
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
case 7:
|
||||
// else fall through
|
||||
case 7: // UNDEFINED
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
// NOTE: We don't change (unsigned) BYTE array wider Java type, as most often BYTE array means
|
||||
// binary data and we want to keep that as a byte array for clients to parse futher
|
||||
|
||||
return bytes;
|
||||
case 3:
|
||||
case 3: // SHORT
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case 8:
|
||||
case 8: // SSHORT
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
|
||||
if (pType == 3) {
|
||||
int[] ints = new int[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
ints[i] = shorts[i] & 0xffff;
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
return shorts;
|
||||
case 4:
|
||||
case 13: // IFD
|
||||
case 4: // LONG
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case 9:
|
||||
case 9: // SLONG
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
|
||||
if (pType == 4 || pType == 13) {
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = ints[i] & 0xffffffffL;
|
||||
}
|
||||
return longs;
|
||||
}
|
||||
|
||||
return ints;
|
||||
case 11:
|
||||
case 11: // FLOAT
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
@@ -195,7 +343,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case 12:
|
||||
case 12: // DOUBLE
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
@@ -204,7 +352,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
case 5:
|
||||
case 5: // RATIONAL
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
@@ -215,7 +363,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case 10:
|
||||
case 10: // SRATIONAL
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
@@ -227,8 +375,32 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
return srationals;
|
||||
|
||||
// BigTiff:
|
||||
case 16: // LONG8
|
||||
case 17: // SLONG8
|
||||
case 18: // IFD8
|
||||
// TODO: Assert BigTiff (version == 43)
|
||||
|
||||
if (pCount == 1) {
|
||||
long val = pInput.readLong();
|
||||
if (pType != 17 && val < 0) {
|
||||
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = pInput.readLong();
|
||||
}
|
||||
|
||||
return longs;
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
||||
// Spec says skip unknown values:
|
||||
// TODO: Rather just return null, UNKNOWN_TYPE or new Unknown(type, count, offset) for value?
|
||||
return new Unknown(pType, pCount, pos);
|
||||
// throw new IIOException(String.format("Unknown EXIF type '%s' at pos %d", pType, pInput.getStreamPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,4 +411,115 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
EXIFReader reader = new EXIFReader();
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));
|
||||
|
||||
long pos = 0;
|
||||
if (args.length > 1) {
|
||||
if (args[1].startsWith("0x")) {
|
||||
pos = Integer.parseInt(args[1].substring(2), 16);
|
||||
}
|
||||
else {
|
||||
pos = Long.parseLong(args[1]);
|
||||
}
|
||||
|
||||
stream.setByteOrder(pos < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
|
||||
pos = Math.abs(pos);
|
||||
|
||||
stream.seek(pos);
|
||||
}
|
||||
|
||||
try {
|
||||
Directory directory;
|
||||
|
||||
if (args.length > 1) {
|
||||
directory = reader.readDirectory(stream, pos);
|
||||
}
|
||||
else {
|
||||
directory = reader.read(stream);
|
||||
}
|
||||
|
||||
for (Entry entry : directory) {
|
||||
System.err.println(entry);
|
||||
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
byte[] bytes = (byte[]) value;
|
||||
System.err.println(HexDump.dump(bytes, 0, Math.min(bytes.length, 128)));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// TODO: Stream based hex dump util?
|
||||
public static class HexDump {
|
||||
private HexDump() {}
|
||||
|
||||
private static final int WIDTH = 32;
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes, int off, int len) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % WIDTH == 0) {
|
||||
if (i > 0 ) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(String.format("%08x: ", i + off));
|
||||
}
|
||||
else if (i > 0 && i % 2 == 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
builder.append(String.format("%02x", bytes[i + off]));
|
||||
|
||||
int next = i + 1;
|
||||
if (next % WIDTH == 0 || next == len) {
|
||||
int leftOver = (WIDTH - (next % WIDTH)) % WIDTH;
|
||||
|
||||
if (leftOver != 0) {
|
||||
// Pad: 5 spaces for every 2 bytes... Special care if padding is non-even.
|
||||
int pad = leftOver / 2;
|
||||
|
||||
if (len % 2 != 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
for (int j = 0; j < pad; j++) {
|
||||
builder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(" ");
|
||||
builder.append(toAsciiString(bytes, next - (WIDTH - leftOver) + off, next + off));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String toAsciiString(final byte[] bytes, final int from, final int to) {
|
||||
byte[] range = Arrays.copyOfRange(bytes, from, to);
|
||||
|
||||
for (int i = 0; i < range.length; i++) {
|
||||
if (range[i] < 32 || range[i] > 126) {
|
||||
range[i] = '.'; // Unreadable char
|
||||
}
|
||||
}
|
||||
|
||||
return new String(range, Charset.forName("ascii"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ public interface TIFF {
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
@@ -57,21 +57,39 @@ public interface TIFF {
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
|
||||
TODO: Verify IFD type
|
||||
See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html
|
||||
13 = IFD, same as LONG
|
||||
|
||||
TODO: BigTiff specifies more types
|
||||
See http://www.awaresystems.be/imaging/tiff/bigtiff.html, http://www.remotesensing.org/libtiff/bigtiffdesign.html
|
||||
(what about 14-15??)
|
||||
16 = TIFF_LONG8, being unsigned 8byte integer
|
||||
17 = TIFF_SLONG8, being signed 8byte integer
|
||||
18 = TIFF_IFD8, being a new unsigned 8byte IFD offset.
|
||||
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
"IFD",
|
||||
null, null,
|
||||
"LONG8", "SLONG8", "IFD8"
|
||||
};
|
||||
int[] TYPE_LENGTHS = {
|
||||
1, 1, 2, 4, 8,
|
||||
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
4,
|
||||
-1, -1,
|
||||
8, 8, 8
|
||||
};
|
||||
|
||||
int IFD_EXIF = 0x8769;
|
||||
int IFD_GPS = 0x8825;
|
||||
int IFD_INTEROP = 0xA005;
|
||||
/// EXIF defined TIFF tags
|
||||
|
||||
int TAG_EXIF_IFD = 34665;
|
||||
int TAG_GPS_IFD = 34853;
|
||||
int TAG_INTEROP_IFD = 40965;
|
||||
|
||||
/// A. Tags relating to image data structure:
|
||||
|
||||
@@ -114,4 +132,22 @@ public interface TIFF {
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
int TAG_COPYRIGHT = 33432;
|
||||
|
||||
int TAG_SUB_IFD = 330;
|
||||
|
||||
int TAG_XMP = 700;
|
||||
int TAG_IPTC = 33723;
|
||||
int TAG_PHOTOSHOP = 34377;
|
||||
int TAG_ICC_PROFILE = 34675;
|
||||
|
||||
// Microsoft Office Document Imaging (MODI)
|
||||
// http://msdn.microsoft.com/en-us/library/aa167596%28office.11%29.aspx
|
||||
int TAG_MODI_BLC = 34718;
|
||||
int TAG_MODI_VECTOR = 34719;
|
||||
int TAG_MODI_PTC = 34720;
|
||||
|
||||
// http://blogs.msdn.com/b/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx
|
||||
int TAG_MODI_PLAIN_TEXT = 37679;
|
||||
int TAG_MODI_OLE_PROPERTY_SET = 37680;
|
||||
int TAG_MODI_TEXT_POS_INFO = 37681;
|
||||
}
|
||||
|
@@ -0,0 +1,45 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
/**
|
||||
* Unknown
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Unknown.java,v 1.0 Oct 8, 2010 3:38:45 PM haraldk Exp$
|
||||
*/
|
||||
final class Unknown {
|
||||
private final short type;
|
||||
private final int count;
|
||||
private final long pos;
|
||||
|
||||
public Unknown(final short type, final int count, final long pos) {
|
||||
this.type = type;
|
||||
this.count = count;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (pos ^ (pos >>> 32)) + count * 37 + type * 97;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other != null && other.getClass() == getClass()){
|
||||
Unknown unknown = (Unknown) other;
|
||||
return pos == unknown.pos && type == unknown.type && count == unknown.count;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (count == 1) {
|
||||
return String.format("Unknown(%d)@%08x", type, pos);
|
||||
}
|
||||
else {
|
||||
return String.format("Unknown(%d)[%d]@%08x", type, count, pos);
|
||||
}
|
||||
}
|
||||
}
|
@@ -49,12 +49,12 @@ public final class JPEGSegmentUtil {
|
||||
|
||||
private JPEGSegmentUtil() {}
|
||||
|
||||
// TODO: Allow for multiple images (multiple SOI markers), using specified index?
|
||||
public static List<Segment> readSegments(final ImageInputStream stream, final int appMarker, final String segmentName) throws IOException {
|
||||
// TODO: Allow for multiple images (multiple SOI markers), using specified index, or document that stream must be placed before SOI of wanted image
|
||||
public static List<Segment> readSegments(final ImageInputStream stream, final int imageIndex, final int appMarker, final String segmentName) throws IOException {
|
||||
return readSegments(stream, Collections.singletonMap(appMarker, Collections.singletonList(segmentName)));
|
||||
}
|
||||
|
||||
public static List<Segment> readSegments(final ImageInputStream stream, Map<Integer, List<String>> segmentIdentifiers) throws IOException {
|
||||
public static List<Segment> readSegments(final ImageInputStream stream, final Map<Integer, List<String>> segmentIdentifiers) throws IOException {
|
||||
readSOI(stream);
|
||||
|
||||
List<Segment> segments = Collections.emptyList();
|
||||
|
@@ -69,7 +69,7 @@ public class XMPScannerTestCase extends TestCase {
|
||||
}
|
||||
|
||||
public void testScanForUTF8singleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'"));
|
||||
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-8");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class XMPScannerTestCase extends TestCase {
|
||||
}
|
||||
|
||||
public void testScanForUTF16BEsingleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'"));
|
||||
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16BE");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class XMPScannerTestCase extends TestCase {
|
||||
}
|
||||
|
||||
public void testScanForUTF16LEsingleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'"));
|
||||
InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16LE");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
|
Reference in New Issue
Block a user