JPEG Exif rotation in metadata + support
@ -0,0 +1,152 @@
|
|||||||
|
package com.twelvemonkeys.contrib.exif;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXIFUtilities.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @version : EXIFUtilities.java,v 1.0 23/06/2020
|
||||||
|
*/
|
||||||
|
public class EXIFUtilities {
|
||||||
|
/**
|
||||||
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
*
|
||||||
|
* @param input a {@code URL}
|
||||||
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||||
|
* @throws IOException if an error occurs during reading.
|
||||||
|
*/
|
||||||
|
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
||||||
|
return readWithOrientation(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
*
|
||||||
|
* @param input an {@code InputStream}
|
||||||
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||||
|
* @throws IOException if an error occurs during reading.
|
||||||
|
*/
|
||||||
|
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
||||||
|
return readWithOrientation(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
*
|
||||||
|
* @param input a {@code File}
|
||||||
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||||
|
* @throws IOException if an error occurs during reading.
|
||||||
|
*/
|
||||||
|
public static IIOImage readWithOrientation(final File input) throws IOException {
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
||||||
|
return readWithOrientation(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
*
|
||||||
|
* @param input an {@code ImageInputStream}
|
||||||
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||||
|
* @throws IOException if an error occurs during reading.
|
||||||
|
*/
|
||||||
|
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||||
|
if (!readers.hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
try {
|
||||||
|
reader.setInput(input, true, false);
|
||||||
|
IIOImage image = reader.readAll(0, reader.getDefaultReadParam());
|
||||||
|
|
||||||
|
BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage());
|
||||||
|
image.setRenderedImage(applyOrientation(bufferedImage, findImageOrientation(image.getMetadata()).value()));
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the {@code ImageOrientation} tag, if any, and returns an {@link Orientation} based on its
|
||||||
|
* {@code value} attribute.
|
||||||
|
* If no match is found or the tag is not present, {@code Normal} (the default orientation) is returned.
|
||||||
|
*
|
||||||
|
* @param metadata an {@code IIOMetadata} object
|
||||||
|
* @return the {@code Orientation} matching the {@code value} attribute of the {@code ImageOrientation} tag,
|
||||||
|
* or {@code Normal}, never {@code null}.
|
||||||
|
* @see Orientation
|
||||||
|
* @see <a href="https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html">Standard (Plug-in Neutral) Metadata Format Specification</a>
|
||||||
|
*/
|
||||||
|
public static Orientation findImageOrientation(final IIOMetadata metadata) {
|
||||||
|
if (metadata != null) {
|
||||||
|
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
NodeList imageOrientations = root.getElementsByTagName("ImageOrientation");
|
||||||
|
|
||||||
|
if (imageOrientations != null && imageOrientations.getLength() > 0) {
|
||||||
|
IIOMetadataNode imageOrientation = (IIOMetadataNode) imageOrientations.item(0);
|
||||||
|
return Orientation.fromMetadataOrientation(imageOrientation.getAttribute("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Orientation.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
for (String arg : args) {
|
||||||
|
File input = new File(arg);
|
||||||
|
|
||||||
|
// Read everything (similar to ImageReader.readAll(0, null)), but applies the correct image orientation
|
||||||
|
IIOImage image = readWithOrientation(input);
|
||||||
|
|
||||||
|
// Finds the orientation as defined by the javax_imageio_1.0 format
|
||||||
|
Orientation orientation = findImageOrientation(image.getMetadata());
|
||||||
|
|
||||||
|
// Retrieve the image as a BufferedImage. The image is already rotated by the readWithOrientation method
|
||||||
|
// In this case it will already be a BufferedImage, so using a cast will also do
|
||||||
|
// (i.e.: BufferedImage bufferedImage = (BufferedImage) image.getRenderedImage())
|
||||||
|
BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage());
|
||||||
|
|
||||||
|
// Demo purpose only, show image with orientation details in title
|
||||||
|
DisplayHelper.showIt(bufferedImage, input.getName() + ": " + orientation.name() + "/" + orientation.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't do this... :-) Provided for convenience/demo only!
|
||||||
|
static abstract class DisplayHelper extends ImageReaderBase {
|
||||||
|
private DisplayHelper() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void showIt(BufferedImage image, String title) {
|
||||||
|
ImageReaderBase.showIt(image, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.twelvemonkeys.contrib.exif;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.contrib.tiff.TIFFUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orientation.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @version : Orientation.java,v 1.0 10/07/2020 harald.kuhr
|
||||||
|
*/
|
||||||
|
public enum Orientation {
|
||||||
|
Normal(TIFFUtilities.TIFFBaseline.ORIENTATION_TOPLEFT),
|
||||||
|
FlipH(TIFFUtilities.TIFFExtension.ORIENTATION_TOPRIGHT),
|
||||||
|
Rotate180(TIFFUtilities.TIFFExtension.ORIENTATION_BOTRIGHT),
|
||||||
|
FlipV(TIFFUtilities.TIFFExtension.ORIENTATION_BOTLEFT),
|
||||||
|
FlipVRotate90(TIFFUtilities.TIFFExtension.ORIENTATION_LEFTTOP),
|
||||||
|
Rotate270(TIFFUtilities.TIFFExtension.ORIENTATION_RIGHTTOP),
|
||||||
|
FlipHRotate90(TIFFUtilities.TIFFExtension.ORIENTATION_RIGHTBOT),
|
||||||
|
Rotate90(TIFFUtilities.TIFFExtension.ORIENTATION_LEFTBOT);
|
||||||
|
|
||||||
|
// name as defined in javax.imageio metadata
|
||||||
|
private final int value; // value as defined in TIFF spec
|
||||||
|
|
||||||
|
Orientation(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Orientation fromMetadataOrientation(final String orientationName) {
|
||||||
|
if (orientationName != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(orientationName);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
// Not found, try ignore case match, as some metadata implementations are known to return "normal" etc.
|
||||||
|
String lowerCaseName = orientationName.toLowerCase();
|
||||||
|
|
||||||
|
for (Orientation orientation : values()) {
|
||||||
|
if (orientation.name().toLowerCase().equals(lowerCaseName)) {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata does not have other orientations, default to Normal
|
||||||
|
return Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Orientation fromTIFFOrientation(final int tiffOrientation) {
|
||||||
|
for (Orientation orientation : values()) {
|
||||||
|
if (orientation.value() == tiffOrientation) {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No other TIFF orientations possible, default to Normal
|
||||||
|
return Normal;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.twelvemonkeys.contrib.exif;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.contrib.exif.Orientation.*;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OrientationTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by : harald.kuhr$
|
||||||
|
* @version : OrientationTest.java,v 1.0 10/07/2020 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public class OrientationTest {
|
||||||
|
@Test
|
||||||
|
public void testFromMetadataOrientationNull() {
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromMetadataOrientation() {
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation("Normal"));
|
||||||
|
assertEquals(Rotate90, Orientation.fromMetadataOrientation("Rotate90"));
|
||||||
|
assertEquals(Rotate180, Orientation.fromMetadataOrientation("Rotate180"));
|
||||||
|
assertEquals(Rotate270, Orientation.fromMetadataOrientation("Rotate270"));
|
||||||
|
assertEquals(FlipH, Orientation.fromMetadataOrientation("FlipH"));
|
||||||
|
assertEquals(FlipV, Orientation.fromMetadataOrientation("FlipV"));
|
||||||
|
assertEquals(FlipHRotate90, Orientation.fromMetadataOrientation("FlipHRotate90"));
|
||||||
|
assertEquals(FlipVRotate90, Orientation.fromMetadataOrientation("FlipVRotate90"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromMetadataOrientationIgnoreCase() {
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation("normal"));
|
||||||
|
assertEquals(Rotate90, Orientation.fromMetadataOrientation("rotate90"));
|
||||||
|
assertEquals(Rotate180, Orientation.fromMetadataOrientation("ROTATE180"));
|
||||||
|
assertEquals(Rotate270, Orientation.fromMetadataOrientation("ROTATE270"));
|
||||||
|
assertEquals(FlipH, Orientation.fromMetadataOrientation("FLIPH"));
|
||||||
|
assertEquals(FlipV, Orientation.fromMetadataOrientation("flipv"));
|
||||||
|
assertEquals(FlipHRotate90, Orientation.fromMetadataOrientation("FLIPhrotate90"));
|
||||||
|
assertEquals(FlipVRotate90, Orientation.fromMetadataOrientation("fLiPVRotAte90"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromMetadataOrientationUnknown() {
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation("foo"));
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation("90"));
|
||||||
|
assertEquals(Normal, Orientation.fromMetadataOrientation("randomStringWithNumbers180"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromTIFFOrientation() {
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(1));
|
||||||
|
assertEquals(FlipH, Orientation.fromTIFFOrientation(2));
|
||||||
|
assertEquals(Rotate180, Orientation.fromTIFFOrientation(3));
|
||||||
|
assertEquals(FlipV, Orientation.fromTIFFOrientation(4));
|
||||||
|
assertEquals(FlipVRotate90, Orientation.fromTIFFOrientation(5));
|
||||||
|
assertEquals(Rotate270, Orientation.fromTIFFOrientation(6));
|
||||||
|
assertEquals(FlipHRotate90, Orientation.fromTIFFOrientation(7));
|
||||||
|
assertEquals(Rotate90, Orientation.fromTIFFOrientation(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromTIFFOrientationUnknown() {
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(-1));
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(0));
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(9));
|
||||||
|
for (int i = 10; i < 1024; i++) {
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(i));
|
||||||
|
}
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(Integer.MAX_VALUE));
|
||||||
|
assertEquals(Normal, Orientation.fromTIFFOrientation(Integer.MIN_VALUE));
|
||||||
|
}
|
||||||
|
}
|
@ -35,27 +35,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
|||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
final class HuffmanTable extends Segment {
|
final class HuffmanTable extends Segment {
|
||||||
|
|
||||||
private final int l[][][] = new int[4][2][16];
|
private final short[][][] l = new short[4][2][16];
|
||||||
private final int th[] = new int[4]; // 1: this table is present
|
private final short[][][][] v = new short[4][2][16][200]; // tables
|
||||||
final int v[][][][] = new int[4][2][16][200]; // tables
|
private final boolean[][] tc = new boolean[4][2]; // 1: this table is present
|
||||||
final int[][] tc = new int[4][2]; // 1: this table is present
|
|
||||||
|
|
||||||
static final int MSB = 0x80000000;
|
private static final int MSB = 0x80000000;
|
||||||
|
|
||||||
private HuffmanTable() {
|
private HuffmanTable() {
|
||||||
super(JPEG.DHT);
|
super(JPEG.DHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void buildHuffTables(final int[][][] HuffTab) throws IOException {
|
void buildHuffTables(final int[][][] huffTab) throws IOException {
|
||||||
for (int t = 0; t < 4; t++) {
|
for (int t = 0; t < 4; t++) {
|
||||||
for (int c = 0; c < 2; c++) {
|
for (int c = 0; c < 2; c++) {
|
||||||
if (tc[t][c] != 0) {
|
if (tc[t][c]) {
|
||||||
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
|
buildHuffTable(huffTab[t][c], l[t][c], v[t][c]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ final class HuffmanTable extends Segment {
|
|||||||
// V[i][j] Huffman Value (length=i)
|
// V[i][j] Huffman Value (length=i)
|
||||||
// Effect:
|
// Effect:
|
||||||
// build up HuffTab[t][c] using L and V.
|
// build up HuffTab[t][c] using L and V.
|
||||||
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
|
private void buildHuffTable(final int[] tab, final short[] L, final short[][] V) throws IOException {
|
||||||
int temp = 256;
|
int temp = 256;
|
||||||
int k = 0;
|
int k = 0;
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ final class HuffmanTable extends Segment {
|
|||||||
|
|
||||||
for (int t = 0; t < tc.length; t++) {
|
for (int t = 0; t < tc.length; t++) {
|
||||||
for (int c = 0; c < tc[t].length; c++) {
|
for (int c = 0; c < tc[t].length; c++) {
|
||||||
if (tc[t][c] != 0) {
|
if (tc[t][c]) {
|
||||||
if (builder.length() > 4) {
|
if (builder.length() > 4) {
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
@ -149,11 +149,10 @@ final class HuffmanTable extends Segment {
|
|||||||
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
|
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.th[t] = 1;
|
table.tc[t][c] = true;
|
||||||
table.tc[t][c] = 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
table.l[t][c][i] = data.readUnsignedByte();
|
table.l[t][c][i] = (short) data.readUnsignedByte();
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +161,7 @@ final class HuffmanTable extends Segment {
|
|||||||
if (count > length) {
|
if (count > length) {
|
||||||
throw new IIOException("JPEG Huffman Table format error");
|
throw new IIOException("JPEG Huffman Table format error");
|
||||||
}
|
}
|
||||||
table.v[t][c][i][j] = data.readUnsignedByte();
|
table.v[t][c][i][j] = (short) data.readUnsignedByte();
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,4 +173,41 @@ final class HuffmanTable extends Segment {
|
|||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPresent(int tableId, int tableClass) {
|
||||||
|
return tc[tableId][tableClass];
|
||||||
|
}
|
||||||
|
|
||||||
|
private short[] lengths(int tableId, int tableClass) {
|
||||||
|
// TODO: Consider stripping the 0s?
|
||||||
|
return l[tableId][tableClass];
|
||||||
|
}
|
||||||
|
|
||||||
|
private short[] tables(int tableId, int tableClass) {
|
||||||
|
// Find sum of lengths
|
||||||
|
short[] lengths = lengths(tableId, tableClass);
|
||||||
|
|
||||||
|
int sumOfLengths = 0;
|
||||||
|
for (int length : lengths) {
|
||||||
|
sumOfLengths += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten the tables
|
||||||
|
short[] tables = new short[sumOfLengths];
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
short[] table = v[tableId][tableClass][i];
|
||||||
|
short length = lengths[i];
|
||||||
|
|
||||||
|
System.arraycopy(table, 0, tables, pos, length);
|
||||||
|
pos += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
JPEGHuffmanTable toNativeTable(int tableId, int tableClass) {
|
||||||
|
return new JPEGHuffmanTable(lengths(tableId, tableClass), tables(tableId, tableClass));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
|
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
|
||||||
|
|
||||||
return thumbnail;
|
return thumbnail;
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,16 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,14 +51,30 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
class JPEGImage10Metadata extends AbstractMetadata {
|
class JPEGImage10Metadata extends AbstractMetadata {
|
||||||
|
|
||||||
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place....
|
// TODO: Create our own native format, which is simply markerSequence from the Sun format, with the segments as-is, in sequence...
|
||||||
|
// + add special case for app segments, containing appXX + identifier (ie. <app0JFIF /> to <app0 identifier="JFIF" /> or <app app="0" identifier="JFIF" />
|
||||||
|
|
||||||
private final List<Segment> segments;
|
private final List<Segment> segments;
|
||||||
|
|
||||||
JPEGImage10Metadata(List<Segment> segments) {
|
private final Frame frame;
|
||||||
|
private final JFIF jfif;
|
||||||
|
private final AdobeDCT adobeDCT;
|
||||||
|
private final JFXX jfxx;
|
||||||
|
private final ICC_Profile embeddedICCProfile;
|
||||||
|
|
||||||
|
private final CompoundDirectory exif;
|
||||||
|
|
||||||
|
// TODO: Consider moving all the metadata stuff from the reader, over here...
|
||||||
|
JPEGImage10Metadata(final List<Segment> segments, Frame frame, JFIF jfif, JFXX jfxx, ICC_Profile embeddedICCProfile, AdobeDCT adobeDCT, final CompoundDirectory exif) {
|
||||||
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
|
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
|
||||||
|
|
||||||
this.segments = segments;
|
this.segments = segments;
|
||||||
|
this.frame = frame;
|
||||||
|
this.jfif = jfif;
|
||||||
|
this.adobeDCT = adobeDCT;
|
||||||
|
this.jfxx = jfxx;
|
||||||
|
this.embeddedICCProfile = embeddedICCProfile;
|
||||||
|
this.exif = exif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -60,16 +82,53 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
|
IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
|
||||||
|
|
||||||
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
|
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
|
||||||
root.appendChild(jpegVariety);
|
boolean isJFIF = jfif != null;
|
||||||
// TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless
|
if (isJFIF) {
|
||||||
|
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
|
||||||
|
app0JFIF.setAttribute("majorVersion", Integer.toString(jfif.majorVersion));
|
||||||
|
app0JFIF.setAttribute("minorVersion", Integer.toString(jfif.minorVersion));
|
||||||
|
|
||||||
|
app0JFIF.setAttribute("resUnits", Integer.toString(jfif.units));
|
||||||
|
app0JFIF.setAttribute("Xdensity", Integer.toString(jfif.xDensity));
|
||||||
|
app0JFIF.setAttribute("Ydensity", Integer.toString(jfif.yDensity));
|
||||||
|
|
||||||
|
app0JFIF.setAttribute("thumbWidth", Integer.toString(jfif.xThumbnail));
|
||||||
|
app0JFIF.setAttribute("thumbHeight", Integer.toString(jfif.yThumbnail));
|
||||||
|
|
||||||
|
jpegVariety.appendChild(app0JFIF);
|
||||||
|
|
||||||
|
// Due to format oddity, add JFXX and app2ICC as subnodes here...
|
||||||
|
// ...and ignore them below, if added...
|
||||||
|
apendJFXX(app0JFIF);
|
||||||
|
appendICCProfile(app0JFIF);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.appendChild(jpegVariety);
|
||||||
|
|
||||||
|
appendMarkerSequence(root, segments, isJFIF);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendMarkerSequence(IIOMetadataNode root, List<Segment> segments, boolean isJFIF) {
|
||||||
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
|
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
|
||||||
root.appendChild(markerSequence);
|
root.appendChild(markerSequence);
|
||||||
|
|
||||||
for (Segment segment : segments)
|
for (Segment segment : segments)
|
||||||
switch (segment.marker) {
|
switch (segment.marker) {
|
||||||
// SOF3 is the only one supported by now
|
case JPEG.SOF0:
|
||||||
|
case JPEG.SOF1:
|
||||||
|
case JPEG.SOF2:
|
||||||
case JPEG.SOF3:
|
case JPEG.SOF3:
|
||||||
|
case JPEG.SOF5:
|
||||||
|
case JPEG.SOF6:
|
||||||
|
case JPEG.SOF7:
|
||||||
|
case JPEG.SOF9:
|
||||||
|
case JPEG.SOF10:
|
||||||
|
case JPEG.SOF11:
|
||||||
|
case JPEG.SOF13:
|
||||||
|
case JPEG.SOF14:
|
||||||
|
case JPEG.SOF15:
|
||||||
Frame sofSegment = (Frame) segment;
|
Frame sofSegment = (Frame) segment;
|
||||||
|
|
||||||
IIOMetadataNode sof = new IIOMetadataNode("sof");
|
IIOMetadataNode sof = new IIOMetadataNode("sof");
|
||||||
@ -96,13 +155,13 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
HuffmanTable huffmanTable = (HuffmanTable) segment;
|
HuffmanTable huffmanTable = (HuffmanTable) segment;
|
||||||
IIOMetadataNode dht = new IIOMetadataNode("dht");
|
IIOMetadataNode dht = new IIOMetadataNode("dht");
|
||||||
|
|
||||||
// Uses fixed tables...
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
for (int j = 0; j < 2; j++) {
|
for (int c = 0; c < 2; c++) {
|
||||||
if (huffmanTable.tc[i][j] != 0) {
|
if (huffmanTable.isPresent(i, c)) {
|
||||||
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
|
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
|
||||||
dhtable.setAttribute("class", String.valueOf(j));
|
dhtable.setAttribute("class", String.valueOf(c));
|
||||||
dhtable.setAttribute("htableId", String.valueOf(i));
|
dhtable.setAttribute("htableId", String.valueOf(i));
|
||||||
|
dhtable.setUserObject(huffmanTable.toNativeTable(i, c));
|
||||||
dht.appendChild(dhtable);
|
dht.appendChild(dhtable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,8 +171,28 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case JPEG.DQT:
|
case JPEG.DQT:
|
||||||
markerSequence.appendChild(new IIOMetadataNode("dqt"));
|
QuantizationTable quantizationTable = (QuantizationTable) segment;
|
||||||
// TODO:
|
IIOMetadataNode dqt = new IIOMetadataNode("dqt");
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (quantizationTable.isPresent(i)) {
|
||||||
|
IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
|
||||||
|
dqtable.setAttribute("elementPrecision", quantizationTable.precision(i) != 16 ? "0" : "1"); // 0 = 8 bits, 1 = 16 bits
|
||||||
|
dqtable.setAttribute("qtableId", Integer.toString(i));
|
||||||
|
dqtable.setUserObject(quantizationTable.toNativeTable(i));
|
||||||
|
dqt.appendChild(dqtable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markerSequence.appendChild(dqt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JPEG.DRI:
|
||||||
|
RestartInterval restartInterval = (RestartInterval) segment;
|
||||||
|
IIOMetadataNode dri = new IIOMetadataNode("dri");
|
||||||
|
dri.setAttribute("interval", Integer.toString(restartInterval.interval));
|
||||||
|
markerSequence.appendChild(dri);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JPEG.SOS:
|
case JPEG.SOS:
|
||||||
@ -144,6 +223,25 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case JPEG.APP0:
|
||||||
|
if (segment instanceof JFIF) {
|
||||||
|
// Either already added, or we'll ignore it anyway...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (isJFIF && segment instanceof JFXX) {
|
||||||
|
// Already added
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, fall through to unknown segment
|
||||||
|
|
||||||
|
case JPEG.APP2:
|
||||||
|
if (isJFIF && segment instanceof ICCProfile) {
|
||||||
|
// Already added
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Else, fall through to unknown segment
|
||||||
|
|
||||||
case JPEG.APP14:
|
case JPEG.APP14:
|
||||||
if (segment instanceof AdobeDCT) {
|
if (segment instanceof AdobeDCT) {
|
||||||
AdobeDCT adobe = (AdobeDCT) segment;
|
AdobeDCT adobe = (AdobeDCT) segment;
|
||||||
@ -165,30 +263,147 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return root;
|
private void appendICCProfile(IIOMetadataNode app0JFIF) {
|
||||||
|
if (embeddedICCProfile != null) {
|
||||||
|
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
|
||||||
|
app2ICC.setUserObject(embeddedICCProfile);
|
||||||
|
|
||||||
|
app0JFIF.appendChild(app2ICC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apendJFXX(IIOMetadataNode app0JFIF) {
|
||||||
|
if (jfxx != null) {
|
||||||
|
IIOMetadataNode jfxxNode = new IIOMetadataNode("JFXX");
|
||||||
|
app0JFIF.appendChild(jfxxNode);
|
||||||
|
|
||||||
|
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
|
||||||
|
app0JFXX.setAttribute("extensionCode", Integer.toString(jfxx.extensionCode));
|
||||||
|
jfxxNode.appendChild(app0JFXX);
|
||||||
|
|
||||||
|
switch (jfxx.extensionCode) {
|
||||||
|
case JFXX.JPEG:
|
||||||
|
IIOMetadataNode thumbJPEG = new IIOMetadataNode("JFIFthumbJPEG");
|
||||||
|
thumbJPEG.appendChild(new IIOMetadataNode("markerSequence"));
|
||||||
|
// TODO: Insert segments in marker sequence...
|
||||||
|
// List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(new ByteArrayImageInputStream(jfxx.thumbnail), JPEGSegmentUtil.ALL_SEGMENTS);
|
||||||
|
// Convert to Segment as in JPEGImageReader...
|
||||||
|
// appendMarkerSequence(thumbJPEG, segments, false);
|
||||||
|
|
||||||
|
app0JFXX.appendChild(thumbJPEG);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JFXX.INDEXED:
|
||||||
|
IIOMetadataNode thumbPalette = new IIOMetadataNode("JFIFthumbPalette");
|
||||||
|
thumbPalette.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
|
||||||
|
thumbPalette.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
|
||||||
|
app0JFXX.appendChild(thumbPalette);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JFXX.RGB:
|
||||||
|
IIOMetadataNode thumbRGB = new IIOMetadataNode("JFIFthumbRGB");
|
||||||
|
thumbRGB.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
|
||||||
|
thumbRGB.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
|
||||||
|
app0JFXX.appendChild(thumbRGB);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardChromaNode() {
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
for (Segment segment : segments) {
|
|
||||||
if (segment instanceof Frame) {
|
|
||||||
Frame sofSegment = (Frame) segment;
|
|
||||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||||
colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "GRAY" : "RGB"); // TODO YCC, YCCK, CMYK etc
|
colorSpaceType.setAttribute("name", getColorSpaceType());
|
||||||
chroma.appendChild(colorSpaceType);
|
chroma.appendChild(colorSpaceType);
|
||||||
|
|
||||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||||
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame()));
|
numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame()));
|
||||||
chroma.appendChild(numChannels);
|
chroma.appendChild(numChannels);
|
||||||
|
|
||||||
break;
|
return chroma;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getColorSpaceType() {
|
||||||
|
try {
|
||||||
|
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
|
||||||
|
|
||||||
|
switch (csType) {
|
||||||
|
case Gray:
|
||||||
|
case GrayA:
|
||||||
|
return "GRAY";
|
||||||
|
case YCbCr:
|
||||||
|
case YCbCrA:
|
||||||
|
return "YCbCr";
|
||||||
|
case RGB:
|
||||||
|
case RGBA:
|
||||||
|
return "RGB";
|
||||||
|
case PhotoYCC:
|
||||||
|
case PhotoYCCA:
|
||||||
|
return "PhotoYCC";
|
||||||
|
case YCCK:
|
||||||
|
return "YCCK";
|
||||||
|
case CMYK:
|
||||||
|
return "CMYK";
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.toString(frame.componentsInFrame(), 16) + "CLR";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAlpha() {
|
||||||
|
try {
|
||||||
|
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
|
||||||
|
|
||||||
|
switch (csType) {
|
||||||
|
case GrayA:
|
||||||
|
case YCbCrA:
|
||||||
|
case RGBA:
|
||||||
|
case PhotoYCCA:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLossess() {
|
||||||
|
switch (frame.marker) {
|
||||||
|
case JPEG.SOF3:
|
||||||
|
case JPEG.SOF7:
|
||||||
|
case JPEG.SOF11:
|
||||||
|
case JPEG.SOF15:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chroma;
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||||
|
if (hasAlpha()) {
|
||||||
|
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||||
|
|
||||||
|
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||||
|
alpha.setAttribute("value", "nonpremultipled");
|
||||||
|
transparency.appendChild(alpha);
|
||||||
|
|
||||||
|
return transparency;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -200,7 +415,7 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
compression.appendChild(compressionTypeName);
|
compression.appendChild(compressionTypeName);
|
||||||
|
|
||||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only
|
lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE");
|
||||||
compression.appendChild(lossless);
|
compression.appendChild(lossless);
|
||||||
|
|
||||||
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
|
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
|
||||||
@ -215,12 +430,67 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
imageOrientation.setAttribute("value", "normal"); // TODO
|
imageOrientation.setAttribute("value", getExifOrientation(exif));
|
||||||
dimension.appendChild(imageOrientation);
|
dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
|
if (jfif != null) {
|
||||||
|
// Aspect ratio
|
||||||
|
float xDensity = Math.max(1, jfif.xDensity);
|
||||||
|
float yDensity = Math.max(1, jfif.yDensity);
|
||||||
|
float aspectRatio = jfif.units == 0 ? xDensity / yDensity : yDensity / xDensity;
|
||||||
|
|
||||||
|
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
pixelAspectRatio.setAttribute("value", Float.toString(aspectRatio));
|
||||||
|
dimension.insertBefore(pixelAspectRatio, imageOrientation); // Keep order
|
||||||
|
|
||||||
|
if (jfif.units != 0) {
|
||||||
|
// Pixel size
|
||||||
|
float scale = jfif.units == 1 ? 25.4F : 10.0F; // DPI or DPcm
|
||||||
|
|
||||||
|
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
|
||||||
|
horizontalPixelSize.setAttribute("value", Float.toString(scale / xDensity));
|
||||||
|
dimension.appendChild(horizontalPixelSize);
|
||||||
|
|
||||||
|
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
|
||||||
|
verticalPixelSize.setAttribute("value", Float.toString(scale / yDensity));
|
||||||
|
dimension.appendChild(verticalPixelSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dimension;
|
return dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getExifOrientation(Directory exif) {
|
||||||
|
if (exif != null) {
|
||||||
|
Entry orientationEntry = exif.getEntryById(TIFF.TAG_ORIENTATION);
|
||||||
|
|
||||||
|
if (orientationEntry != null) {
|
||||||
|
switch (((Number) orientationEntry.getValue()).intValue()) {
|
||||||
|
case 2:
|
||||||
|
return "FlipH";
|
||||||
|
case 3:
|
||||||
|
return "Rotate180";
|
||||||
|
case 4:
|
||||||
|
return "FlipV";
|
||||||
|
case 5:
|
||||||
|
return "FlipVRotate90";
|
||||||
|
case 6:
|
||||||
|
return "Rotate270";
|
||||||
|
case 7:
|
||||||
|
return "FlipHRotate90";
|
||||||
|
case 8:
|
||||||
|
return "Rotate90";
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
// Fall-through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Normal";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardTextNode() {
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
@ -235,6 +505,10 @@ class JPEGImage10Metadata extends AbstractMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add the following from Exif (as in TIFFMetadata)
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||||
|
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||||
|
|
||||||
return text.hasChildNodes() ? text : null;
|
return text.hasChildNodes() ? text : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,6 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
/** Internal constant for referring all APP segments */
|
/** Internal constant for referring all APP segments */
|
||||||
static final int ALL_APP_MARKERS = -1;
|
static final int ALL_APP_MARKERS = -1;
|
||||||
|
|
||||||
/** Segment identifiers for the JPEG segments we care about reading. */
|
|
||||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
|
|
||||||
|
|
||||||
/** Our JPEG reading delegate */
|
/** Our JPEG reading delegate */
|
||||||
private final ImageReader delegate;
|
private final ImageReader delegate;
|
||||||
|
|
||||||
@ -534,7 +531,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
|
static JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
|
||||||
// Adapted from libjpeg jdapimin.c:
|
// Adapted from libjpeg jdapimin.c:
|
||||||
// Guess the input colorspace
|
// Guess the input colorspace
|
||||||
// (Wish JPEG committee had provided a real way to specify this...)
|
// (Wish JPEG committee had provided a real way to specify this...)
|
||||||
@ -717,7 +714,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private void initHeader(final int imageIndex) throws IOException {
|
private void initHeader(final int imageIndex) throws IOException {
|
||||||
if (imageIndex < 0) {
|
if (imageIndex < 0) {
|
||||||
throw new IllegalArgumentException("imageIndex < 0: " + imageIndex);
|
throw new IndexOutOfBoundsException("imageIndex < 0: " + imageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageIndex == currentStreamIndex) {
|
if (imageIndex == currentStreamIndex) {
|
||||||
@ -837,7 +834,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
try {
|
try {
|
||||||
imageInput.seek(streamOffsets.get(currentStreamIndex));
|
imageInput.seek(streamOffsets.get(currentStreamIndex));
|
||||||
|
|
||||||
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
return JPEGSegmentUtil.readSegments(imageInput, JPEGSegmentUtil.ALL_SEGMENTS);
|
||||||
}
|
}
|
||||||
catch (IIOException | IllegalArgumentException ignore) {
|
catch (IIOException | IllegalArgumentException ignore) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -1218,38 +1215,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
|
|
||||||
checkBounds(imageIndex);
|
|
||||||
initHeader(imageIndex);
|
initHeader(imageIndex);
|
||||||
|
|
||||||
IIOMetadata imageMetadata;
|
return new JPEGImage10Metadata(segments, getSOF(), getJFIF(), getJFXX(), getEmbeddedICCProfile(true), getAdobeDCT(), getExif());
|
||||||
|
|
||||||
if (isLossless()) {
|
|
||||||
return new JPEGImage10Metadata(segments);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
imageMetadata = delegate.getImageMetadata(0);
|
|
||||||
}
|
|
||||||
catch (IndexOutOfBoundsException knownIssue) {
|
|
||||||
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
|
||||||
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
|
|
||||||
}
|
|
||||||
catch (NegativeArraySizeException knownIssue) {
|
|
||||||
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
|
|
||||||
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
|
|
||||||
if (metadataCleaner == null) {
|
|
||||||
metadataCleaner = new JPEGImage10MetadataCleaner(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadataCleaner.cleanMetadata(imageMetadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageMetadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,6 +39,7 @@ import javax.imageio.IIOException;
|
|||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
final class JPEGLosslessDecoder {
|
final class JPEGLosslessDecoder {
|
||||||
@ -51,12 +52,12 @@ final class JPEGLosslessDecoder {
|
|||||||
private final QuantizationTable quantTable;
|
private final QuantizationTable quantTable;
|
||||||
private Scan scan;
|
private Scan scan;
|
||||||
|
|
||||||
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
|
private final int[][][] HuffTab = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
|
||||||
private final int IDCT_Source[] = new int[64];
|
private final int[] IDCT_Source = new int[64];
|
||||||
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan
|
private final int[] nBlock = new int[10]; // number of blocks in the i-th Comp in a scan
|
||||||
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan
|
private final int[][] acTab = new int[10][]; // ac HuffTab for the i-th Comp in a scan
|
||||||
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan
|
private final int[][] dcTab = new int[10][]; // dc HuffTab for the i-th Comp in a scan
|
||||||
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
|
private final int[][] qTab = new int[10][]; // quantization table for the i-th Comp in a scan
|
||||||
|
|
||||||
private boolean restarting;
|
private boolean restarting;
|
||||||
private int marker;
|
private int marker;
|
||||||
@ -70,7 +71,7 @@ final class JPEGLosslessDecoder {
|
|||||||
private int mask;
|
private int mask;
|
||||||
private int[][] outputData;
|
private int[][] outputData;
|
||||||
|
|
||||||
private static final int IDCT_P[] = {
|
private static final int[] IDCT_P = {
|
||||||
0, 5, 40, 16, 45, 2, 7, 42,
|
0, 5, 40, 16, 45, 2, 7, 42,
|
||||||
21, 56, 8, 61, 18, 47, 1, 4,
|
21, 56, 8, 61, 18, 47, 1, 4,
|
||||||
41, 23, 58, 13, 32, 24, 37, 10,
|
41, 23, 58, 13, 32, 24, 37, 10,
|
||||||
@ -80,16 +81,6 @@ final class JPEGLosslessDecoder {
|
|||||||
50, 55, 25, 36, 11, 62, 14, 35,
|
50, 55, 25, 36, 11, 62, 14, 35,
|
||||||
28, 49, 52, 27, 38, 30, 51, 54
|
28, 49, 52, 27, 38, 30, 51, 54
|
||||||
};
|
};
|
||||||
private static final int TABLE[] = {
|
|
||||||
0, 1, 5, 6, 14, 15, 27, 28,
|
|
||||||
2, 4, 7, 13, 16, 26, 29, 42,
|
|
||||||
3, 8, 12, 17, 25, 30, 41, 43,
|
|
||||||
9, 11, 18, 24, 31, 40, 44, 53,
|
|
||||||
10, 19, 23, 32, 39, 45, 52, 54,
|
|
||||||
20, 22, 33, 38, 46, 51, 55, 60,
|
|
||||||
21, 34, 37, 47, 50, 56, 59, 61,
|
|
||||||
35, 36, 48, 49, 57, 58, 62, 63
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
|
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
|
||||||
private static final int RESTART_MARKER_END = 0xFFD7;
|
private static final int RESTART_MARKER_END = 0xFFD7;
|
||||||
@ -158,7 +149,7 @@ final class JPEGLosslessDecoder {
|
|||||||
huffTable.buildHuffTables(HuffTab);
|
huffTable.buildHuffTables(HuffTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
quantTable.enhanceTables(TABLE);
|
quantTable.enhanceTables();
|
||||||
|
|
||||||
current = input.readUnsignedShort();
|
current = input.readUnsignedShort();
|
||||||
|
|
||||||
@ -185,11 +176,10 @@ final class JPEGLosslessDecoder {
|
|||||||
selection = scan.spectralSelStart;
|
selection = scan.spectralSelStart;
|
||||||
|
|
||||||
final Scan.Component[] scanComps = scan.components;
|
final Scan.Component[] scanComps = scan.components;
|
||||||
final int[][] quantTables = quantTable.quantTables;
|
|
||||||
|
|
||||||
for (int i = 0; i < numComp; i++) {
|
for (int i = 0; i < numComp; i++) {
|
||||||
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
|
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
|
||||||
qTab[i] = quantTables[component.qtSel];
|
qTab[i] = quantTable.qTable(component.qtSel);
|
||||||
nBlock[i] = component.vSub * component.hSub;
|
nBlock[i] = component.vSub * component.hSub;
|
||||||
|
|
||||||
int dcTabSel = scanComps[i].dcTabSel;
|
int dcTabSel = scanComps[i].dcTabSel;
|
||||||
@ -220,18 +210,18 @@ final class JPEGLosslessDecoder {
|
|||||||
outputData[componentIndex] = new int[xDim * yDim];
|
outputData[componentIndex] = new int[xDim * yDim];
|
||||||
}
|
}
|
||||||
|
|
||||||
final int firstValue[] = new int[numComp];
|
final int[] firstValue = new int[numComp];
|
||||||
for (int i = 0; i < numComp; i++) {
|
for (int i = 0; i < numComp; i++) {
|
||||||
firstValue[i] = (1 << (precision - 1));
|
firstValue[i] = (1 << (precision - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int pred[] = new int[numComp];
|
final int[] pred = new int[numComp];
|
||||||
|
|
||||||
scanNum++;
|
scanNum++;
|
||||||
|
|
||||||
while (true) { // Decode one scan
|
while (true) { // Decode one scan
|
||||||
int temp[] = new int[1]; // to store remainder bits
|
int[] temp = new int[1]; // to store remainder bits
|
||||||
int index[] = new int[1];
|
int[] index = new int[1];
|
||||||
|
|
||||||
System.arraycopy(firstValue, 0, pred, 0, numComp);
|
System.arraycopy(firstValue, 0, pred, 0, numComp);
|
||||||
|
|
||||||
@ -288,7 +278,7 @@ final class JPEGLosslessDecoder {
|
|||||||
private boolean useACForDC(final int dcTabSel) {
|
private boolean useACForDC(final int dcTabSel) {
|
||||||
if (isLossless()) {
|
if (isLossless()) {
|
||||||
for (HuffmanTable huffTable : huffTables) {
|
for (HuffmanTable huffTable : huffTables) {
|
||||||
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
|
if (!huffTable.isPresent(dcTabSel, 0) && huffTable.isPresent(dcTabSel, 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,7 +314,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return Scan.read(input, length);
|
return Scan.read(input, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decode(final int prev[], final int temp[], final int index[]) throws IOException {
|
private int decode(final int[] prev, final int[] temp, final int[] index) throws IOException {
|
||||||
if (numComp == 1) {
|
if (numComp == 1) {
|
||||||
return decodeSingle(prev, temp, index);
|
return decodeSingle(prev, temp, index);
|
||||||
}
|
}
|
||||||
@ -336,7 +326,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException {
|
private int decodeSingle(final int[] prev, final int[] temp, final int[] index) throws IOException {
|
||||||
// At the beginning of the first line and
|
// At the beginning of the first line and
|
||||||
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
|
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
|
||||||
if (restarting) {
|
if (restarting) {
|
||||||
@ -390,7 +380,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
|
private int decodeRGB(final int[] prev, final int[] temp, final int[] index) throws IOException {
|
||||||
final int[] outputRedData = outputData[0];
|
final int[] outputRedData = outputData[0];
|
||||||
final int[] outputGreenData = outputData[1];
|
final int[] outputGreenData = outputData[1];
|
||||||
final int[] outputBlueData = outputData[2];
|
final int[] outputBlueData = outputData[2];
|
||||||
@ -435,7 +425,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return decode0(prev, temp, index);
|
return decode0(prev, temp, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decodeAny(final int prev[], final int temp[], final int index[]) throws IOException {
|
private int decodeAny(final int[] prev, final int[] temp, final int[] index) throws IOException {
|
||||||
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
|
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
|
||||||
final int[] outputData = this.outputData[componentIndex];
|
final int[] outputData = this.outputData[componentIndex];
|
||||||
final int previous;
|
final int previous;
|
||||||
@ -469,17 +459,17 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
|
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
|
||||||
int value, actab[], dctab[];
|
int value;
|
||||||
int qtab[];
|
int[] actab;
|
||||||
|
int[] dctab;
|
||||||
|
int[] qtab;
|
||||||
|
|
||||||
for (int ctrC = 0; ctrC < numComp; ctrC++) {
|
for (int ctrC = 0; ctrC < numComp; ctrC++) {
|
||||||
qtab = qTab[ctrC];
|
qtab = qTab[ctrC];
|
||||||
actab = acTab[ctrC];
|
actab = acTab[ctrC];
|
||||||
dctab = dcTab[ctrC];
|
dctab = dcTab[ctrC];
|
||||||
for (int i = 0; i < nBlock[ctrC]; i++) {
|
for (int i = 0; i < nBlock[ctrC]; i++) {
|
||||||
for (int k = 0; k < IDCT_Source.length; k++) {
|
Arrays.fill(IDCT_Source, 0);
|
||||||
IDCT_Source[k] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = getHuffmanValue(dctab, temp, index);
|
value = getHuffmanValue(dctab, temp, index);
|
||||||
|
|
||||||
@ -545,7 +535,7 @@ final class JPEGLosslessDecoder {
|
|||||||
// and marker_index=9
|
// and marker_index=9
|
||||||
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
||||||
// will not be called
|
// will not be called
|
||||||
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException {
|
private int getHuffmanValue(final int[] table, final int[] temp, final int[] index) throws IOException {
|
||||||
int code, input;
|
int code, input;
|
||||||
final int mask = 0xFFFF;
|
final int mask = 0xFFFF;
|
||||||
|
|
||||||
@ -603,7 +593,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return code & 0xFF;
|
return code & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException {
|
private int getn(final int[] pred, final int n, final int[] temp, final int[] index) throws IOException {
|
||||||
int result;
|
int result;
|
||||||
final int one = 1;
|
final int one = 1;
|
||||||
final int n_one = -1;
|
final int n_one = -1;
|
||||||
@ -688,7 +678,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPreviousX(final int data[]) {
|
private int getPreviousX(final int[] data) {
|
||||||
if (xLoc > 0) {
|
if (xLoc > 0) {
|
||||||
return data[((yLoc * xDim) + xLoc) - 1];
|
return data[((yLoc * xDim) + xLoc) - 1];
|
||||||
}
|
}
|
||||||
@ -700,7 +690,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPreviousXY(final int data[]) {
|
private int getPreviousXY(final int[] data) {
|
||||||
if ((xLoc > 0) && (yLoc > 0)) {
|
if ((xLoc > 0) && (yLoc > 0)) {
|
||||||
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
|
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
|
||||||
}
|
}
|
||||||
@ -709,7 +699,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPreviousY(final int data[]) {
|
private int getPreviousY(final int[] data) {
|
||||||
if (yLoc > 0) {
|
if (yLoc > 0) {
|
||||||
return data[((yLoc - 1) * xDim) + xLoc];
|
return data[((yLoc - 1) * xDim) + xLoc];
|
||||||
}
|
}
|
||||||
@ -722,7 +712,7 @@ final class JPEGLosslessDecoder {
|
|||||||
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
|
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void output(final int pred[]) {
|
private void output(final int[] pred) {
|
||||||
if (numComp == 1) {
|
if (numComp == 1) {
|
||||||
outputSingle(pred);
|
outputSingle(pred);
|
||||||
}
|
}
|
||||||
@ -734,7 +724,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputSingle(final int pred[]) {
|
private void outputSingle(final int[] pred) {
|
||||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||||
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
|
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
|
||||||
xLoc++;
|
xLoc++;
|
||||||
@ -746,7 +736,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputRGB(final int pred[]) {
|
private void outputRGB(final int[] pred) {
|
||||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||||
final int index = (yLoc * xDim) + xLoc;
|
final int index = (yLoc * xDim) + xLoc;
|
||||||
outputData[0][index] = pred[0];
|
outputData[0][index] = pred[0];
|
||||||
@ -761,7 +751,7 @@ final class JPEGLosslessDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputAny(final int pred[]) {
|
private void outputAny(final int[] pred) {
|
||||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||||
final int index = (yLoc * xDim) + xLoc;
|
final int index = (yLoc * xDim) + xLoc;
|
||||||
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
|
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
|
||||||
|
@ -35,35 +35,42 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
|||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.plugins.jpeg.JPEGQTable;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
final class QuantizationTable extends Segment {
|
final class QuantizationTable extends Segment {
|
||||||
|
|
||||||
private final int precision[] = new int[4]; // Quantization precision 8 or 16
|
private static final int[] ZIGZAG = {
|
||||||
private final int[] tq = new int[4]; // 1: this table is presented
|
0, 1, 5, 6, 14, 15, 27, 28,
|
||||||
|
2, 4, 7, 13, 16, 26, 29, 42,
|
||||||
|
3, 8, 12, 17, 25, 30, 41, 43,
|
||||||
|
9, 11, 18, 24, 31, 40, 44, 53,
|
||||||
|
10, 19, 23, 32, 39, 45, 52, 54,
|
||||||
|
20, 22, 33, 38, 46, 51, 55, 60,
|
||||||
|
21, 34, 37, 47, 50, 56, 59, 61,
|
||||||
|
35, 36, 48, 49, 57, 58, 62, 63
|
||||||
|
};
|
||||||
|
|
||||||
final int quantTables[][] = new int[4][64]; // Tables
|
private final int[] precision = new int[4]; // Quantization precision 8 or 16
|
||||||
|
private final boolean[] tq = new boolean[4]; // 1: this table is present
|
||||||
|
|
||||||
|
private final int[][] quantTables = new int[4][64]; // Tables
|
||||||
|
|
||||||
QuantizationTable() {
|
QuantizationTable() {
|
||||||
super(JPEG.DQT);
|
super(JPEG.DQT);
|
||||||
|
|
||||||
tq[0] = 0;
|
|
||||||
tq[1] = 0;
|
|
||||||
tq[2] = 0;
|
|
||||||
tq[3] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get rid of table param, make it a member?
|
// TODO: Consider creating a copy for the decoder here, as we need to keep the original values for the metadata
|
||||||
void enhanceTables(final int[] table) throws IOException {
|
void enhanceTables() {
|
||||||
for (int t = 0; t < 4; t++) {
|
for (int t = 0; t < 4; t++) {
|
||||||
if (tq[t] != 0) {
|
if (tq[t]) {
|
||||||
enhanceQuantizationTable(quantTables[t], table);
|
enhanceQuantizationTable(quantTables[t], ZIGZAG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enhanceQuantizationTable(final int qtab[], final int[] table) {
|
private void enhanceQuantizationTable(final int[] qtab, final int[] table) {
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
qtab[table[ i]] *= 90;
|
qtab[table[ i]] *= 90;
|
||||||
qtab[table[(4 * 8) + i]] *= 90;
|
qtab[table[(4 * 8) + i]] *= 90;
|
||||||
@ -122,7 +129,7 @@ final class QuantizationTable extends Segment {
|
|||||||
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
|
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.tq[t] = 1;
|
table.tq[t] = true;
|
||||||
|
|
||||||
if (table.precision[t] == 8) {
|
if (table.precision[t] == 8) {
|
||||||
for (int i = 0; i < 64; i++) {
|
for (int i = 0; i < 64; i++) {
|
||||||
@ -152,4 +159,28 @@ final class QuantizationTable extends Segment {
|
|||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPresent(int tabelId) {
|
||||||
|
return tq[tabelId];
|
||||||
|
}
|
||||||
|
|
||||||
|
int precision(int tableId) {
|
||||||
|
return precision[tableId];
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] qTable(int tabelId) {
|
||||||
|
return quantTables[tabelId];
|
||||||
|
}
|
||||||
|
|
||||||
|
JPEGQTable toNativeTable(int tableId) {
|
||||||
|
// TODO: Should de-zigzag (ie. "natural order") while reading
|
||||||
|
// TODO: ...and make sure the table isn't "enhanced"...
|
||||||
|
int[] qTable = new int[quantTables[tableId].length];
|
||||||
|
|
||||||
|
for (int i = 0; i < qTable.length; i++) {
|
||||||
|
qTable[i] = quantTables[tableId][ZIGZAG[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JPEGQTable(qTable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,9 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
import org.hamcrest.core.IsInstanceOf;
|
import org.hamcrest.core.IsInstanceOf;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.internal.matchers.GreaterThan;
|
import org.mockito.internal.matchers.GreaterThan;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@ -182,7 +183,9 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
public void testICCProfileCMYKClassOutputColors() throws IOException {
|
public void testICCProfileCMYKClassOutputColors() throws IOException {
|
||||||
// Make sure ICC profile with class output isn't converted to too bright values
|
// Make sure ICC profile with class output isn't converted to too bright values
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg")));
|
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
param.setSourceRegion(new Rectangle(800, 800, 64, 8));
|
param.setSourceRegion(new Rectangle(800, 800, 64, 8));
|
||||||
@ -197,9 +200,11 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertEquals(expectedData.length, data.length);
|
assertEquals(expectedData.length, data.length);
|
||||||
|
|
||||||
assertJPEGPixelsEqual(expectedData, data, 0);
|
assertJPEGPixelsEqual(expectedData, data, 0);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, int actualOffset) {
|
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, int actualOffset) {
|
||||||
for (int i = 0; i < expected.length; i++) {
|
for (int i = 0; i < expected.length; i++) {
|
||||||
@ -211,7 +216,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
public void testICCDuplicateSequence() throws IOException {
|
public void testICCDuplicateSequence() throws IOException {
|
||||||
// Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1
|
// Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(345, reader.getWidth(0));
|
assertEquals(345, reader.getWidth(0));
|
||||||
assertEquals(540, reader.getHeight(0));
|
assertEquals(540, reader.getHeight(0));
|
||||||
@ -221,15 +227,18 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(345, image.getWidth());
|
assertEquals(345, image.getWidth());
|
||||||
assertEquals(540, image.getHeight());
|
assertEquals(540, image.getHeight());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testICCDuplicateSequenceZeroBased() throws IOException {
|
public void testICCDuplicateSequenceZeroBased() throws IOException {
|
||||||
// File contains multiple ICC chunks, with all counts and sequence numbers == 0
|
// File contains multiple ICC chunks, with all counts and sequence numbers == 0
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(3874, reader.getWidth(0));
|
assertEquals(3874, reader.getWidth(0));
|
||||||
assertEquals(5480, reader.getHeight(0));
|
assertEquals(5480, reader.getHeight(0));
|
||||||
@ -241,9 +250,11 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(3874, image.getWidth());
|
assertEquals(3874, image.getWidth());
|
||||||
assertEquals(16, image.getHeight());
|
assertEquals(16, image.getHeight());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTruncatedICCProfile() throws IOException {
|
public void testTruncatedICCProfile() throws IOException {
|
||||||
@ -251,7 +262,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
// Profile should have been about 550 000 bytes, split into multiple chunks. Written by GIMP 2.6.11
|
// Profile should have been about 550 000 bytes, split into multiple chunks. Written by GIMP 2.6.11
|
||||||
// See: https://bugzilla.redhat.com/show_bug.cgi?id=695246
|
// See: https://bugzilla.redhat.com/show_bug.cgi?id=695246
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(1993, reader.getWidth(0));
|
assertEquals(1993, reader.getWidth(0));
|
||||||
assertEquals(1038, reader.getHeight(0));
|
assertEquals(1038, reader.getHeight(0));
|
||||||
@ -263,16 +275,19 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(1993, image.getWidth());
|
assertEquals(1993, image.getWidth());
|
||||||
assertEquals(8, image.getHeight());
|
assertEquals(8, image.getHeight());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCCOIllegalArgument() throws IOException {
|
public void testCCOIllegalArgument() throws IOException {
|
||||||
// File contains CMYK ICC profile ("Coated FOGRA27 (ISO 12647-2:2004)"), but image data is 3 channel YCC/RGB
|
// File contains CMYK ICC profile ("Coated FOGRA27 (ISO 12647-2:2004)"), but image data is 3 channel YCC/RGB
|
||||||
// JFIF 1.1 with unknown origin.
|
// JFIF 1.1 with unknown origin.
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cco-illegalargument-rgb-coated-fogra27.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cco-illegalargument-rgb-coated-fogra27.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(281, reader.getWidth(0));
|
assertEquals(281, reader.getWidth(0));
|
||||||
assertEquals(449, reader.getHeight(0));
|
assertEquals(449, reader.getHeight(0));
|
||||||
@ -284,8 +299,11 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertEquals(449, image.getHeight());
|
assertEquals(449, image.getHeight());
|
||||||
|
|
||||||
// TODO: Need to test colors!
|
// TODO: Need to test colors!
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoImageTypesRGBWithCMYKProfile() throws IOException {
|
public void testNoImageTypesRGBWithCMYKProfile() throws IOException {
|
||||||
@ -293,7 +311,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
// but image data is plain 3 channel YCC/RGB.
|
// but image data is plain 3 channel YCC/RGB.
|
||||||
// EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"...
|
// EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"...
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(1743, reader.getWidth(0));
|
assertEquals(1743, reader.getWidth(0));
|
||||||
assertEquals(2551, reader.getHeight(0));
|
assertEquals(2551, reader.getHeight(0));
|
||||||
@ -310,13 +329,18 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
|
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCMYKWithRGBProfile() throws IOException {
|
public void testCMYKWithRGBProfile() throws IOException {
|
||||||
// File contains JFIF (!), RGB ICC profile AND Adobe App14 specifying unknown conversion,
|
// File contains JFIF (!), RGB ICC profile AND Adobe App14 specifying unknown conversion,
|
||||||
// but image data is 4 channel CMYK (from SOF0 channel Ids 'C', 'M', 'Y', 'K').
|
// but image data is 4 channel CMYK (from SOF0 channel Ids 'C', 'M', 'Y', 'K').
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-cmyk-invalid-icc-profile-srgb.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-cmyk-invalid-icc-profile-srgb.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(493, reader.getWidth(0));
|
assertEquals(493, reader.getWidth(0));
|
||||||
assertEquals(500, reader.getHeight(0));
|
assertEquals(500, reader.getHeight(0));
|
||||||
@ -333,11 +357,16 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
assertFalse(reader.hasThumbnails(0)); // Should not blow up!
|
assertFalse(reader.hasThumbnails(0)); // Should not blow up!
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException {
|
public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/warning-embedded-color-profile-invalid-ignored-cmyk.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/warning-embedded-color-profile-invalid-ignored-cmyk.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(183, reader.getWidth(0));
|
assertEquals(183, reader.getWidth(0));
|
||||||
assertEquals(283, reader.getHeight(0));
|
assertEquals(283, reader.getHeight(0));
|
||||||
@ -350,12 +379,17 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
// TODO: Need to test colors!
|
// TODO: Need to test colors!
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEOFSOSSegment() throws IOException {
|
public void testEOFSOSSegment() throws IOException {
|
||||||
// Regression...
|
// Regression...
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/eof-sos-segment-bug.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/eof-sos-segment-bug.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(266, reader.getWidth(0));
|
assertEquals(266, reader.getWidth(0));
|
||||||
assertEquals(400, reader.getHeight(0));
|
assertEquals(400, reader.getHeight(0));
|
||||||
@ -366,14 +400,18 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertEquals(266, image.getWidth());
|
assertEquals(266, image.getWidth());
|
||||||
assertEquals(400, image.getHeight());
|
assertEquals(400, image.getHeight());
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidICCSingleChunkBadSequence() throws IOException {
|
public void testInvalidICCSingleChunkBadSequence() throws IOException {
|
||||||
// Regression
|
// Regression
|
||||||
// Single segment ICC profile, with chunk index/count == 0
|
// Single segment ICC profile, with chunk index/count == 0
|
||||||
|
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-single-chunk-bad-sequence-number.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-single-chunk-bad-sequence-number.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(1772, reader.getWidth(0));
|
assertEquals(1772, reader.getWidth(0));
|
||||||
assertEquals(2126, reader.getHeight(0));
|
assertEquals(2126, reader.getHeight(0));
|
||||||
@ -392,12 +430,17 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
|
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException {
|
public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException {
|
||||||
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes
|
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(600, reader.getWidth(0));
|
assertEquals(600, reader.getWidth(0));
|
||||||
assertEquals(600, reader.getHeight(0));
|
assertEquals(600, reader.getHeight(0));
|
||||||
@ -418,13 +461,18 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCorbisRGB() throws IOException {
|
public void testCorbisRGB() throws IOException {
|
||||||
// Special case, throws exception below without special treatment
|
// Special case, throws exception below without special treatment
|
||||||
// java.awt.color.CMMException: General CMM error517
|
// java.awt.color.CMMException: General CMM error517
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-corbis-rgb.jpg")));
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-corbis-rgb.jpg"))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
assertEquals(512, reader.getWidth(0));
|
assertEquals(512, reader.getWidth(0));
|
||||||
assertEquals(384, reader.getHeight(0));
|
assertEquals(384, reader.getHeight(0));
|
||||||
@ -434,26 +482,76 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(512, image.getWidth());
|
assertEquals(512, image.getWidth());
|
||||||
assertEquals(384, image.getHeight());
|
assertEquals(384, image.getHeight());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore("Known issue in com.sun...JPEGMetadata")
|
|
||||||
@Test
|
@Test
|
||||||
public void testStandardMetadataColorSpaceTypeRGBForYCbCr() {
|
public void testStandardMetadataColorSpaceTypeRGBForYCbCr() throws IOException {
|
||||||
// These reports RGB in standard metadata, while the data is really YCbCr.
|
List<TestData> ycbcr = Arrays.asList(
|
||||||
|
// This reports RGB in standard metadata, while the data is really YCbCr.
|
||||||
// Exif files are always YCbCr AFAIK.
|
// Exif files are always YCbCr AFAIK.
|
||||||
fail("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg");
|
new TestData(getClassLoaderResource("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg"), new Dimension(2437, 1662)),
|
||||||
fail("/jpeg/exif-pspro-13-inverted-colors.jpg");
|
|
||||||
// Not Exif, but same issue: SOF comp ids are JFIF standard 1-3 and
|
// Not Exif, but same issue: SOF comp ids are JFIF standard 1-3 and
|
||||||
// *should* be interpreted as YCbCr but isn't.
|
// *should* be interpreted as YCbCr but isn't.
|
||||||
// Possible fix for this, is to insert a fake JFIF segment, as this image
|
// Possible fix for this, is to insert a fake JFIF segment, as this image
|
||||||
// conforms to the JFIF spec (but it won't work for the Exif samples)
|
// conforms to the JFIF spec (but it won't work for the Exif samples)
|
||||||
fail("/jpeg/no-jfif-ycbcr.jpg");
|
new TestData(getClassLoaderResource("/jpeg/no-jfif-ycbcr.jpg"), new Dimension(310, 206))
|
||||||
|
);
|
||||||
|
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (TestData broken : ycbcr) {
|
||||||
|
reader.setInput(broken.getInputStream());
|
||||||
|
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
|
|
||||||
|
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
NodeList colorSpaceTypes = root.getElementsByTagName("ColorSpaceType");
|
||||||
|
assertEquals(1, colorSpaceTypes.getLength());
|
||||||
|
IIOMetadataNode csType = (IIOMetadataNode) colorSpaceTypes.item(0);
|
||||||
|
assertEquals("YCbCr", csType.getAttribute("name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenReadRasterAfterGetMetadataException() throws IOException {
|
public void testGetExifOrientationFromMetadata() throws IOException {
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
// TODO: Find better sample data. Should have an uppercase F ;-)
|
||||||
|
// Test all 9 mutations + missing Exif
|
||||||
|
List<String> expectedOrientations = Arrays.asList("Normal", "Normal", "FlipH", "Rotate180", "FlipV", "FlipVRotate90", "Rotate270", "FlipHRotate90", "Rotate90");
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource(String.format("/exif/Landscape_%d.jpg", i)))) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
|
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList orientationNodes = root.getElementsByTagName("ImageOrientation");
|
||||||
|
assertEquals(1, orientationNodes.getLength());
|
||||||
|
|
||||||
|
IIOMetadataNode orientationNode = (IIOMetadataNode) orientationNodes.item(0);
|
||||||
|
String orientationValue = orientationNode.getAttribute("value");
|
||||||
|
assertEquals(expectedOrientations.get(i), orientationValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBrokenReadRasterAfterGetMetadataException() {
|
||||||
// See issue #107, from PDFBox team
|
// See issue #107, from PDFBox team
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
@ -497,7 +595,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
// TODO: Consider wrapping the delegate in JPEGImageReader with methods that don't throw
|
// TODO: Consider wrapping the delegate in JPEGImageReader with methods that don't throw
|
||||||
// runtime exceptions, and instead throw IIOException?
|
// runtime exceptions, and instead throw IIOException?
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenGetRawImageType() throws IOException {
|
public void testBrokenGetRawImageType() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -523,7 +621,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 200)
|
@Test(timeout = 200)
|
||||||
public void testBrokenGetRawImageTypeIgnoreMetadata() throws IOException {
|
public void testBrokenGetRawImageTypeIgnoreMetadata() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -549,7 +647,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenGetImageTypes() throws IOException {
|
public void testBrokenGetImageTypes() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -575,7 +673,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 200)
|
@Test(timeout = 200)
|
||||||
public void testBrokenGetImageTypesIgnoreMetadata() throws IOException {
|
public void testBrokenGetImageTypesIgnoreMetadata() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -601,7 +699,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenRead() throws IOException {
|
public void testBrokenRead() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -627,7 +725,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenGetDimensions() throws IOException {
|
public void testBrokenGetDimensions() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -656,7 +754,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBrokenGetImageMetadata() throws IOException {
|
public void testBrokenGetImageMetadata() {
|
||||||
JPEGImageReader reader = createReader();
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1284,6 +1382,9 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
// Assume that the aspect ratio is 1 if both x/y density is 0.
|
// Assume that the aspect ratio is 1 if both x/y density is 0.
|
||||||
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
|
||||||
|
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false);
|
||||||
NodeList dimensions = tree.getElementsByTagName("Dimension");
|
NodeList dimensions = tree.getElementsByTagName("Dimension");
|
||||||
assertEquals(1, dimensions.getLength());
|
assertEquals(1, dimensions.getLength());
|
||||||
assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName());
|
assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName());
|
||||||
@ -1321,7 +1422,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
NodeList markerSequences = iioTree.getElementsByTagName("markerSequence");
|
NodeList markerSequences = iioTree.getElementsByTagName("markerSequence");
|
||||||
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
|
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
|
||||||
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(0);
|
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
|
||||||
assertNotNull(markerSequence);
|
assertNotNull(markerSequence);
|
||||||
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
|
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
|
||||||
|
|
||||||
@ -1379,6 +1480,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
|
|
||||||
for (TestData testData : getTestData()) {
|
for (TestData testData : getTestData()) {
|
||||||
reader.setInput(testData.getInputStream());
|
reader.setInput(testData.getInputStream());
|
||||||
|
assert referenceReader != null;
|
||||||
referenceReader.setInput(testData.getInputStream());
|
referenceReader.setInput(testData.getInputStream());
|
||||||
|
|
||||||
for (int i = 0; i < reader.getNumImages(true); i++) {
|
for (int i = 0; i < reader.getNumImages(true); i++) {
|
||||||
@ -1393,6 +1495,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
Node referenceTree = reference.getAsTree(formatName);
|
Node referenceTree = reference.getAsTree(formatName);
|
||||||
Node actualTree = metadata.getAsTree(formatName);
|
Node actualTree = metadata.getAsTree(formatName);
|
||||||
|
|
||||||
|
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(referenceTree, false);
|
||||||
|
// System.out.println("--------");
|
||||||
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(actualTree, false);
|
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(actualTree, false);
|
||||||
assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree);
|
assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree);
|
||||||
}
|
}
|
||||||
@ -1432,8 +1536,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expectedTree == null) {
|
if (expectedTree == null) {
|
||||||
assertNull(actualTree);
|
fail("Expected tree is null, actual tree is non-null");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(String.format("%s: Node names differ", message), expectedTree.getNodeName(), actualTree.getNodeName());
|
assertEquals(String.format("%s: Node names differ", message), expectedTree.getNodeName(), actualTree.getNodeName());
|
||||||
@ -1443,7 +1546,14 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertEquals(String.format("%s: Number of attributes for <%s> differ", message, expectedTree.getNodeName()), expectedAttributes.getLength(), actualAttributes.getLength());
|
assertEquals(String.format("%s: Number of attributes for <%s> differ", message, expectedTree.getNodeName()), expectedAttributes.getLength(), actualAttributes.getLength());
|
||||||
for (int i = 0; i < expectedAttributes.getLength(); i++) {
|
for (int i = 0; i < expectedAttributes.getLength(); i++) {
|
||||||
Node item = expectedAttributes.item(i);
|
Node item = expectedAttributes.item(i);
|
||||||
assertEquals(String.format("%s: \"%s\" attribute for <%s> differ", message, item.getNodeName(), expectedTree.getNodeName()), item.getNodeValue(), actualAttributes.getNamedItem(item.getNodeName()).getNodeValue());
|
String nodeValue = item.getNodeValue();
|
||||||
|
|
||||||
|
// NOTE: com.sun...JPEGMetadata javax_imageio_1.0 format bug: Uses "normal" instead of "Normal" ImageOrientation
|
||||||
|
if ("ImageOrientation".equals(expectedTree.getNodeName()) && "value".equals(item.getNodeName())) {
|
||||||
|
nodeValue = StringUtil.capitalize(nodeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(String.format("%s: \"%s\" attribute for <%s> differ", message, item.getNodeName(), expectedTree.getNodeName()), nodeValue, actualAttributes.getNamedItem(item.getNodeName()).getNodeValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for equal user objects.
|
// Test for equal user objects.
|
||||||
@ -1460,6 +1570,11 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("markerSequence".equals(expectedTree.getNodeName()) && "JFIFthumbJPEG".equals(expectedTree.getParentNode().getNodeName())) {
|
||||||
|
// TODO: We haven't implemented this yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort nodes to make sure that sequence of equally named tags does not matter
|
// Sort nodes to make sure that sequence of equally named tags does not matter
|
||||||
List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes());
|
List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes());
|
||||||
List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes());
|
List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes());
|
||||||
|
19
imageio/imageio-jpeg/src/test/resources/exif/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2010 Dave Perrett, http://recursive-design.com/
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_0.jpg
Normal file
After Width: | Height: | Size: 342 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_1.jpg
Normal file
After Width: | Height: | Size: 339 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_2.jpg
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_3.jpg
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_4.jpg
Normal file
After Width: | Height: | Size: 340 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_5.jpg
Normal file
After Width: | Height: | Size: 343 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_6.jpg
Normal file
After Width: | Height: | Size: 344 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_7.jpg
Normal file
After Width: | Height: | Size: 344 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Landscape_8.jpg
Normal file
After Width: | Height: | Size: 344 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_0.jpg
Normal file
After Width: | Height: | Size: 243 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_1.jpg
Normal file
After Width: | Height: | Size: 240 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_2.jpg
Normal file
After Width: | Height: | Size: 241 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_3.jpg
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_4.jpg
Normal file
After Width: | Height: | Size: 241 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_5.jpg
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_6.jpg
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_7.jpg
Normal file
After Width: | Height: | Size: 245 KiB |
BIN
imageio/imageio-jpeg/src/test/resources/exif/Portrait_8.jpg
Normal file
After Width: | Height: | Size: 246 KiB |
82
imageio/imageio-jpeg/src/test/resources/exif/README.markdown
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
EXIF Orientation-flag example images
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Example images using each of the EXIF orientation flags (0-to-8), in both landscape and portrait orientations.
|
||||||
|
|
||||||
|
[See here](http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/) for more information.
|
||||||
|
|
||||||
|
|
||||||
|
Generating your own images
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
If you would like to generate test images based on your own photos, you can use the `generate.rb` script included in the `generator` folder.
|
||||||
|
|
||||||
|
The instructions below assume you are running on macOS - if not, you will need to install the Ghostscript fonts (`brew install gs`) some other way.
|
||||||
|
|
||||||
|
To install the dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
> brew install gs exiftool imagemagick@6
|
||||||
|
> cd generator
|
||||||
|
> gem install bundler
|
||||||
|
> bundle install
|
||||||
|
```
|
||||||
|
|
||||||
|
To generate test images:
|
||||||
|
|
||||||
|
```
|
||||||
|
> cd generator
|
||||||
|
> ./generate.rb path/to/image.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create images `image_0.jpg` through to `image_8.jpg`.
|
||||||
|
|
||||||
|
|
||||||
|
Re-generating sample images
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Simply run `make` to regenerate the included sample images. This will download random portrait and landscape orientation images from [unsplash.com](https://unsplash.com/) and generate sample images for each of them.
|
||||||
|
|
||||||
|
Generating these images depends on having the generator dependencies installed - see the *Generating your own images* section for instructions on installing dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
* The sample landscape image is by [Pierre Bouillot](https://unsplash.com/photos/v15iOM6pWgI).
|
||||||
|
* The sample portrait image is by [John Salvino](https://unsplash.com/photos/1PPpwrTNkJI).
|
||||||
|
|
||||||
|
|
||||||
|
Change history
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* **Version 2.0.0 (2017-08-05)** : Add a script to generate example images from the command line.
|
||||||
|
* **Version 1.0.2 (2017-03-06)** : Remove Apple Copyrighted ICC profile from orientations 2-8 (thanks @mans0954!).
|
||||||
|
* **Version 1.0.1 (2013-03-10)** : Add MIT license and some contact details.
|
||||||
|
* **Version 1.0.0 (2012-07-28)** : 1.0 release.
|
||||||
|
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Once you've made your commits:
|
||||||
|
|
||||||
|
1. [Fork](http://help.github.com/fork-a-repo/) exif-orientation-examples
|
||||||
|
2. Create a topic branch - `git checkout -b my_branch`
|
||||||
|
3. Push to your branch - `git push origin my_branch`
|
||||||
|
4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch
|
||||||
|
5. That's it!
|
||||||
|
|
||||||
|
|
||||||
|
Author
|
||||||
|
------
|
||||||
|
|
||||||
|
Dave Perrett :: hello@daveperrett.com :: [@daveperrett](http://twitter.com/daveperrett)
|
||||||
|
|
||||||
|
|
||||||
|
Copyright
|
||||||
|
---------
|
||||||
|
|
||||||
|
These images are licensed under the [MIT License](http://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
Copyright (c) 2010 Dave Perrett. See [License](https://github.com/recurser/exif-orientation-examples/blob/master/LICENSE) for details.
|
1
imageio/imageio-jpeg/src/test/resources/exif/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
2.0.1
|