mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 12:05:29 -04:00
Merge pull request #206 from Schmidor/TIFFUtilities
Merging Tiff utilities to new contrib module.
This commit is contained in:
commit
a9428a1ecf
72
contrib/pom.xml
Normal file
72
contrib/pom.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
|
<version>3.3-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||||
|
<artifactId>contrib</artifactId>
|
||||||
|
<name>TwelveMonkeys :: Contrib</name>
|
||||||
|
<description>
|
||||||
|
Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
|
<artifactId>common-lang</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
|
<artifactId>common-io</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
|
<artifactId>common-image</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
|
<artifactId>common-lang</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
|
<artifactId>common-io</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-metadata</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-tiff</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-tiff</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,514 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.contrib.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.AffineTransformOp;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFFUtilities for manipulation TIFF Images and Metadata
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||||
|
* @author last modified by $Author$
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class TIFFUtilities {
|
||||||
|
private TIFFUtilities() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all pages from the input TIFF files into one TIFF file at the
|
||||||
|
* output location.
|
||||||
|
*
|
||||||
|
* @param inputFiles
|
||||||
|
* @param outputFile
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void merge(List<File> inputFiles, File outputFile) throws IOException {
|
||||||
|
ImageOutputStream output = null;
|
||||||
|
try {
|
||||||
|
output = ImageIO.createImageOutputStream(outputFile);
|
||||||
|
|
||||||
|
for (File file : inputFiles) {
|
||||||
|
ImageInputStream input = null;
|
||||||
|
try {
|
||||||
|
input = ImageIO.createImageInputStream(file);
|
||||||
|
List<TIFFPage> pages = getPages(input);
|
||||||
|
writePages(output, pages);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (input != null) {
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (output != null) {
|
||||||
|
output.flush();
|
||||||
|
output.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits all pages from the input TIFF file to one file per page in the
|
||||||
|
* output directory.
|
||||||
|
*
|
||||||
|
* @param inputFile
|
||||||
|
* @param outputDirectory
|
||||||
|
* @return generated files
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static List<File> split(File inputFile, File outputDirectory) throws IOException {
|
||||||
|
ImageInputStream input = null;
|
||||||
|
List<File> outputFiles = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
input = ImageIO.createImageInputStream(inputFile);
|
||||||
|
List<TIFFPage> pages = getPages(input);
|
||||||
|
int pageNo = 1;
|
||||||
|
for (TIFFPage tiffPage : pages) {
|
||||||
|
ArrayList<TIFFPage> outputPages = new ArrayList<TIFFPage>(1);
|
||||||
|
ImageOutputStream outputStream = null;
|
||||||
|
try {
|
||||||
|
File outputFile = new File(outputDirectory, String.format("%04d", pageNo) + ".tif");
|
||||||
|
outputStream = ImageIO.createImageOutputStream(outputFile);
|
||||||
|
outputPages.clear();
|
||||||
|
outputPages.add(tiffPage);
|
||||||
|
writePages(outputStream, outputPages);
|
||||||
|
outputFiles.add(outputFile);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++pageNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (input != null) {
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates all pages of a TIFF file by changing TIFF.TAG_ORIENTATION.
|
||||||
|
* <p>
|
||||||
|
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
|
||||||
|
* displayed. Other metadata, such as width and height, relate to the image
|
||||||
|
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
|
||||||
|
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||||
|
* applying TIFF.TAG_ORIENTATION.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param imageInput
|
||||||
|
* @param imageOutput
|
||||||
|
* @param degree Rotation amount, supports 90<EFBFBD>, 180<EFBFBD> and 270<EFBFBD>.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void rotatePages(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree)
|
||||||
|
throws IOException {
|
||||||
|
rotatePage(imageInput, imageOutput, degree, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates a page of a TIFF file by changing TIFF.TAG_ORIENTATION.
|
||||||
|
* <p>
|
||||||
|
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
|
||||||
|
* displayed. Other metadata, such as width and height, relate to the image
|
||||||
|
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
|
||||||
|
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||||
|
* applying TIFF.TAG_ORIENTATION.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param imageInput
|
||||||
|
* @param imageOutput
|
||||||
|
* @param degree Rotation amount, supports 90<EFBFBD>, 180<EFBFBD> and 270<EFBFBD>.
|
||||||
|
* @param pageIndex page which should be rotated or -1 for all pages.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void rotatePage(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree, int pageIndex)
|
||||||
|
throws IOException {
|
||||||
|
ImageInputStream input = null;
|
||||||
|
try {
|
||||||
|
List<TIFFPage> pages = getPages(imageInput);
|
||||||
|
if (pageIndex != -1) {
|
||||||
|
pages.get(pageIndex).rotate(degree);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (TIFFPage tiffPage : pages) {
|
||||||
|
tiffPage.rotate(degree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writePages(imageOutput, pages);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (input != null) {
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
|
||||||
|
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
|
||||||
|
|
||||||
|
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
|
||||||
|
|
||||||
|
int pageCount = IFDs.directoryCount();
|
||||||
|
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||||
|
pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writePages(ImageOutputStream imageOutput, List<TIFFPage> pages) throws IOException {
|
||||||
|
EXIFWriter exif = new EXIFWriter();
|
||||||
|
long nextPagePos = imageOutput.getStreamPosition();
|
||||||
|
if (nextPagePos == 0) {
|
||||||
|
exif.writeTIFFHeader(imageOutput);
|
||||||
|
nextPagePos = imageOutput.getStreamPosition();
|
||||||
|
imageOutput.writeInt(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// already has pages, so remember place of EOF to replace with
|
||||||
|
// IFD offset
|
||||||
|
nextPagePos -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TIFFPage tiffPage : pages) {
|
||||||
|
long ifdOffset = tiffPage.write(imageOutput, exif);
|
||||||
|
|
||||||
|
long tmp = imageOutput.getStreamPosition();
|
||||||
|
imageOutput.seek(nextPagePos);
|
||||||
|
imageOutput.writeInt((int) ifdOffset);
|
||||||
|
imageOutput.seek(tmp);
|
||||||
|
nextPagePos = tmp;
|
||||||
|
imageOutput.writeInt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage applyOrientation(BufferedImage input, int orientation) {
|
||||||
|
boolean flipExtends = false;
|
||||||
|
int w = input.getWidth();
|
||||||
|
int h = input.getHeight();
|
||||||
|
double cW = w / 2.0;
|
||||||
|
double cH = h / 2.0;
|
||||||
|
|
||||||
|
AffineTransform orientationTransform = new AffineTransform();
|
||||||
|
switch (orientation) {
|
||||||
|
case TIFFBaseline.ORIENTATION_TOPLEFT:
|
||||||
|
// normal
|
||||||
|
return input;
|
||||||
|
case TIFFExtension.ORIENTATION_TOPRIGHT:
|
||||||
|
// flipped vertically
|
||||||
|
orientationTransform.translate(cW, cH);
|
||||||
|
orientationTransform.scale(-1, 1);
|
||||||
|
orientationTransform.translate(-cW, -cH);
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_BOTRIGHT:
|
||||||
|
// rotated 180
|
||||||
|
orientationTransform.quadrantRotate(2, cW, cH);
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_BOTLEFT:
|
||||||
|
// flipped horizontally
|
||||||
|
orientationTransform.translate(cW, cH);
|
||||||
|
orientationTransform.scale(1, -1);
|
||||||
|
orientationTransform.translate(-cW, -cH);
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_LEFTTOP:
|
||||||
|
orientationTransform.translate(cW, cH);
|
||||||
|
orientationTransform.scale(-1, 1);
|
||||||
|
orientationTransform.quadrantRotate(1);
|
||||||
|
orientationTransform.translate(-cW, -cH);
|
||||||
|
flipExtends = true;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_RIGHTTOP:
|
||||||
|
// rotated 90
|
||||||
|
orientationTransform.quadrantRotate(1, cW, cH);
|
||||||
|
flipExtends = true;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_RIGHTBOT:
|
||||||
|
orientationTransform.translate(cW, cH);
|
||||||
|
orientationTransform.scale(1, -1);
|
||||||
|
orientationTransform.quadrantRotate(1);
|
||||||
|
orientationTransform.translate(-cW, -cH);
|
||||||
|
flipExtends = true;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_LEFTBOT:
|
||||||
|
// rotated 270
|
||||||
|
orientationTransform.quadrantRotate(3, cW, cH);
|
||||||
|
flipExtends = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newW, newH;
|
||||||
|
if (flipExtends) {
|
||||||
|
newW = h;
|
||||||
|
newH = w;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newW = w;
|
||||||
|
newH = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
|
||||||
|
transform.concatenate(orientationTransform);
|
||||||
|
AffineTransformOp transformOp = new AffineTransformOp(transform, null);
|
||||||
|
return transformOp.filter(input, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TIFFPage {
|
||||||
|
private Directory IFD;
|
||||||
|
private ImageInputStream stream;
|
||||||
|
|
||||||
|
private TIFFPage(Directory IFD, ImageInputStream stream) {
|
||||||
|
this.IFD = IFD;
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long write(ImageOutputStream outputStream, EXIFWriter exifWriter) throws IOException {
|
||||||
|
Entry stipOffsetsEntry = IFD.getEntryById(TIFF.TAG_STRIP_OFFSETS);
|
||||||
|
long[] offsets;
|
||||||
|
if (stipOffsetsEntry.valueCount() == 1) {
|
||||||
|
offsets = new long[] {(long) stipOffsetsEntry.getValue()};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offsets = (long[]) stipOffsetsEntry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry stipByteCountsEntry = IFD.getEntryById(TIFF.TAG_STRIP_BYTE_COUNTS);
|
||||||
|
long[] byteCounts;
|
||||||
|
if (stipOffsetsEntry.valueCount() == 1) {
|
||||||
|
byteCounts = new long[] {(long) stipByteCountsEntry.getValue()};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byteCounts = (long[]) stipByteCountsEntry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] newOffsets = new int[offsets.length];
|
||||||
|
for (int i = 0; i < offsets.length; i++) {
|
||||||
|
newOffsets[i] = (int) outputStream.getStreamPosition();
|
||||||
|
stream.seek(offsets[i]);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[(int) byteCounts[i]];
|
||||||
|
stream.readFully(buffer);
|
||||||
|
outputStream.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Entry> newIFD = new ArrayList<Entry>();
|
||||||
|
Iterator<Entry> it = IFD.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
newIFD.add(it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
newIFD.remove(stipOffsetsEntry);
|
||||||
|
newIFD.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, newOffsets));
|
||||||
|
return exifWriter.writeIFD(newIFD, outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the image by changing TIFF.TAG_ORIENTATION.
|
||||||
|
* <p>
|
||||||
|
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do
|
||||||
|
* be displayed. Other metadata, such as width and height, relate to the
|
||||||
|
* image as how it is stored. The ImageIO TIFF plugin does not handle
|
||||||
|
* orientation. Use
|
||||||
|
* {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||||
|
* applying TIFF.TAG_ORIENTATION.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param degree Rotation amount, supports 90<EFBFBD>, 180<EFBFBD> and 270<EFBFBD>.
|
||||||
|
*/
|
||||||
|
public void rotate(int degree) {
|
||||||
|
Validate.isTrue(degree % 90 == 0 && degree > 0 && degree < 360,
|
||||||
|
"Only rotations by 90, 180 and 270 degree are supported");
|
||||||
|
|
||||||
|
ArrayList<Entry> newIDFData = new ArrayList<>();
|
||||||
|
Iterator<Entry> it = IFD.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
newIDFData.add(it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
short orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
|
||||||
|
Entry orientationEntry = IFD.getEntryById(TIFF.TAG_ORIENTATION);
|
||||||
|
if (orientationEntry != null) {
|
||||||
|
orientation = ((Number) orientationEntry.getValue()).shortValue();
|
||||||
|
newIDFData.remove(orientationEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
int steps = degree / 90;
|
||||||
|
for (int i = 0; i < steps; i++) {
|
||||||
|
switch (orientation) {
|
||||||
|
case TIFFBaseline.ORIENTATION_TOPLEFT:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_RIGHTTOP;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_TOPRIGHT:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_RIGHTBOT;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_BOTRIGHT:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_LEFTBOT;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_BOTLEFT:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_LEFTTOP;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_LEFTTOP:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_TOPRIGHT;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_RIGHTTOP:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_BOTRIGHT;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_RIGHTBOT:
|
||||||
|
orientation = TIFFExtension.ORIENTATION_BOTLEFT;
|
||||||
|
break;
|
||||||
|
case TIFFExtension.ORIENTATION_LEFTBOT:
|
||||||
|
orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
|
||||||
|
IFD = new AbstractDirectory(newIDFData) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||||
|
*/
|
||||||
|
public static final class TIFFEntry extends AbstractEntry {
|
||||||
|
// TODO: Expose a merge of this and the EXIFEntry class...
|
||||||
|
private final short type;
|
||||||
|
|
||||||
|
private static short guessType(final Object val) {
|
||||||
|
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
|
||||||
|
Object value = Validate.notNull(val);
|
||||||
|
|
||||||
|
boolean array = value.getClass().isArray();
|
||||||
|
if (array) {
|
||||||
|
value = Array.get(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||||
|
// TODO: Check for negative values and use signed types?
|
||||||
|
if (value instanceof Byte) {
|
||||||
|
return TIFF.TYPE_BYTE;
|
||||||
|
}
|
||||||
|
if (value instanceof Short) {
|
||||||
|
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TIFF.TYPE_SHORT;
|
||||||
|
}
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TIFF.TYPE_LONG;
|
||||||
|
}
|
||||||
|
if (value instanceof Long) {
|
||||||
|
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||||
|
return TIFF.TYPE_LONG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Rational) {
|
||||||
|
return TIFF.TYPE_RATIONAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof String) {
|
||||||
|
return TIFF.TYPE_ASCII;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: More types
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TIFFEntry(final int identifier, final Object value) {
|
||||||
|
this(identifier, guessType(value), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TIFFEntry(int identifier, short type, Object value) {
|
||||||
|
super(identifier, value);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return TIFF.TYPE_NAMES[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||||
|
*/
|
||||||
|
public interface TIFFExtension {
|
||||||
|
int ORIENTATION_TOPRIGHT = 2;
|
||||||
|
int ORIENTATION_BOTRIGHT = 3;
|
||||||
|
int ORIENTATION_BOTLEFT = 4;
|
||||||
|
int ORIENTATION_LEFTTOP = 5;
|
||||||
|
int ORIENTATION_RIGHTTOP = 6;
|
||||||
|
int ORIENTATION_RIGHTBOT = 7;
|
||||||
|
int ORIENTATION_LEFTBOT = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||||
|
*/
|
||||||
|
public interface TIFFBaseline {
|
||||||
|
int ORIENTATION_TOPLEFT = 1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.contrib.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
|
||||||
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import javax.xml.xpath.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFFUtilitiesTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||||
|
* @author last modified by $Author$
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class TIFFUtilitiesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMerge() throws IOException {
|
||||||
|
// Files from ImageIO TIFF Plugin
|
||||||
|
InputStream stream1 = getClassLoaderResource("/tiff/ccitt/group3_1d.tif").openStream();
|
||||||
|
InputStream stream2 = getClassLoaderResource("/tiff/ccitt/group3_2d.tif").openStream();
|
||||||
|
InputStream stream3 = getClassLoaderResource("/tiff/ccitt/group4.tif").openStream();
|
||||||
|
|
||||||
|
File file1 = File.createTempFile("imageiotest", ".tif");
|
||||||
|
File file2 = File.createTempFile("imageiotest", ".tif");
|
||||||
|
File file3 = File.createTempFile("imageiotest", ".tif");
|
||||||
|
File output = File.createTempFile("imageiotest", ".tif");
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
|
||||||
|
data = FileUtil.read(stream1);
|
||||||
|
FileUtil.write(file1, data);
|
||||||
|
stream1.close();
|
||||||
|
|
||||||
|
data = FileUtil.read(stream2);
|
||||||
|
FileUtil.write(file2, data);
|
||||||
|
stream2.close();
|
||||||
|
|
||||||
|
data = FileUtil.read(stream3);
|
||||||
|
FileUtil.write(file3, data);
|
||||||
|
stream3.close();
|
||||||
|
|
||||||
|
List<File> input = Arrays.asList(file1, file2, file3);
|
||||||
|
TIFFUtilities.merge(input, output);
|
||||||
|
|
||||||
|
ImageInputStream iis = ImageIO.createImageInputStream(output);
|
||||||
|
ImageReader reader = ImageIO.getImageReaders(iis).next();
|
||||||
|
reader.setInput(iis);
|
||||||
|
Assert.assertEquals(3, reader.getNumImages(true));
|
||||||
|
|
||||||
|
iis.close();
|
||||||
|
output.delete();
|
||||||
|
file1.delete();
|
||||||
|
file2.delete();
|
||||||
|
file3.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSplit() throws IOException {
|
||||||
|
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||||
|
File inputFile = File.createTempFile("imageiotest", "tif");
|
||||||
|
byte[] data = FileUtil.read(inputStream);
|
||||||
|
FileUtil.write(inputFile, data);
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
File outputDirectory = Files.createTempDirectory("imageio").toFile();
|
||||||
|
|
||||||
|
TIFFUtilities.split(inputFile, outputDirectory);
|
||||||
|
|
||||||
|
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
|
||||||
|
|
||||||
|
File[] outputFiles = outputDirectory.listFiles();
|
||||||
|
Assert.assertEquals(3, outputFiles.length);
|
||||||
|
for (File outputFile : outputFiles) {
|
||||||
|
ImageInputStream iis = ImageIO.createImageInputStream(outputFile);
|
||||||
|
reader.setInput(iis);
|
||||||
|
Assert.assertEquals(1, reader.getNumImages(true));
|
||||||
|
iis.close();
|
||||||
|
outputFile.delete();
|
||||||
|
}
|
||||||
|
outputDirectory.delete();
|
||||||
|
inputFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRotate() throws IOException, XPathExpressionException {
|
||||||
|
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
|
||||||
|
|
||||||
|
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||||
|
File inputFile = File.createTempFile("imageiotest", ".tif");
|
||||||
|
byte[] data = FileUtil.read(inputStream);
|
||||||
|
FileUtil.write(inputFile, data);
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||||
|
XPathExpression expression = xPath.compile("TIFFIFD/TIFFField[@number='274']/TIFFBytes/TIFFByte/@value");
|
||||||
|
|
||||||
|
// rotate all pages
|
||||||
|
ImageInputStream inputTest1 = ImageIO.createImageInputStream(inputFile);
|
||||||
|
File outputTest1 = File.createTempFile("imageiotest", ".tif");
|
||||||
|
ImageOutputStream iosTest1 = ImageIO.createImageOutputStream(outputTest1);
|
||||||
|
TIFFUtilities.rotatePages(inputTest1, iosTest1, 90);
|
||||||
|
iosTest1.close();
|
||||||
|
|
||||||
|
ImageInputStream checkTest1 = ImageIO.createImageInputStream(outputTest1);
|
||||||
|
reader.setInput(checkTest1);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
Node metaData = reader.getImageMetadata(i)
|
||||||
|
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
|
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||||
|
}
|
||||||
|
checkTest1.close();
|
||||||
|
|
||||||
|
// rotate single page further
|
||||||
|
ImageInputStream inputTest2 = ImageIO.createImageInputStream(outputTest1);
|
||||||
|
File outputTest2 = File.createTempFile("imageiotest", ".tif");
|
||||||
|
ImageOutputStream iosTest2 = ImageIO.createImageOutputStream(outputTest2);
|
||||||
|
TIFFUtilities.rotatePage(inputTest2, iosTest2, 90, 1);
|
||||||
|
iosTest2.close();
|
||||||
|
|
||||||
|
ImageInputStream checkTest2 = ImageIO.createImageInputStream(outputTest2);
|
||||||
|
reader.setInput(checkTest2);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
Node metaData = reader.getImageMetadata(i)
|
||||||
|
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
|
Assert.assertEquals(orientation, i == 1
|
||||||
|
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||||
|
: TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||||
|
}
|
||||||
|
checkTest2.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApplyOrientation() throws IOException {
|
||||||
|
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||||
|
File inputFile = File.createTempFile("imageiotest", "tif");
|
||||||
|
byte[] data = FileUtil.read(inputStream);
|
||||||
|
FileUtil.write(inputFile, data);
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
BufferedImage image = ImageIO.read(inputFile);
|
||||||
|
|
||||||
|
// rotate by 90<EFBFBD>
|
||||||
|
BufferedImage image90 = TIFFUtilities.applyOrientation(image, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||||
|
// rotate by 270<EFBFBD>
|
||||||
|
BufferedImage image360 = TIFFUtilities.applyOrientation(image90, TIFFExtension.ORIENTATION_LEFTBOT);
|
||||||
|
|
||||||
|
byte[] original = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
|
byte[] rotated = ((DataBufferByte) image360.getData().getDataBuffer()).getData();
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(original, rotated);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URL getClassLoaderResource(final String pName) {
|
||||||
|
return getClass().getResource(pName);
|
||||||
|
}
|
||||||
|
}
|
BIN
contrib/src/test/resources/contrib/tiff/multipage.tif
Normal file
BIN
contrib/src/test/resources/contrib/tiff/multipage.tif
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user