Compare commits

..

25 Commits

Author SHA1 Message Date
Harald Kuhr 7384118357 [maven-release-plugin] prepare release twelvemonkeys-3.6 2020-07-10 22:49:16 +02:00
Harald Kuhr 4d833a50e5 TIFF constants. 2020-07-10 22:41:47 +02:00
Harald Kuhr 57b0fdac0b Fix JPEG tests mk II. 2020-07-10 22:29:23 +02:00
Harald Kuhr e6bd94025f JPEG Metadata clean-up 2020-07-10 22:26:53 +02:00
Harald Kuhr 330a0414f0 Fix JPEG tests 2020-07-10 22:26:12 +02:00
Harald Kuhr 5cc201b46d JPEG Exif rotation in metadata + support 2020-07-10 22:05:46 +02:00
Harald Kuhr 7e55d7765d #550 Adobe path points now constrained to a more robust [-16...16] range 2020-07-10 19:29:50 +02:00
Harald Kuhr 8f942922fd #547 BMPImageWriterSpi now only claims to write TYPE_4BYTE_ABGR, and registers with low pri.
Better exception message for other image types.
2020-06-28 11:50:17 +02:00
Harald Kuhr db5635e844 #535: Detect incorrect compression in TIFF CCITT stream. 2020-06-16 21:54:16 +02:00
Harald Kuhr 8bc952ba66 #464/#465 Collection fixes for forward compatibility. 2020-04-15 16:03:12 +02:00
Harald Kuhr 96cb3a07f4 #525: Fix for negative arrays size in old style JPEG in TIFF. 2020-04-15 13:28:56 +02:00
Harald Kuhr cd6a6258b6 Formatting and proper comments 2020-04-15 12:26:41 +02:00
Harald Kuhr a0fa2c08ac Merge pull request #529 from actinium15/issue/526
#526 Preventing SSRF due to external resource refs in SVGs
2020-04-03 13:57:57 +02:00
Ashish Chopra 6642b1647a #526 Incorporating review comments
* moved the system-property-evaluation to static-block for more
  reliability
* updated impacted existing tests (which relied on external-resources) to
  leverage the new API
* set the value of system-property controlling external-resource-access
  to true in the test JVM for the sake of existing tests using
  SVGImageReader APIs not backed by SVRReadParams (.getWidth/.getHeight
  and such)
2020-03-30 18:29:34 +05:30
Ashish Chopra 5315caf830 #526 Fixed unnecessary 'overenginnering' in previous commit 🤦 2020-03-30 10:38:52 +05:30
Ashish Chopra 872523b0f0 #526 Incorporating review comments
* renaming accessors
* changing the default to disallow external resources
* introduced system-property for backwards compatibility
* honor system-property (if present) and SVGReadParams.isAllowExternalResources hasn't been called
  (as against ignoring system-property and reverting to 'block-by-default' if SVGReadParams.isAllowExternalResources invoked)
* added + updated test cases
2020-03-24 18:40:01 +05:30
Ashish Chopra 7bf99fb496 #526 Preventing SSRF due to external resource refs in SVGs 2020-02-25 11:26:16 +05:30
Harald Kuhr a1047edddb Merge pull request #522 from Schmidor/svg_size
#518 Parsing SVG width/height attributes
2020-01-31 15:50:24 +01:00
Oliver Schmidtmer e956176872 #518 Fallbacks for aspect ratio, if only one dimension is given 2020-01-30 18:40:11 +01:00
Oliver Schmidtmer 6d2947b080 #518 Parsing SVG width/height attributes 2020-01-30 15:54:59 +01:00
Harald Kuhr fb304d6c27 #520: Fix for incorrect serialization of single element arrays in metadata. 2020-01-29 20:58:34 +01:00
Harald Kuhr 903289caa4 Merge pull request #517 from KoenDG/small_cleanup
Minor code cleanup: Intellij suggested changes from static code analysis.
2020-01-29 09:38:57 +01:00
Koen De Groote aff31ebd1b Intellij suggested changes from static code analysis. 2020-01-28 16:18:07 +01:00
Harald Kuhr b6773f6983 Updated versions. 2020-01-22 22:43:42 +01:00
Harald Kuhr 0d28eb31d2 [maven-release-plugin] prepare for next development iteration 2020-01-22 21:47:25 +01:00
99 changed files with 1710 additions and 497 deletions
+33 -33
View File
@@ -2,7 +2,7 @@
Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys)
Latest release is TwelveMonkeys ImageIO [3.4.3](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio%20AND%20v:3.4.3) (Jan. 9th, 2020).
Latest release is TwelveMonkeys ImageIO [3.5](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio%20AND%20v:3.5) (Jan. 22nd, 2020).
[Release notes](https://github.com/haraldk/TwelveMonkeys/releases/latest).
## About
@@ -494,12 +494,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.4.3</version>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.4.3</version>
<version>3.5</version>
</dependency>
<!--
@@ -509,7 +509,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.4.3</version>
<version>3.5</version>
</dependency>
</dependencies>
```
@@ -518,52 +518,52 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.4.3.jar
twelvemonkeys-common-io-3.4.3.jar
twelvemonkeys-common-image-3.4.3.jar
twelvemonkeys-imageio-core-3.4.3.jar
twelvemonkeys-imageio-metadata-3.4.3.jar
twelvemonkeys-imageio-jpeg-3.4.3.jar
twelvemonkeys-imageio-tiff-3.4.3.jar
twelvemonkeys-common-lang-3.5.jar
twelvemonkeys-common-io-3.5.jar
twelvemonkeys-common-image-3.5.jar
twelvemonkeys-imageio-core-3.5.jar
twelvemonkeys-imageio-metadata-3.5.jar
twelvemonkeys-imageio-jpeg-3.5.jar
twelvemonkeys-imageio-tiff-3.5.jar
### Links to prebuilt binaries
##### Latest version (3.4.3)
##### Latest version (3.5)
Requires Java 7 or later.
Common dependencies
* [common-lang-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.4.3/common-lang-3.4.3.jar)
* [common-io-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.4.3/common-io-3.4.3.jar)
* [common-image-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.4.3/common-image-3.4.3.jar)
* [common-lang-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.5/common-lang-3.5.jar)
* [common-io-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.5/common-io-3.5.jar)
* [common-image-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.5/common-image-3.5.jar)
ImageIO dependencies
* [imageio-core-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.4.3/imageio-core-3.4.3.jar)
* [imageio-metadata-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.4.3/imageio-metadata-3.4.3.jar)
* [imageio-core-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.5/imageio-core-3.5.jar)
* [imageio-metadata-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.5/imageio-metadata-3.5.jar)
ImageIO plugins
* [imageio-bmp-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.4.3/imageio-bmp-3.4.3.jar)
* [imageio-jpeg-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.4.3/imageio-jpeg-3.4.3.jar)
* [imageio-tiff-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.4.3/imageio-tiff-3.4.3.jar)
* [imageio-pnm-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.4.3/imageio-pnm-3.4.3.jar)
* [imageio-psd-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.4.3/imageio-psd-3.4.3.jar)
* [imageio-hdr-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.4.3/imageio-hdr-3.4.3.jar)
* [imageio-iff-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.4.3/imageio-iff-3.4.3.jar)
* [imageio-pcx-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.4.3/imageio-pcx-3.4.3.jar)
* [imageio-pict-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.4.3/imageio-pict-3.4.3.jar)
* [imageio-sgi-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.4.3/imageio-sgi-3.4.3.jar)
* [imageio-tga-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.4.3/imageio-tga-3.4.3.jar)
* [imageio-icns-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.4.3/imageio-icns-3.4.3.jar)
* [imageio-thumbsdb-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.4.3/imageio-thumbsdb-3.4.3.jar)
* [imageio-bmp-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.5/imageio-bmp-3.5.jar)
* [imageio-jpeg-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.5/imageio-jpeg-3.5.jar)
* [imageio-tiff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.5/imageio-tiff-3.5.jar)
* [imageio-pnm-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.5/imageio-pnm-3.5.jar)
* [imageio-psd-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.5/imageio-psd-3.5.jar)
* [imageio-hdr-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.5/imageio-hdr-3.5.jar)
* [imageio-iff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.5/imageio-iff-3.5.jar)
* [imageio-pcx-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.5/imageio-pcx-3.5.jar)
* [imageio-pict-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.5/imageio-pict-3.5.jar)
* [imageio-sgi-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.5/imageio-sgi-3.5.jar)
* [imageio-tga-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.5/imageio-tga-3.5.jar)
* [imageio-icns-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.5/imageio-icns-3.5.jar)
* [imageio-thumbsdb-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.5/imageio-thumbsdb-3.5.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.4.3/imageio-batik-3.4.3.jar)
* [imageio-batik-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.5/imageio-batik-3.5.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.4.3/imageio-clippath-3.4.3.jar)
* [imageio-clippath-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.5/imageio-clippath-3.5.jar)
Servlet support
* [servlet-3.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.4.3/servlet-3.4.3.jar)
* [servlet-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.5/servlet-3.5.jar)
##### Old version (3.0.x)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
@@ -129,8 +129,7 @@ public class Time {
* @see #toString(String)
*/
public String toString() {
return "" + getMinutes() + ":"
+ (getSeconds() < 10 ? "0" : "") + getSeconds();
return getMinutes() + ":" + (getSeconds() < 10 ? "0" : "") + getSeconds();
}
/**
@@ -44,12 +44,12 @@ import java.util.Vector;
* The format is expressed in a string as follows:
* <DL>
* <DD>m (or any multiple of m's)
* <DT>the minutes part (padded with 0's, if number has less digits than
* <DT>the minutes part (padded with 0's, if number has less digits than
* the number of m's)
* m -&gt; 0,1,...,59,60,61,...
* mm -&gt; 00,01,...,59,60,61,...
* <DD>s or ss
* <DT>the seconds part (padded with 0's, if number has less digits than
* <DT>the seconds part (padded with 0's, if number has less digits than
* the number of s's)
* s -&gt; 0,1,...,59
* ss -&gt; 00,01,...,59
@@ -62,7 +62,7 @@ import java.util.Vector;
* <P>
* Known bugs:
* <P>
* The last character in the formatString is not escaped, while it should be.
* The last character in the formatString is not escaped, while it should be.
* The first character after an escaped character is escaped while is shouldn't
* be.
* <P>
@@ -81,15 +81,15 @@ public class TimeFormat extends Format {
final static String SECOND = "s";
final static String TIME = "S";
final static String ESCAPE = "\\";
/**
* The default time format
* The default time format
*/
private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss");
protected String formatString = null;
/**
/**
* Main method for testing ONLY
*/
@@ -122,7 +122,7 @@ public class TimeFormat extends Format {
}
else
time = new Time();
System.out.println("Time is \"" + out.format(time) +
"\" according to format \"" + out.formatString + "\"");
}
@@ -147,18 +147,18 @@ public class TimeFormat extends Format {
String previous = null;
String current = null;
int previousCount = 0;
while (tok.hasMoreElements()) {
current = tok.nextToken();
if (previous != null && previous.equals(ESCAPE)) {
// Handle escaping of s, S or m
current = ((current != null) ? current : "")
current = ((current != null) ? current : "")
+ (tok.hasMoreElements() ? tok.nextToken() : "");
previous = null;
previousCount = 0;
}
// Skip over first,
// or if current is the same, increase count, and try again
if (previous == null || previous.equals(current)) {
@@ -173,12 +173,12 @@ public class TimeFormat extends Format {
formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1));
else
else
formatter.add(new TextFormatter(previous));
previousCount = 1;
previous = current;
}
}
@@ -197,7 +197,7 @@ public class TimeFormat extends Format {
// Debug
/*
for (int i = 0; i < formatter.size(); i++) {
System.out.println("Formatter " + formatter.get(i).getClass()
System.out.println("Formatter " + formatter.get(i).getClass()
+ ": length=" + ((TimeFormatter) formatter.get(i)).digits);
}
*/
@@ -206,7 +206,7 @@ public class TimeFormat extends Format {
}
/**
/**
* DUMMY IMPLEMENTATION!!
* Not locale specific.
*/
@@ -259,9 +259,9 @@ public class TimeFormat extends Format {
/** DUMMY IMPLEMENTATION!! */
public Object parseObject(String pStr, ParsePosition pStatus) {
Time t = parse(pStr);
pStatus.setIndex(pStr.length()); // Not 100%
return t;
}
@@ -270,7 +270,7 @@ public class TimeFormat extends Format {
* <p>
* Will bug on some formats. It's safest to always use delimiters between
* the minutes (m) and seconds (s) part.
*
*
*/
public Time parse(String pStr) {
Time time = new Time();
@@ -286,7 +286,7 @@ public class TimeFormat extends Format {
&& (pos + skip < pStr.length()) ; i++) {
// Go to next offset
pos += skip;
if (formatter[i] instanceof MinutesFormatter) {
// Parse MINUTES
if ((i + 1) < formatter.length
@@ -327,9 +327,9 @@ public class TimeFormat extends Format {
else {
// Cannot possibly know how long?
skip = 0;
continue;
continue;
}
// Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip));
// System.out.println("Only seconds: " + sec);
@@ -343,7 +343,7 @@ public class TimeFormat extends Format {
&& formatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos);
}
else if ((i + 1) >= formatter.length) {
// Skip until end of string
@@ -359,7 +359,7 @@ public class TimeFormat extends Format {
else if (formatter[i] instanceof TextFormatter) {
skip = formatter[i].digits;
}
}
// Set the minutes part if we should
@@ -390,7 +390,7 @@ class SecondsFormatter extends TimeFormatter {
SecondsFormatter(int pDigits) {
digits = pDigits;
}
String format(Time t) {
// Negative number of digits, means all seconds, no padding
if (digits < 0) {
@@ -404,7 +404,7 @@ class SecondsFormatter extends TimeFormatter {
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getSeconds(), digits);
return StringUtil.pad("" + t.getSeconds(), digits, "0", true);
return StringUtil.pad(String.valueOf(t.getSeconds()), digits, "0", true);
}
}
@@ -425,7 +425,7 @@ class MinutesFormatter extends TimeFormatter {
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getMinutes(), digits);
return StringUtil.pad("" + t.getMinutes(), digits, "0", true);
return StringUtil.pad(String.valueOf(t.getMinutes()), digits, "0", true);
}
}
@@ -1117,18 +1117,19 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
/**
* Tests {@link Collection#toArray(Object[])}.
*/
@SuppressWarnings({"SuspiciousToArrayCall", "RedundantCast"})
@Test
public void testCollectionToArray2() {
resetEmpty();
Object[] a = new Object[] { new Object(), null, null };
Object[] array = collection.toArray(a);
assertArrayEquals("Given array shouldn't shrink", array, a);
assertEquals("Last element should be set to null", a[0], null);
assertNull("Last element should be set to null", a[0]);
verifyAll();
resetFull();
try {
array = collection.toArray(new Void[0]);
collection.toArray(new Void[0]);
fail("toArray(new Void[0]) should raise ArrayStore");
} catch (ArrayStoreException e) {
// expected
@@ -1136,7 +1137,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
verifyAll();
try {
array = collection.toArray(null);
collection.toArray((Object[]) null);
fail("toArray(null) should raise NPE");
} catch (NullPointerException e) {
// expected
@@ -1150,13 +1151,13 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
// Figure out if they're all the same class
// TODO: It'd be nicer to detect a common superclass
HashSet classes = new HashSet();
HashSet<Class<?>> classes = new HashSet<>();
for (int i = 0; i < array.length; i++) {
classes.add((array[i] == null) ? null : array[i].getClass());
}
if (classes.size() > 1) return;
Class cl = (Class)classes.iterator().next();
Class<?> cl = (Class<?>)classes.iterator().next();
if (Map.Entry.class.isAssignableFrom(cl)) { // check needed for protective cases like Predicated/Unmod map entrySet
cl = Map.Entry.class;
}
@@ -250,12 +250,12 @@ public class CollectionUtilTest {
assertCorrectListIterator(new ArrayList<String>(Arrays.asList(new String[] {"foo", "bar", "baz", "boo"})).subList(1, 3).listIterator(0), new String[] {"bar", "baz"}, true, true);
}
private void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements) {
private static void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements) {
assertCorrectListIterator(iterator, elements, false, false);
}
// NOTE: The test is can only test list iterators with a starting index == 0
private void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements, boolean skipRemove, boolean skipAdd) {
private static void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements, boolean skipRemove, boolean skipAdd) {
// Index is now "before 0"
assertEquals(-1, iterator.previousIndex());
assertEquals(0, iterator.nextIndex());
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
@@ -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;
}
}
@@ -200,11 +200,10 @@ public final class TIFFUtilities {
}
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(imageInput);
int pageCount = IFDs.directoryCount();
final int pageCount = IFDs.directoryCount();
List<TIFFPage> pages = new ArrayList<>(pageCount);
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput));
}
@@ -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));
}
}
+17 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -14,6 +14,21 @@
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
for more information.]]>
</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
true
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
@@ -88,6 +103,6 @@
</dependencies>
<properties>
<batik.version>1.9</batik.version>
<batik.version>1.12</batik.version>
</properties>
</project>
@@ -33,9 +33,11 @@ package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.bridge.*;
import org.apache.batik.css.parser.CSSLexicalUnit;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.gvt.CanvasGraphicsNode;
@@ -47,6 +49,7 @@ import org.apache.batik.transcoder.*;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.SVGConstants;
import org.apache.batik.xml.LexicalUnits;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement;
@@ -76,7 +79,12 @@ import java.util.Map;
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
*/
public class SVGImageReader extends ImageReaderBase {
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources"));
private Rasterizer rasterizer;
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
/**
* Creates an {@code SVGImageReader}.
@@ -113,6 +121,9 @@ public class SVGImageReader extends ImageReaderBase {
if (pParam instanceof SVGReadParam) {
SVGReadParam svgParam = (SVGReadParam) pParam;
// set the external-resource-resolution preference
allowExternalResources = svgParam.isAllowExternalResources();
// Get the base URI
// This must be done before converting the params to hints
String baseURI = svgParam.getBaseURI();
@@ -324,24 +335,54 @@ public class SVGImageReader extends ImageReaderBase {
}
// ----
SVGSVGElement rootElement = svgDoc.getRootElement();
// get the 'width' and 'height' attributes of the SVG document
Dimension2D docSize = ctx.getDocumentSize();
if (docSize != null) {
defaultWidth = (float) docSize.getWidth();
defaultHeight = (float) docSize.getHeight();
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, rootElement);
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
if (!StringUtil.isEmpty(widthStr)) {
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
}
else {
defaultWidth = 200;
defaultHeight = 200;
if(!StringUtil.isEmpty(heightStr)){
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
}
SVGSVGElement rootElement = svgDoc.getRootElement();
String viewBoxStr = rootElement.getAttributeNS
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
if (viewBoxStr.length() != 0) {
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
defaultWidth = rect[2];
defaultHeight = rect[3];
boolean hasWidth = defaultWidth > 0.0;
boolean hasHeight = defaultHeight > 0.0;
if (!hasWidth || !hasHeight) {
String viewBoxStr = rootElement.getAttributeNS
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
if (viewBoxStr.length() != 0) {
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
// if one dimension is given, calculate other by aspect ratio in viewBox
// or use viewBox if no dimension is given
if (hasWidth) {
defaultHeight = defaultWidth * rect[3] / rect[2];
}
else if (hasHeight) {
defaultWidth = defaultHeight * rect[2] / rect[3];
}
else {
defaultWidth = rect[2];
defaultHeight = rect[3];
}
}
else {
if (hasHeight) {
defaultWidth = defaultHeight;
}
else if (hasWidth) {
defaultHeight = defaultWidth;
}
else {
// fallback to batik default sizes
defaultWidth = 400;
defaultHeight = 400;
}
}
}
// Hack to work around exception above
@@ -608,6 +649,14 @@ public class SVGImageReader extends ImageReaderBase {
public void displayMessage(String message) {
processWarningOccurred(message.replaceAll("[\\r\\n]+", " "));
}
@Override
public ExternalResourceSecurity getExternalResourceSecurity(ParsedURL resourceURL, ParsedURL docURL) {
if (allowExternalResources) {
return super.getExternalResourceSecurity(resourceURL, docURL);
}
return new NoLoadExternalResourceSecurity();
}
}
}
}
@@ -41,6 +41,11 @@ import java.awt.*;
public class SVGReadParam extends ImageReadParam {
private Paint background;
private String baseURI;
private boolean allowExternalResources = SVGImageReader.DEFAULT_ALLOW_EXTERNAL_RESOURCES;
public SVGReadParam() {
super();
}
public Paint getBackgroundColor() {
return background;
@@ -58,6 +63,14 @@ public class SVGReadParam extends ImageReadParam {
baseURI = pBaseURI;
}
public void setAllowExternalResources(boolean allow) {
allowExternalResources = allow;
}
public boolean isAllowExternalResources() {
return allowExternalResources;
}
@Override
public boolean canSetSourceRenderSize() {
return true;
@@ -49,7 +49,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.Buffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -67,6 +66,7 @@ import static org.mockito.Mockito.*;
* @version $Id: SVGImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
protected List<TestData> getTestData() {
@@ -74,7 +74,11 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)),
new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(294, 345))
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(294, 345)),
new TestData(getClassLoaderResource("/svg/sizes/w50h50.svg"), new Dimension(50, 50)),
new TestData(getClassLoaderResource("/svg/sizes/w50_1to2.svg"), new Dimension(25, 50)),
new TestData(getClassLoaderResource("/svg/sizes/h50_1to2.svg"), new Dimension(50, 100)),
new TestData(getClassLoaderResource("/svg/sizes/w50noview.svg"), new Dimension(50, 50))
);
}
@@ -224,6 +228,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
reader.addIIOReadWarningListener(listener);
SVGReadParam param = reader.getDefaultReadParam();
param.setAllowExternalResources(true);
param.setBaseURI(resource.toURI().toASCIIString());
BufferedImage image = reader.read(0, param);
@@ -244,6 +249,8 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
// Asking for metadata, width, height etc, before attempting to read using a param,
// will cause the document to be parsed without a base URI.
// This will work, but may not use the CSS...
// since the param is not available before the read operation is invoked,
// this test-case MUST use the system-property for backwards compatibility
URL resource = getClassLoaderResource("/svg/barChart.svg");
SVGImageReader reader = createReader();
@@ -282,18 +289,17 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
public void testEmbeddedNoBaseURI() throws IOException {
// With no base URI, we will throw an exception, about the missing embedded resource
URL resource = getClassLoaderResource("/svg/barChart.svg");
SVGImageReader reader = createReader();
TestData data = new TestData(resource, (Dimension) null);
try (ImageInputStream stream = data.getInputStream()) {
reader.setInput(stream);
BufferedImage image = reader.read(0);
SVGReadParam params = reader.getDefaultReadParam();
params.setAllowExternalResources(true);
reader.read(0, params);
assertNotNull(image);
assertEquals(450, image.getWidth());
assertEquals(500, image.getHeight());
assertTrue("reader.read should've thrown an exception, but didn't", false);
}
catch (IIOException allowed) {
assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find
@@ -302,4 +308,28 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
reader.dispose();
}
}
}
@Test(expected = SecurityException.class)
public void testDisallowedExternalResources() throws URISyntaxException, IOException {
// system-property set to true in surefire-plugin-settings in the pom
URL resource = getClassLoaderResource("/svg/barChart.svg");
SVGImageReader reader = createReader();
TestData data = new TestData(resource, (Dimension) null);
try (ImageInputStream stream = data.getInputStream()) {
reader.setInput(stream);
SVGReadParam param = reader.getDefaultReadParam();
param.setBaseURI(resource.toURI().toASCIIString());
param.setAllowExternalResources(false);
// even when the system-property is set to true,
// `reader.read` for `/svg/barChart.svg` should raise
// a SecurityException when External Resources are blocked
// because the API invocation gets preference
reader.read(0, param);
}
finally {
reader.dispose();
}
}
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="50" viewBox="0 0 100 200" version="1.1">
<g id="layer1">
<rect id="rect2985" width="50" height="50" x="0" y="0"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 427 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="50" viewBox="0 0 100 200" version="1.1">
<g id="layer1">
<rect id="rect2985" width="50" height="50" x="0" y="0"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 428 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 100 200" version="1.1">
<g id="layer1">
<rect id="rect2985" width="50" height="50" x="0" y="0"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 439 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="50" version="1.1">
<g id="layer1">
<rect id="rect2985" width="50" height="50" x="0" y="0"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 405 B

+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -33,9 +33,13 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import java.io.IOException;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
import java.awt.image.BufferedImage;
import java.util.Locale;
import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
/**
* BMPImageWriterSpi
*/
@@ -45,17 +49,28 @@ public final class BMPImageWriterSpi extends ImageWriterSpiBase {
}
@Override
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return true;
public void onRegistration(ServiceRegistry registry, Class<?> category) {
// Make sure we register BEHIND the built-in BMP writer
ImageWriterSpi sunSpi = lookupProviderByName(registry, "com.sun.imageio.plugins.bmp.BMPImageWriterSpi", ImageWriterSpi.class);
if (sunSpi != null && sunSpi.getVendorName() != null) {
registry.setOrdering((Class<ImageWriterSpi>) category, sunSpi, this);
}
}
@Override
public BMPImageWriter createWriterInstance(final Object extension) throws IOException {
public boolean canEncodeImage(final ImageTypeSpecifier type) {
// TODO: Support more types, as time permits.
return type.getBufferedImageType() == BufferedImage.TYPE_4BYTE_ABGR;
}
@Override
public BMPImageWriter createWriterInstance(final Object extension) {
return new BMPImageWriter(this);
}
@Override
public String getDescription(final Locale locale) {
return "Windows Device Independent Bitmap Format (BMP) Reader";
return "Windows Device Independent Bitmap Format (BMP) Writer";
}
}
@@ -40,7 +40,7 @@ import javax.imageio.metadata.IIOMetadataNode;
* BMPMetadata.
*/
final class BMPMetadata extends AbstractMetadata {
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the DIBImageWriter. */
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */
public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
private final DIBHeader header;
@@ -84,7 +84,7 @@ abstract class DIBImageReader extends ImageReaderBase {
protected void resetMembers() {
directory = null;
headers.clear();
descriptors.clear();
@@ -551,7 +551,7 @@ abstract class DIBImageReader extends ImageReaderBase {
if (abortRequested()) {
processReadAborted();
break;
}
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
@@ -591,7 +591,7 @@ abstract class DIBImageReader extends ImageReaderBase {
return directory.getEntry(pImageIndex);
}
/// Test code below, ignore.. :-)
public static void main(final String[] pArgs) throws IOException {
if (pArgs.length == 0) {
@@ -701,10 +701,10 @@ abstract class DIBImageReader extends ImageReaderBase {
}
});
button.setText("" + image.getWidth() + "x" +
button.setText(image.getWidth() + "x" +
image.getHeight() + ": "
+ ((image.getColorModel() instanceof IndexColorModel) ?
"" + ((IndexColorModel) image.getColorModel()).getMapSize() :
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
"TrueColor"));
pParent.add(button);
@@ -43,6 +43,7 @@ import java.nio.ByteOrder;
* DIBImageWriter
*/
abstract class DIBImageWriter extends ImageWriterBase {
DIBImageWriter(ImageWriterSpi provider) {
super(provider);
}
@@ -50,7 +51,9 @@ abstract class DIBImageWriter extends ImageWriterBase {
@Override
public void setOutput(Object output) {
super.setOutput(output);
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
if (imageOutput != null) {
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
}
void writeDIBHeader(int infoHeaderSize, int width, int height, boolean isTopDown, int pixelSize, int compression) throws IOException {
@@ -82,9 +85,8 @@ abstract class DIBImageWriter extends ImageWriterBase {
}
void writeUncompressed(boolean isTopDown, BufferedImage img, int height, int width) throws IOException {
// TODO: Fix
if (img.getType() != BufferedImage.TYPE_4BYTE_ABGR) {
throw new IIOException("Blows!");
throw new IIOException("Only TYPE_4BYTE_ABGR supported");
}
// Support
@@ -93,12 +95,13 @@ abstract class DIBImageWriter extends ImageWriterBase {
// - TODO: Packed/DirectColorModel (16 and 32 bit, BI_BITFIELDS, BI_PNG? BI_JPEG?)
Raster raster = img.getRaster();
WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 4, 4, new int[]{2, 1, 0, 3}, null);
WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 4, 4, new int[] {2, 1, 0, 3}, null);
byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
final int[] bandList = {2, 1, 0, 3};
for (int i = 0; i < height; i++) {
int line = isTopDown ? i : height - 1 - i;
rowRaster.setDataElements(0, 0, raster.createChild(0, line, width, 1, 0, 0, new int[]{2, 1, 0, 3}));
rowRaster.setDataElements(0, 0, raster.createChild(0, line, width, 1, 0, 0, bandList));
imageOutput.write(row);
@@ -143,7 +143,7 @@ public final class ICOImageWriter extends DIBImageWriter {
}
if (image.hasRaster()) {
throw new UnsupportedOperationException("image has a Raster");
throw new UnsupportedOperationException("Raster not supported");
}
if (sequenceIndex >= INITIAL_ENTRY_COUNT) {
@@ -155,7 +155,7 @@ public final class ICOImageWriter extends DIBImageWriter {
ColorModel colorModel = image.getRenderedImage().getColorModel();
// TODO: The output size may depend on the param (subsampling, source region, etc)
if (width > ICO_MAX_DIMENSION && height > ICO_MAX_DIMENSION) {
if (width > ICO_MAX_DIMENSION || height > ICO_MAX_DIMENSION) {
throw new IIOException(String.format("ICO maximum width or height (%d) exceeded", ICO_MAX_DIMENSION));
}
@@ -33,7 +33,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import java.io.IOException;
import java.awt.image.BufferedImage;
import java.util.Locale;
/**
@@ -46,11 +46,13 @@ public final class ICOImageWriterSpi extends ImageWriterSpiBase {
@Override
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return true;
// TODO: Support more types, as time permits.
// NOTE: We do support more types, if writing using PNG compression
return type.getBufferedImageType() == BufferedImage.TYPE_4BYTE_ABGR;
}
@Override
public ICOImageWriter createWriterInstance(final Object extension) throws IOException {
public ICOImageWriter createWriterInstance(final Object extension) {
return new ICOImageWriter(this);
}
@@ -0,0 +1,33 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.ImageWriter;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.List;
/**
* BMPImageWriterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by : harald.kuhr$
* @version : BMPImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
*/
public class BMPImageWriterTest extends ImageWriterAbstractTest {
private final BMPImageWriterSpi provider = new BMPImageWriterSpi();
@Override
protected ImageWriter createImageWriter() {
return provider.createWriterInstance(null);
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR)
);
}
}
@@ -0,0 +1,36 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.ImageWriter;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.List;
/**
* ICOImageWriterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by : harald.kuhr$
* @version : ICOImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
*/
public class ICOImageWriterTest extends ImageWriterAbstractTest {
private final ICOImageWriterSpi provider = new ICOImageWriterSpi();
@Override
protected ImageWriter createImageWriter() {
return provider.createWriterInstance(null);
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(8, 8, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(16, 16, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(32, 32, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(64, 64, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(128, 128, BufferedImage.TYPE_4BYTE_ABGR)
);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
@@ -107,8 +107,8 @@ final class AdobePathSegment {
case OPEN_SUBPATH_BEZIER_LINKED:
case OPEN_SUBPATH_BEZIER_UNLINKED:
isTrue(
cppx >= 0 && cppx <= 1 && cppy >= 0 && cppy <= 1,
String.format("Expected point in range [0...1]: (%f, %f)", cppx ,cppy)
cppx >= -16 && cppx <= 16 && cppy >= -16 && cppy <= 16,
String.format("Expected point in range [-16...16]: (%f, %f)", cppx ,cppy)
);
break;
case PATH_FILL_RULE_RECORD:
@@ -113,8 +113,13 @@ public class AdobePathSegmentTest {
}
@Test(expected = IllegalArgumentException.class)
public void testCreateOpenLinkedRecordNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1);
public void testCreateOpenLinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, -16.1, -16.1, 0, 0, 1, 1);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateOpenLinkedRecordOutOfRangePositive() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, 16.1, 16.1, 0, 0, 1, 1);
}
@Test
@@ -138,8 +143,13 @@ public class AdobePathSegmentTest {
@Test(expected = IllegalArgumentException.class)
public void testCreateOpenUnlinkedRecordNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1);
public void testCreateOpenUnlinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, -16.5, 0, 0, 0, 1, 1);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateOpenUnlinkedRecorOutOfRangePositive() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, 0, -17, 0, 0, 16.5, 1);
}
/// Closed subpath
@@ -164,8 +174,13 @@ public class AdobePathSegmentTest {
}
@Test(expected = IllegalArgumentException.class)
public void testCreateClosedLinkedRecordNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1);
public void testCreateClosedLinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, -16.5, -.5, 0, 0, 1, 1);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateClosedLinkedRecordOutOfRangePositive() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, .5, 16.5, 0, 0, 1, 1);
}
@Test
@@ -189,8 +204,13 @@ public class AdobePathSegmentTest {
@Test(expected = IllegalArgumentException.class)
public void testCreateClosedUnlinkedRecordNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1);
public void testCreateClosedUnlinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, -.5, -16.5, 0, 0, 1, 1);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateClosedUnlinkedRecordOutOfRangePositive() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, 16.5, .5, 0, 0, 1, 1);
}
@Test
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -129,7 +129,7 @@ public abstract class ImageWriterAbstractTest {
writer.write(drawSomething((BufferedImage) testData));
}
catch (IOException e) {
fail(e.getMessage());
throw new AssertionError(e.getMessage(), e);
}
assertTrue("No image data written", buffer.size() > 0);
@@ -149,10 +149,10 @@ public abstract class ImageWriterAbstractTest {
catch(IllegalArgumentException ignore) {
}
catch (IOException e) {
fail(e.getMessage());
throw new AssertionError(e.getMessage(), e);
}
assertTrue("Image data written", buffer.size() == 0);
assertEquals("Image data written", 0, buffer.size());
}
@Test(expected = IllegalStateException.class)
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -35,27 +35,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is present
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is present
private final short[][][] l = new short[4][2][16];
private final short[][][][] v = new short[4][2][16][200]; // tables
private final boolean[][] tc = new boolean[4][2]; // 1: this table is present
static final int MSB = 0x80000000;
private static final int MSB = 0x80000000;
private HuffmanTable() {
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 c = 0; c < 2; c++) {
if (tc[t][c] != 0) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
if (tc[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)
// Effect:
// 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 k = 0;
@@ -112,7 +112,7 @@ final class HuffmanTable extends Segment {
for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) {
if (tc[t][c]) {
if (builder.length() > 4) {
builder.append(", ");
}
@@ -149,11 +149,10 @@ final class HuffmanTable extends Segment {
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
}
table.th[t] = 1;
table.tc[t][c] = 1;
table.tc[t][c] = true;
for (int i = 0; i < 16; i++) {
table.l[t][c][i] = data.readUnsignedByte();
table.l[t][c][i] = (short) data.readUnsignedByte();
count++;
}
@@ -162,7 +161,7 @@ final class HuffmanTable extends Segment {
if (count > length) {
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++;
}
}
@@ -174,4 +173,41 @@ final class HuffmanTable extends Segment {
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;
}
@@ -30,10 +30,16 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
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.tiff.TIFF;
import org.w3c.dom.Node;
import javax.imageio.IIOException;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.util.List;
/**
@@ -45,31 +51,89 @@ import java.util.List;
*/
class JPEGImage10Metadata extends AbstractMetadata {
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place....
/**
* Native metadata format name
*/
static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0";
// 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;
JPEGImage10Metadata(List<Segment> segments) {
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
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, JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
this.segments = segments;
this.frame = frame;
this.jfif = jfif;
this.adobeDCT = adobeDCT;
this.jfxx = jfxx;
this.embeddedICCProfile = embeddedICCProfile;
this.exif = exif;
}
@Override
protected Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode root = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
root.appendChild(jpegVariety);
// TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless
boolean isJFIF = jfif != null;
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");
root.appendChild(markerSequence);
for (Segment segment : segments)
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.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;
IIOMetadataNode sof = new IIOMetadataNode("sof");
@@ -96,13 +160,13 @@ class JPEGImage10Metadata extends AbstractMetadata {
HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dht = new IIOMetadataNode("dht");
// Uses fixed tables...
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) {
if (huffmanTable.tc[i][j] != 0) {
for (int c = 0; c < 2; c++) {
if (huffmanTable.isPresent(i, c)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(j));
dhtable.setAttribute("class", String.valueOf(c));
dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, c));
dht.appendChild(dhtable);
}
}
@@ -112,8 +176,28 @@ class JPEGImage10Metadata extends AbstractMetadata {
break;
case JPEG.DQT:
markerSequence.appendChild(new IIOMetadataNode("dqt"));
// TODO:
QuantizationTable quantizationTable = (QuantizationTable) segment;
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;
case JPEG.SOS:
@@ -144,6 +228,25 @@ class JPEGImage10Metadata extends AbstractMetadata {
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:
if (segment instanceof AdobeDCT) {
AdobeDCT adobe = (AdobeDCT) segment;
@@ -165,32 +268,149 @@ class JPEGImage10Metadata extends AbstractMetadata {
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
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
for (Segment segment : segments) {
if (segment instanceof Frame) {
Frame sofSegment = (Frame) segment;
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "GRAY" : "RGB"); // TODO YCC, YCCK, CMYK etc
chroma.appendChild(colorSpaceType);
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", getColorSpaceType());
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame()));
chroma.appendChild(numChannels);
break;
}
}
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame()));
chroma.appendChild(numChannels);
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;
}
}
@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
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
@@ -200,7 +420,7 @@ class JPEGImage10Metadata extends AbstractMetadata {
compression.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only
lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE");
compression.appendChild(lossless);
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
@@ -215,12 +435,67 @@ class JPEGImage10Metadata extends AbstractMetadata {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "normal"); // TODO
imageOrientation.setAttribute("value", getExifOrientation(exif));
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;
}
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
protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -235,6 +510,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;
}
}
@@ -52,11 +52,6 @@ import java.util.List;
*/
final class JPEGImage10MetadataCleaner {
/**
* Native metadata format name
*/
static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0";
private final JPEGImageReader reader;
JPEGImage10MetadataCleaner(final JPEGImageReader reader) {
@@ -93,7 +88,7 @@ final class JPEGImage10MetadataCleaner {
the version to the method/constructor used to obtain an IIOMetadata object.)
*/
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
@@ -145,7 +140,7 @@ final class JPEGImage10MetadataCleaner {
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
IIOMetadata thumbMeta = thumbnailReader.readMetadata();
Node thumbTree = thumbMeta.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
Node thumbTree = thumbMeta.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
jfifThumb.appendChild(thumbTree.getLastChild());
app0JFXX.appendChild(jfifThumb);
break;
@@ -307,11 +302,11 @@ final class JPEGImage10MetadataCleaner {
}
try {
imageMetadata.setFromTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree);
imageMetadata.setFromTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree);
}
catch (IIOInvalidTreeException e) {
if (JPEGImageReader.DEBUG) {
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0), false);
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0), false);
System.out.println("-- 8< --");
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false);
}
@@ -113,9 +113,6 @@ public final class JPEGImageReader extends ImageReaderBase {
/** Internal constant for referring all APP segments */
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 */
private final ImageReader delegate;
@@ -534,7 +531,7 @@ public final class JPEGImageReader extends ImageReaderBase {
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:
// Guess the input colorspace
// (Wish JPEG committee had provided a real way to specify this...)
@@ -717,10 +714,11 @@ public final class JPEGImageReader extends ImageReaderBase {
private void initHeader(final int imageIndex) throws IOException {
if (imageIndex < 0) {
throw new IllegalArgumentException("imageIndex < 0: " + imageIndex);
throw new IndexOutOfBoundsException("imageIndex < 0: " + imageIndex);
}
if (imageIndex == currentStreamIndex) {
initHeader();
return;
}
@@ -837,7 +835,7 @@ public final class JPEGImageReader extends ImageReaderBase {
try {
imageInput.seek(streamOffsets.get(currentStreamIndex));
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
return JPEGSegmentUtil.readSegments(imageInput, JPEGSegmentUtil.ALL_SEGMENTS);
}
catch (IIOException | IllegalArgumentException ignore) {
if (DEBUG) {
@@ -1218,38 +1216,9 @@ public final class JPEGImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
checkBounds(imageIndex);
initHeader(imageIndex);
IIOMetadata imageMetadata;
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;
return new JPEGImage10Metadata(segments, getSOF(), getJFIF(), getJFXX(), getEmbeddedICCProfile(true), getAdobeDCT(), getExif());
}
@Override
@@ -51,7 +51,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import static com.twelvemonkeys.imageio.plugins.jpeg.JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0;
import static com.twelvemonkeys.imageio.plugins.jpeg.JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0;
/**
* JPEGImageWriter
@@ -159,6 +159,17 @@ public final class JPEGImageWriter extends ImageWriterBase {
writeCMYK(streamMetadata, image, param);
}
else {
// If the image metadata is our substitute, convert it back to native com.sun format
if (image.getMetadata() instanceof JPEGImage10Metadata) {
ImageTypeSpecifier type = image.hasRaster() ? null : ImageTypeSpecifier.createFromRenderedImage(image.getRenderedImage());
IIOMetadata nativeMetadata = delegate.getDefaultImageMetadata(type, param);
JPEGImage10Metadata metadata = (JPEGImage10Metadata) image.getMetadata();
nativeMetadata.setFromTree(metadata.getNativeMetadataFormatName(), metadata.getNativeTree());
image.setMetadata(nativeMetadata);
}
delegate.write(streamMetadata, image, param);
}
}
@@ -39,6 +39,7 @@ import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
final class JPEGLosslessDecoder {
@@ -51,12 +52,12 @@ final class JPEGLosslessDecoder {
private final QuantizationTable quantTable;
private Scan scan;
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
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[] 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[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
private final int[][][] HuffTab = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
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[][] 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[][] qTab = new int[10][]; // quantization table for the i-th Comp in a scan
private boolean restarting;
private int marker;
@@ -70,7 +71,7 @@ final class JPEGLosslessDecoder {
private int mask;
private int[][] outputData;
private static final int IDCT_P[] = {
private static final int[] IDCT_P = {
0, 5, 40, 16, 45, 2, 7, 42,
21, 56, 8, 61, 18, 47, 1, 4,
41, 23, 58, 13, 32, 24, 37, 10,
@@ -80,16 +81,6 @@ final class JPEGLosslessDecoder {
50, 55, 25, 36, 11, 62, 14, 35,
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_END = 0xFFD7;
@@ -158,7 +149,7 @@ final class JPEGLosslessDecoder {
huffTable.buildHuffTables(HuffTab);
}
quantTable.enhanceTables(TABLE);
quantTable.enhanceTables();
current = input.readUnsignedShort();
@@ -185,11 +176,10 @@ final class JPEGLosslessDecoder {
selection = scan.spectralSelStart;
final Scan.Component[] scanComps = scan.components;
final int[][] quantTables = quantTable.quantTables;
for (int i = 0; i < numComp; i++) {
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;
int dcTabSel = scanComps[i].dcTabSel;
@@ -220,18 +210,18 @@ final class JPEGLosslessDecoder {
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++) {
firstValue[i] = (1 << (precision - 1));
}
final int pred[] = new int[numComp];
final int[] pred = new int[numComp];
scanNum++;
while (true) { // Decode one scan
int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1];
int[] temp = new int[1]; // to store remainder bits
int[] index = new int[1];
System.arraycopy(firstValue, 0, pred, 0, numComp);
@@ -288,7 +278,7 @@ final class JPEGLosslessDecoder {
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
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;
}
}
@@ -324,7 +314,7 @@ final class JPEGLosslessDecoder {
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) {
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 each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
if (restarting) {
@@ -390,7 +380,7 @@ final class JPEGLosslessDecoder {
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[] outputGreenData = outputData[1];
final int[] outputBlueData = outputData[2];
@@ -435,7 +425,7 @@ final class JPEGLosslessDecoder {
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) {
final int[] outputData = this.outputData[componentIndex];
final int previous;
@@ -469,17 +459,17 @@ final class JPEGLosslessDecoder {
}
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
int value, actab[], dctab[];
int qtab[];
int value;
int[] actab;
int[] dctab;
int[] qtab;
for (int ctrC = 0; ctrC < numComp; ctrC++) {
qtab = qTab[ctrC];
actab = acTab[ctrC];
dctab = dcTab[ctrC];
for (int i = 0; i < nBlock[ctrC]; i++) {
for (int k = 0; k < IDCT_Source.length; k++) {
IDCT_Source[k] = 0;
}
Arrays.fill(IDCT_Source, 0);
value = getHuffmanValue(dctab, temp, index);
@@ -545,7 +535,7 @@ final class JPEGLosslessDecoder {
// and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue()
// 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;
final int mask = 0xFFFF;
@@ -603,7 +593,7 @@ final class JPEGLosslessDecoder {
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;
final int one = 1;
final int n_one = -1;
@@ -688,7 +678,7 @@ final class JPEGLosslessDecoder {
return result;
}
private int getPreviousX(final int data[]) {
private int getPreviousX(final int[] data) {
if (xLoc > 0) {
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)) {
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) {
return data[((yLoc - 1) * xDim) + xLoc];
}
@@ -722,7 +712,7 @@ final class JPEGLosslessDecoder {
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
}
private void output(final int pred[]) {
private void output(final int[] pred) {
if (numComp == 1) {
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)) {
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
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)) {
final int index = (yLoc * xDim) + xLoc;
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)) {
final int index = (yLoc * xDim) + xLoc;
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 javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGQTable;
import java.io.DataInput;
import java.io.IOException;
final class QuantizationTable extends Segment {
private final int precision[] = new int[4]; // Quantization precision 8 or 16
private final int[] tq = new int[4]; // 1: this table is presented
private static final int[] ZIGZAG = {
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() {
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?
void enhanceTables(final int[] table) throws IOException {
// TODO: Consider creating a copy for the decoder here, as we need to keep the original values for the metadata
void enhanceTables() {
for (int t = 0; t < 4; t++) {
if (tq[t] != 0) {
enhanceQuantizationTable(quantTables[t], table);
if (tq[t]) {
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++) {
qtab[table[ 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]);
}
table.tq[t] = 1;
table.tq[t] = true;
if (table.precision[t] == 8) {
for (int i = 0; i < 64; i++) {
@@ -152,4 +159,28 @@ final class QuantizationTable extends Segment {
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);
}
}
@@ -85,12 +85,12 @@ public class JPEGImage10MetadataCleanerTest {
reader.setInput(input);
IIOMetadata original = origReader.getImageMetadata(0);
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0));
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList origDHT = origTree.getElementsByTagName("dht");
assertEquals(1, origDHT.getLength());
@@ -31,8 +31,8 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.lang.StringUtil;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.internal.matchers.GreaterThan;
import org.w3c.dom.Element;
@@ -182,23 +182,27 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
public void testICCProfileCMYKClassOutputColors() throws IOException {
// Make sure ICC profile with class output isn't converted to too bright values
JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg")));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(800, 800, 64, 8));
param.setSourceSubsampling(8, 8, 2, 2);
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"))) {
reader.setInput(stream);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(800, 800, 64, 8));
param.setSourceSubsampling(8, 8, 2, 2);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
byte[] expectedData = {34, 37, 34, 47, 47, 44, 22, 26, 28, 23, 26, 28, 20, 23, 26, 20, 22, 25, 22, 25, 27, 18, 21, 24};
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(expectedData.length, data.length);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
byte[] expectedData = {34, 37, 34, 47, 47, 44, 22, 26, 28, 23, 26, 28, 20, 23, 26, 20, 22, 25, 22, 25, 27, 18, 21, 24};
assertJPEGPixelsEqual(expectedData, data, 0);
assertEquals(expectedData.length, data.length);
reader.dispose();
assertJPEGPixelsEqual(expectedData, data, 0);
}
finally {
reader.dispose();
}
}
private static void assertJPEGPixelsEqual(byte[] expected, byte[] actual, int actualOffset) {
@@ -211,38 +215,44 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
public void testICCDuplicateSequence() throws IOException {
// Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1
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(540, reader.getHeight(0));
assertEquals(345, reader.getWidth(0));
assertEquals(540, reader.getHeight(0));
BufferedImage image = reader.read(0);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(345, image.getWidth());
assertEquals(540, image.getHeight());
reader.dispose();
assertNotNull(image);
assertEquals(345, image.getWidth());
assertEquals(540, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testICCDuplicateSequenceZeroBased() throws IOException {
// File contains multiple ICC chunks, with all counts and sequence numbers == 0
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(5480, reader.getHeight(0));
assertEquals(3874, reader.getWidth(0));
assertEquals(5480, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(3874, image.getWidth());
assertEquals(16, image.getHeight());
reader.dispose();
assertNotNull(image);
assertEquals(3874, image.getWidth());
assertEquals(16, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
@@ -251,20 +261,23 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// 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
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(1038, reader.getHeight(0));
assertEquals(1993, reader.getWidth(0));
assertEquals(1038, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
BufferedImage image = reader.read(0, param);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1993, image.getWidth());
assertEquals(8, image.getHeight());
reader.dispose();
assertNotNull(image);
assertEquals(1993, image.getWidth());
assertEquals(8, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
@@ -272,19 +285,23 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// 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.
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(449, reader.getHeight(0));
assertEquals(281, reader.getWidth(0));
assertEquals(449, reader.getHeight(0));
BufferedImage image = reader.read(0);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(281, image.getWidth());
assertEquals(449, image.getHeight());
assertNotNull(image);
assertEquals(281, image.getWidth());
assertEquals(449, image.getHeight());
// TODO: Need to test colors!
reader.dispose();
// TODO: Need to test colors!
}
finally {
reader.dispose();
}
}
@Test
@@ -293,22 +310,27 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// but image data is plain 3 channel YCC/RGB.
// EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"...
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(2551, reader.getHeight(0));
assertEquals(1743, reader.getWidth(0));
assertEquals(2551, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 1743, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 1743, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1743, image.getWidth());
assertEquals(16, image.getHeight());
assertNotNull(image);
assertEquals(1743, image.getWidth());
assertEquals(16, image.getHeight());
// TODO: Need to test colors!
// TODO: Need to test colors!
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
}
finally {
reader.dispose();
}
}
@Test
@@ -316,107 +338,131 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// 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').
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(500, reader.getHeight(0));
assertEquals(493, reader.getWidth(0));
assertEquals(500, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 493, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 493, 16)); // Save some memory
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(493, image.getWidth());
assertEquals(16, image.getHeight());
assertNotNull(image);
assertEquals(493, image.getWidth());
assertEquals(16, image.getHeight());
// TODO: Need to test colors!
// TODO: Need to test colors!
assertFalse(reader.hasThumbnails(0)); // Should not blow up!
assertFalse(reader.hasThumbnails(0)); // Should not blow up!
}
finally {
reader.dispose();
}
}
@Test
public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException {
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(283, reader.getHeight(0));
assertEquals(183, reader.getWidth(0));
assertEquals(283, reader.getHeight(0));
BufferedImage image = reader.read(0);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(183, image.getWidth());
assertEquals(283, image.getHeight());
assertNotNull(image);
assertEquals(183, image.getWidth());
assertEquals(283, image.getHeight());
// TODO: Need to test colors!
// TODO: Need to test colors!
}
finally {
reader.dispose();
}
}
@Test
public void testEOFSOSSegment() throws IOException {
// Regression...
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(400, reader.getHeight(0));
assertEquals(266, reader.getWidth(0));
assertEquals(400, reader.getHeight(0));
BufferedImage image = reader.read(0);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(266, image.getWidth());
assertEquals(400, image.getHeight());
assertNotNull(image);
assertEquals(266, image.getWidth());
assertEquals(400, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testInvalidICCSingleChunkBadSequence() throws IOException {
// Regression
// Single segment ICC profile, with chunk index/count == 0
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(2126, reader.getHeight(0));
assertEquals(1772, reader.getWidth(0));
assertEquals(2126, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(0, param);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(1772, image.getWidth());
assertEquals(8, image.getHeight());
assertNotNull(image);
assertEquals(1772, image.getWidth());
assertEquals(8, image.getHeight());
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
}
finally {
reader.dispose();
}
}
@Test
public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException {
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes
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.getHeight(0));
assertEquals(600, reader.getWidth(0));
assertEquals(600, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(8, 8));
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(8, 8));
BufferedImage image = reader.read(0, param);
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(8, image.getWidth());
assertEquals(8, image.getHeight());
assertNotNull(image);
assertEquals(8, image.getWidth());
assertEquals(8, image.getHeight());
// QnD test: Make sure all pixels are white (if treated as RGB, they will be pink-ish)
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals(0xffffff, image.getRGB(x, y) & 0xffffff);
// QnD test: Make sure all pixels are white (if treated as RGB, they will be pink-ish)
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals(0xffffff, image.getRGB(x, y) & 0xffffff);
}
}
}
finally {
reader.dispose();
}
}
@Test
@@ -424,36 +470,87 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// Special case, throws exception below without special treatment
// java.awt.color.CMMException: General CMM error517
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(384, reader.getHeight(0));
assertEquals(512, reader.getWidth(0));
assertEquals(384, reader.getHeight(0));
BufferedImage image = reader.read(0);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(512, image.getWidth());
assertEquals(384, image.getHeight());
reader.dispose();
}
@Ignore("Known issue in com.sun...JPEGMetadata")
@Test
public void testStandardMetadataColorSpaceTypeRGBForYCbCr() {
// These reports RGB in standard metadata, while the data is really YCbCr.
// Exif files are always YCbCr AFAIK.
fail("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg");
fail("/jpeg/exif-pspro-13-inverted-colors.jpg");
// Not Exif, but same issue: SOF comp ids are JFIF standard 1-3 and
// *should* be interpreted as YCbCr but isn't.
// 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)
fail("/jpeg/no-jfif-ycbcr.jpg");
assertNotNull(image);
assertEquals(512, image.getWidth());
assertEquals(384, image.getHeight());
}
finally {
reader.dispose();
}
}
@Test
public void testBrokenReadRasterAfterGetMetadataException() throws IOException {
public void testStandardMetadataColorSpaceTypeRGBForYCbCr() throws IOException {
List<TestData> ycbcr = Arrays.asList(
// This reports RGB in standard metadata, while the data is really YCbCr.
// Exif files are always YCbCr AFAIK.
new TestData(getClassLoaderResource("/jpeg/exif-jpeg-thumbnail-sony-dsc-p150-inverted-colors.jpg"), new Dimension(2437, 1662)),
// Not Exif, but same issue: SOF comp ids are JFIF standard 1-3 and
// *should* be interpreted as YCbCr but isn't.
// 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)
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
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
JPEGImageReader reader = createReader();
@@ -497,7 +594,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// TODO: Consider wrapping the delegate in JPEGImageReader with methods that don't throw
// runtime exceptions, and instead throw IIOException?
@Test
public void testBrokenGetRawImageType() throws IOException {
public void testBrokenGetRawImageType() {
JPEGImageReader reader = createReader();
try {
@@ -523,7 +620,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test(timeout = 200)
public void testBrokenGetRawImageTypeIgnoreMetadata() throws IOException {
public void testBrokenGetRawImageTypeIgnoreMetadata() {
JPEGImageReader reader = createReader();
try {
@@ -549,7 +646,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test
public void testBrokenGetImageTypes() throws IOException {
public void testBrokenGetImageTypes() {
JPEGImageReader reader = createReader();
try {
@@ -575,7 +672,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test(timeout = 200)
public void testBrokenGetImageTypesIgnoreMetadata() throws IOException {
public void testBrokenGetImageTypesIgnoreMetadata() {
JPEGImageReader reader = createReader();
try {
@@ -601,7 +698,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test
public void testBrokenRead() throws IOException {
public void testBrokenRead() {
JPEGImageReader reader = createReader();
try {
@@ -627,7 +724,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test
public void testBrokenGetDimensions() throws IOException {
public void testBrokenGetDimensions() {
JPEGImageReader reader = createReader();
try {
@@ -656,7 +753,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
@Test
public void testBrokenGetImageMetadata() throws IOException {
public void testBrokenGetImageMetadata() {
JPEGImageReader reader = createReader();
try {
@@ -1284,6 +1381,9 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// Assume that the aspect ratio is 1 if both x/y density is 0.
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");
assertEquals(1, dimensions.getLength());
assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName());
@@ -1321,7 +1421,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
NodeList markerSequences = iioTree.getElementsByTagName("markerSequence");
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);
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
@@ -1379,6 +1479,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
for (TestData testData : getTestData()) {
reader.setInput(testData.getInputStream());
assert referenceReader != null;
referenceReader.setInput(testData.getInputStream());
for (int i = 0; i < reader.getNumImages(true); i++) {
@@ -1393,6 +1494,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
Node referenceTree = reference.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);
assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree);
}
@@ -1432,8 +1535,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
if (expectedTree == null) {
assertNull(actualTree);
return;
fail("Expected tree is null, actual tree is non-null");
}
assertEquals(String.format("%s: Node names differ", message), expectedTree.getNodeName(), actualTree.getNodeName());
@@ -1443,7 +1545,14 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
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++) {
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.
@@ -1460,6 +1569,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
List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes());
List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes());
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

@@ -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.
@@ -0,0 +1 @@
2.0.1
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -60,10 +60,10 @@ public abstract class AbstractEntry implements Entry {
}
/**
* Returns a format-native identifier.
* For example {@code "2:00"} for IPTC "Record Version" field, or {@code "0x040c"} for PSD "Thumbnail" resource.
* Returns a format-native identifier.
* For example {@code "2:00"} for IPTC "Record Version" field, or {@code "0x040c"} for PSD "Thumbnail" resource.
* This default implementation simply returns {@code String.valueOf(getIdentifier())}.
*
*
* @return a format-native identifier.
*/
protected String getNativeIdentifier() {
@@ -174,7 +174,7 @@ public abstract class AbstractEntry implements Entry {
}
AbstractEntry other = (AbstractEntry) pOther;
return identifier.equals(other.identifier) && (
value == null && other.value == null || value != null && valueEquals(other)
);
@@ -144,7 +144,7 @@ public final class XMPReader extends MetadataReader {
parseAttributesForKnownElements(subsubdirs, node);
if (!subsubdirs.isEmpty()) {
List<Entry> entries = new ArrayList<Entry>();
List<Entry> entries = new ArrayList<>(subsubdirs.size());
for (Map.Entry<String, List<Entry>> entry : subsubdirs.entrySet()) {
entries.addAll(entry.getValue());
@@ -161,7 +161,7 @@ public final class XMPReader extends MetadataReader {
}
}
List<Directory> entries = new ArrayList<Directory>();
List<Directory> entries = new ArrayList<Directory>(subdirs.size());
// TODO: Should we still allow asking for a subdirectory by item id?
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: reference test cases</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
@@ -89,16 +89,10 @@ final class TGAImageWriter extends ImageWriterBase {
processImageStarted(0);
WritableRaster rowRaster = header.getPixelDepth() == 32
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false)
.createBufferedImage(renderedImage.getWidth(), 1)
.getRaster()
: renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)
.createBufferedImage(renderedImage.getWidth(), 1)
.getRaster()
: ImageTypeSpecifier.createFromRenderedImage(renderedImage)
.createBufferedImage(renderedImage.getWidth(), 1)
.getRaster();
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
: renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
: ImageTypeSpecifier.createFromRenderedImage(renderedImage).createBufferedImage(renderedImage.getWidth(), 1).getRaster();
DataBuffer buffer = rowRaster.getDataBuffer();
@@ -142,17 +136,14 @@ final class TGAImageWriter extends ImageWriterBase {
}
// Vi kan lage en DataBuffer wrapper-klasse,
// som gjør TYPE_INT_RGB/INT_ARGB/INT_ARGB_PRE/INT_BGR til tilsvarende TYPE_xBYTE-klasser.
// Ytelse er ikke viktig her, siden vi uansett mĂĄ konvertere nĂĄr vi skal skrive/lese.
// TODO: Refactore dette til felles lag?
// TODO: Implementere writable ogsĂĄ, slik at vi kan bruke i lesing?
// TODO: Refactor to common util
// TODO: Implement WritableRaster too, for use in reading
private Raster asByteRaster(final Raster raster, ColorModel colorModel) {
switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
return raster;
case DataBuffer.TYPE_USHORT:
return raster; // TODO: we handle ushort especially for now..
return raster; // TODO: We handle USHORT especially for now..
case DataBuffer.TYPE_INT:
final int bands = colorModel.getNumComponents();
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
@@ -340,7 +340,7 @@ public final class ThumbsDBImageReader extends ImageReaderBase {
// TODO: Rethink this...
// Seems to be up to Windows and the installed programs what formats
// are supported...
// Some thumbs are just icons, and it might be better to use ImageIO to create thumbs for these... :-/
// Some thumbs are just icons, and it might be better to use ImageIO to create thumbs for these... :-/
// At least this seems fine for now
String extension = FileUtil.getExtension(pFileName);
if (StringUtil.isEmpty(extension)) {
@@ -351,7 +351,7 @@ public final class ThumbsDBImageReader extends ImageReaderBase {
return !"psd".equals(extension) && !"svg".equals(extension) && catalog != null && catalog.getIndex(pFileName) != -1;
}
/// Test code below
/// Test code below
public static void main(String[] pArgs) throws IOException {
ThumbsDBImageReader reader = new ThumbsDBImageReader();
@@ -421,7 +421,7 @@ public final class ThumbsDBImageReader extends ImageReaderBase {
return SIZE;
}
});
label.setText("" + image.getWidth() + "x" + image.getHeight() + ": " + pName);
label.setText(image.getWidth() + "x" + image.getHeight() + ": " + pName);
label.setToolTipText(image.toString());
pParent.add(label);
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -91,11 +91,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
super(Validate.notNull(stream, "stream"));
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
this.type = Validate.isTrue(
type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6,
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"
);
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
type == TIFFExtension.COMPRESSION_CCITT_T6,
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s");
this.fillOrder = Validate.isTrue(
fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
fillOrder, "Expected fill order 1 or 2: %s"
@@ -150,6 +149,46 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
this(stream, columns, type, fillOrder, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
}
static int findCompressionType(final int type, final InputStream in) throws IOException {
// Discover possible incorrect type, revert to RLE
if (type == TIFFExtension.COMPRESSION_CCITT_T4 && in.markSupported()) {
byte[] streamData = new byte[20];
try {
in.mark(streamData.length);
int offset = 0;
while (offset < streamData.length) {
int read = in.read(streamData, offset, streamData.length - offset);
if (read <= 0) {
break;
}
offset += read;
}
}
finally {
in.reset();
}
if (streamData[0] != 0 || (streamData[1] >> 4 != 1 && streamData[1] != 1)) {
// Leading EOL (0b000000000001) not found, search further and try RLE if not found
short b = (short) (((streamData[0] << 8) + streamData[1]) >> 4);
for (int i = 12; i < 160; i++) {
b = (short) ((b << 1) + ((streamData[(i / 8)] >> (7 - (i % 8))) & 0x01));
if ((b & 0xFFF) == 1) {
return TIFFExtension.COMPRESSION_CCITT_T4;
}
}
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
}
}
return type;
}
private void fetch() throws IOException {
if (decodedPos >= decodedLength) {
decodedLength = 0;
@@ -101,5 +101,8 @@ interface TIFFExtension {
int GROUP3OPT_2DENCODING = 1;
int GROUP3OPT_UNCOMPRESSED = 2;
int GROUP3OPT_FILLBITS = 4;
int GROUP3OPT_BYTEALIGNED = 8;
int GROUP4OPT_UNCOMPRESSED = 2;
int GROUP4OPT_BYTEALIGNED = 4;
}
@@ -144,7 +144,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
IIOMetadataNode valueNode = new IIOMetadataNode("TIFFUndefined");
tagNode.appendChild(valueNode);
if (count == 1) {
if (count == 1 && (value == null || !value.getClass().isArray())) {
valueNode.setAttribute("value", String.valueOf(value));
}
else {
@@ -160,7 +160,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
String typeName = getMetadataType(tag);
// NOTE: ASCII/Strings have count 1, always. This seems consistent with the JAI ImageIO version.
if (count == 1) {
if (count == 1 && (value == null || !value.getClass().isArray())) {
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
valueNode.appendChild(elementNode);
@@ -915,7 +915,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException {
// Standard validation
super.mergeTree(formatName, root);
super.setFromTree(formatName, root);
// Set by "merging" with empty map
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>();
@@ -1144,10 +1144,11 @@ public final class TIFFImageMetadata extends AbstractMetadata {
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode);
}
List<Entry> entries = new ArrayList<>();
NodeList nodes = ifdNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
final int size = nodes.getLength();
List<Entry> entries = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
entries.add(toEntry(nodes.item(i)));
}
@@ -1293,21 +1293,22 @@ public final class TIFFImageReader extends ImageReaderBase {
imageInput.seek(stripTileOffsets[0]);
if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) {
int len = 2 + (imageInput.readByte() << 8 | imageInput.readByte());
processWarningOccurred("Incorrect StripOffsets/TileOffsets, points to SOS marker, ignoring offsets/byte counts.");
int len = 2 + (imageInput.readUnsignedByte() << 8 | imageInput.readUnsignedByte());
stripTileOffsets[0] += len;
stripTileByteCounts[0] -= len;
}
// We'll prepend each tile with a JFIF "header" (SOI...SOS)
imageInput.seek(realJPEGOffset);
jpegHeader = new byte[(int) (stripTileOffsets[0] - realJPEGOffset)];
jpegHeader = new byte[Math.max(0, (int) (stripTileOffsets[0] - realJPEGOffset))];
imageInput.readFully(jpegHeader);
}
// In case of single tile, make sure we read the entire JFIF stream
if (stripTileByteCounts != null && stripTileByteCounts.length == 1) {
// TODO: Consider issue warning here!
stripTileByteCounts[0] = Math.max(stripTileByteCounts[0], jpegLength);
if (stripTileByteCounts != null && stripTileByteCounts.length == 1 && stripTileByteCounts[0] < jpegLength) {
processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead.");
stripTileByteCounts[0] = jpegLength;
}
// Read data
@@ -2307,12 +2308,22 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
case TIFFExtension.COMPRESSION_CCITT_T4:
case TIFFExtension.COMPRESSION_CCITT_T6:
return new CCITTFaxDecoderStream(stream, width, compression, fillOrder, getCCITTOptions(compression));
return new CCITTFaxDecoderStream(stream, width, findCCITTType(compression, stream), fillOrder, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
default:
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
}
}
private int findCCITTType(final int encodedCompression, final InputStream stream) throws IOException {
int compressionType = CCITTFaxDecoderStream.findCompressionType(encodedCompression, stream);
if (compressionType != encodedCompression) {
processWarningOccurred(String.format("Detected compression type %d, does not match encoded compression type: %d", compressionType, encodedCompression));
}
return compressionType;
}
private InputStream createFillOrderStream(final int fillOrder, final InputStream stream) {
switch (fillOrder) {
case TIFFBaseline.FILL_LEFT_TO_RIGHT:
@@ -59,6 +59,14 @@ public class CCITTFaxDecoderStreamTest {
static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01,
(byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 };
// group3_1d_premature_eol.tif
// 0011 0101 | 0000 0010 1011 | 0110 0111 | 0010 1001 | 0100
// 0W | 59B | 640W | 40W
static final byte[] DATA_G3_1D_PREMATURE_EOL = {
0x35, 0x02, (byte) 0xB6, 0x72, (byte) 0x94, (byte) 0xE8, 0x74, 0x38, 0x1C, (byte) 0x81, 0x64, (byte) 0xD4,
0x0A, (byte) 0xD9, (byte) 0xD2, 0x27, 0x50, (byte) 0x90, (byte) 0xA6, (byte) 0x87, 0x43, (byte) 0xE3
};
// group3_2d.tif: EOL|k=1|3W|1B|2W|EOL|k=0|V|V|V|EOL|k=1|3W|1B|2W|EOL|k=0|V-1|V|V|6*F
static final byte[] DATA_G3_2D = { 0x00, 0x1C, 0x27, 0x00, 0x17, 0x00, 0x1C, 0x27, 0x00, 0x12, (byte) 0xC0 };
@@ -170,6 +178,27 @@ public class CCITTFaxDecoderStreamTest {
assertArrayEquals(imageData, bytes);
}
@Test
public void testFidCompressionType() throws IOException {
// RLE
assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, new ByteArrayInputStream(DATA_RLE_UNALIGNED)));
// Group 3/CCITT_T4
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D_FILL)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_FILL)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_lsb2msb)));
// Group 4/CCITT_T6
assertEquals(TIFFExtension.COMPRESSION_CCITT_T6, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T6, new ByteArrayInputStream(DATA_G4)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T6, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T6, new ByteArrayInputStream(DATA_G4_ALIGNED)));
// From sample file encoded with RLE, but with CCITT_T4 compression tag
assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D_PREMATURE_EOL)));
assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_RLE_UNALIGNED)));
}
@Test
public void testDecodeType3_2D() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6,
@@ -308,6 +308,27 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
assertNotNull(image);
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Old-style JPEG"), contains("tables")));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Incorrect StripOffsets/TileOffsets"), contains("SOS marker")));
}
}
@Test
public void testReadOldStyleWangMultiStrip2() throws IOException {
TestData testData = new TestData(getClassLoaderResource("/tiff/662260-color.tif"), new Dimension(1600, 1200));
try (ImageInputStream stream = testData.getInputStream()) {
TIFFImageReader reader = createReader();
reader.setInput(stream);
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(1);
assertNotNull(image);
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Old-style JPEG"), contains("tables")));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Incorrect StripOffsets/TileOffsets"), contains("SOS marker")));
}
}
@@ -617,6 +638,25 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
}
}
@Test
public void testReadIncorrectCompressionRLEAsG3() throws IOException {
TestData testData = new TestData(getClassLoaderResource("/tiff/incorrect-compression-rle-as-g3.tif"), new Dimension(1700, 32));
try (ImageInputStream stream = testData.getInputStream()) {
TIFFImageReader reader = createReader();
reader.setInput(stream);
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("compression type"), contains("does not match")));
}
}
@Test
public void testReadMultipleExtraSamples() throws IOException {
ImageReader reader = createReader();
@@ -1072,6 +1072,45 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest {
assertArrayEquals(new byte[] {'I', 'I', 42, 0}, Arrays.copyOf(bytes, 4));
}
@Test
public void testMergeTreeARGB() throws IOException {
ImageWriter writer = createImageWriter();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("LZW");
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR), writeParam);
IIOMetadataNode tiffTree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName());
metadata.setFromTree(metadata.getNativeMetadataFormatName(), tiffTree);
}
@Test
public void testMergeTreeGray() throws IOException {
ImageWriter writer = createImageWriter();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("LZW");
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY), writeParam);
IIOMetadataNode tiffTree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName());
metadata.setFromTree(metadata.getNativeMetadataFormatName(), tiffTree);
}
@Test
public void testMergeTreeBW() throws IOException {
ImageWriter writer = createImageWriter();
ImageWriteParam writeParam = writer.getDefaultWriteParam();
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("CCITT T.6");
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY), writeParam);
IIOMetadataNode tiffTree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName());
metadata.setFromTree(metadata.getNativeMetadataFormatName(), tiffTree);
}
@Test
public void testRewrite() throws IOException {
ImageWriter writer = createImageWriter();
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
<packaging>pom</packaging>
<name>Twelvemonkeys</name>
@@ -86,7 +86,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.5</tag>
<tag>twelvemonkeys-3.6</tag>
</scm>
<properties>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.5</version>
<version>3.6</version>
</parent>
<modelVersion>4.0.0</modelVersion>