mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-03 23:53:15 -04:00
JPEG Exif rotation in metadata + support
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user