mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -04:00
Merge branch 'master' of git://github.com/haraldk/TwelveMonkeys
This commit is contained in:
commit
c310083d2a
@ -172,14 +172,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
|||||||
try {
|
try {
|
||||||
return mStream.length();
|
return mStream.length();
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException ignore) {
|
||||||
throw unchecked(e, RuntimeException.class);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
|
return -1;
|
||||||
private <T extends Throwable> T unchecked(IOException pExcption, Class<T> pClass) {
|
|
||||||
// Ugly hack to fool the compiler..
|
|
||||||
return (T) pExcption;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for {@link ImageInputStream} to limit the number of bytes that can be read.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SubImageInputStream.java,v 1.0 Nov 8, 2009 2:50:58 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||||
|
// NOTE: This class is based on com.sun.imageio.plugins.common.SubImageInputStream, but fixes some of its bugs.
|
||||||
|
|
||||||
|
private final ImageInputStream mStream;
|
||||||
|
private final long mStartPos;
|
||||||
|
private final long mLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
|
||||||
|
*
|
||||||
|
* @param pStream the underlying stream
|
||||||
|
* @param pLength the maximum length to read from the stream.
|
||||||
|
* Note that {@code pStream} may contain less than this maximum number of bytes.
|
||||||
|
*
|
||||||
|
* @throws IOException if {@code pStream}'s position can't be determined.
|
||||||
|
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
|
||||||
|
*/
|
||||||
|
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
|
||||||
|
Validate.notNull(pStream, "stream");
|
||||||
|
if (pLength < 0) {
|
||||||
|
throw new IllegalArgumentException("length < 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
mStream = pStream;
|
||||||
|
mStartPos = pStream.getStreamPosition();
|
||||||
|
mLength = pLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (streamPos >= mLength) { // Local EOF
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int read = mStream.read();
|
||||||
|
|
||||||
|
if (read >= 0) {
|
||||||
|
streamPos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||||
|
if (streamPos >= mLength) { // Local EOF
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe cast, as pLength can never cause int overflow
|
||||||
|
int length = (int) Math.min(pLength, mLength - streamPos);
|
||||||
|
int count = mStream.read(pBytes, pOffset, length);
|
||||||
|
|
||||||
|
if (count >= 0) {
|
||||||
|
streamPos += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long length() {
|
||||||
|
try {
|
||||||
|
long length = mStream.length();
|
||||||
|
return length < 0 ? -1 : Math.min(length - mStartPos, mLength);
|
||||||
|
}
|
||||||
|
catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(final long pPosition) throws IOException {
|
||||||
|
if (pPosition < getFlushedPosition()) {
|
||||||
|
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
||||||
|
}
|
||||||
|
|
||||||
|
mStream.seek(mStartPos + pPosition);
|
||||||
|
streamPos = pPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubImageInputStreamTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SubImageInputStreamTestCase.java,v 1.0 Nov 8, 2009 3:03:32 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class SubImageInputStreamTestCase extends TestCase {
|
||||||
|
// TODO: Extract super test case for all stream tests
|
||||||
|
private final Random mRandom = new Random(837468l);
|
||||||
|
|
||||||
|
private ImageInputStream createStream(final int pSize) {
|
||||||
|
byte[] bytes = new byte[pSize];
|
||||||
|
|
||||||
|
mRandom.nextBytes(bytes);
|
||||||
|
|
||||||
|
return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) {
|
||||||
|
@Override
|
||||||
|
public long length() {
|
||||||
|
return pSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateNullStream() throws IOException {
|
||||||
|
try {
|
||||||
|
new SubImageInputStream(null, 1);
|
||||||
|
fail("Expected IllegalArgumentException with null stream");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateNegativeLength() throws IOException {
|
||||||
|
try {
|
||||||
|
new SubImageInputStream(createStream(0), -1);
|
||||||
|
fail("Expected IllegalArgumentException with negative length");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreate() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(11), 7);
|
||||||
|
|
||||||
|
assertEquals(0, stream.getStreamPosition());
|
||||||
|
assertEquals(7, stream.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWraphBeyondWrappedLength() throws IOException {
|
||||||
|
SubImageInputStream stream = new SubImageInputStream(createStream(5), 6);
|
||||||
|
assertEquals(5, stream.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWrapUnknownLength() throws IOException {
|
||||||
|
SubImageInputStream stream = new SubImageInputStream(new ImageInputStreamImpl() {
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Method read not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Method read not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long length() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}, 6);
|
||||||
|
|
||||||
|
assertEquals(-1, stream.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRead() throws IOException {
|
||||||
|
ImageInputStream wrapped = createStream(42);
|
||||||
|
|
||||||
|
wrapped.skipBytes(13);
|
||||||
|
|
||||||
|
ImageInputStream stream = new SubImageInputStream(wrapped, 27);
|
||||||
|
|
||||||
|
assertEquals(0, stream.getStreamPosition());
|
||||||
|
assertEquals(27, stream.length());
|
||||||
|
|
||||||
|
stream.read();
|
||||||
|
assertEquals(1, stream.getStreamPosition());
|
||||||
|
assertEquals(27, stream.length());
|
||||||
|
|
||||||
|
stream.readFully(new byte[11]);
|
||||||
|
assertEquals(12, stream.getStreamPosition());
|
||||||
|
assertEquals(27, stream.length());
|
||||||
|
|
||||||
|
assertEquals(25, wrapped.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadResetRead() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(32), 16);
|
||||||
|
stream.mark();
|
||||||
|
|
||||||
|
byte[] first = new byte[16];
|
||||||
|
stream.readFully(first);
|
||||||
|
|
||||||
|
stream.reset();
|
||||||
|
|
||||||
|
byte[] second = new byte[16];
|
||||||
|
stream.readFully(second);
|
||||||
|
|
||||||
|
assertTrue(Arrays.equals(first, second));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSeekNegative() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||||
|
try {
|
||||||
|
stream.seek(-2);
|
||||||
|
fail("Expected IndexOutOfBoundsException");
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, stream.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSeekBeforeFlushedPos() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||||
|
stream.seek(3);
|
||||||
|
stream.flushBefore(3);
|
||||||
|
|
||||||
|
assertEquals(3, stream.getStreamPosition());
|
||||||
|
|
||||||
|
try {
|
||||||
|
stream.seek(0);
|
||||||
|
fail("Expected IndexOutOfBoundsException");
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(3, stream.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSeekAfterEOF() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||||
|
stream.seek(6);
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSeek() throws IOException {
|
||||||
|
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||||
|
stream.seek(5);
|
||||||
|
assertEquals(5, stream.getStreamPosition());
|
||||||
|
|
||||||
|
stream.seek(1);
|
||||||
|
assertEquals(1, stream.getStreamPosition());
|
||||||
|
}
|
||||||
|
}
|
@ -86,7 +86,7 @@ class CMAPChunk extends IFFChunk {
|
|||||||
if (numColors == 32) {
|
if (numColors == 32) {
|
||||||
paletteSize = 64;
|
paletteSize = 64;
|
||||||
}
|
}
|
||||||
else {
|
else if (numColors != 64) {
|
||||||
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +100,9 @@ class CMAPChunk extends IFFChunk {
|
|||||||
mGreens[i] = pInput.readByte();
|
mGreens[i] = pInput.readByte();
|
||||||
mBlues[i] = pInput.readByte();
|
mBlues[i] = pInput.readByte();
|
||||||
}
|
}
|
||||||
if (isEHB) {
|
|
||||||
|
if (isEHB && numColors == 32) {
|
||||||
|
// Create the half-brite colors
|
||||||
for (int i = 0; i < numColors; i++) {
|
for (int i = 0; i < numColors; i++) {
|
||||||
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
|
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
|
||||||
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
|
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
|
||||||
|
31
twelvemonkeys-imageio/metadata/pom.xml
Normal file
31
twelvemonkeys-imageio/metadata/pom.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||||
|
<version>2.3-SNAPSHOT</version>
|
||||||
|
<name>TwelveMonkeys ImageIO Metadata</name>
|
||||||
|
<description>
|
||||||
|
ImageIO metadata module.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||||
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
|
<version>2.3-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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 java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractDirectory
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: AbstractDirectory.java,v 1.0 Nov 11, 2009 5:31:04 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDirectory implements Directory {
|
||||||
|
private final List<Entry> mEntries = new ArrayList<Entry>();
|
||||||
|
|
||||||
|
protected AbstractDirectory(final Collection<? extends Entry> pEntries) {
|
||||||
|
if (pEntries != null) {
|
||||||
|
mEntries.addAll(pEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry getEntryById(final Object pIdentifier) {
|
||||||
|
for (Entry entry : this) {
|
||||||
|
if (entry.getIdentifier().equals(pIdentifier)) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry getEntryByFieldName(final String pFieldName) {
|
||||||
|
for (Entry entry : this) {
|
||||||
|
if (entry.getFieldName() != null && entry.getFieldName().equals(pFieldName)) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<Entry> iterator() {
|
||||||
|
return mEntries.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws {@code UnsupportedOperationException} if this directory is read-only.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if this directory is read-only.
|
||||||
|
* @see #isReadOnly()
|
||||||
|
*/
|
||||||
|
protected final void assertMutable() {
|
||||||
|
if (isReadOnly()) {
|
||||||
|
throw new UnsupportedOperationException("Directory is read-only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean add(final Entry pEntry) {
|
||||||
|
assertMutable();
|
||||||
|
|
||||||
|
// TODO: Replace if entry is already present?
|
||||||
|
// Some directories may need special ordering, or may/may not support multiple entries for certain ids...
|
||||||
|
return mEntries.add(pEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"SuspiciousMethodCalls"})
|
||||||
|
public boolean remove(final Object pEntry) {
|
||||||
|
assertMutable();
|
||||||
|
|
||||||
|
return mEntries.remove(pEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return mEntries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This implementation returns {@code true}.
|
||||||
|
* Subclasses should override this method, if the directory is mutable.
|
||||||
|
*
|
||||||
|
* @return {@code true}
|
||||||
|
*/
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard object support
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mEntries.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object pOther) {
|
||||||
|
if (this == pOther) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClass() != pOther.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe cast, as it must be a subclass for the classes to be equal
|
||||||
|
AbstractDirectory other = (AbstractDirectory) pOther;
|
||||||
|
|
||||||
|
return mEntries.equals(other.mEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s%s", getClass().getSimpleName(), mEntries.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.lang.Validate;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractEntry
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: AbstractEntry.java,v 1.0 Nov 12, 2009 12:43:13 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public abstract class AbstractEntry implements Entry {
|
||||||
|
|
||||||
|
private final Object mIdentifier;
|
||||||
|
private final Object mValue; // TODO: Might need to be mutable..
|
||||||
|
|
||||||
|
protected AbstractEntry(final Object pIdentifier, final Object pValue) {
|
||||||
|
Validate.notNull(pIdentifier, "identifier");
|
||||||
|
|
||||||
|
mIdentifier = pIdentifier;
|
||||||
|
mValue = pValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Object getIdentifier() {
|
||||||
|
return mIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code null}, meaning unknown or undefined.
|
||||||
|
*
|
||||||
|
* @return {@code null}.
|
||||||
|
*/
|
||||||
|
public String getFieldName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValueAsString() {
|
||||||
|
return String.valueOf(mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTypeName() {
|
||||||
|
if (mValue == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mValue.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int valueCount() {
|
||||||
|
// TODO: Collection support?
|
||||||
|
if (mValue != null && mValue.getClass().isArray()) {
|
||||||
|
return Array.getLength(mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Object
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mIdentifier.hashCode() + 31 * mValue.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object pOther) {
|
||||||
|
if (this == pOther) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(pOther instanceof AbstractEntry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractEntry other = (AbstractEntry) pOther;
|
||||||
|
|
||||||
|
return mIdentifier.equals(other.mIdentifier) && (
|
||||||
|
mValue == null && other.mValue == null || mValue != null && mValue.equals(other.mValue)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String name = getFieldName();
|
||||||
|
String nameStr = name != null ? "/" + name + "" : "";
|
||||||
|
|
||||||
|
String type = getTypeName();
|
||||||
|
String typeStr = type != null ? " (" + type + ")" : "";
|
||||||
|
|
||||||
|
return String.format("%s%s: %s%s", getIdentifier(), nameStr, getValueAsString(), typeStr);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: Directory.java,v 1.0 Nov 11, 2009 4:20:58 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public interface Directory extends Iterable<Entry> {
|
||||||
|
// TODO: Spec when more entries exist? Or make Entry support multi-values!?
|
||||||
|
// For multiple entries with same id in directory, the first entry (using the order from the stream) will be returned
|
||||||
|
Entry getEntryById(Object pIdentifier);
|
||||||
|
|
||||||
|
Entry getEntryByFieldName(String pName);
|
||||||
|
|
||||||
|
// Iterator containing the entries in
|
||||||
|
//Iterator<Entry> getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage);
|
||||||
|
|
||||||
|
|
||||||
|
/// Collection-like API
|
||||||
|
// TODO: addOrReplaceIfPresent... (trouble for multi-values) Or mutable entries?
|
||||||
|
// boolean replace(Entry pEntry)??
|
||||||
|
// boolean contains(Object pIdentifier)?
|
||||||
|
|
||||||
|
boolean add(Entry pEntry);
|
||||||
|
|
||||||
|
boolean remove(Object pEntry); // Object in case we retro-fit Collection/Map..
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
boolean isReadOnly();
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: Entry.java,v 1.0 Nov 11, 2009 4:21:08 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public interface Entry {
|
||||||
|
// "tag" identifier from spec
|
||||||
|
Object getIdentifier();
|
||||||
|
|
||||||
|
// Human readable "tag" (field) name from sepc
|
||||||
|
String getFieldName();
|
||||||
|
|
||||||
|
// The internal "tag" value as stored in the stream, may be a Directory
|
||||||
|
Object getValue();
|
||||||
|
|
||||||
|
// Human readable "tag" value
|
||||||
|
String getValueAsString();
|
||||||
|
|
||||||
|
//void setValue(Object pValue); // TODO: qualifiers...
|
||||||
|
|
||||||
|
// Optional, implementation/spec specific type, describing the object returned from getValue
|
||||||
|
String getTypeName();
|
||||||
|
|
||||||
|
// TODO: Or something like getValue(qualifierType, qualifierValue) + getQualifiers()/getQualifierValues
|
||||||
|
// TODO: The problem with current model is getEntry() which only has single value support
|
||||||
|
|
||||||
|
// Optional, xml:lang-support
|
||||||
|
//String getLanguage();
|
||||||
|
|
||||||
|
// Optional, XMP alt-support. TODO: Do we need both?
|
||||||
|
//Object getQualifier();
|
||||||
|
|
||||||
|
// For arrays only
|
||||||
|
int valueCount();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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 javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetadataReader
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: MetadataReader.java,v 1.0 Nov 13, 2009 8:38:11 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public abstract class MetadataReader {
|
||||||
|
public abstract Directory read(ImageInputStream pInput) throws IOException;
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.exif;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIF
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public interface EXIF {
|
||||||
|
int TAG_COLOR_SPACE = 40961;
|
||||||
|
int TAG_PIXEL_X_DIMENSION = 40962;
|
||||||
|
int TAG_PIXEL_Y_DIMENSION = 40963;
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.exif;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIFDirectory
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: EXIFDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class EXIFDirectory extends AbstractDirectory {
|
||||||
|
EXIFDirectory(final Collection<? extends Entry> pEntries) {
|
||||||
|
super(pEntries);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.exif;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIFEntry
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class EXIFEntry extends AbstractEntry {
|
||||||
|
final private short mType;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
mType = pType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFieldName() {
|
||||||
|
switch ((Integer) getIdentifier()) {
|
||||||
|
case TIFF.TAG_COMPRESSION:
|
||||||
|
return "Compression";
|
||||||
|
case TIFF.TAG_ORIENTATION:
|
||||||
|
return "Orientation";
|
||||||
|
case TIFF.TAG_X_RESOLUTION:
|
||||||
|
return "XResolution";
|
||||||
|
case TIFF.TAG_Y_RESOLUTION:
|
||||||
|
return "YResolution";
|
||||||
|
case TIFF.TAG_RESOLUTION_UNIT:
|
||||||
|
return "ResolutionUnit";
|
||||||
|
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||||
|
return "JPEGInterchangeFormat";
|
||||||
|
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||||
|
return "JPEGInterchangeFormatLength";
|
||||||
|
case TIFF.TAG_SOFTWARE:
|
||||||
|
return "Software";
|
||||||
|
case TIFF.TAG_DATE_TIME:
|
||||||
|
return "DateTime";
|
||||||
|
case TIFF.TAG_ARTIST:
|
||||||
|
return "Artist";
|
||||||
|
case TIFF.TAG_COPYRIGHT:
|
||||||
|
return "Copyright";
|
||||||
|
|
||||||
|
case EXIF.TAG_COLOR_SPACE:
|
||||||
|
return "ColorSpace";
|
||||||
|
case EXIF.TAG_PIXEL_X_DIMENSION:
|
||||||
|
return "PixelXDimension";
|
||||||
|
case EXIF.TAG_PIXEL_Y_DIMENSION:
|
||||||
|
return "PixelYDimension";
|
||||||
|
// TODO: More field names
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return TIFF.TYPE_NAMES[mType - 1];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.exif;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIFReader
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class EXIFReader extends MetadataReader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||||
|
byte[] bom = new byte[2];
|
||||||
|
pInput.readFully(bom);
|
||||||
|
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||||
|
pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||||
|
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
long directoryOffset = pInput.readUnsignedInt();
|
||||||
|
|
||||||
|
return readDirectory(pInput, directoryOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException {
|
||||||
|
List<Entry> entries = new ArrayList<Entry>();
|
||||||
|
|
||||||
|
pInput.seek(pOffset);
|
||||||
|
int entryCount = pInput.readUnsignedShort();
|
||||||
|
|
||||||
|
for (int i = 0; i < entryCount; i++) {
|
||||||
|
entries.add(readEntry(pInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
long nextOffset = pInput.readUnsignedInt();
|
||||||
|
|
||||||
|
if (nextOffset != 0) {
|
||||||
|
EXIFDirectory next = readDirectory(pInput, nextOffset);
|
||||||
|
|
||||||
|
for (Entry entry : next) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EXIFDirectory(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException {
|
||||||
|
int tagId = pInput.readUnsignedShort();
|
||||||
|
|
||||||
|
short type = pInput.readShort();
|
||||||
|
int count = pInput.readInt(); // Number of values
|
||||||
|
|
||||||
|
Object value;
|
||||||
|
|
||||||
|
if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) {
|
||||||
|
// Parse sub IFDs
|
||||||
|
long offset = pInput.readUnsignedInt();
|
||||||
|
pInput.mark();
|
||||||
|
|
||||||
|
try {
|
||||||
|
value = readDirectory(pInput, offset);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EXIFEntry(tagId, value, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readValue(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);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
pInput.seek(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||||
|
return readValueDirect(pInput, pType, pCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||||
|
switch (pType) {
|
||||||
|
case 2:
|
||||||
|
// TODO: This might be UTF-8 or ISO-8859-1, 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:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readUnsignedByte();
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readByte();
|
||||||
|
}
|
||||||
|
case 7:
|
||||||
|
byte[] bytes = new byte[pCount];
|
||||||
|
pInput.readFully(bytes);
|
||||||
|
return bytes;
|
||||||
|
case 3:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readUnsignedShort();
|
||||||
|
}
|
||||||
|
case 8:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
short[] shorts = new short[pCount];
|
||||||
|
pInput.readFully(shorts, 0, shorts.length);
|
||||||
|
return shorts;
|
||||||
|
case 4:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readUnsignedInt();
|
||||||
|
}
|
||||||
|
case 9:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] ints = new int[pCount];
|
||||||
|
pInput.readFully(ints, 0, ints.length);
|
||||||
|
return ints;
|
||||||
|
case 11:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] floats = new float[pCount];
|
||||||
|
pInput.readFully(floats, 0, floats.length);
|
||||||
|
return floats;
|
||||||
|
case 12:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return pInput.readDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] doubles = new double[pCount];
|
||||||
|
pInput.readFully(doubles, 0, doubles.length);
|
||||||
|
return doubles;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
Rational[] rationals = new Rational[pCount];
|
||||||
|
for (int i = 0; i < rationals.length; i++) {
|
||||||
|
rationals[i] = new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rationals;
|
||||||
|
case 10:
|
||||||
|
if (pCount == 1) {
|
||||||
|
return new Rational(pInput.readInt(), pInput.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
Rational[] srationals = new Rational[pCount];
|
||||||
|
for (int i = 0; i < srationals.length; i++) {
|
||||||
|
srationals[i] = new Rational(pInput.readInt(), pInput.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
return srationals;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueLength(final int pType, final int pCount) {
|
||||||
|
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
|
||||||
|
return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adapted from sample code featured in
|
||||||
|
* "Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)
|
||||||
|
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.metadata.exif;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a rational number with a {@code long} numerator and {@code long} denominator.
|
||||||
|
* Rational numbers are stored in reduced form with the sign stored with the numerator.
|
||||||
|
* Rationals are immutable.
|
||||||
|
* <p/>
|
||||||
|
* Adapted from sample code featured in
|
||||||
|
* <a href="http://www.cs.princeton.edu/introcs/home/">"Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)</a>
|
||||||
|
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author <a href="http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html">Robert Sedgewick and Kevin Wayne (original version)</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class Rational extends Number implements Comparable<Rational> {
|
||||||
|
// TODO: Document public API
|
||||||
|
// TODO: Move to com.tm.lang?
|
||||||
|
// Inspired by http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html and java.lang.Integer
|
||||||
|
static final Rational ZERO = new Rational(0, 1);
|
||||||
|
|
||||||
|
private final long mNumerator;
|
||||||
|
private final long mDenominator;
|
||||||
|
|
||||||
|
public Rational(final long pNumber) {
|
||||||
|
this(pNumber, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rational(final long pNumerator, final long pDenominator) {
|
||||||
|
if (pDenominator == 0) {
|
||||||
|
throw new IllegalArgumentException("denominator == 0");
|
||||||
|
}
|
||||||
|
if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) {
|
||||||
|
throw new IllegalArgumentException("value == Long.MIN_VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce fractions
|
||||||
|
long gcd = gcd(pNumerator, pDenominator);
|
||||||
|
long num = pNumerator / gcd;
|
||||||
|
long den = pDenominator / gcd;
|
||||||
|
|
||||||
|
mNumerator = pDenominator >= 0 ? num : -num;
|
||||||
|
mDenominator = pDenominator >= 0 ? den : -den;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long gcd(final long m, final long n) {
|
||||||
|
if (m < 0) {
|
||||||
|
return gcd(n, -m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return n == 0 ? m : gcd(n, m % n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long lcm(final long m, final long n) {
|
||||||
|
if (m < 0) {
|
||||||
|
return lcm(n, -m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m * (n / gcd(m, n)); // parentheses important to avoid overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
public long numerator() {
|
||||||
|
return mNumerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long denominator() {
|
||||||
|
return mDenominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int intValue() {
|
||||||
|
return (int) doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long longValue() {
|
||||||
|
return (long) doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float floatValue() {
|
||||||
|
return (float) doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double doubleValue() {
|
||||||
|
return mNumerator / (double) mDenominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Comparable implementation
|
||||||
|
|
||||||
|
public int compareTo(final Rational pOther) {
|
||||||
|
double thisVal = doubleValue();
|
||||||
|
double otherVal = pOther.doubleValue();
|
||||||
|
|
||||||
|
return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Object overrides
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Float.floatToIntBits(floatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object pOther) {
|
||||||
|
return pOther == this || pOther instanceof Rational && compareTo((Rational) pOther) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mDenominator == 1 ? Long.toString(mNumerator) : String.format("%s/%s", mNumerator, mDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Operations (adapted from http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html)
|
||||||
|
// TODO: Naming! multiply/divide/add/subtract or times/divides/plus/minus
|
||||||
|
|
||||||
|
// return a * b, staving off overflow as much as possible by cross-cancellation
|
||||||
|
public Rational times(final Rational pOther) {
|
||||||
|
// special cases
|
||||||
|
if (equals(ZERO) || pOther.equals(ZERO)) {
|
||||||
|
return ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2
|
||||||
|
Rational c = new Rational(mNumerator, pOther.mDenominator);
|
||||||
|
Rational d = new Rational(pOther.mNumerator, mDenominator);
|
||||||
|
|
||||||
|
return new Rational(c.mNumerator * d.mNumerator, c.mDenominator * d.mDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a + b, staving off overflow
|
||||||
|
public Rational plus(final Rational pOther) {
|
||||||
|
// special cases
|
||||||
|
if (equals(ZERO)) {
|
||||||
|
return pOther;
|
||||||
|
}
|
||||||
|
if (pOther.equals(ZERO)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find gcd of numerators and denominators
|
||||||
|
long f = gcd(mNumerator, pOther.mNumerator);
|
||||||
|
long g = gcd(mDenominator, pOther.mDenominator);
|
||||||
|
|
||||||
|
// add cross-product terms for numerator
|
||||||
|
// multiply back in
|
||||||
|
return new Rational(
|
||||||
|
((mNumerator / f) * (pOther.mDenominator / g) + (pOther.mNumerator / f) * (mDenominator / g)) * f,
|
||||||
|
lcm(mDenominator, pOther.mDenominator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return -a
|
||||||
|
public Rational negate() {
|
||||||
|
return new Rational(-mNumerator, mDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a - b
|
||||||
|
public Rational minus(final Rational pOther) {
|
||||||
|
return plus(pOther.negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rational reciprocal() {
|
||||||
|
return new Rational(mDenominator, mNumerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a / b
|
||||||
|
public Rational divides(final Rational pOther) {
|
||||||
|
if (pOther.equals(ZERO)) {
|
||||||
|
throw new ArithmeticException("/ by zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
return times(pOther.reciprocal());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.exif;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFF
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public interface TIFF {
|
||||||
|
int TIFF_MAGIC = 42;
|
||||||
|
|
||||||
|
/*
|
||||||
|
1 = BYTE 8-bit unsigned integer.
|
||||||
|
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||||
|
must be NUL (binary zero).
|
||||||
|
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||||
|
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||||
|
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||||
|
fraction; the second, the denominator.
|
||||||
|
|
||||||
|
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.
|
||||||
|
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||||
|
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||||
|
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||||
|
fraction, the second the denominator.
|
||||||
|
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||||
|
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||||
|
*/
|
||||||
|
String[] TYPE_NAMES = {
|
||||||
|
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||||
|
|
||||||
|
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||||
|
};
|
||||||
|
int[] TYPE_LENGTHS = {
|
||||||
|
1, 1, 2, 4, 8,
|
||||||
|
|
||||||
|
1, 1, 2, 4, 8, 4, 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
int IFD_EXIF = 0x8769;
|
||||||
|
int IFD_GPS = 0x8825;
|
||||||
|
int IFD_INTEROP = 0xA005;
|
||||||
|
|
||||||
|
/// A. Tags relating to image data structure:
|
||||||
|
|
||||||
|
int TAG_IMAGE_WIDTH = 256;
|
||||||
|
int TAG_IMAGE_HEIGHT = 257;
|
||||||
|
int TAG_BITS_PER_SAMPLE = 258;
|
||||||
|
int TAG_COMPRESSION = 259;
|
||||||
|
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||||
|
int TAG_ORIENTATION = 274;
|
||||||
|
int TAG_SAMPLES_PER_PIXELS = 277;
|
||||||
|
int TAG_PLANAR_CONFIGURATION = 284;
|
||||||
|
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||||
|
int TAG_YCBCR_POSITIONING = 531;
|
||||||
|
int TAG_X_RESOLUTION = 282;
|
||||||
|
int TAG_Y_RESOLUTION = 283;
|
||||||
|
int TAG_RESOLUTION_UNIT = 296;
|
||||||
|
|
||||||
|
/// B. Tags relating to recording offset
|
||||||
|
|
||||||
|
int TAG_STRIP_OFFSETS = 273;
|
||||||
|
int TAG_ROWS_PER_STRIP = 278;
|
||||||
|
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||||
|
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||||
|
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||||
|
|
||||||
|
/// C. Tags relating to image data characteristics
|
||||||
|
|
||||||
|
int TAG_TRANSFER_FUNCTION = 301;
|
||||||
|
int TAG_WHITE_POINT = 318;
|
||||||
|
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||||
|
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||||
|
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||||
|
|
||||||
|
/// D. Other tags
|
||||||
|
|
||||||
|
int TAG_DATE_TIME = 306;
|
||||||
|
int TAG_IMAGE_DESCRIPTION = 270;
|
||||||
|
int TAG_MAKE = 271;
|
||||||
|
int TAG_MODEL = 272;
|
||||||
|
int TAG_SOFTWARE = 305;
|
||||||
|
int TAG_ARTIST = 315;
|
||||||
|
int TAG_COPYRIGHT = 33432;
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.iptc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPTC
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
||||||
|
|
||||||
|
/** 2:00 Record Version (mandatory) */
|
||||||
|
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||||
|
|
||||||
|
/** 2:03 Object Type Reference */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:05 Object Name */
|
||||||
|
public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
||||||
|
/** 2:07 Edit Status */
|
||||||
|
public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||||
|
/** 2:08 Editorial Update */
|
||||||
|
public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||||
|
/** 2:10 Urgency */
|
||||||
|
public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||||
|
/** 2:12 Subect Reference (repeatable) */
|
||||||
|
public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
||||||
|
/** 2:15 Category */
|
||||||
|
public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||||
|
/** 2:20 Supplemental Category (repeatable) */
|
||||||
|
public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
||||||
|
/** 2:22 Fixture Identifier */
|
||||||
|
public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||||
|
/** 2:25 Keywords (repeatable) */
|
||||||
|
public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
||||||
|
/** 2:26 Content Locataion Code (repeatable) */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:30 Release Date */
|
||||||
|
public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
||||||
|
/** 2:35 Release Time */
|
||||||
|
public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||||
|
/** 2:37 Expiration Date */
|
||||||
|
public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
||||||
|
/** 2:38 Expiration Time */
|
||||||
|
public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||||
|
/** 2:40 Special Instructions */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:50 Reference Number (mandatory if 2:45 present) */
|
||||||
|
public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||||
|
/** 2:55 Date Created */
|
||||||
|
public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
||||||
|
/** 2:60 Time Created */
|
||||||
|
public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||||
|
/** 2:62 Digital Creation Date */
|
||||||
|
public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
||||||
|
/** 2:63 Digital Creation Date */
|
||||||
|
public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||||
|
/** 2:65 Originating Program */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:75 Object Cycle (a: morning, p: evening, b: both) */
|
||||||
|
public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||||
|
/** 2:80 By-line (repeatable) */
|
||||||
|
public static final 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
|
||||||
|
/** 2:90 City */
|
||||||
|
public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||||
|
/** 2:92 Sub-location */
|
||||||
|
public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
||||||
|
/** 2:95 Province/State */
|
||||||
|
public static final 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;
|
||||||
|
/** 2:101 Country/Primary Location Name */
|
||||||
|
public static final 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
|
||||||
|
/** 2:105 Headline */
|
||||||
|
public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||||
|
/** 2:110 Credit */
|
||||||
|
public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||||
|
/** 2:115 Source */
|
||||||
|
public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||||
|
/** 2:116 Copyright Notice */
|
||||||
|
public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
||||||
|
/** 2:118 Contact */
|
||||||
|
public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||||
|
/** 2:120 Catption/Abstract */
|
||||||
|
public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
||||||
|
/** 2:122 Writer/Editor (repeatable) */
|
||||||
|
public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||||
|
/** 2:125 Rasterized Caption (binary data) */
|
||||||
|
public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
||||||
|
/** 2:130 Image Type */
|
||||||
|
public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||||
|
/** 2:131 Image Orientation */
|
||||||
|
public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
||||||
|
/** 2:135 Language Identifier */
|
||||||
|
public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||||
|
|
||||||
|
// TODO: 2:150-2:154 Audio
|
||||||
|
|
||||||
|
// TODO: Should we expose this field?
|
||||||
|
/**
|
||||||
|
* 2:199 JobMinder Assignment Data (Custom IPTC field).
|
||||||
|
* A common custom IPTC field used by a now discontinued application called JobMinder.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
|
||||||
|
*/
|
||||||
|
static final 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
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.iptc;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPTCDirectory
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: IPTCDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class IPTCDirectory extends AbstractDirectory {
|
||||||
|
IPTCDirectory(final Collection<? extends Entry> pEntries) {
|
||||||
|
super(pEntries);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.iptc;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPTCEntry
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: IPTCEntry.java,v 1.0 Nov 13, 2009 8:57:04 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
class IPTCEntry extends AbstractEntry {
|
||||||
|
public IPTCEntry(final int pTagId, final Object pValue) {
|
||||||
|
super(pTagId, pValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFieldName() {
|
||||||
|
switch ((Integer) getIdentifier()) {
|
||||||
|
case IPTC.TAG_SOURCE:
|
||||||
|
return "Source";
|
||||||
|
// TODO: More tags...
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.iptc;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPTCReader
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: IPTCReader.java,v 1.0 Nov 13, 2009 8:37:23 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class IPTCReader extends MetadataReader {
|
||||||
|
private static final int ENCODING_UNKNOWN = -1;
|
||||||
|
private static final int ENCODING_UNSPECIFIED = 0;
|
||||||
|
private static final int ENCODING_UTF_8 = 0x1b2547;
|
||||||
|
|
||||||
|
private int mEncoding = ENCODING_UNSPECIFIED;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||||
|
final List<Entry> entries = new ArrayList<Entry>();
|
||||||
|
|
||||||
|
// 0x1c identifies start of a tag
|
||||||
|
while (pInput.read() == 0x1c) {
|
||||||
|
short tagId = pInput.readShort();
|
||||||
|
int tagByteCount = pInput.readUnsignedShort();
|
||||||
|
Entry entry = readEntry(pInput, tagId, tagByteCount);
|
||||||
|
|
||||||
|
if (entry != null) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IPTCDirectory(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException {
|
||||||
|
Object value = null;
|
||||||
|
|
||||||
|
switch (pTagId) {
|
||||||
|
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||||
|
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||||
|
// TODO: Move somewhere else?
|
||||||
|
mEncoding = parseEncoding(pInput, pLength);
|
||||||
|
return null;
|
||||||
|
case IPTC.TAG_RECORD_VERSION:
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
|
||||||
|
return tagByteCount == 3
|
||||||
|
&& (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8
|
||||||
|
? ENCODING_UTF_8 : ENCODING_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pass encoding as parameter? Use if specified
|
||||||
|
private String parseString(final ImageInputStream pInput, final int pLength) throws IOException {
|
||||||
|
byte[] data = new byte[pLength];
|
||||||
|
pInput.readFully(data);
|
||||||
|
|
||||||
|
// NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding.
|
||||||
|
// UTF-8 contains all 646 characters, but not 2022.
|
||||||
|
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
|
||||||
|
Charset charset = Charset.forName("UTF-8");
|
||||||
|
CharsetDecoder decoder = charset.newDecoder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
||||||
|
// Will fail fast on illegal UTF-8-sequences
|
||||||
|
CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
||||||
|
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||||
|
.decode(ByteBuffer.wrap(data));
|
||||||
|
return chars.toString();
|
||||||
|
}
|
||||||
|
catch (CharacterCodingException notUTF8) {
|
||||||
|
if (mEncoding == ENCODING_UTF_8) {
|
||||||
|
throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to use ISO-8859-1
|
||||||
|
// This will not fail, but may may create wrong fallback-characters
|
||||||
|
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMP
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMP.java,v 1.0 Nov 12, 2009 12:19:32 AM haraldk Exp$
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.adobe.com/products/xmp/">Extensible Metadata Platform (XMP)</a>
|
||||||
|
*/
|
||||||
|
public interface XMP {
|
||||||
|
/** W3C Resource Description Format namespace */
|
||||||
|
String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
||||||
|
|
||||||
|
/** Dublin Core Metadata Initiative namespace */
|
||||||
|
String NS_DC = "http://purl.org/dc/elements/1.1/";
|
||||||
|
|
||||||
|
String NS_EXIF = "http://ns.adobe.com/exif/1.0/";
|
||||||
|
|
||||||
|
String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/";
|
||||||
|
|
||||||
|
String NS_ST_REF = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#";
|
||||||
|
|
||||||
|
String NS_TIFF = "http://ns.adobe.com/tiff/1.0/";
|
||||||
|
|
||||||
|
String NS_XAP = "http://ns.adobe.com/xap/1.0/";
|
||||||
|
|
||||||
|
String NS_XAP_MM = "http://ns.adobe.com/xap/1.0/mm/";
|
||||||
|
|
||||||
|
/** Contains the mapping from URI to default namespace prefix. */
|
||||||
|
Map<String, String> DEFAULT_NS_MAPPING = Collections.unmodifiableMap(new XMPNamespaceMapping());
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPDirectory
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPDirectory.java,v 1.0 Nov 17, 2009 9:38:58 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class XMPDirectory extends AbstractDirectory {
|
||||||
|
// TODO: Store size of root directory, to allow serializing
|
||||||
|
// TODO: XMPDirectory, maybe not even an AbstractDirectory
|
||||||
|
// - Keeping the Document would allow for easier serialization
|
||||||
|
// TODO: Or use direct SAX parsing
|
||||||
|
public XMPDirectory(List<Entry> pEntries) {
|
||||||
|
super(pEntries);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPEntry
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPEntry.java,v 1.0 Nov 17, 2009 9:38:39 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class XMPEntry extends AbstractEntry {
|
||||||
|
private final String mFieldName;
|
||||||
|
|
||||||
|
public XMPEntry(final String pIdentifier, final Object pValue) {
|
||||||
|
this(pIdentifier, null, pValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public XMPEntry(final String pIdentifier, final String pFieldName, final Object pValue) {
|
||||||
|
super(pIdentifier, pValue);
|
||||||
|
mFieldName = pFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"SuspiciousMethodCalls"})
|
||||||
|
@Override
|
||||||
|
public String getFieldName() {
|
||||||
|
return mFieldName != null ? mFieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPNamespaceMapping
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPNamespaceMapping.java,v 1.0 Nov 17, 2009 6:35:21 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class XMPNamespaceMapping extends HashMap<String, String> {
|
||||||
|
public XMPNamespaceMapping() {
|
||||||
|
put(XMP.NS_RDF, "rdf");
|
||||||
|
put(XMP.NS_DC, "dc");
|
||||||
|
put(XMP.NS_EXIF, "exif");
|
||||||
|
put(XMP.NS_PHOTOSHOP, "photoshop");
|
||||||
|
put(XMP.NS_ST_REF, "stRef");
|
||||||
|
put(XMP.NS_TIFF, "tiff");
|
||||||
|
put(XMP.NS_XAP, "xap");
|
||||||
|
put(XMP.NS_XAP_MM, "xapMM");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.*;
|
||||||
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPReader
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPReader.java,v 1.0 Nov 14, 2009 11:04:30 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class XMPReader extends MetadataReader {
|
||||||
|
@Override
|
||||||
|
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||||
|
pInput.mark();
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(IIOUtil.createStreamAdapter(pInput), Charset.forName("UTF-8")));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
pInput.reset();
|
||||||
|
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
factory.setNamespaceAware(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Consider parsing using SAX?
|
||||||
|
// TODO: Determine encoding and parse using a Reader...
|
||||||
|
// TODO: Refactor scanner to return inputstream?
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(pInput)));
|
||||||
|
|
||||||
|
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
|
||||||
|
// serializer.serialize(document);
|
||||||
|
|
||||||
|
|
||||||
|
// Each rdf:Description is a Directory (but we can't really rely on that structure.. it's only convention)
|
||||||
|
// - Each element inside the rdf:Desc is an Entry
|
||||||
|
|
||||||
|
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
|
||||||
|
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
|
||||||
|
|
||||||
|
return parseDirectories(rdfRoot, descriptions);
|
||||||
|
}
|
||||||
|
catch (SAXException e) {
|
||||||
|
throw new IIOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
catch (ParserConfigurationException e) {
|
||||||
|
throw new RuntimeException(e); // TODO: Or IOException?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes) {
|
||||||
|
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
|
||||||
|
|
||||||
|
for (Node desc : asIterable(pNodes)) {
|
||||||
|
if (desc.getParentNode() != pParentNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Node node : asIterable(desc.getChildNodes())) {
|
||||||
|
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup
|
||||||
|
List<Entry> dir = subdirs.get(node.getNamespaceURI());
|
||||||
|
if (dir == null) {
|
||||||
|
dir = new ArrayList<Entry>();
|
||||||
|
subdirs.put(node.getNamespaceURI(), dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value;
|
||||||
|
|
||||||
|
Node parseType = node.getAttributes().getNamedItemNS(XMP.NS_RDF, "parseType");
|
||||||
|
if (parseType != null && "Resource".equals(parseType.getNodeValue())) {
|
||||||
|
List<Entry> entries = new ArrayList<Entry>();
|
||||||
|
|
||||||
|
for (Node child : asIterable(node.getChildNodes())) {
|
||||||
|
if (child.getNodeType() != Node.ELEMENT_NODE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(new XMPEntry(child.getNamespaceURI() + child.getLocalName(), child.getLocalName(), getChildTextValue(child)));
|
||||||
|
}
|
||||||
|
value = new XMPDirectory(entries);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Support alternative RDF syntax (short-form), using attributes on desc
|
||||||
|
// NamedNodeMap attributes = node.getAttributes();
|
||||||
|
//
|
||||||
|
// for (Node attr : asIterable(attributes)) {
|
||||||
|
// System.out.println("attr.getNodeName(): " + attr.getNodeName());
|
||||||
|
// System.out.println("attr.getNodeValue(): " + attr.getNodeValue());
|
||||||
|
// }
|
||||||
|
|
||||||
|
value = getChildTextValue(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
XMPEntry entry = new XMPEntry(node.getNamespaceURI() + node.getLocalName(), node.getLocalName(), value);
|
||||||
|
dir.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider flattening the somewhat artificial directory structure
|
||||||
|
List<Entry> entries = new ArrayList<Entry>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
|
||||||
|
entries.add(new XMPEntry(entry.getKey(), new XMPDirectory(entry.getValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new XMPDirectory(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getChildTextValue(Node node) {
|
||||||
|
Object value;
|
||||||
|
Node child = node.getFirstChild();
|
||||||
|
|
||||||
|
String strVal = null;
|
||||||
|
if (child != null) {
|
||||||
|
strVal = child.getNodeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strVal != null ? strVal.trim() : "";
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Iterable<? extends Node> asIterable(final NamedNodeMap pNodeList) {
|
||||||
|
return new Iterable<Node>() {
|
||||||
|
public Iterator<Node> iterator() {
|
||||||
|
return new Iterator<Node>() {
|
||||||
|
private int mIndex;
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return pNodeList != null && pNodeList.getLength() > mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node next() {
|
||||||
|
return pNodeList.item(mIndex++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("Method remove not supported");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Iterable<? extends Node> asIterable(final NodeList pNodeList) {
|
||||||
|
return new Iterable<Node>() {
|
||||||
|
public Iterator<Node> iterator() {
|
||||||
|
return new Iterator<Node>() {
|
||||||
|
private int mIndex;
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return pNodeList != null && pNodeList.getLength() > mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node next() {
|
||||||
|
return pNodeList.item(mIndex++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("Method remove not supported");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009, 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.xmp;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPScanner
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPScanner.java,v 1.0 Nov 11, 2009 4:49:00 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class XMPScanner {
|
||||||
|
/**
|
||||||
|
* {@code <?xpacket begin=}
|
||||||
|
* <p/>
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* 8-bit (UTF-8):
|
||||||
|
* 0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20
|
||||||
|
* 0x62 0x65 0x67 0x69 0x6E 0x3D
|
||||||
|
* </li>
|
||||||
|
* <li>16-bit encoding (UCS-2, UTF-16): (either big- or little-endian order)
|
||||||
|
* 0x3C 0x00 0x3F 0x00 0x78 0x00 0x70 0x00 0x61 0x00
|
||||||
|
* 0x63 0x00 0x6B 0x00 0x65 0x00 0x74 0x00 0x20 0x00 0x62 0x00
|
||||||
|
* 0x65 0x00 0x67 0x00 0x69 0x00 0x6E 0x00 0x3D [0x00]
|
||||||
|
* </li>
|
||||||
|
* <li>32-bit encoding (UCS-4):
|
||||||
|
* As 16 bit UCS2, with three 0x00 instead of one.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private static final byte[] XMP_PACKET_BEGIN = {
|
||||||
|
0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20,
|
||||||
|
0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code <?xpacket end=}
|
||||||
|
*/
|
||||||
|
private static final byte[] XMP_PACKET_END = {
|
||||||
|
0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20,
|
||||||
|
0x65, 0x6E, 0x64, 0x3D
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the given input for an XML metadata packet.
|
||||||
|
* The scanning process involves reading every byte in the file, while searching for an XMP packet.
|
||||||
|
* This process is very inefficient, compared to reading a known file format.
|
||||||
|
* <p/>
|
||||||
|
* <em>NOTE: The XMP Specification says this method of reading an XMP packet
|
||||||
|
* should be considered a last resort.</em><br/>
|
||||||
|
* This is because files may contain multiple XMP packets, some which may be related to embedded resources,
|
||||||
|
* some which may be obsolete (or even incomplete).
|
||||||
|
*
|
||||||
|
* @param pInput the input to scan. The input may be an {@link javax.imageio.stream.ImageInputStream} or
|
||||||
|
* any object that can be passed to {@link ImageIO#createImageInputStream(Object)}.
|
||||||
|
* Typically this may be a {@link File}, {@link InputStream} or {@link java.io.RandomAccessFile}.
|
||||||
|
*
|
||||||
|
* @return a character Reader
|
||||||
|
*
|
||||||
|
* @throws java.nio.charset.UnsupportedCharsetException if the encoding specified within the BOM is not supported
|
||||||
|
* by the JRE.
|
||||||
|
* @throws IOException if an I/O exception occurs reading from {@code pInput}.
|
||||||
|
* @see ImageIO#createImageInputStream(Object)
|
||||||
|
*/
|
||||||
|
static public Reader scanForXMPPacket(final Object pInput) throws IOException {
|
||||||
|
ImageInputStream stream = pInput instanceof ImageInputStream ? (ImageInputStream) pInput : ImageIO.createImageInputStream(pInput);
|
||||||
|
|
||||||
|
// TODO: Consider if BufferedIIS is a good idea
|
||||||
|
if (!(stream instanceof BufferedImageInputStream)) {
|
||||||
|
stream = new BufferedImageInputStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Might be more than one XMP block per file (it's possible to re-start for now)..
|
||||||
|
long pos;
|
||||||
|
pos = scanForSequence(stream, XMP_PACKET_BEGIN);
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
// Skip ' OR " (plus possible nulls for 16/32 bit)
|
||||||
|
byte quote = stream.readByte();
|
||||||
|
|
||||||
|
if (quote == '\'' || quote == '"') {
|
||||||
|
Charset cs = null;
|
||||||
|
|
||||||
|
// Read BOM
|
||||||
|
byte[] bom = new byte[4];
|
||||||
|
stream.readFully(bom);
|
||||||
|
|
||||||
|
// NOTE: Empty string should be treated as UTF-8 for backwards compatibility
|
||||||
|
if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF && bom[3] == quote ||
|
||||||
|
bom[0] == quote) {
|
||||||
|
// UTF-8
|
||||||
|
cs = Charset.forName("UTF-8");
|
||||||
|
}
|
||||||
|
else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF && bom[2] == 0x00 && bom[3] == quote) {
|
||||||
|
// UTF-16 BIG endian
|
||||||
|
cs = Charset.forName("UTF-16BE");
|
||||||
|
}
|
||||||
|
else if (bom[0] == 0x00 && bom[1] == (byte) 0xFF && bom[2] == (byte) 0xFE && bom[3] == quote) {
|
||||||
|
stream.skipBytes(1); // Alignment
|
||||||
|
|
||||||
|
// UTF-16 little endian
|
||||||
|
cs = Charset.forName("UTF-16LE");
|
||||||
|
}
|
||||||
|
else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF) {
|
||||||
|
// NOTE: 32-bit character set not supported by default
|
||||||
|
// UTF 32 BIG endian
|
||||||
|
cs = Charset.forName("UTF-32BE");
|
||||||
|
}
|
||||||
|
else if (bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) {
|
||||||
|
// TODO: FixMe..
|
||||||
|
// NOTE: 32-bit character set not supported by default
|
||||||
|
// UTF 32 little endian
|
||||||
|
cs = Charset.forName("UTF-32LE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cs != null) {
|
||||||
|
// Read all bytes until <?xpacket end= up-front or filter stream
|
||||||
|
stream.mark();
|
||||||
|
long end = scanForSequence(stream, XMP_PACKET_END);
|
||||||
|
stream.reset();
|
||||||
|
|
||||||
|
long length = end - stream.getStreamPosition();
|
||||||
|
Reader reader = new InputStreamReader(IIOUtil.createStreamAdapter(stream, length), cs);
|
||||||
|
|
||||||
|
// Skip until ?>
|
||||||
|
while (reader.read() != '>') {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return reader?
|
||||||
|
// How to decide between w or r?!
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans for a given ASCII sequence.
|
||||||
|
*
|
||||||
|
* @param pStream the stream to scan
|
||||||
|
* @param pSequence the byte sequence to search for
|
||||||
|
*
|
||||||
|
* @return the start position of the given sequence.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O exception occurs during scanning
|
||||||
|
*/
|
||||||
|
private static long scanForSequence(final ImageInputStream pStream, final byte[] pSequence) throws IOException {
|
||||||
|
long start = -1l;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int nullBytes = 0;
|
||||||
|
|
||||||
|
for (int read; (read = pStream.read()) >= 0;) {
|
||||||
|
if (pSequence[index] == (byte) read) {
|
||||||
|
// If this is the first byte in the sequence, store position
|
||||||
|
if (start == -1) {
|
||||||
|
start = pStream.getStreamPosition() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside the sequence, there might be 1 or 3 null bytes, depending on 16/32 byte encoding
|
||||||
|
if (nullBytes == 1 || nullBytes == 3) {
|
||||||
|
pStream.skipBytes(nullBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// If we found the entire sequence, we're done, return start position
|
||||||
|
if (index == pSequence.length) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (index == 1 && read == 0 && nullBytes < 3) {
|
||||||
|
// Skip 1 or 3 null bytes for 16/32 bit encoding
|
||||||
|
nullBytes++;
|
||||||
|
}
|
||||||
|
else if (index != 0) {
|
||||||
|
// Start over
|
||||||
|
index = 0;
|
||||||
|
start = -1;
|
||||||
|
nullBytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1l;
|
||||||
|
}
|
||||||
|
|
||||||
|
//static public XMPDirectory parse(input);
|
||||||
|
|
||||||
|
public static void main(final String[] pArgs) throws IOException {
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(new File(pArgs[0]));
|
||||||
|
|
||||||
|
Reader xmp;
|
||||||
|
while ((xmp = scanForXMPPacket(stream)) != null) {
|
||||||
|
BufferedReader reader = new BufferedReader(xmp);
|
||||||
|
String line;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
// else {
|
||||||
|
// System.err.println("XMP not found");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
package com.twelvemonkeys.imageio.metadata.exif;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RationalTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: RationalTestCase.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class RationalTestCase extends TestCase {
|
||||||
|
public void testZeroDenominator() {
|
||||||
|
try {
|
||||||
|
new Rational(1, 0);
|
||||||
|
fail("IllegalArgumentException expected");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Find a solution to this problem, as we should be able to work with it...
|
||||||
|
public void testLongMinValue() {
|
||||||
|
try {
|
||||||
|
new Rational(Long.MIN_VALUE, 1);
|
||||||
|
fail("IllegalArgumentException expected");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Rational(1, Long.MIN_VALUE);
|
||||||
|
fail("IllegalArgumentException expected");
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEquals() {
|
||||||
|
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
||||||
|
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
||||||
|
assertEquals(new Rational(1, 2), new Rational(1000000, 2000000));
|
||||||
|
assertEquals(new Rational(1, -2), new Rational(-1, 2));
|
||||||
|
|
||||||
|
Rational x = new Rational(1, -2);
|
||||||
|
Rational y = new Rational(-1000000, 2000000);
|
||||||
|
assertEquals(x, y);
|
||||||
|
assertEquals(x.numerator(), y.numerator());
|
||||||
|
assertEquals(x.denominator(), y.denominator());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEqualsBoundaries() {
|
||||||
|
assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1));
|
||||||
|
|
||||||
|
// NOTE: Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE... :-P
|
||||||
|
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MIN_VALUE + 1), new Rational(1, 1));
|
||||||
|
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MAX_VALUE), new Rational(-1, 1));
|
||||||
|
assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReciprocal() {
|
||||||
|
assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal());
|
||||||
|
assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNegate() {
|
||||||
|
assertEquals(new Rational(-1, 99), new Rational(1, 99).negate());
|
||||||
|
assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPlus() {
|
||||||
|
Rational x, y;
|
||||||
|
|
||||||
|
// 1/2 + 1/3 = 5/6
|
||||||
|
x = new Rational(1, 2);
|
||||||
|
y = new Rational(1, 3);
|
||||||
|
assertEquals(new Rational(5, 6), x.plus(y));
|
||||||
|
|
||||||
|
// 8/9 + 1/9 = 1
|
||||||
|
x = new Rational(8, 9);
|
||||||
|
y = new Rational(1, 9);
|
||||||
|
assertEquals(new Rational(1, 1), x.plus(y));
|
||||||
|
|
||||||
|
// 1/200000000 + 1/300000000 = 1/120000000
|
||||||
|
x = new Rational(1, 200000000);
|
||||||
|
y = new Rational(1, 300000000);
|
||||||
|
assertEquals(new Rational(1, 120000000), x.plus(y));
|
||||||
|
|
||||||
|
// 1073741789/20 + 1073741789/30 = 1073741789/12
|
||||||
|
x = new Rational(1073741789, 20);
|
||||||
|
y = new Rational(1073741789, 30);
|
||||||
|
assertEquals(new Rational(1073741789, 12), x.plus(y));
|
||||||
|
|
||||||
|
// x + 0 = x
|
||||||
|
assertEquals(x, x.plus(Rational.ZERO));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTimes() {
|
||||||
|
Rational x, y;
|
||||||
|
|
||||||
|
// 4/17 * 17/4 = 1
|
||||||
|
x = new Rational(4, 17);
|
||||||
|
y = new Rational(17, 4);
|
||||||
|
assertEquals(new Rational(1, 1), x.times(y));
|
||||||
|
|
||||||
|
// 3037141/3247033 * 3037547/3246599 = 841/961
|
||||||
|
x = new Rational(3037141, 3247033);
|
||||||
|
y = new Rational(3037547, 3246599);
|
||||||
|
assertEquals(new Rational(841, 961), x.times(y));
|
||||||
|
|
||||||
|
// x * 0 = 0
|
||||||
|
assertEquals(Rational.ZERO, x.times(Rational.ZERO));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMinus() {
|
||||||
|
// 1/6 - -4/-8 = -1/3
|
||||||
|
Rational x = new Rational(1, 6);
|
||||||
|
Rational y = new Rational(-4, -8);
|
||||||
|
assertEquals(new Rational(-1, 3), x.minus(y));
|
||||||
|
|
||||||
|
// x - 0 = x
|
||||||
|
assertEquals(x, x.minus(Rational.ZERO));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDivides() {
|
||||||
|
// 3037141/3247033 / 3246599/3037547 = 841/961
|
||||||
|
Rational x = new Rational(3037141, 3247033);
|
||||||
|
Rational y = new Rational(3246599, 3037547);
|
||||||
|
assertEquals(new Rational(841, 961), x.divides(y));
|
||||||
|
|
||||||
|
// 0 / x = 0
|
||||||
|
assertEquals(Rational.ZERO, new Rational(0, 386).divides(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDivideZero() {
|
||||||
|
try {
|
||||||
|
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
||||||
|
}
|
||||||
|
catch (ArithmeticException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XMPScannerTestCase
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: XMPScannerTestCase.java,v 1.0 Nov 13, 2009 3:59:43 PM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class XMPScannerTestCase extends TestCase {
|
||||||
|
|
||||||
|
static final String XMP =
|
||||||
|
"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>" +
|
||||||
|
"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 4.1-c036 46.276720, Fri Nov 13 2009 15:59:43 \">\n"+
|
||||||
|
" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"+
|
||||||
|
" <rdf:Description rdf:about=\"\"\n"+
|
||||||
|
" xmlns:photoshop=\"http://ns.adobe.com/photoshop/1.0/\">\n"+
|
||||||
|
" <photoshop:Source>twelvemonkeys.com</photoshop:Source>\n"+
|
||||||
|
" </rdf:Description>\n"+
|
||||||
|
" <rdf:Description rdf:about=\"\"\n"+
|
||||||
|
" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n"+
|
||||||
|
" <dc:format>application/vnd.adobe.photoshop</dc:format>\n"+
|
||||||
|
" </rdf:Description>\n"+
|
||||||
|
" </rdf:RDF>\n"+
|
||||||
|
"</x:xmpmeta>" +
|
||||||
|
"<?xpacket end=\"w\"?>";
|
||||||
|
|
||||||
|
final Random mRandom = new Random(4934638567l);
|
||||||
|
|
||||||
|
private InputStream createRandomStream(final int pLength) {
|
||||||
|
byte[] bytes = new byte[pLength];
|
||||||
|
mRandom.nextBytes(bytes);
|
||||||
|
return new ByteArrayInputStream(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream createXMPStream(final String pXMP, final String pCharsetName) {
|
||||||
|
try {
|
||||||
|
return new SequenceInputStream(
|
||||||
|
Collections.enumeration(
|
||||||
|
Arrays.asList(
|
||||||
|
createRandomStream(79),
|
||||||
|
new ByteArrayInputStream(pXMP.getBytes(pCharsetName)),
|
||||||
|
createRandomStream(31)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e) {
|
||||||
|
UnsupportedCharsetException uce = new UnsupportedCharsetException(pCharsetName);
|
||||||
|
uce.initCause(e);
|
||||||
|
throw uce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF8() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-8");
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF8singleQuote() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'"));
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF16BE() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-16BE");
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF16BEsingleQuote() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'"));
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF16LE() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-16LE");
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScanForUTF16LEsingleQuote() throws IOException {
|
||||||
|
InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'"));
|
||||||
|
|
||||||
|
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
|
||||||
|
assertNotNull(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Default Java installation on OS X don't seem to have UTF-32 installed. Hmmm..
|
||||||
|
// public void testUTF32BE() throws IOException {
|
||||||
|
// InputStream stream = createXMPStream("UTF-32BE");
|
||||||
|
//
|
||||||
|
// Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
//
|
||||||
|
// assertNotNull(reader);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void testUTF32LE() throws IOException {
|
||||||
|
// InputStream stream = createXMPStream("UTF-32LE");
|
||||||
|
//
|
||||||
|
// Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||||
|
//
|
||||||
|
// assertNotNull(reader);
|
||||||
|
// }
|
||||||
|
}
|
@ -29,6 +29,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<!-- Support -->
|
<!-- Support -->
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
|
<module>metadata</module>
|
||||||
|
|
||||||
<!-- Stand-alone readers/writers -->
|
<!-- Stand-alone readers/writers -->
|
||||||
<module>ico</module>
|
<module>ico</module>
|
||||||
@ -96,6 +97,13 @@
|
|||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||||
|
<version>${imageio.core.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
@ -1,31 +1,35 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>twelvemonkeys-imageio-psd</artifactId>
|
<artifactId>twelvemonkeys-imageio-psd</artifactId>
|
||||||
<version>2.3-SNAPSHOT</version>
|
<version>2.3-SNAPSHOT</version>
|
||||||
<name>TwelveMonkeys ImageIO PSD plugin</name>
|
<name>TwelveMonkeys ImageIO PSD plugin</name>
|
||||||
<description>
|
<description>
|
||||||
ImageIO plugin for Adobe Photoshop Document (PSD).
|
ImageIO plugin for Adobe Photoshop Document (PSD).
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<version>2.3-SNAPSHOT</version>
|
<version>2.3-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
<dependency>
|
||||||
</project>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractMetadata
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||||
|
// TODO: Move to core...
|
||||||
|
|
||||||
|
protected AbstractMetadata(final boolean pStandardFormatSupported,
|
||||||
|
final String pNativeFormatName, final String pNativeFormatClassName,
|
||||||
|
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
|
||||||
|
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation returns {@code true}.
|
||||||
|
* Mutable subclasses should override this method.
|
||||||
|
*
|
||||||
|
* @return {@code true}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getAsTree(final String pFormatName) {
|
||||||
|
validateFormatName(pFormatName);
|
||||||
|
|
||||||
|
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||||
|
return getNativeTree();
|
||||||
|
}
|
||||||
|
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||||
|
return getStandardTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: What about extra formats??
|
||||||
|
throw new AssertionError("Unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||||
|
assertMutable();
|
||||||
|
|
||||||
|
validateFormatName(pFormatName);
|
||||||
|
|
||||||
|
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||||
|
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = pRoot.getFirstChild();
|
||||||
|
while (node != null) {
|
||||||
|
// TODO: Merge values from node into this
|
||||||
|
|
||||||
|
// Move to the next sibling
|
||||||
|
node = node.getNextSibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
assertMutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that this meta data is mutable.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if {@link #isReadOnly()} returns {@code true}.
|
||||||
|
*/
|
||||||
|
protected final void assertMutable() {
|
||||||
|
if (isReadOnly()) {
|
||||||
|
throw new IllegalStateException("Metadata is read-only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Node getNativeTree();
|
||||||
|
|
||||||
|
protected final void validateFormatName(final String pFormatName) {
|
||||||
|
String[] metadataFormatNames = getMetadataFormatNames();
|
||||||
|
|
||||||
|
if (metadataFormatNames != null) {
|
||||||
|
for (String metadataFormatName : metadataFormatNames) {
|
||||||
|
if (metadataFormatName.equals(pFormatName)) {
|
||||||
|
return; // Found, we're ok!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -103,10 +103,10 @@ Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
|
|||||||
}
|
}
|
||||||
|
|
||||||
public float[] toCIEXYZ(float[] colorvalue) {
|
public float[] toCIEXYZ(float[] colorvalue) {
|
||||||
throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
|
return sRGB.toCIEXYZ(toRGB(colorvalue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] fromCIEXYZ(float[] colorvalue) {
|
public float[] fromCIEXYZ(float[] colorvalue) {
|
||||||
throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
|
return sRGB.fromCIEXYZ(fromRGB(colorvalue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PSDAlhpaChannelInfo
|
* PSDAlphaChannelInfo
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
* @version $Id: PSDAlphaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
class PSDAlphaChannelInfo extends PSDImageResource {
|
class PSDAlphaChannelInfo extends PSDImageResource {
|
||||||
List<String> mNames;
|
List<String> mNames;
|
||||||
@ -50,6 +50,7 @@ class PSDAlphaChannelInfo extends PSDImageResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
mNames = new ArrayList<String>();
|
mNames = new ArrayList<String>();
|
||||||
|
|
||||||
long left = mSize;
|
long left = mSize;
|
||||||
while (left > 0) {
|
while (left > 0) {
|
||||||
String name = PSDUtil.readPascalString(pInput);
|
String name = PSDUtil.readPascalString(pInput);
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIF metadata.
|
* EXIF metadata.
|
||||||
@ -25,7 +18,6 @@ import java.util.List;
|
|||||||
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
||||||
*/
|
*/
|
||||||
final class PSDEXIF1Data extends PSDImageResource {
|
final class PSDEXIF1Data extends PSDImageResource {
|
||||||
// protected byte[] mData;
|
|
||||||
protected Directory mDirectory;
|
protected Directory mDirectory;
|
||||||
|
|
||||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||||
@ -35,308 +27,16 @@ final class PSDEXIF1Data extends PSDImageResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
// This is in essence an embedded TIFF file.
|
// This is in essence an embedded TIFF file.
|
||||||
// TODO: Extract TIFF parsing to more general purpose package
|
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||||
// TODO: Instead, read the byte data, store for later parsing (or store offset, and read on request)
|
mDirectory = new EXIFReader().read(pInput);
|
||||||
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize));
|
|
||||||
|
|
||||||
byte[] bom = new byte[2];
|
|
||||||
stream.readFully(bom);
|
|
||||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
|
||||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
}
|
|
||||||
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
|
||||||
throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream.readUnsignedShort() != 42) {
|
|
||||||
throw new IIOException("Wrong TIFF magic in EXIF data.");
|
|
||||||
}
|
|
||||||
|
|
||||||
long directoryOffset = stream.readUnsignedInt();
|
|
||||||
mDirectory = Directory.read(stream, directoryOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = toStringBuilder();
|
StringBuilder builder = toStringBuilder();
|
||||||
|
|
||||||
builder.append(", ").append(mDirectory);
|
builder.append(", ").append(mDirectory);
|
||||||
|
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TIFF Image file directory (IFD)
|
|
||||||
static class Directory implements Iterable<Entry> {
|
|
||||||
private List<Entry> mEntries = new ArrayList<Entry>();
|
|
||||||
|
|
||||||
private Directory() {}
|
|
||||||
|
|
||||||
public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
|
|
||||||
Directory directory = new Directory();
|
|
||||||
|
|
||||||
pInput.seek(pOffset);
|
|
||||||
int entryCount = pInput.readUnsignedShort();
|
|
||||||
for (int i = 0; i < entryCount; i++) {
|
|
||||||
directory.mEntries.add(Entry.read(pInput));
|
|
||||||
}
|
|
||||||
|
|
||||||
long nextOffset = pInput.readUnsignedInt();
|
|
||||||
if (nextOffset != 0) {
|
|
||||||
Directory next = Directory.read(pInput, nextOffset);
|
|
||||||
directory.mEntries.addAll(next.mEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry get(int pTag) {
|
|
||||||
for (Entry entry : mEntries) {
|
|
||||||
if (entry.mTag == pTag) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<Entry> iterator() {
|
|
||||||
return mEntries.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("Directory%s", mEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TIFF IFD Entry
|
|
||||||
static class Entry {
|
|
||||||
private static final int EXIF_IFD = 0x8769;
|
|
||||||
|
|
||||||
private final static String[] TYPE_NAMES = {
|
|
||||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
|
||||||
|
|
||||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
|
||||||
};
|
|
||||||
|
|
||||||
private final static int[] TYPE_LENGTHS = {
|
|
||||||
1, 1, 2, 4, 8,
|
|
||||||
|
|
||||||
1, 1, 2, 4, 8, 4, 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
private int mTag;
|
|
||||||
/*
|
|
||||||
1 = BYTE 8-bit unsigned integer.
|
|
||||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
|
||||||
must be NUL (binary zero).
|
|
||||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
|
||||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
|
||||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
|
||||||
fraction; the second, the denominator.
|
|
||||||
|
|
||||||
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.
|
|
||||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
|
||||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
|
||||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
|
||||||
fraction, the second the denominator.
|
|
||||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
|
||||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
|
||||||
*/
|
|
||||||
private short mType;
|
|
||||||
private int mCount;
|
|
||||||
private long mValueOffset;
|
|
||||||
private Object mValue;
|
|
||||||
|
|
||||||
private Entry() {}
|
|
||||||
|
|
||||||
public static Entry read(final ImageInputStream pInput) throws IOException {
|
|
||||||
Entry entry = new Entry();
|
|
||||||
|
|
||||||
entry.mTag = pInput.readUnsignedShort();
|
|
||||||
entry.mType = pInput.readShort();
|
|
||||||
entry.mCount = pInput.readInt(); // Number of values
|
|
||||||
|
|
||||||
// TODO: Handle other sub-IFDs
|
|
||||||
if (entry.mTag == EXIF_IFD) {
|
|
||||||
long offset = pInput.readUnsignedInt();
|
|
||||||
pInput.mark();
|
|
||||||
try {
|
|
||||||
entry.mValue = Directory.read(pInput, offset);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
pInput.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int valueLength = entry.getValueLength();
|
|
||||||
if (valueLength > 0 && valueLength <= 4) {
|
|
||||||
entry.readValueInLine(pInput);
|
|
||||||
pInput.skipBytes(4 - valueLength);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
|
||||||
entry.readValue(pInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readValue(final ImageInputStream pInput) throws IOException {
|
|
||||||
long pos = pInput.getStreamPosition();
|
|
||||||
try {
|
|
||||||
pInput.seek(mValueOffset);
|
|
||||||
readValueInLine(pInput);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
pInput.seek(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readValueInLine(ImageInputStream pInput) throws IOException {
|
|
||||||
mValue = readValueDirect(pInput, mType, mCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
|
||||||
switch (pType) {
|
|
||||||
case 2:
|
|
||||||
// TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
|
|
||||||
byte[] ascii = new byte[pCount];
|
|
||||||
pInput.readFully(ascii);
|
|
||||||
return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
|
|
||||||
case 1:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedByte();
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readByte();
|
|
||||||
}
|
|
||||||
case 7:
|
|
||||||
byte[] bytes = new byte[pCount];
|
|
||||||
pInput.readFully(bytes);
|
|
||||||
return bytes;
|
|
||||||
case 3:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedShort();
|
|
||||||
}
|
|
||||||
case 8:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
short[] shorts = new short[pCount];
|
|
||||||
pInput.readFully(shorts, 0, shorts.length);
|
|
||||||
return shorts;
|
|
||||||
case 4:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
case 9:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] ints = new int[pCount];
|
|
||||||
pInput.readFully(ints, 0, ints.length);
|
|
||||||
return ints;
|
|
||||||
case 11:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readFloat();
|
|
||||||
}
|
|
||||||
|
|
||||||
float[] floats = new float[pCount];
|
|
||||||
pInput.readFully(floats, 0, floats.length);
|
|
||||||
return floats;
|
|
||||||
case 12:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] doubles = new double[pCount];
|
|
||||||
pInput.readFully(doubles, 0, doubles.length);
|
|
||||||
return doubles;
|
|
||||||
|
|
||||||
// TODO: Consider using a Rational class
|
|
||||||
case 5:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] rationals = new double[pCount];
|
|
||||||
for (int i = 0; i < rationals.length; i++) {
|
|
||||||
rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return rationals;
|
|
||||||
case 10:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readInt() / (double) pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] srationals = new double[pCount];
|
|
||||||
for (int i = 0; i < srationals.length; i++) {
|
|
||||||
srationals[i] = pInput.readInt() / (double) pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return srationals;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getValueLength() {
|
|
||||||
if (mType > 0 && mType <= TYPE_LENGTHS.length) {
|
|
||||||
return TYPE_LENGTHS[mType - 1] * mCount;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTypeName() {
|
|
||||||
if (mType > 0 && mType <= TYPE_NAMES.length) {
|
|
||||||
return TYPE_NAMES[mType - 1];
|
|
||||||
}
|
|
||||||
return "Unknown type";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Tag names!
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getValueAsString() {
|
|
||||||
if (mValue instanceof String) {
|
|
||||||
return String.format("\"%s\"", mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mValue != null && mValue.getClass().isArray()) {
|
|
||||||
Class<?> type = mValue.getClass().getComponentType();
|
|
||||||
if (byte.class == type) {
|
|
||||||
return Arrays.toString((byte[]) mValue);
|
|
||||||
}
|
|
||||||
if (short.class == type) {
|
|
||||||
return Arrays.toString((short[]) mValue);
|
|
||||||
}
|
|
||||||
if (int.class == type) {
|
|
||||||
return Arrays.toString((int[]) mValue);
|
|
||||||
}
|
|
||||||
if (float.class == type) {
|
|
||||||
return Arrays.toString((float[]) mValue);
|
|
||||||
}
|
|
||||||
if (double.class == type) {
|
|
||||||
return Arrays.toString((double[]) mValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.valueOf(mValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -48,17 +48,19 @@ class PSDGlobalLayerMask {
|
|||||||
final int mKind;
|
final int mKind;
|
||||||
|
|
||||||
PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException {
|
PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException {
|
||||||
mColorSpace = pInput.readUnsignedShort();
|
mColorSpace = pInput.readUnsignedShort(); // Undocumented
|
||||||
|
|
||||||
mColor1 = pInput.readUnsignedShort();
|
mColor1 = pInput.readUnsignedShort();
|
||||||
mColor2 = pInput.readUnsignedShort();
|
mColor2 = pInput.readUnsignedShort();
|
||||||
mColor3 = pInput.readUnsignedShort();
|
mColor3 = pInput.readUnsignedShort();
|
||||||
mColor4 = pInput.readUnsignedShort();
|
mColor4 = pInput.readUnsignedShort();
|
||||||
|
|
||||||
mOpacity = pInput.readUnsignedShort();
|
mOpacity = pInput.readUnsignedShort(); // 0-100
|
||||||
|
|
||||||
|
mKind = pInput.readUnsignedByte(); // 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer
|
||||||
|
|
||||||
|
// TODO: Variable: Filler zeros
|
||||||
|
|
||||||
mKind = pInput.readUnsignedByte();
|
|
||||||
|
|
||||||
pInput.readByte(); // Pad
|
pInput.readByte(); // Pad
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.charset.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PSDIPTCData
|
* PSDIPTCData
|
||||||
@ -18,8 +14,6 @@ import java.util.*;
|
|||||||
* @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$
|
* @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class PSDIPTCData extends PSDImageResource {
|
final class PSDIPTCData extends PSDImageResource {
|
||||||
// TODO: Refactor to be more like PSDEXIF1Data...
|
|
||||||
// TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s)
|
|
||||||
Directory mDirectory;
|
Directory mDirectory;
|
||||||
|
|
||||||
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||||
@ -28,7 +22,8 @@ final class PSDIPTCData extends PSDImageResource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
mDirectory = Directory.read(pInput, mSize);
|
// Read IPTC directory
|
||||||
|
mDirectory = new IPTCReader().read(pInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -36,383 +31,7 @@ final class PSDIPTCData extends PSDImageResource {
|
|||||||
StringBuilder builder = toStringBuilder();
|
StringBuilder builder = toStringBuilder();
|
||||||
builder.append(", ").append(mDirectory);
|
builder.append(", ").append(mDirectory);
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Entry {
|
|
||||||
private int mTagId;
|
|
||||||
private String mValue;
|
|
||||||
|
|
||||||
public Entry(int pTagId, String pValue) {
|
|
||||||
mTagId = pTagId;
|
|
||||||
mValue = pValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return (mTagId >> 8) + ":" + (mTagId & 0xff) + ": " + mValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Directory implements Iterable<Entry> {
|
|
||||||
private static final int ENCODING_UNKNOWN = -1;
|
|
||||||
private static final int ENCODING_UNSPECIFIED = 0;
|
|
||||||
private static final int ENCODING_UTF_8 = 0x1b2547;
|
|
||||||
|
|
||||||
private int mEncoding = ENCODING_UNSPECIFIED;
|
|
||||||
final List<Entry> mEntries = new ArrayList<Entry>();
|
|
||||||
|
|
||||||
private Directory() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Directory" + mEntries.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<Entry> iterator() {
|
|
||||||
return mEntries.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Directory read(final ImageInputStream pInput, final long pSize) throws IOException {
|
|
||||||
Directory directory = new Directory();
|
|
||||||
|
|
||||||
final long streamEnd = pInput.getStreamPosition() + pSize;
|
|
||||||
|
|
||||||
// For each tag
|
|
||||||
while (pInput.getStreamPosition() < streamEnd) {
|
|
||||||
// Identifies start of a tag
|
|
||||||
byte b = pInput.readByte();
|
|
||||||
if (b != 0x1c) {
|
|
||||||
throw new IIOException("Corrupt IPTC stream segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need at least four bytes left to read a tag
|
|
||||||
if (pInput.getStreamPosition() + 4 >= streamEnd) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int directoryType = pInput.readUnsignedByte();
|
|
||||||
int tagType = pInput.readUnsignedByte();
|
|
||||||
int tagByteCount = pInput.readUnsignedShort();
|
|
||||||
|
|
||||||
if (pInput.getStreamPosition() + tagByteCount > streamEnd) {
|
|
||||||
throw new IIOException("Data for tag extends beyond end of IPTC segment: " + (tagByteCount + pInput.getStreamPosition() - streamEnd));
|
|
||||||
}
|
|
||||||
|
|
||||||
directory.processTag(pInput, directoryType, tagType, tagByteCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTag(ImageInputStream pInput, int directoryType, int tagType, int tagByteCount) throws IOException {
|
|
||||||
int tagIdentifier = (directoryType << 8) | tagType;
|
|
||||||
|
|
||||||
String str = null;
|
|
||||||
switch (tagIdentifier) {
|
|
||||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
|
||||||
// TODO: Use this encoding!?
|
|
||||||
// TODO: Move somewhere else?
|
|
||||||
mEncoding = parseEncoding(pInput, tagByteCount);
|
|
||||||
return;
|
|
||||||
case IPTC.TAG_RECORD_VERSION:
|
|
||||||
// short
|
|
||||||
str = Integer.toString(pInput.readUnsignedShort());
|
|
||||||
break;
|
|
||||||
// case IPTC.TAG_RELEASE_DATE:
|
|
||||||
// case IPTC.TAG_EXPIRATION_DATE:
|
|
||||||
// case IPTC.TAG_REFERENCE_DATE:
|
|
||||||
// case IPTC.TAG_DATE_CREATED:
|
|
||||||
// case IPTC.TAG_DIGITAL_CREATION_DATE:
|
|
||||||
// // Date object
|
|
||||||
// Date date = parseISO8601DatePart(pInput, tagByteCount);
|
|
||||||
// if (date != null) {
|
|
||||||
// directory.setDate(tagIdentifier, date);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// case IPTC.TAG_RELEASE_TIME:
|
|
||||||
// case IPTC.TAG_EXPIRATION_TIME:
|
|
||||||
// case IPTC.TAG_TIME_CREATED:
|
|
||||||
// case IPTC.TAG_DIGITAL_CREATION_TIME:
|
|
||||||
// // NOTE: Spec says fields should be sent in order, so this is okay
|
|
||||||
// date = getDateForTime(directory, tagIdentifier);
|
|
||||||
//
|
|
||||||
// Date time = parseISO8601TimePart(pInput, tagByteCount, date);
|
|
||||||
// if (time != null) {
|
|
||||||
// directory.setDate(tagIdentifier, time);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
default:
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip non-Application fields, as they are typically not human readable
|
|
||||||
if (directoryType << 8 != IPTC.APPLICATION_RECORD) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have a value, treat it as a string
|
|
||||||
if (str == null) {
|
|
||||||
if (tagByteCount < 1) {
|
|
||||||
str = "(No value)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str = String.format("\"%s\"", parseString(pInput, tagByteCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mEntries.add(new Entry(tagIdentifier, str));
|
|
||||||
|
|
||||||
// if (directory.containsTag(tagIdentifier)) {
|
|
||||||
// // TODO: Does that REALLY help for performance?!
|
|
||||||
// // this fancy string[] business avoids using an ArrayList for performance reasons
|
|
||||||
// String[] oldStrings;
|
|
||||||
// String[] newStrings;
|
|
||||||
// try {
|
|
||||||
// oldStrings = directory.getStringArray(tagIdentifier);
|
|
||||||
// }
|
|
||||||
// catch (MetadataException e) {
|
|
||||||
// oldStrings = null;
|
|
||||||
// }
|
|
||||||
// if (oldStrings == null) {
|
|
||||||
// newStrings = new String[1];
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// newStrings = new String[oldStrings.length + 1];
|
|
||||||
// System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length);
|
|
||||||
// }
|
|
||||||
// newStrings[newStrings.length - 1] = str;
|
|
||||||
// directory.setStringArray(tagIdentifier, newStrings);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// directory.setString(tagIdentifier, str);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// private Date getDateForTime(final Directory directory, final int tagIdentifier) {
|
|
||||||
// int dateTag;
|
|
||||||
//
|
|
||||||
// switch (tagIdentifier) {
|
|
||||||
// case IPTC.TAG_RELEASE_TIME:
|
|
||||||
// dateTag = IPTC.TAG_RELEASE_DATE;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_EXPIRATION_TIME:
|
|
||||||
// dateTag = IPTC.TAG_EXPIRATION_DATE;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_TIME_CREATED:
|
|
||||||
// dateTag = IPTC.TAG_DATE_CREATED;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_DIGITAL_CREATION_TIME:
|
|
||||||
// dateTag = IPTC.TAG_DIGITAL_CREATION_DATE;
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// return new Date(0l);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return directory.containsTag(dateTag) ? directory.getDate(dateTag) : new Date(0l);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
|
|
||||||
return tagByteCount == 3
|
|
||||||
&& (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8
|
|
||||||
? ENCODING_UTF_8 : ENCODING_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private Date parseISO8601TimePart(final ImageInputStream pInputStream, int tagByteCount, final Date date) throws IOException {
|
|
||||||
// // ISO 8601: HHMMSS±HHMM
|
|
||||||
// if (tagByteCount >= 11) {
|
|
||||||
// String timeStr = parseString(pInputStream, tagByteCount);
|
|
||||||
// try {
|
|
||||||
// int hour = Integer.parseInt(timeStr.substring(0, 2));
|
|
||||||
// int minute = Integer.parseInt(timeStr.substring(2, 4));
|
|
||||||
// int second = Integer.parseInt(timeStr.substring(4, 6));
|
|
||||||
// String tzOffset = timeStr.substring(6, 11);
|
|
||||||
//
|
|
||||||
// TimeZone zone = new SimpleTimeZone(Integer.parseInt(tzOffset.charAt(0) == '+' ? tzOffset.substring(1) : tzOffset), tzOffset);
|
|
||||||
//
|
|
||||||
// GregorianCalendar calendar = new GregorianCalendar(zone);
|
|
||||||
// calendar.setTime(date);
|
|
||||||
//
|
|
||||||
// calendar.add(Calendar.HOUR_OF_DAY, hour);
|
|
||||||
// calendar.add(Calendar.MINUTE, minute);
|
|
||||||
// calendar.add(Calendar.SECOND, second);
|
|
||||||
//
|
|
||||||
// return calendar.getTime();
|
|
||||||
// }
|
|
||||||
// catch (NumberFormatException e) {
|
|
||||||
// // fall through and we'll store whatever was there as a String
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private Date parseISO8601DatePart(final ImageInputStream pInputStream, int tagByteCount) throws IOException {
|
|
||||||
// // ISO 8601: CCYYMMDD
|
|
||||||
// if (tagByteCount >= 8) {
|
|
||||||
// String dateStr = parseString(pInputStream, tagByteCount);
|
|
||||||
// try {
|
|
||||||
// int year = Integer.parseInt(dateStr.substring(0, 4));
|
|
||||||
// int month = Integer.parseInt(dateStr.substring(4, 6)) - 1;
|
|
||||||
// int day = Integer.parseInt(dateStr.substring(6, 8));
|
|
||||||
// GregorianCalendar calendar = new GregorianCalendar(year, month, day);
|
|
||||||
// return calendar.getTime();
|
|
||||||
// }
|
|
||||||
// catch (NumberFormatException e) {
|
|
||||||
// // fall through and we'll store whatever was there as a String
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: Pass encoding as parameter? Use if specified
|
|
||||||
private String parseString(final ImageInputStream pInput, int length) throws IOException {
|
|
||||||
// NOTE: The IPTC "spec" says ISO 646 or ISO 2022 encoding. UTF-8 contains all 646 characters, but not 2022.
|
|
||||||
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
|
|
||||||
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
|
||||||
String str;
|
|
||||||
Charset charset = Charset.forName("UTF-8");
|
|
||||||
CharsetDecoder decoder = charset.newDecoder();
|
|
||||||
CharBuffer chars;
|
|
||||||
byte[] data = new byte[length];
|
|
||||||
pInput.readFully(data);
|
|
||||||
try {
|
|
||||||
// Will fail fast on illegal UTF-8-sequences
|
|
||||||
chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
|
||||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
|
||||||
.decode(ByteBuffer.wrap(data));
|
|
||||||
str = chars.toString();
|
|
||||||
}
|
|
||||||
catch (CharacterCodingException notUTF8) {
|
|
||||||
if (mEncoding == ENCODING_UTF_8) {
|
|
||||||
throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to use ISO-8859-1
|
|
||||||
// This will not fail, but may may create wrong fallback-characters
|
|
||||||
str = StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static interface IPTC {
|
|
||||||
static final int ENVELOPE_RECORD = 1 << 8;
|
|
||||||
static final int APPLICATION_RECORD = 2 << 8;
|
|
||||||
|
|
||||||
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
|
||||||
|
|
||||||
/** 2:00 Record Version (mandatory) */
|
|
||||||
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
|
||||||
// /** 2:03 Object Type Reference */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:05 Object Name */
|
|
||||||
// public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
|
||||||
// /** 2:07 Edit Status */
|
|
||||||
// public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
|
||||||
// /** 2:08 Editorial Update */
|
|
||||||
// public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
|
||||||
// /** 2:10 Urgency */
|
|
||||||
// public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
|
||||||
// /** 2:12 Subect Reference (repeatable) */
|
|
||||||
// public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
|
||||||
// /** 2:15 Category */
|
|
||||||
// public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
|
||||||
// /** 2:20 Supplemental Category (repeatable) */
|
|
||||||
// public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
|
||||||
// /** 2:22 Fixture Identifier */
|
|
||||||
// public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
|
||||||
// /** 2:25 Keywords (repeatable) */
|
|
||||||
// public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
|
||||||
// /** 2:26 Content Locataion Code (repeatable) */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:30 Release Date */
|
|
||||||
// public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
|
||||||
// /** 2:35 Release Time */
|
|
||||||
// public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
|
||||||
// /** 2:37 Expiration Date */
|
|
||||||
// public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
|
||||||
// /** 2:38 Expiration Time */
|
|
||||||
// public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
|
||||||
// /** 2:40 Special Instructions */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:50 Reference Number (mandatory if 2:45 present) */
|
|
||||||
// public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
|
||||||
// /** 2:55 Date Created */
|
|
||||||
// public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
|
||||||
// /** 2:60 Time Created */
|
|
||||||
// public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
|
||||||
// /** 2:62 Digital Creation Date */
|
|
||||||
// public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
|
||||||
// /** 2:63 Digital Creation Date */
|
|
||||||
// public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
|
||||||
// /** 2:65 Originating Program */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:75 Object Cycle (a: morning, p: evening, b: both) */
|
|
||||||
// public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
|
||||||
// /** 2:80 By-line (repeatable) */
|
|
||||||
// public static final 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
|
|
||||||
// /** 2:90 City */
|
|
||||||
// public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
|
||||||
// /** 2:92 Sub-location */
|
|
||||||
// public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
|
||||||
// /** 2:95 Province/State */
|
|
||||||
// public static final 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;
|
|
||||||
// /** 2:101 Country/Primary Location Name */
|
|
||||||
// public static final 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
|
|
||||||
// /** 2:105 Headline */
|
|
||||||
// public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
|
||||||
// /** 2:110 Credit */
|
|
||||||
// public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
|
||||||
// /** 2:115 Source */
|
|
||||||
// public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
|
||||||
// /** 2:116 Copyright Notice */
|
|
||||||
// public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
|
||||||
// /** 2:118 Contact */
|
|
||||||
// public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
|
||||||
// /** 2:120 Catption/Abstract */
|
|
||||||
// public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
|
||||||
// /** 2:122 Writer/Editor (repeatable) */
|
|
||||||
// public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
|
||||||
// /** 2:125 Rasterized Caption (binary data) */
|
|
||||||
// public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
|
||||||
// /** 2:130 Image Type */
|
|
||||||
// public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
|
||||||
// /** 2:131 Image Orientation */
|
|
||||||
// public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
|
||||||
// /** 2:135 Language Identifier */
|
|
||||||
// public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
|
||||||
//
|
|
||||||
// // TODO: Should we expose this field?
|
|
||||||
// /**
|
|
||||||
// * 2:199 JobMinder Assignment Data (Custom IPTC field).
|
|
||||||
// * A common custom IPTC field used by a now discontinued application called JobMinder.
|
|
||||||
// *
|
|
||||||
// * @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
|
|
||||||
// */
|
|
||||||
// static final int CUSTOM_TAG_JOBMINDER_ASSIGMENT_DATA = APPLICATION_RECORD | 199;
|
|
||||||
//
|
|
||||||
// // TODO: 2:150-2:154 Audio and 2:200-2:202 Object Preview Data
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,9 @@ import java.util.List;
|
|||||||
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
// TODO: Implement ImageIO meta data interface
|
// TODO: Implement ImageIO meta data interface
|
||||||
// TODO: API for reading separate layers
|
// TODO: Allow reading the extra alpha channels (index after composite data)
|
||||||
|
// TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0)
|
||||||
|
// TODO: Support for API for reading separate layers (index after composite data, and optional alpha channels)
|
||||||
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
|
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
|
||||||
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
|
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
|
||||||
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
|
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
|
||||||
@ -1144,11 +1146,12 @@ public class PSDImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||||
|
serializer.setIndentation(" ");
|
||||||
serializer.serialize(node, true);
|
serializer.serialize(node, true);
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME);
|
node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME);
|
||||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
// serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||||
serializer.serialize(node, true);
|
serializer.serialize(node, true);
|
||||||
|
|
||||||
if (imageReader.hasThumbnails(0)) {
|
if (imageReader.hasThumbnails(0)) {
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
@ -62,11 +63,16 @@ class PSDImageResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mSize = pInput.readUnsignedInt();
|
mSize = pInput.readUnsignedInt();
|
||||||
readData(pInput);
|
long startPos = pInput.getStreamPosition();
|
||||||
|
|
||||||
// TODO: Sanity check reading here?
|
readData(new SubImageInputStream(pInput, mSize));
|
||||||
|
|
||||||
// Data is even-padded
|
// NOTE: This should never happen, however it's safer to keep it here to
|
||||||
|
if (pInput.getStreamPosition() != startPos + mSize) {
|
||||||
|
pInput.seek(startPos + mSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data is even-padded (word aligned)
|
||||||
if (mSize % 2 != 0) {
|
if (mSize % 2 != 0) {
|
||||||
pInput.read();
|
pInput.read();
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.iptc.IPTC;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
import com.twelvemonkeys.util.FilterIterator;
|
import com.twelvemonkeys.util.FilterIterator;
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -24,7 +21,7 @@ import java.util.List;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$
|
* @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
public final class PSDMetadata extends AbstractMetadata {
|
||||||
|
|
||||||
// TODO: Decide on image/stream metadata...
|
// TODO: Decide on image/stream metadata...
|
||||||
static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0";
|
static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0";
|
||||||
@ -60,98 +57,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
|
|
||||||
static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"};
|
static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"};
|
||||||
|
|
||||||
|
|
||||||
protected PSDMetadata() {
|
protected PSDMetadata() {
|
||||||
// TODO: Allow XMP, EXIF and IPTC as extra formats?
|
// TODO: Allow XMP, EXIF and IPTC as extra formats?
|
||||||
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
|
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReadOnly() {
|
|
||||||
// TODO: Extract to abstract metadata impl class?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getAsTree(final String pFormatName) {
|
|
||||||
validateFormatName(pFormatName);
|
|
||||||
|
|
||||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
|
||||||
return getNativeTree();
|
|
||||||
}
|
|
||||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
|
||||||
return getStandardTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertionError("Unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
|
||||||
// TODO: Extract to abstract metadata impl class?
|
|
||||||
assertMutable();
|
|
||||||
|
|
||||||
validateFormatName(pFormatName);
|
|
||||||
|
|
||||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
|
||||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node node = pRoot.getFirstChild();
|
|
||||||
while (node != null) {
|
|
||||||
// TODO: Merge values from node into this
|
|
||||||
|
|
||||||
// Move to the next sibling
|
|
||||||
node = node.getNextSibling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
// TODO: Extract to abstract metadata impl class?
|
|
||||||
assertMutable();
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Method reset not implemented"); // TODO: Implement
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Extract to abstract metadata impl class?
|
|
||||||
private void assertMutable() {
|
|
||||||
if (isReadOnly()) {
|
|
||||||
throw new IllegalStateException("Metadata is read-only");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Extract to abstract metadata impl class?
|
|
||||||
private void validateFormatName(final String pFormatName) {
|
|
||||||
String[] metadataFormatNames = getMetadataFormatNames();
|
|
||||||
|
|
||||||
if (metadataFormatNames != null) {
|
|
||||||
for (String metadataFormatName : metadataFormatNames) {
|
|
||||||
if (metadataFormatName.equals(pFormatName)) {
|
|
||||||
return; // Found, we're ok!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object clone() {
|
|
||||||
// TODO: Make it a deep clone
|
|
||||||
try {
|
|
||||||
return super.clone();
|
|
||||||
}
|
|
||||||
catch (CloneNotSupportedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Native format support
|
/// Native format support
|
||||||
|
|
||||||
private Node getNativeTree() {
|
@Override
|
||||||
|
protected Node getNativeTree() {
|
||||||
IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME);
|
IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME);
|
||||||
|
|
||||||
root.appendChild(createHeaderNode());
|
root.appendChild(createHeaderNode());
|
||||||
@ -195,6 +109,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
// TODO: Format spec
|
// TODO: Format spec
|
||||||
node = new IIOMetadataNode("ICCProfile");
|
node = new IIOMetadataNode("ICCProfile");
|
||||||
node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]);
|
node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]);
|
||||||
|
//
|
||||||
|
// FastByteArrayOutputStream data = new FastByteArrayOutputStream(0);
|
||||||
|
// EncoderStream base64 = new EncoderStream(data, new Base64Encoder(), true);
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// base64.write(profile.getProfile().getData());
|
||||||
|
// }
|
||||||
|
// catch (IOException ignore) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// byte[] bytes = data.toByteArray();
|
||||||
|
// node.setAttribute("data", StringUtil.decode(bytes, 0, bytes.length, "ASCII"));
|
||||||
node.setUserObject(profile.getProfile());
|
node.setUserObject(profile.getProfile());
|
||||||
}
|
}
|
||||||
else if (imageResource instanceof PSDAlphaChannelInfo) {
|
else if (imageResource instanceof PSDAlphaChannelInfo) {
|
||||||
@ -215,10 +141,12 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]);
|
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]);
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
for (short color : displayInfo.mColors) {
|
for (short color : displayInfo.mColors) {
|
||||||
if (builder.length() > 0) {
|
if (builder.length() > 0) {
|
||||||
builder.append(" ");
|
builder.append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.append(Integer.toString(color));
|
builder.append(Integer.toString(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,41 +249,46 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
}
|
}
|
||||||
else if (imageResource instanceof PSDIPTCData) {
|
else if (imageResource instanceof PSDIPTCData) {
|
||||||
// TODO: Revise/rethink this...
|
// TODO: Revise/rethink this...
|
||||||
// Transcode to XMP? ;-)
|
|
||||||
PSDIPTCData iptc = (PSDIPTCData) imageResource;
|
PSDIPTCData iptc = (PSDIPTCData) imageResource;
|
||||||
|
|
||||||
node = new IIOMetadataNode("IPTC");
|
node = new IIOMetadataNode("DirectoryResource");
|
||||||
|
node.setAttribute("type", "IPTC");
|
||||||
node.setUserObject(iptc.mDirectory);
|
node.setUserObject(iptc.mDirectory);
|
||||||
|
|
||||||
|
appendEntries(node, "IPTC", iptc.mDirectory);
|
||||||
}
|
}
|
||||||
else if (imageResource instanceof PSDEXIF1Data) {
|
else if (imageResource instanceof PSDEXIF1Data) {
|
||||||
// TODO: Revise/rethink this...
|
// TODO: Revise/rethink this...
|
||||||
// Transcode to XMP? ;-)
|
|
||||||
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
|
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
|
||||||
|
|
||||||
node = new IIOMetadataNode("EXIF");
|
node = new IIOMetadataNode("DirectoryResource");
|
||||||
|
node.setAttribute("type", "EXIF");
|
||||||
|
// TODO: Set byte[] data instead
|
||||||
node.setUserObject(exif.mDirectory);
|
node.setUserObject(exif.mDirectory);
|
||||||
|
|
||||||
|
appendEntries(node, "EXIF", exif.mDirectory);
|
||||||
}
|
}
|
||||||
else if (imageResource instanceof PSDXMPData) {
|
else if (imageResource instanceof PSDXMPData) {
|
||||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
||||||
|
// Or maybe use the Directory approach used by IPTC and EXIF..
|
||||||
PSDXMPData xmp = (PSDXMPData) imageResource;
|
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||||
|
|
||||||
node = new IIOMetadataNode("XMP");
|
node = new IIOMetadataNode("DirectoryResource");
|
||||||
|
node.setAttribute("type", "XMP");
|
||||||
|
appendEntries(node, "XMP", xmp.mDirectory);
|
||||||
|
|
||||||
try {
|
// Set the entire XMP document as user data
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
node.setUserObject(xmp.mData);
|
||||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
||||||
Document document = builder.parse(new InputSource(xmp.getData()));
|
|
||||||
|
|
||||||
// Set the entire XMP document as user data
|
|
||||||
node.setUserObject(document);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Generic resource..
|
// Generic resource..
|
||||||
node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId));
|
node = new IIOMetadataNode("ImageResource");
|
||||||
|
String value = PSDImageResource.resourceTypeForId(imageResource.mId);
|
||||||
|
if (!"UnknownResource".equals(value)) {
|
||||||
|
node.setAttribute("name", value);
|
||||||
|
}
|
||||||
|
node.setAttribute("length", String.valueOf(imageResource.mSize));
|
||||||
|
// TODO: Set user object: byte array
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: More resources
|
// TODO: More resources
|
||||||
@ -364,9 +297,43 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
resource.appendChild(node);
|
resource.appendChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Layers and layer info
|
||||||
|
|
||||||
|
// TODO: Global mask etc..
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void appendEntries(final IIOMetadataNode pNode, final String pType, final Directory pDirectory) {
|
||||||
|
for (Entry entry : pDirectory) {
|
||||||
|
Object tagId = entry.getIdentifier();
|
||||||
|
|
||||||
|
IIOMetadataNode tag = new IIOMetadataNode("Entry");
|
||||||
|
tag.setAttribute("tag", String.format("%s", tagId));
|
||||||
|
|
||||||
|
String field = entry.getFieldName();
|
||||||
|
if (field != null) {
|
||||||
|
tag.setAttribute("field", String.format("%s", field));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ("IPTC".equals(pType)) {
|
||||||
|
tag.setAttribute("field", String.format("%s:%s", (Integer) tagId >> 8, (Integer) tagId & 0xff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.getValue() instanceof Directory) {
|
||||||
|
appendEntries(tag, pType, (Directory) entry.getValue());
|
||||||
|
tag.setAttribute("type", "Directory");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tag.setAttribute("value", entry.getValueAsString());
|
||||||
|
tag.setAttribute("type", entry.getTypeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
pNode.appendChild(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Standard format support
|
/// Standard format support
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -461,7 +428,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
|
|
||||||
private String getMultiChannelCS(short pChannels) {
|
private String getMultiChannelCS(short pChannels) {
|
||||||
if (pChannels < 16) {
|
if (pChannels < 16) {
|
||||||
return Integer.toHexString(pChannels) + "CLR";
|
return String.format("%xCLR", pChannels);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels");
|
throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels");
|
||||||
@ -469,88 +436,101 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardCompressionNode() {
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
|
IIOMetadataNode compressionNode = new IIOMetadataNode("Compression");
|
||||||
IIOMetadataNode node; // scratch node
|
IIOMetadataNode node; // scratch node
|
||||||
|
|
||||||
node = new IIOMetadataNode("CompressionTypeName");
|
node = new IIOMetadataNode("CompressionTypeName");
|
||||||
String compression;
|
String compression;
|
||||||
|
|
||||||
switch (mCompression) {
|
switch (mCompression) {
|
||||||
case PSD.COMPRESSION_NONE:
|
case PSD.COMPRESSION_NONE:
|
||||||
compression = "none";
|
compression = "none";
|
||||||
break;
|
break;
|
||||||
case PSD.COMPRESSION_RLE:
|
case PSD.COMPRESSION_RLE:
|
||||||
compression = "packbits";
|
compression = "PackBits";
|
||||||
break;
|
break;
|
||||||
case PSD.COMPRESSION_ZIP:
|
case PSD.COMPRESSION_ZIP:
|
||||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||||
compression = "zip";
|
compression = "Deflate"; // TODO: ZLib? (TIFF native metadata format specifies both.. :-P)
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unreachable");
|
throw new AssertionError("Unreachable");
|
||||||
}
|
}
|
||||||
node.setAttribute("value", compression);
|
|
||||||
compression_node.appendChild(node);
|
|
||||||
|
|
||||||
|
node.setAttribute("value", compression);
|
||||||
|
compressionNode.appendChild(node);
|
||||||
|
|
||||||
|
// TODO: Does it make sense to specify lossless for compression "none"?
|
||||||
node = new IIOMetadataNode("Lossless");
|
node = new IIOMetadataNode("Lossless");
|
||||||
node.setAttribute("value", "true");
|
node.setAttribute("value", "true");
|
||||||
compression_node.appendChild(node);
|
compressionNode.appendChild(node);
|
||||||
|
|
||||||
return compression_node;
|
return compressionNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardDataNode() {
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
IIOMetadataNode data_node = new IIOMetadataNode("Data");
|
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||||
IIOMetadataNode node; // scratch node
|
IIOMetadataNode node; // scratch node
|
||||||
|
|
||||||
node = new IIOMetadataNode("PlanarConfiguration");
|
node = new IIOMetadataNode("PlanarConfiguration");
|
||||||
node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec
|
node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec
|
||||||
data_node.appendChild(node);
|
dataNode.appendChild(node);
|
||||||
|
|
||||||
node = new IIOMetadataNode("SampleFormat");
|
node = new IIOMetadataNode("SampleFormat");
|
||||||
node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral");
|
node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral");
|
||||||
data_node.appendChild(node);
|
dataNode.appendChild(node);
|
||||||
|
|
||||||
String bitDepth = Integer.toString(mHeader.mBits); // bits per plane
|
String bitDepth = Integer.toString(mHeader.mBits); // bits per plane
|
||||||
|
|
||||||
// TODO: Channels might be 5 for RGB + A + Mask...
|
// TODO: Channels might be 5 for RGB + A + Mask...
|
||||||
String[] bps = new String[mHeader.mChannels];
|
String[] bps = new String[mHeader.mChannels];
|
||||||
Arrays.fill(bps, bitDepth);
|
Arrays.fill(bps, bitDepth);
|
||||||
|
|
||||||
node = new IIOMetadataNode("BitsPerSample");
|
node = new IIOMetadataNode("BitsPerSample");
|
||||||
node.setAttribute("value", StringUtil.toCSVString(bps, " "));
|
node.setAttribute("value", StringUtil.toCSVString(bps, " "));
|
||||||
data_node.appendChild(node);
|
dataNode.appendChild(node);
|
||||||
|
|
||||||
// TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed?
|
// TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed?
|
||||||
|
|
||||||
return data_node;
|
return dataNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardDimensionNode() {
|
protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
|
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||||
IIOMetadataNode node; // scratch node
|
IIOMetadataNode node; // scratch node
|
||||||
|
|
||||||
node = new IIOMetadataNode("PixelAspectRatio");
|
node = new IIOMetadataNode("PixelAspectRatio");
|
||||||
// TODO: This is not incorrect wrt resolution info
|
|
||||||
float ratio = 1f;
|
// TODO: This is not correct wrt resolution info
|
||||||
node.setAttribute("value", Float.toString(ratio));
|
float aspect = 1f;
|
||||||
dimension_node.appendChild(node);
|
|
||||||
|
Iterator<PSDPixelAspectRatio> ratios = getResources(PSDPixelAspectRatio.class);
|
||||||
|
if (ratios.hasNext()) {
|
||||||
|
PSDPixelAspectRatio ratio = ratios.next();
|
||||||
|
aspect = (float) ratio.mAspect;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setAttribute("value", Float.toString(aspect));
|
||||||
|
dimensionNode.appendChild(node);
|
||||||
|
|
||||||
node = new IIOMetadataNode("ImageOrientation");
|
node = new IIOMetadataNode("ImageOrientation");
|
||||||
node.setAttribute("value", "Normal");
|
node.setAttribute("value", "Normal");
|
||||||
dimension_node.appendChild(node);
|
dimensionNode.appendChild(node);
|
||||||
|
|
||||||
|
// TODO: If no PSDResolutionInfo, this might still be available in the EXIF data...
|
||||||
Iterator<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class);
|
Iterator<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class);
|
||||||
if (!resolutionInfos.hasNext()) {
|
if (!resolutionInfos.hasNext()) {
|
||||||
PSDResolutionInfo resolutionInfo = resolutionInfos.next();
|
PSDResolutionInfo resolutionInfo = resolutionInfos.next();
|
||||||
|
|
||||||
node = new IIOMetadataNode("HorizontalPixelSize");
|
node = new IIOMetadataNode("HorizontalPixelSize");
|
||||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes)));
|
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes)));
|
||||||
dimension_node.appendChild(node);
|
dimensionNode.appendChild(node);
|
||||||
|
|
||||||
node = new IIOMetadataNode("VerticalPixelSize");
|
node = new IIOMetadataNode("VerticalPixelSize");
|
||||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes)));
|
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes)));
|
||||||
dimension_node.appendChild(node);
|
dimensionNode.appendChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
@ -580,7 +560,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
<!-- Data type: Integer -->
|
<!-- Data type: Integer -->
|
||||||
|
|
||||||
*/
|
*/
|
||||||
return dimension_node;
|
return dimensionNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float asMM(final short pUnit, final float pResolution) {
|
private static float asMM(final short pUnit, final float pResolution) {
|
||||||
@ -603,18 +583,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
PSDEXIF1Data data = exif.next();
|
PSDEXIF1Data data = exif.next();
|
||||||
|
|
||||||
// Get the EXIF DateTime (aka ModifyDate) tag if present
|
// Get the EXIF DateTime (aka ModifyDate) tag if present
|
||||||
PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant
|
Entry dateTime = data.mDirectory.getEntryById(TIFF.TAG_DATE_TIME);
|
||||||
if (dateTime != null) {
|
if (dateTime != null) {
|
||||||
node = new IIOMetadataNode("ImageModificationTime");
|
node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime
|
||||||
// Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P)
|
// Format: "YYYY:MM:DD hh:mm:ss"
|
||||||
String value = dateTime.getValueAsString();
|
String value = dateTime.getValueAsString();
|
||||||
|
|
||||||
node.setAttribute("year", value.substring(1, 5));
|
node.setAttribute("year", value.substring(0, 4));
|
||||||
node.setAttribute("month", value.substring(6, 8));
|
node.setAttribute("month", value.substring(5, 7));
|
||||||
node.setAttribute("day", value.substring(9, 11));
|
node.setAttribute("day", value.substring(8, 10));
|
||||||
node.setAttribute("hour", value.substring(12, 14));
|
node.setAttribute("hour", value.substring(11, 13));
|
||||||
node.setAttribute("minute", value.substring(15, 17));
|
node.setAttribute("minute", value.substring(14, 16));
|
||||||
node.setAttribute("second", value.substring(18, 20));
|
node.setAttribute("second", value.substring(17, 19));
|
||||||
|
|
||||||
document_node.appendChild(node);
|
document_node.appendChild(node);
|
||||||
}
|
}
|
||||||
@ -625,61 +605,95 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardTextNode() {
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
// TODO: CaptionDigest?, EXIF, XMP
|
// TODO: TIFF uses
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||||
|
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||||
|
// Example: TIFF Software field => /Text/TextEntry@keyword = "Software",
|
||||||
|
// /Text/TextEntry@value = Name and version number of the software package(s) used to create the image.
|
||||||
|
|
||||||
Iterator<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class);
|
Iterator<PSDImageResource> textResources = getResources(PSD.RES_IPTC_NAA, PSD.RES_EXIF_DATA_1, PSD.RES_XMP_DATA);
|
||||||
|
|
||||||
|
if (!textResources.hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
|
IIOMetadataNode node;
|
||||||
|
|
||||||
|
// TODO: Alpha channel names? (PSDAlphaChannelInfo/PSDUnicodeAlphaNames)
|
||||||
|
// TODO: Reader/writer (PSDVersionInfo)
|
||||||
|
|
||||||
while (textResources.hasNext()) {
|
while (textResources.hasNext()) {
|
||||||
PSDImageResource textResource = textResources.next();
|
PSDImageResource textResource = textResources.next();
|
||||||
|
|
||||||
}
|
if (textResource instanceof PSDIPTCData) {
|
||||||
|
PSDIPTCData iptc = (PSDIPTCData) textResource;
|
||||||
// int numEntries = tEXt_keyword.size() +
|
|
||||||
// iTXt_keyword.size() + zTXt_keyword.size();
|
|
||||||
// if (numEntries == 0) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// IIOMetadataNode text_node = new IIOMetadataNode("Text");
|
|
||||||
// IIOMetadataNode node = null; // scratch node
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < tEXt_keyword.size(); i++) {
|
|
||||||
// node = new IIOMetadataNode("TextEntry");
|
|
||||||
// node.setAttribute("keyword", (String)tEXt_keyword.get(i));
|
|
||||||
// node.setAttribute("value", (String)tEXt_text.get(i));
|
|
||||||
// node.setAttribute("encoding", "ISO-8859-1");
|
|
||||||
// node.setAttribute("compression", "none");
|
|
||||||
//
|
|
||||||
// text_node.appendChild(node);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < iTXt_keyword.size(); i++) {
|
|
||||||
// node = new IIOMetadataNode("TextEntry");
|
|
||||||
// node.setAttribute("keyword", iTXt_keyword.get(i));
|
|
||||||
// node.setAttribute("value", iTXt_text.get(i));
|
|
||||||
// node.setAttribute("language",
|
|
||||||
// iTXt_languageTag.get(i));
|
|
||||||
// if (iTXt_compressionFlag.get(i)) {
|
|
||||||
// node.setAttribute("compression", "deflate");
|
|
||||||
// } else {
|
|
||||||
// node.setAttribute("compression", "none");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// text_node.appendChild(node);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < zTXt_keyword.size(); i++) {
|
|
||||||
// node = new IIOMetadataNode("TextEntry");
|
|
||||||
// node.setAttribute("keyword", (String)zTXt_keyword.get(i));
|
|
||||||
// node.setAttribute("value", (String)zTXt_text.get(i));
|
|
||||||
// node.setAttribute("compression", "deflate");
|
|
||||||
//
|
|
||||||
// text_node.appendChild(node);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return text_node;
|
|
||||||
return null;
|
|
||||||
|
|
||||||
|
appendTextEntriesFlat(text, iptc.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||||
|
public boolean accept(final Entry pEntry) {
|
||||||
|
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||||
|
|
||||||
|
switch (tagId) {
|
||||||
|
case IPTC.TAG_SOURCE:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (textResource instanceof PSDEXIF1Data) {
|
||||||
|
PSDEXIF1Data exif = (PSDEXIF1Data) textResource;
|
||||||
|
|
||||||
|
appendTextEntriesFlat(text, exif.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||||
|
public boolean accept(final Entry pEntry) {
|
||||||
|
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||||
|
|
||||||
|
switch (tagId) {
|
||||||
|
case TIFF.TAG_SOFTWARE:
|
||||||
|
case TIFF.TAG_ARTIST:
|
||||||
|
case TIFF.TAG_COPYRIGHT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (textResource instanceof PSDXMPData) {
|
||||||
|
// TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF?
|
||||||
|
// TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync...
|
||||||
|
PSDXMPData xmp = (PSDXMPData) textResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendTextEntriesFlat(final IIOMetadataNode pNode, final Directory pDirectory, final FilterIterator.Filter<Entry> pFilter) {
|
||||||
|
FilterIterator<Entry> pEntries = new FilterIterator<Entry>(pDirectory.iterator(), pFilter);
|
||||||
|
while (pEntries.hasNext()) {
|
||||||
|
Entry entry = pEntries.next();
|
||||||
|
|
||||||
|
if (entry.getValue() instanceof Directory) {
|
||||||
|
appendTextEntriesFlat(pNode, (Directory) entry.getValue(), pFilter);
|
||||||
|
}
|
||||||
|
else if (entry.getValue() instanceof String) {
|
||||||
|
IIOMetadataNode tag = new IIOMetadataNode("TextEntry");
|
||||||
|
String fieldName = entry.getFieldName();
|
||||||
|
|
||||||
|
if (fieldName != null) {
|
||||||
|
tag.setAttribute("keyword", String.format("%s", fieldName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: This should never happen, as we filter out only specific nodes
|
||||||
|
tag.setAttribute("keyword", String.format("%s", entry.getIdentifier()));
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.setAttribute("value", entry.getValueAsString());
|
||||||
|
pNode.appendChild(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -693,7 +707,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
IIOMetadataNode node; // scratch node
|
IIOMetadataNode node; // scratch node
|
||||||
|
|
||||||
node = new IIOMetadataNode("Alpha");
|
node = new IIOMetadataNode("Alpha");
|
||||||
node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec
|
node.setAttribute("value", hasAlpha() ? "nonpremultiplied" : "none"); // TODO: Check spec
|
||||||
transparency_node.appendChild(node);
|
transparency_node.appendChild(node);
|
||||||
|
|
||||||
return transparency_node;
|
return transparency_node;
|
||||||
@ -716,13 +730,13 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<PSDImageResource> getResources(final Class<? extends PSDImageResource>... pResourceTypes) {
|
Iterator<PSDImageResource> getResources(final int... pResourceTypes) {
|
||||||
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
||||||
|
|
||||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||||
public boolean accept(final PSDImageResource pElement) {
|
public boolean accept(final PSDImageResource pResource) {
|
||||||
for (Class<?> type : pResourceTypes) {
|
for (int type : pResourceTypes) {
|
||||||
if (type.isInstance(pElement)) {
|
if (type == pResource.mId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -731,4 +745,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
// TODO: Make it a deep clone
|
||||||
|
try {
|
||||||
|
return super.clone();
|
||||||
|
}
|
||||||
|
catch (CloneNotSupportedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
@ -44,7 +45,6 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
|||||||
// columns?
|
// columns?
|
||||||
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||||
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
||||||
// TODO: Consider using more readable names?!
|
|
||||||
addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -99,7 +99,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
|||||||
|
|
||||||
// root -> ImageResources -> EXIF
|
// root -> ImageResources -> EXIF
|
||||||
addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY);
|
addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY);
|
||||||
addObjectValue("EXIF", PSDEXIF1Data.Directory.class, true, null);
|
addObjectValue("EXIF", Directory.class, true, null);
|
||||||
// TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?)
|
// TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?)
|
||||||
|
|
||||||
// root -> ImageResources -> GridAndGuideInfo
|
// root -> ImageResources -> GridAndGuideInfo
|
||||||
@ -117,7 +117,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
|||||||
|
|
||||||
// root -> ImageResources -> IPTC
|
// root -> ImageResources -> IPTC
|
||||||
addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY);
|
addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY);
|
||||||
addObjectValue("IPTC", PSDIPTCData.Directory.class, true, null);
|
addObjectValue("IPTC", Directory.class, true, null);
|
||||||
// TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?)
|
// TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?)
|
||||||
|
|
||||||
// root -> ImageResources -> PixelAspectRatio
|
// root -> ImageResources -> PixelAspectRatio
|
||||||
|
@ -25,7 +25,7 @@ final class PSDUnicodeAlphaNames extends PSDImageResource {
|
|||||||
|
|
||||||
long left = mSize;
|
long left = mSize;
|
||||||
while (left > 0) {
|
while (left > 0) {
|
||||||
String name = PSDUtil.readUTF16String(pInput);
|
String name = PSDUtil.readUnicodeString(pInput);
|
||||||
mNames.add(name);
|
mNames.add(name);
|
||||||
left -= name.length() * 2 + 4;
|
left -= name.length() * 2 + 4;
|
||||||
}
|
}
|
||||||
|
@ -60,17 +60,27 @@ final class PSDUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||||
// TODO: Is this REALLY different from the previous method? Maybe the pad should not be read..
|
|
||||||
static String readPascalString(final DataInput pInput) throws IOException {
|
static String readPascalString(final DataInput pInput) throws IOException {
|
||||||
int length = pInput.readUnsignedByte();
|
int length = pInput.readUnsignedByte();
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[length];
|
byte[] bytes = new byte[length];
|
||||||
pInput.readFully(bytes);
|
pInput.readFully(bytes);
|
||||||
|
|
||||||
return StringUtil.decode(bytes, 0, bytes.length, "ASCII");
|
return StringUtil.decode(bytes, 0, bytes.length, "ASCII");
|
||||||
}
|
}
|
||||||
|
|
||||||
static String readUTF16String(final DataInput pInput) throws IOException {
|
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||||
|
static String readUnicodeString(final DataInput pInput) throws IOException {
|
||||||
int length = pInput.readInt();
|
int length = pInput.readInt();
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[length * 2];
|
byte[] bytes = new byte[length * 2];
|
||||||
pInput.readFully(bytes);
|
pInput.readFully(bytes);
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ final class PSDVersionInfo extends PSDImageResource {
|
|||||||
mVersion = pInput.readInt();
|
mVersion = pInput.readInt();
|
||||||
mHasRealMergedData = pInput.readBoolean();
|
mHasRealMergedData = pInput.readBoolean();
|
||||||
|
|
||||||
mWriter = PSDUtil.readUTF16String(pInput);
|
mWriter = PSDUtil.readUnicodeString(pInput);
|
||||||
mReader = PSDUtil.readUTF16String(pInput);
|
mReader = PSDUtil.readUnicodeString(pInput);
|
||||||
|
|
||||||
mFileVersion = pInput.readInt();
|
mFileVersion = pInput.readInt();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
@ -21,6 +23,7 @@ import java.nio.charset.Charset;
|
|||||||
*/
|
*/
|
||||||
final class PSDXMPData extends PSDImageResource {
|
final class PSDXMPData extends PSDImageResource {
|
||||||
protected byte[] mData;
|
protected byte[] mData;
|
||||||
|
Directory mDirectory;
|
||||||
|
|
||||||
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
|
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||||
super(pId, pInput);
|
super(pId, pInput);
|
||||||
@ -29,7 +32,9 @@ final class PSDXMPData extends PSDImageResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
||||||
pInput.readFully(mData);
|
//pInput.readFully(mData);
|
||||||
|
|
||||||
|
mDirectory = new XMPReader().read(pInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -47,7 +52,7 @@ final class PSDXMPData extends PSDImageResource {
|
|||||||
builder.append("\"]");
|
builder.append("\"]");
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a character stream containing the XMP metadata (XML).
|
* Returns a character stream containing the XMP metadata (XML).
|
||||||
|
@ -131,9 +131,9 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima
|
|||||||
* @param pMimeType the content (MIME) type
|
* @param pMimeType the content (MIME) type
|
||||||
*/
|
*/
|
||||||
public void setContentType(final String pMimeType) {
|
public void setContentType(final String pMimeType) {
|
||||||
// Throw exception is allready set
|
// Throw exception is already set
|
||||||
if (mOriginalContentType != null) {
|
if (mOriginalContentType != null) {
|
||||||
throw new IllegalStateException("ContentType allready set.");
|
throw new IllegalStateException("ContentType already set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
mOriginalContentType = pMimeType;
|
mOriginalContentType = pMimeType;
|
||||||
@ -187,17 +187,13 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima
|
|||||||
getImage();
|
getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is stupid, but don't know how to work around...
|
// For known formats that don't support transparency, convert to opaque
|
||||||
// TODO: Test what types of images that work with JPEG, consider reporting it as a bug
|
|
||||||
if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType)
|
if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType)
|
||||||
|| "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) &&
|
|| "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) &&
|
||||||
mImage instanceof BufferedImage && ((BufferedImage) mImage).getType() == BufferedImage.TYPE_INT_ARGB) {
|
mImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
|
||||||
mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB);
|
mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
//System.out.println("Writing image, content-type: " + getContentType(outputType));
|
|
||||||
//System.out.println("Writing image, outputType: " + outputType);
|
|
||||||
//System.out.println("Writing image: " + mImage);
|
|
||||||
if (mImage != null) {
|
if (mImage != null) {
|
||||||
Iterator writers = ImageIO.getImageWritersByMIMEType(outputType);
|
Iterator writers = ImageIO.getImageWritersByMIMEType(outputType);
|
||||||
if (writers.hasNext()) {
|
if (writers.hasNext()) {
|
||||||
@ -218,10 +214,6 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima
|
|||||||
|
|
||||||
ImageOutputStream stream = ImageIO.createImageOutputStream(out);
|
ImageOutputStream stream = ImageIO.createImageOutputStream(out);
|
||||||
|
|
||||||
//System.out.println("-ISR- Image: " + mImage);
|
|
||||||
//System.out.println("-ISR- ImageWriter: " + writer);
|
|
||||||
//System.out.println("-ISR- ImageOutputStream: " + stream);
|
|
||||||
|
|
||||||
writer.setOutput(stream);
|
writer.setOutput(stream);
|
||||||
try {
|
try {
|
||||||
writer.write(null, new IIOImage(mImage, null, null), param);
|
writer.write(null, new IIOImage(mImage, null, null), param);
|
||||||
@ -233,12 +225,10 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima
|
|||||||
finally {
|
finally {
|
||||||
writer.dispose();
|
writer.dispose();
|
||||||
out.flush();
|
out.flush();
|
||||||
// out.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mContext.log("ERROR: No writer for content-type: " + outputType);
|
mContext.log("ERROR: No writer for content-type: " + outputType);
|
||||||
// sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to encode image: No writer for content-type " + outputType);
|
|
||||||
throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ").");
|
throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ").");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,8 +355,6 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima
|
|||||||
// Fill bgcolor behind image, if transparent
|
// Fill bgcolor behind image, if transparent
|
||||||
extractAndSetBackgroundColor(image);
|
extractAndSetBackgroundColor(image);
|
||||||
|
|
||||||
//System.out.println("-ISR- Image: " + image);
|
|
||||||
|
|
||||||
// Set image
|
// Set image
|
||||||
mImage = image;
|
mImage = image;
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,17 @@ import java.util.Arrays;
|
|||||||
public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
||||||
private static final String CONTENT_TYPE_BMP = "image/bmp";
|
private static final String CONTENT_TYPE_BMP = "image/bmp";
|
||||||
private static final String CONTENT_TYPE_FOO = "foo/bar";
|
private static final String CONTENT_TYPE_FOO = "foo/bar";
|
||||||
|
private static final String CONTENT_TYPE_GIF = "image/gif";
|
||||||
private static final String CONTENT_TYPE_JPEG = "image/jpeg";
|
private static final String CONTENT_TYPE_JPEG = "image/jpeg";
|
||||||
private static final String CONTENT_TYPE_PNG = "image/png";
|
private static final String CONTENT_TYPE_PNG = "image/png";
|
||||||
private static final String CONTENT_TYPE_TEXT = "text/plain";
|
private static final String CONTENT_TYPE_TEXT = "text/plain";
|
||||||
|
|
||||||
private static final String IMAGE_NAME = "12monkeys-splash.png";
|
private static final String IMAGE_NAME_PNG = "12monkeys-splash.png";
|
||||||
|
private static final String IMAGE_NAME_GIF = "tux.gif";
|
||||||
|
|
||||||
|
private static final Dimension IMAGE_DIMENSION_PNG = new Dimension(300, 410);
|
||||||
|
private static final Dimension IMAGE_DIMENSION_GIF = new Dimension(250, 250);
|
||||||
|
|
||||||
private static final Dimension IMAGE_DIMENSION = new Dimension(300, 410);
|
|
||||||
private HttpServletRequest mRequest;
|
private HttpServletRequest mRequest;
|
||||||
private ServletContext mContext;
|
private ServletContext mContext;
|
||||||
|
|
||||||
@ -43,15 +47,17 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
Mock mockRequest = mock(HttpServletRequest.class);
|
Mock mockRequest = mock(HttpServletRequest.class);
|
||||||
mockRequest.stubs().method("getAttribute").will(returnValue(null));
|
mockRequest.stubs().method("getAttribute").will(returnValue(null));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
mRequest = (HttpServletRequest) mockRequest.proxy();
|
mRequest = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
Mock mockContext = mock(ServletContext.class);
|
Mock mockContext = mock(ServletContext.class);
|
||||||
mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME)).will(returnValue(getClass().getResource(IMAGE_NAME)));
|
mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME_PNG)).will(returnValue(getClass().getResource(IMAGE_NAME_PNG)));
|
||||||
mockContext.stubs().method("log").withAnyArguments(); // Just supress the logging
|
mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME_GIF)).will(returnValue(getClass().getResource(IMAGE_NAME_GIF)));
|
||||||
|
mockContext.stubs().method("log").withAnyArguments(); // Just suppress the logging
|
||||||
mockContext.stubs().method("getMimeType").with(eq("file.bmp")).will(returnValue(CONTENT_TYPE_BMP));
|
mockContext.stubs().method("getMimeType").with(eq("file.bmp")).will(returnValue(CONTENT_TYPE_BMP));
|
||||||
mockContext.stubs().method("getMimeType").with(eq("file.foo")).will(returnValue(CONTENT_TYPE_FOO));
|
mockContext.stubs().method("getMimeType").with(eq("file.foo")).will(returnValue(CONTENT_TYPE_FOO));
|
||||||
|
mockContext.stubs().method("getMimeType").with(eq("file.gif")).will(returnValue(CONTENT_TYPE_GIF));
|
||||||
mockContext.stubs().method("getMimeType").with(eq("file.jpeg")).will(returnValue(CONTENT_TYPE_JPEG));
|
mockContext.stubs().method("getMimeType").with(eq("file.jpeg")).will(returnValue(CONTENT_TYPE_JPEG));
|
||||||
mockContext.stubs().method("getMimeType").with(eq("file.png")).will(returnValue(CONTENT_TYPE_PNG));
|
mockContext.stubs().method("getMimeType").with(eq("file.png")).will(returnValue(CONTENT_TYPE_PNG));
|
||||||
mockContext.stubs().method("getMimeType").with(eq("file.txt")).will(returnValue(CONTENT_TYPE_TEXT));
|
mockContext.stubs().method("getMimeType").with(eq("file.txt")).will(returnValue(CONTENT_TYPE_TEXT));
|
||||||
@ -98,11 +104,11 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext);
|
||||||
fakeResponse(mRequest, imageResponse);
|
fakeResponse(mRequest, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(IMAGE_DIMENSION.width, image.getWidth());
|
assertEquals(IMAGE_DIMENSION_PNG.width, image.getWidth());
|
||||||
assertEquals(IMAGE_DIMENSION.height, image.getHeight());
|
assertEquals(IMAGE_DIMENSION_PNG.height, image.getHeight());
|
||||||
|
|
||||||
// Flush image to wrapped response
|
// Flush image to wrapped response
|
||||||
imageResponse.flush();
|
imageResponse.flush();
|
||||||
@ -136,7 +142,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
assertTrue("Content has no data", out.size() > 0);
|
assertTrue("Content has no data", out.size() > 0);
|
||||||
|
|
||||||
// Test that image data is untouched
|
// Test that image data is untouched
|
||||||
assertTrue("Data differs", Arrays.equals(FileUtil.read(getClass().getResourceAsStream(IMAGE_NAME)), out.toByteArray()));
|
assertTrue("Data differs", Arrays.equals(FileUtil.read(getClass().getResourceAsStream(IMAGE_NAME_PNG)), out.toByteArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transcode original PNG to JPEG with no other changes
|
// Transcode original PNG to JPEG with no other changes
|
||||||
@ -161,8 +167,67 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
// Test that image data is still readable
|
// Test that image data is still readable
|
||||||
BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(out.toByteArray()));
|
BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(out.toByteArray()));
|
||||||
assertNotNull(outImage);
|
assertNotNull(outImage);
|
||||||
assertEquals(IMAGE_DIMENSION.width, outImage.getWidth());
|
assertEquals(IMAGE_DIMENSION_PNG.width, outImage.getWidth());
|
||||||
assertEquals(IMAGE_DIMENSION.height, outImage.getHeight());
|
assertEquals(IMAGE_DIMENSION_PNG.height, outImage.getHeight());
|
||||||
|
assertSimilarImage(ImageIO.read(mContext.getResource("/" + IMAGE_NAME_PNG)), outImage, 96f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTranscodeResponseIndexedCM() throws IOException {
|
||||||
|
// Custom setup
|
||||||
|
Mock mockRequest = mock(HttpServletRequest.class);
|
||||||
|
mockRequest.stubs().method("getAttribute").will(returnValue(null));
|
||||||
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_GIF));
|
||||||
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
|
Mock mockResponse = mock(HttpServletResponse.class);
|
||||||
|
mockResponse.expects(once()).method("setContentType").with(eq(CONTENT_TYPE_JPEG));
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
mockResponse.expects(once()).method("getOutputStream").will(returnValue(new OutputStreamAdapter(out)));
|
||||||
|
HttpServletResponse response = (HttpServletResponse) mockResponse.proxy();
|
||||||
|
|
||||||
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
|
// Force transcode to JPEG
|
||||||
|
imageResponse.setOutputContentType("image/jpeg");
|
||||||
|
|
||||||
|
// Flush image to wrapped response
|
||||||
|
imageResponse.flush();
|
||||||
|
|
||||||
|
assertTrue("Content has no data", out.size() > 0);
|
||||||
|
|
||||||
|
// Test that image data is still readable
|
||||||
|
BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(out.toByteArray()));
|
||||||
|
assertNotNull(outImage);
|
||||||
|
assertEquals(IMAGE_DIMENSION_GIF.width, outImage.getWidth());
|
||||||
|
assertEquals(IMAGE_DIMENSION_GIF.height, outImage.getHeight());
|
||||||
|
assertSimilarImage(ImageIO.read(mContext.getResource("/" + IMAGE_NAME_GIF)), outImage, 96f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure images are the same, taking JPEG artifacts into account.
|
||||||
|
*
|
||||||
|
* @param pExpected the expected image
|
||||||
|
* @param pActual the actual image
|
||||||
|
* @param pArtifactThreshold the maximum allowed difference between the expected and actual pixel value
|
||||||
|
*/
|
||||||
|
private void assertSimilarImage(final BufferedImage pExpected, final BufferedImage pActual, final float pArtifactThreshold) {
|
||||||
|
for (int y = 0; y < pExpected.getHeight(); y++) {
|
||||||
|
for (int x = 0; x < pExpected.getWidth(); x++) {
|
||||||
|
int original = pExpected.getRGB(x, y);
|
||||||
|
int actual = pActual.getRGB(x, y);
|
||||||
|
|
||||||
|
// Multiply in the alpha component
|
||||||
|
float alpha = ((original >> 24) & 0xff) / 255f;
|
||||||
|
|
||||||
|
assertEquals(alpha * ((original >> 16) & 0xff), (actual >> 16) & 0xff, pArtifactThreshold);
|
||||||
|
assertEquals(alpha * ((original >> 8) & 0xff), (actual >> 8) & 0xff, pArtifactThreshold);
|
||||||
|
assertEquals(alpha * ((original) & 0xff), actual & 0xff, pArtifactThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReplaceResponse() throws IOException {
|
public void testReplaceResponse() throws IOException {
|
||||||
@ -175,7 +240,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext);
|
||||||
fakeResponse(mRequest, imageResponse);
|
fakeResponse(mRequest, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
@ -298,7 +363,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -311,7 +376,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(sourceRegion.width, image.getWidth());
|
assertEquals(sourceRegion.width, image.getWidth());
|
||||||
@ -337,7 +402,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -350,7 +415,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(sourceRegion.width, image.getWidth());
|
assertEquals(sourceRegion.width, image.getWidth());
|
||||||
@ -379,7 +444,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -392,24 +457,24 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
assertEquals(sourceRegion.width, image.getWidth());
|
assertEquals(sourceRegion.width, image.getWidth());
|
||||||
assertEquals(sourceRegion.height, image.getHeight());
|
assertEquals(sourceRegion.height, image.getHeight());
|
||||||
|
|
||||||
BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME));
|
BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME_PNG));
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
assertEquals(IMAGE_DIMENSION.width, original.getWidth());
|
assertEquals(IMAGE_DIMENSION_PNG.width, original.getWidth());
|
||||||
assertEquals(IMAGE_DIMENSION.height, original.getHeight());
|
assertEquals(IMAGE_DIMENSION_PNG.height, original.getHeight());
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
sourceRegion.setLocation(
|
sourceRegion.setLocation(
|
||||||
(int) Math.round((IMAGE_DIMENSION.width - sourceRegion.getWidth()) / 2.0),
|
(int) Math.round((IMAGE_DIMENSION_PNG.width - sourceRegion.getWidth()) / 2.0),
|
||||||
(int) Math.round((IMAGE_DIMENSION.height - sourceRegion.getHeight()) / 2.0)
|
(int) Math.round((IMAGE_DIMENSION_PNG.height - sourceRegion.getHeight()) / 2.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test that we have exactly the pixels we should
|
// Test that we have exactly the pixels we should
|
||||||
@ -442,7 +507,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -455,49 +520,49 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
// Flush image to wrapped response
|
// Flush image to wrapped response
|
||||||
imageResponse.flush();
|
imageResponse.flush();
|
||||||
|
|
||||||
assertTrue("Image wider than bounding box", IMAGE_DIMENSION.width >= image.getWidth());
|
assertTrue("Image wider than bounding box", IMAGE_DIMENSION_PNG.width >= image.getWidth());
|
||||||
assertTrue("Image taller than bounding box", IMAGE_DIMENSION.height >= image.getHeight());
|
assertTrue("Image taller than bounding box", IMAGE_DIMENSION_PNG.height >= image.getHeight());
|
||||||
assertTrue("Image not maximized to bounding box", IMAGE_DIMENSION.width == image.getWidth() || IMAGE_DIMENSION.height == image.getHeight());
|
assertTrue("Image not maximized to bounding box", IMAGE_DIMENSION_PNG.width == image.getWidth() || IMAGE_DIMENSION_PNG.height == image.getHeight());
|
||||||
|
|
||||||
// Above tests that one of the sides equal, we now need to test that the other follows aspect
|
// Above tests that one of the sides equal, we now need to test that the other follows aspect
|
||||||
double destAspect = sourceRegion.getWidth() / sourceRegion.getHeight();
|
double destAspect = sourceRegion.getWidth() / sourceRegion.getHeight();
|
||||||
double srcAspect = IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight();
|
double srcAspect = IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight();
|
||||||
|
|
||||||
if (srcAspect >= destAspect) {
|
if (srcAspect >= destAspect) {
|
||||||
// Dst is narrower than src
|
// Dst is narrower than src
|
||||||
assertEquals(IMAGE_DIMENSION.height, image.getHeight());
|
assertEquals(IMAGE_DIMENSION_PNG.height, image.getHeight());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Image width does not follow aspect",
|
"Image width does not follow aspect",
|
||||||
Math.round(IMAGE_DIMENSION.getHeight() * destAspect), image.getWidth()
|
Math.round(IMAGE_DIMENSION_PNG.getHeight() * destAspect), image.getWidth()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Dst is wider than src
|
// Dst is wider than src
|
||||||
assertEquals(IMAGE_DIMENSION.width, image.getWidth());
|
assertEquals(IMAGE_DIMENSION_PNG.width, image.getWidth());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Image height does not follow aspect",
|
"Image height does not follow aspect",
|
||||||
Math.round(IMAGE_DIMENSION.getWidth() / destAspect), image.getHeight()
|
Math.round(IMAGE_DIMENSION_PNG.getWidth() / destAspect), image.getHeight()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME));
|
BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME_PNG));
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
assertEquals(IMAGE_DIMENSION.width, original.getWidth());
|
assertEquals(IMAGE_DIMENSION_PNG.width, original.getWidth());
|
||||||
assertEquals(IMAGE_DIMENSION.height, original.getHeight());
|
assertEquals(IMAGE_DIMENSION_PNG.height, original.getHeight());
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
sourceRegion.setLocation(
|
sourceRegion.setLocation(
|
||||||
(int) Math.round((IMAGE_DIMENSION.width - image.getWidth()) / 2.0),
|
(int) Math.round((IMAGE_DIMENSION_PNG.width - image.getWidth()) / 2.0),
|
||||||
(int) Math.round((IMAGE_DIMENSION.height - image.getHeight()) / 2.0)
|
(int) Math.round((IMAGE_DIMENSION_PNG.height - image.getHeight()) / 2.0)
|
||||||
);
|
);
|
||||||
sourceRegion.setSize(image.getWidth(), image.getHeight());
|
sourceRegion.setSize(image.getWidth(), image.getHeight());
|
||||||
|
|
||||||
@ -526,7 +591,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -539,7 +604,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
@ -549,10 +614,10 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
|
|
||||||
// Above tests that one of the sides equal, we now need to test that the other follows aspect
|
// Above tests that one of the sides equal, we now need to test that the other follows aspect
|
||||||
if (size.width == image.getWidth()) {
|
if (size.width == image.getWidth()) {
|
||||||
assertEquals(Math.round(size.getWidth() * IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight()), image.getHeight());
|
assertEquals(Math.round(size.getWidth() * IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight()), image.getHeight());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assertEquals(Math.round(size.getHeight() * IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight()), image.getWidth());
|
assertEquals(Math.round(size.getHeight() * IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight()), image.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush image to wrapped response
|
// Flush image to wrapped response
|
||||||
@ -576,7 +641,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -589,7 +654,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(size.width, image.getWidth());
|
assertEquals(size.width, image.getWidth());
|
||||||
@ -618,7 +683,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -631,7 +696,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
@ -658,7 +723,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
assertEquals(image.getWidth(), outImage.getWidth());
|
assertEquals(image.getWidth(), outImage.getWidth());
|
||||||
assertEquals(image.getHeight(), outImage.getHeight());
|
assertEquals(image.getHeight(), outImage.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadWithSourceRegionAndNonUniformResize() throws IOException {
|
public void testReadWithSourceRegionAndNonUniformResize() throws IOException {
|
||||||
Rectangle sourceRegion = new Rectangle(100, 100, 200, 200);
|
Rectangle sourceRegion = new Rectangle(100, 100, 200, 200);
|
||||||
@ -670,7 +735,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -683,7 +748,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(size.width, image.getWidth());
|
assertEquals(size.width, image.getWidth());
|
||||||
@ -713,7 +778,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -726,7 +791,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
@ -774,7 +839,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -787,7 +852,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
@ -839,7 +904,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion));
|
||||||
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size));
|
||||||
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
mockRequest.stubs().method("getContextPath").will(returnValue("/ape"));
|
||||||
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME));
|
mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG));
|
||||||
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
mockRequest.stubs().method("getParameter").will(returnValue(null));
|
||||||
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
HttpServletRequest request = (HttpServletRequest) mockRequest.proxy();
|
||||||
|
|
||||||
@ -852,7 +917,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase {
|
|||||||
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext);
|
||||||
fakeResponse(request, imageResponse);
|
fakeResponse(request, imageResponse);
|
||||||
|
|
||||||
// Make sure image is correctly loaedd
|
// Make sure image is correctly loaded
|
||||||
BufferedImage image = imageResponse.getImage();
|
BufferedImage image = imageResponse.getImage();
|
||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
|
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Loading…
x
Reference in New Issue
Block a user