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) 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). [Release notes](https://github.com/haraldk/TwelveMonkeys/releases/latest).
## About ## About
@@ -494,12 +494,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<version>3.4.3</version> <version>3.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<version>3.4.3</version> <version>3.5</version>
</dependency> </dependency>
<!-- <!--
@@ -509,7 +509,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.servlet</groupId> <groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<version>3.4.3</version> <version>3.5</version>
</dependency> </dependency>
</dependencies> </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: 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-lang-3.5.jar
twelvemonkeys-common-io-3.4.3.jar twelvemonkeys-common-io-3.5.jar
twelvemonkeys-common-image-3.4.3.jar twelvemonkeys-common-image-3.5.jar
twelvemonkeys-imageio-core-3.4.3.jar twelvemonkeys-imageio-core-3.5.jar
twelvemonkeys-imageio-metadata-3.4.3.jar twelvemonkeys-imageio-metadata-3.5.jar
twelvemonkeys-imageio-jpeg-3.4.3.jar twelvemonkeys-imageio-jpeg-3.5.jar
twelvemonkeys-imageio-tiff-3.4.3.jar twelvemonkeys-imageio-tiff-3.5.jar
### Links to prebuilt binaries ### Links to prebuilt binaries
##### Latest version (3.4.3) ##### Latest version (3.5)
Requires Java 7 or later. Requires Java 7 or later.
Common dependencies 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-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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.4.3/common-io-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.4.3/common-image-3.4.3.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 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-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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.4.3/imageio-metadata-3.4.3.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 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-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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.4.3/imageio-jpeg-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.4.3/imageio-tiff-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.4.3/imageio-pnm-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.4.3/imageio-psd-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.4.3/imageio-hdr-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.4.3/imageio-iff-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.4.3/imageio-pcx-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.4.3/imageio-pict-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.4.3/imageio-sgi-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.4.3/imageio-tga-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.4.3/imageio-icns-3.4.3.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.4.3.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.4.3/imageio-thumbsdb-3.4.3.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 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 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 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) ##### Old version (3.0.x)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<groupId>com.twelvemonkeys.bom</groupId> <groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>common-image</artifactId> <artifactId>common-image</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -129,8 +129,7 @@ public class Time {
* @see #toString(String) * @see #toString(String)
*/ */
public String toString() { public String toString() {
return "" + getMinutes() + ":" return getMinutes() + ":" + (getSeconds() < 10 ? "0" : "") + getSeconds();
+ (getSeconds() < 10 ? "0" : "") + getSeconds();
} }
/** /**
@@ -44,12 +44,12 @@ import java.util.Vector;
* The format is expressed in a string as follows: * The format is expressed in a string as follows:
* <DL> * <DL>
* <DD>m (or any multiple of m's) * <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) * the number of m's)
* m -&gt; 0,1,...,59,60,61,... * m -&gt; 0,1,...,59,60,61,...
* mm -&gt; 00,01,...,59,60,61,... * mm -&gt; 00,01,...,59,60,61,...
* <DD>s or ss * <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) * the number of s's)
* s -&gt; 0,1,...,59 * s -&gt; 0,1,...,59
* ss -&gt; 00,01,...,59 * ss -&gt; 00,01,...,59
@@ -62,7 +62,7 @@ import java.util.Vector;
* <P> * <P>
* Known bugs: * Known bugs:
* <P> * <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 * The first character after an escaped character is escaped while is shouldn't
* be. * be.
* <P> * <P>
@@ -81,15 +81,15 @@ public class TimeFormat extends Format {
final static String SECOND = "s"; final static String SECOND = "s";
final static String TIME = "S"; final static String TIME = "S";
final static String ESCAPE = "\\"; final static String ESCAPE = "\\";
/** /**
* The default time format * The default time format
*/ */
private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss"); private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss");
protected String formatString = null; protected String formatString = null;
/** /**
* Main method for testing ONLY * Main method for testing ONLY
*/ */
@@ -122,7 +122,7 @@ public class TimeFormat extends Format {
} }
else else
time = new Time(); time = new Time();
System.out.println("Time is \"" + out.format(time) + System.out.println("Time is \"" + out.format(time) +
"\" according to format \"" + out.formatString + "\""); "\" according to format \"" + out.formatString + "\"");
} }
@@ -147,18 +147,18 @@ public class TimeFormat extends Format {
String previous = null; String previous = null;
String current = null; String current = null;
int previousCount = 0; int previousCount = 0;
while (tok.hasMoreElements()) { while (tok.hasMoreElements()) {
current = tok.nextToken(); current = tok.nextToken();
if (previous != null && previous.equals(ESCAPE)) { if (previous != null && previous.equals(ESCAPE)) {
// Handle escaping of s, S or m // Handle escaping of s, S or m
current = ((current != null) ? current : "") current = ((current != null) ? current : "")
+ (tok.hasMoreElements() ? tok.nextToken() : ""); + (tok.hasMoreElements() ? tok.nextToken() : "");
previous = null; previous = null;
previousCount = 0; previousCount = 0;
} }
// Skip over first, // Skip over first,
// or if current is the same, increase count, and try again // or if current is the same, increase count, and try again
if (previous == null || previous.equals(current)) { if (previous == null || previous.equals(current)) {
@@ -173,12 +173,12 @@ public class TimeFormat extends Format {
formatter.add(new SecondsFormatter(previousCount)); formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME)) else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1)); formatter.add(new SecondsFormatter(-1));
else else
formatter.add(new TextFormatter(previous)); formatter.add(new TextFormatter(previous));
previousCount = 1; previousCount = 1;
previous = current; previous = current;
} }
} }
@@ -197,7 +197,7 @@ public class TimeFormat extends Format {
// Debug // Debug
/* /*
for (int i = 0; i < formatter.size(); i++) { 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); + ": length=" + ((TimeFormatter) formatter.get(i)).digits);
} }
*/ */
@@ -206,7 +206,7 @@ public class TimeFormat extends Format {
} }
/** /**
* DUMMY IMPLEMENTATION!! * DUMMY IMPLEMENTATION!!
* Not locale specific. * Not locale specific.
*/ */
@@ -259,9 +259,9 @@ public class TimeFormat extends Format {
/** DUMMY IMPLEMENTATION!! */ /** DUMMY IMPLEMENTATION!! */
public Object parseObject(String pStr, ParsePosition pStatus) { public Object parseObject(String pStr, ParsePosition pStatus) {
Time t = parse(pStr); Time t = parse(pStr);
pStatus.setIndex(pStr.length()); // Not 100% pStatus.setIndex(pStr.length()); // Not 100%
return t; return t;
} }
@@ -270,7 +270,7 @@ public class TimeFormat extends Format {
* <p> * <p>
* Will bug on some formats. It's safest to always use delimiters between * Will bug on some formats. It's safest to always use delimiters between
* the minutes (m) and seconds (s) part. * the minutes (m) and seconds (s) part.
* *
*/ */
public Time parse(String pStr) { public Time parse(String pStr) {
Time time = new Time(); Time time = new Time();
@@ -286,7 +286,7 @@ public class TimeFormat extends Format {
&& (pos + skip < pStr.length()) ; i++) { && (pos + skip < pStr.length()) ; i++) {
// Go to next offset // Go to next offset
pos += skip; pos += skip;
if (formatter[i] instanceof MinutesFormatter) { if (formatter[i] instanceof MinutesFormatter) {
// Parse MINUTES // Parse MINUTES
if ((i + 1) < formatter.length if ((i + 1) < formatter.length
@@ -327,9 +327,9 @@ public class TimeFormat extends Format {
else { else {
// Cannot possibly know how long? // Cannot possibly know how long?
skip = 0; skip = 0;
continue; continue;
} }
// Get seconds // Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip)); sec = Integer.parseInt(pStr.substring(pos, skip));
// System.out.println("Only seconds: " + sec); // System.out.println("Only seconds: " + sec);
@@ -343,7 +343,7 @@ public class TimeFormat extends Format {
&& formatter[i + 1] instanceof TextFormatter) { && formatter[i + 1] instanceof TextFormatter) {
// Skip until next format element // Skip until next format element
skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos); skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos);
} }
else if ((i + 1) >= formatter.length) { else if ((i + 1) >= formatter.length) {
// Skip until end of string // Skip until end of string
@@ -359,7 +359,7 @@ public class TimeFormat extends Format {
else if (formatter[i] instanceof TextFormatter) { else if (formatter[i] instanceof TextFormatter) {
skip = formatter[i].digits; skip = formatter[i].digits;
} }
} }
// Set the minutes part if we should // Set the minutes part if we should
@@ -390,7 +390,7 @@ class SecondsFormatter extends TimeFormatter {
SecondsFormatter(int pDigits) { SecondsFormatter(int pDigits) {
digits = pDigits; digits = pDigits;
} }
String format(Time t) { String format(Time t) {
// Negative number of digits, means all seconds, no padding // Negative number of digits, means all seconds, no padding
if (digits < 0) { if (digits < 0) {
@@ -404,7 +404,7 @@ class SecondsFormatter extends TimeFormatter {
// Else return it with leading 0's // Else return it with leading 0's
//return StringUtil.formatNumber(t.getSeconds(), digits); //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 // Else return it with leading 0's
//return StringUtil.formatNumber(t.getMinutes(), digits); //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[])}. * Tests {@link Collection#toArray(Object[])}.
*/ */
@SuppressWarnings({"SuspiciousToArrayCall", "RedundantCast"})
@Test @Test
public void testCollectionToArray2() { public void testCollectionToArray2() {
resetEmpty(); resetEmpty();
Object[] a = new Object[] { new Object(), null, null }; Object[] a = new Object[] { new Object(), null, null };
Object[] array = collection.toArray(a); Object[] array = collection.toArray(a);
assertArrayEquals("Given array shouldn't shrink", array, 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(); verifyAll();
resetFull(); resetFull();
try { try {
array = collection.toArray(new Void[0]); collection.toArray(new Void[0]);
fail("toArray(new Void[0]) should raise ArrayStore"); fail("toArray(new Void[0]) should raise ArrayStore");
} catch (ArrayStoreException e) { } catch (ArrayStoreException e) {
// expected // expected
@@ -1136,7 +1137,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
verifyAll(); verifyAll();
try { try {
array = collection.toArray(null); collection.toArray((Object[]) null);
fail("toArray(null) should raise NPE"); fail("toArray(null) should raise NPE");
} catch (NullPointerException e) { } catch (NullPointerException e) {
// expected // expected
@@ -1150,13 +1151,13 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
// Figure out if they're all the same class // Figure out if they're all the same class
// TODO: It'd be nicer to detect a common superclass // 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++) { for (int i = 0; i < array.length; i++) {
classes.add((array[i] == null) ? null : array[i].getClass()); classes.add((array[i] == null) ? null : array[i].getClass());
} }
if (classes.size() > 1) return; 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 if (Map.Entry.class.isAssignableFrom(cl)) { // check needed for protective cases like Predicated/Unmod map entrySet
cl = Map.Entry.class; 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); 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); assertCorrectListIterator(iterator, elements, false, false);
} }
// NOTE: The test is can only test list iterators with a starting index == 0 // 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" // Index is now "before 0"
assertEquals(-1, iterator.previousIndex()); assertEquals(-1, iterator.previousIndex());
assertEquals(0, iterator.nextIndex()); assertEquals(0, iterator.nextIndex());
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<groupId>com.twelvemonkeys.contrib</groupId> <groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId> <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 { public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(imageInput); 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++) { for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput)); 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> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-batik</artifactId> <artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name> <name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -14,6 +14,21 @@
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a> See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
for more information.]]> for more information.]]>
</description> </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> <dependencies>
<dependency> <dependency>
@@ -88,6 +103,6 @@
</dependencies> </dependencies>
<properties> <properties>
<batik.version>1.9</batik.version> <batik.version>1.12</batik.version>
</properties> </properties>
</project> </project>
@@ -33,9 +33,11 @@ package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import org.apache.batik.anim.dom.SVGDOMImplementation; import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument; import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.bridge.*; import org.apache.batik.bridge.*;
import org.apache.batik.css.parser.CSSLexicalUnit;
import org.apache.batik.dom.util.DOMUtilities; import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.ext.awt.image.GraphicsUtil; import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.gvt.CanvasGraphicsNode; 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.transcoder.image.ImageTranscoder;
import org.apache.batik.util.ParsedURL; import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.SVGConstants; import org.apache.batik.util.SVGConstants;
import org.apache.batik.xml.LexicalUnits;
import org.w3c.dom.DOMImplementation; import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement; 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> * @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
*/ */
public class SVGImageReader extends ImageReaderBase { 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 Rasterizer rasterizer;
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
/** /**
* Creates an {@code SVGImageReader}. * Creates an {@code SVGImageReader}.
@@ -113,6 +121,9 @@ public class SVGImageReader extends ImageReaderBase {
if (pParam instanceof SVGReadParam) { if (pParam instanceof SVGReadParam) {
SVGReadParam svgParam = (SVGReadParam) pParam; SVGReadParam svgParam = (SVGReadParam) pParam;
// set the external-resource-resolution preference
allowExternalResources = svgParam.isAllowExternalResources();
// Get the base URI // Get the base URI
// This must be done before converting the params to hints // This must be done before converting the params to hints
String baseURI = svgParam.getBaseURI(); 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 // get the 'width' and 'height' attributes of the SVG document
Dimension2D docSize = ctx.getDocumentSize(); UnitProcessor.Context uctx
if (docSize != null) { = UnitProcessor.createContext(ctx, rootElement);
defaultWidth = (float) docSize.getWidth(); String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
defaultHeight = (float) docSize.getHeight(); 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 { if(!StringUtil.isEmpty(heightStr)){
defaultWidth = 200; defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
defaultHeight = 200;
} }
SVGSVGElement rootElement = svgDoc.getRootElement();
String viewBoxStr = rootElement.getAttributeNS boolean hasWidth = defaultWidth > 0.0;
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE); boolean hasHeight = defaultHeight > 0.0;
if (viewBoxStr.length() != 0) {
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null); if (!hasWidth || !hasHeight) {
defaultWidth = rect[2]; String viewBoxStr = rootElement.getAttributeNS
defaultHeight = rect[3]; (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 // Hack to work around exception above
@@ -608,6 +649,14 @@ public class SVGImageReader extends ImageReaderBase {
public void displayMessage(String message) { public void displayMessage(String message) {
processWarningOccurred(message.replaceAll("[\\r\\n]+", " ")); 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 { public class SVGReadParam extends ImageReadParam {
private Paint background; private Paint background;
private String baseURI; private String baseURI;
private boolean allowExternalResources = SVGImageReader.DEFAULT_ALLOW_EXTERNAL_RESOURCES;
public SVGReadParam() {
super();
}
public Paint getBackgroundColor() { public Paint getBackgroundColor() {
return background; return background;
@@ -58,6 +63,14 @@ public class SVGReadParam extends ImageReadParam {
baseURI = pBaseURI; baseURI = pBaseURI;
} }
public void setAllowExternalResources(boolean allow) {
allowExternalResources = allow;
}
public boolean isAllowExternalResources() {
return allowExternalResources;
}
@Override @Override
public boolean canSetSourceRenderSize() { public boolean canSetSourceRenderSize() {
return true; return true;
@@ -49,7 +49,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.Buffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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$ * @version $Id: SVGImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/ */
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> { public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
private SVGImageReaderSpi provider = new SVGImageReaderSpi(); private SVGImageReaderSpi provider = new SVGImageReaderSpi();
protected List<TestData> getTestData() { 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/batikLogo.svg"), new Dimension(450, 500)),
new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)), 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/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); reader.addIIOReadWarningListener(listener);
SVGReadParam param = reader.getDefaultReadParam(); SVGReadParam param = reader.getDefaultReadParam();
param.setAllowExternalResources(true);
param.setBaseURI(resource.toURI().toASCIIString()); param.setBaseURI(resource.toURI().toASCIIString());
BufferedImage image = reader.read(0, param); 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, // Asking for metadata, width, height etc, before attempting to read using a param,
// will cause the document to be parsed without a base URI. // will cause the document to be parsed without a base URI.
// This will work, but may not use the CSS... // 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"); URL resource = getClassLoaderResource("/svg/barChart.svg");
SVGImageReader reader = createReader(); SVGImageReader reader = createReader();
@@ -282,18 +289,17 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
public void testEmbeddedNoBaseURI() throws IOException { public void testEmbeddedNoBaseURI() throws IOException {
// With no base URI, we will throw an exception, about the missing embedded resource // With no base URI, we will throw an exception, about the missing embedded resource
URL resource = getClassLoaderResource("/svg/barChart.svg"); URL resource = getClassLoaderResource("/svg/barChart.svg");
SVGImageReader reader = createReader(); SVGImageReader reader = createReader();
TestData data = new TestData(resource, (Dimension) null); TestData data = new TestData(resource, (Dimension) null);
try (ImageInputStream stream = data.getInputStream()) { try (ImageInputStream stream = data.getInputStream()) {
reader.setInput(stream); reader.setInput(stream);
BufferedImage image = reader.read(0); SVGReadParam params = reader.getDefaultReadParam();
params.setAllowExternalResources(true);
reader.read(0, params);
assertNotNull(image); assertTrue("reader.read should've thrown an exception, but didn't", false);
assertEquals(450, image.getWidth());
assertEquals(500, image.getHeight());
} }
catch (IIOException allowed) { catch (IIOException allowed) {
assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find 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(); 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> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-bmp</artifactId> <artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name> <name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -33,9 +33,13 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier; 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 java.util.Locale;
import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
/** /**
* BMPImageWriterSpi * BMPImageWriterSpi
*/ */
@@ -45,17 +49,28 @@ public final class BMPImageWriterSpi extends ImageWriterSpiBase {
} }
@Override @Override
public boolean canEncodeImage(final ImageTypeSpecifier type) { public void onRegistration(ServiceRegistry registry, Class<?> category) {
return true; // 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 @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); return new BMPImageWriter(this);
} }
@Override @Override
public String getDescription(final Locale locale) { 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. * BMPMetadata.
*/ */
final class BMPMetadata extends AbstractMetadata { 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"; public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
private final DIBHeader header; private final DIBHeader header;
@@ -84,7 +84,7 @@ abstract class DIBImageReader extends ImageReaderBase {
protected void resetMembers() { protected void resetMembers() {
directory = null; directory = null;
headers.clear(); headers.clear();
descriptors.clear(); descriptors.clear();
@@ -551,7 +551,7 @@ abstract class DIBImageReader extends ImageReaderBase {
if (abortRequested()) { if (abortRequested()) {
processReadAborted(); processReadAborted();
break; break;
} }
processImageProgress(100 * y / (float) pBitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
@@ -591,7 +591,7 @@ abstract class DIBImageReader extends ImageReaderBase {
return directory.getEntry(pImageIndex); return directory.getEntry(pImageIndex);
} }
/// Test code below, ignore.. :-) /// Test code below, ignore.. :-)
public static void main(final String[] pArgs) throws IOException { public static void main(final String[] pArgs) throws IOException {
if (pArgs.length == 0) { 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.getHeight() + ": "
+ ((image.getColorModel() instanceof IndexColorModel) ? + ((image.getColorModel() instanceof IndexColorModel) ?
"" + ((IndexColorModel) image.getColorModel()).getMapSize() : String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
"TrueColor")); "TrueColor"));
pParent.add(button); pParent.add(button);
@@ -43,6 +43,7 @@ import java.nio.ByteOrder;
* DIBImageWriter * DIBImageWriter
*/ */
abstract class DIBImageWriter extends ImageWriterBase { abstract class DIBImageWriter extends ImageWriterBase {
DIBImageWriter(ImageWriterSpi provider) { DIBImageWriter(ImageWriterSpi provider) {
super(provider); super(provider);
} }
@@ -50,7 +51,9 @@ abstract class DIBImageWriter extends ImageWriterBase {
@Override @Override
public void setOutput(Object output) { public void setOutput(Object output) {
super.setOutput(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 { 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 { void writeUncompressed(boolean isTopDown, BufferedImage img, int height, int width) throws IOException {
// TODO: Fix
if (img.getType() != BufferedImage.TYPE_4BYTE_ABGR) { if (img.getType() != BufferedImage.TYPE_4BYTE_ABGR) {
throw new IIOException("Blows!"); throw new IIOException("Only TYPE_4BYTE_ABGR supported");
} }
// Support // Support
@@ -93,12 +95,13 @@ abstract class DIBImageWriter extends ImageWriterBase {
// - TODO: Packed/DirectColorModel (16 and 32 bit, BI_BITFIELDS, BI_PNG? BI_JPEG?) // - TODO: Packed/DirectColorModel (16 and 32 bit, BI_BITFIELDS, BI_PNG? BI_JPEG?)
Raster raster = img.getRaster(); 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(); byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
final int[] bandList = {2, 1, 0, 3};
for (int i = 0; i < height; i++) { for (int i = 0; i < height; i++) {
int line = isTopDown ? i : height - 1 - 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); imageOutput.write(row);
@@ -143,7 +143,7 @@ public final class ICOImageWriter extends DIBImageWriter {
} }
if (image.hasRaster()) { if (image.hasRaster()) {
throw new UnsupportedOperationException("image has a Raster"); throw new UnsupportedOperationException("Raster not supported");
} }
if (sequenceIndex >= INITIAL_ENTRY_COUNT) { if (sequenceIndex >= INITIAL_ENTRY_COUNT) {
@@ -155,7 +155,7 @@ public final class ICOImageWriter extends DIBImageWriter {
ColorModel colorModel = image.getRenderedImage().getColorModel(); ColorModel colorModel = image.getRenderedImage().getColorModel();
// TODO: The output size may depend on the param (subsampling, source region, etc) // 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)); 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 com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageTypeSpecifier;
import java.io.IOException; import java.awt.image.BufferedImage;
import java.util.Locale; import java.util.Locale;
/** /**
@@ -46,11 +46,13 @@ public final class ICOImageWriterSpi extends ImageWriterSpiBase {
@Override @Override
public boolean canEncodeImage(final ImageTypeSpecifier type) { 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 @Override
public ICOImageWriter createWriterInstance(final Object extension) throws IOException { public ICOImageWriter createWriterInstance(final Object extension) {
return new ICOImageWriter(this); 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> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-clippath</artifactId> <artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name> <name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
@@ -107,8 +107,8 @@ final class AdobePathSegment {
case OPEN_SUBPATH_BEZIER_LINKED: case OPEN_SUBPATH_BEZIER_LINKED:
case OPEN_SUBPATH_BEZIER_UNLINKED: case OPEN_SUBPATH_BEZIER_UNLINKED:
isTrue( isTrue(
cppx >= 0 && cppx <= 1 && cppy >= 0 && cppy <= 1, cppx >= -16 && cppx <= 16 && cppy >= -16 && cppy <= 16,
String.format("Expected point in range [0...1]: (%f, %f)", cppx ,cppy) String.format("Expected point in range [-16...16]: (%f, %f)", cppx ,cppy)
); );
break; break;
case PATH_FILL_RULE_RECORD: case PATH_FILL_RULE_RECORD:
@@ -113,8 +113,13 @@ public class AdobePathSegmentTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testCreateOpenLinkedRecordNegative() { public void testCreateOpenLinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1); 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 @Test
@@ -138,8 +143,13 @@ public class AdobePathSegmentTest {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testCreateOpenUnlinkedRecordNegative() { public void testCreateOpenUnlinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1); 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 /// Closed subpath
@@ -164,8 +174,13 @@ public class AdobePathSegmentTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testCreateClosedLinkedRecordNegative() { public void testCreateClosedLinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, -.5, -.5, 0, 0, 1, 1); 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 @Test
@@ -189,8 +204,13 @@ public class AdobePathSegmentTest {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testCreateClosedUnlinkedRecordNegative() { public void testCreateClosedUnlinkedRecordOutOfRangeNegative() {
new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, -.5, -.5, 0, 0, 1, 1); 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 @Test
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-core</artifactId> <artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name> <name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -129,7 +129,7 @@ public abstract class ImageWriterAbstractTest {
writer.write(drawSomething((BufferedImage) testData)); writer.write(drawSomething((BufferedImage) testData));
} }
catch (IOException e) { catch (IOException e) {
fail(e.getMessage()); throw new AssertionError(e.getMessage(), e);
} }
assertTrue("No image data written", buffer.size() > 0); assertTrue("No image data written", buffer.size() > 0);
@@ -149,10 +149,10 @@ public abstract class ImageWriterAbstractTest {
catch(IllegalArgumentException ignore) { catch(IllegalArgumentException ignore) {
} }
catch (IOException e) { 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) @Test(expected = IllegalStateException.class)
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-hdr</artifactId> <artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name> <name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-icns</artifactId> <artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name> <name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-iff</artifactId> <artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name> <name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name> <name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -35,27 +35,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import java.io.DataInput; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
final class HuffmanTable extends Segment { final class HuffmanTable extends Segment {
private final int l[][][] = new int[4][2][16]; private final short[][][] l = new short[4][2][16];
private final int th[] = new int[4]; // 1: this table is present private final short[][][][] v = new short[4][2][16][200]; // tables
final int v[][][][] = new int[4][2][16][200]; // tables private final boolean[][] tc = new boolean[4][2]; // 1: this table is present
final int[][] tc = new int[4][2]; // 1: this table is present
static final int MSB = 0x80000000; private static final int MSB = 0x80000000;
private HuffmanTable() { private HuffmanTable() {
super(JPEG.DHT); super(JPEG.DHT);
} }
void buildHuffTables(final int[][][] HuffTab) throws IOException { void buildHuffTables(final int[][][] huffTab) throws IOException {
for (int t = 0; t < 4; t++) { for (int t = 0; t < 4; t++) {
for (int c = 0; c < 2; c++) { for (int c = 0; c < 2; c++) {
if (tc[t][c] != 0) { if (tc[t][c]) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]); buildHuffTable(huffTab[t][c], l[t][c], v[t][c]);
} }
} }
} }
@@ -68,7 +68,7 @@ final class HuffmanTable extends Segment {
// V[i][j] Huffman Value (length=i) // V[i][j] Huffman Value (length=i)
// Effect: // Effect:
// build up HuffTab[t][c] using L and V. // build up HuffTab[t][c] using L and V.
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException { private void buildHuffTable(final int[] tab, final short[] L, final short[][] V) throws IOException {
int temp = 256; int temp = 256;
int k = 0; int k = 0;
@@ -112,7 +112,7 @@ final class HuffmanTable extends Segment {
for (int t = 0; t < tc.length; t++) { for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) { for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) { if (tc[t][c]) {
if (builder.length() > 4) { if (builder.length() > 4) {
builder.append(", "); builder.append(", ");
} }
@@ -149,11 +149,10 @@ final class HuffmanTable extends Segment {
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c); throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
} }
table.th[t] = 1; table.tc[t][c] = true;
table.tc[t][c] = 1;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
table.l[t][c][i] = data.readUnsignedByte(); table.l[t][c][i] = (short) data.readUnsignedByte();
count++; count++;
} }
@@ -162,7 +161,7 @@ final class HuffmanTable extends Segment {
if (count > length) { if (count > length) {
throw new IIOException("JPEG Huffman Table format error"); throw new IIOException("JPEG Huffman Table format error");
} }
table.v[t][c][i][j] = data.readUnsignedByte(); table.v[t][c][i][j] = (short) data.readUnsignedByte();
count++; count++;
} }
} }
@@ -174,4 +173,41 @@ final class HuffmanTable extends Segment {
return table; return table;
} }
public boolean isPresent(int tableId, int tableClass) {
return tc[tableId][tableClass];
}
private short[] lengths(int tableId, int tableClass) {
// TODO: Consider stripping the 0s?
return l[tableId][tableClass];
}
private short[] tables(int tableId, int tableClass) {
// Find sum of lengths
short[] lengths = lengths(tableId, tableClass);
int sumOfLengths = 0;
for (int length : lengths) {
sumOfLengths += length;
}
// Flatten the tables
short[] tables = new short[sumOfLengths];
int pos = 0;
for (int i = 0; i < 16; i++) {
short[] table = v[tableId][tableClass][i];
short length = lengths[i];
System.arraycopy(table, 0, tables, pos, length);
pos += length;
}
return tables;
}
JPEGHuffmanTable toNativeTable(int tableId, int tableClass) {
return new JPEGHuffmanTable(lengths(tableId, tableClass), tables(tableId, tableClass));
}
} }
@@ -113,7 +113,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
} }
} }
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail); cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
return thumbnail; return thumbnail;
} }
@@ -30,10 +30,16 @@
package com.twelvemonkeys.imageio.plugins.jpeg; package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import javax.imageio.IIOException;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.util.List; import java.util.List;
/** /**
@@ -45,31 +51,89 @@ import java.util.List;
*/ */
class JPEGImage10Metadata extends AbstractMetadata { class JPEGImage10Metadata extends AbstractMetadata {
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place.... /**
* 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; private final List<Segment> segments;
JPEGImage10Metadata(List<Segment> segments) { private final Frame frame;
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null); 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.segments = segments;
this.frame = frame;
this.jfif = jfif;
this.adobeDCT = adobeDCT;
this.jfxx = jfxx;
this.embeddedICCProfile = embeddedICCProfile;
this.exif = exif;
} }
@Override @Override
protected Node getNativeTree() { 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"); IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
root.appendChild(jpegVariety); boolean isJFIF = jfif != null;
// TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless if (isJFIF) {
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
app0JFIF.setAttribute("majorVersion", Integer.toString(jfif.majorVersion));
app0JFIF.setAttribute("minorVersion", Integer.toString(jfif.minorVersion));
app0JFIF.setAttribute("resUnits", Integer.toString(jfif.units));
app0JFIF.setAttribute("Xdensity", Integer.toString(jfif.xDensity));
app0JFIF.setAttribute("Ydensity", Integer.toString(jfif.yDensity));
app0JFIF.setAttribute("thumbWidth", Integer.toString(jfif.xThumbnail));
app0JFIF.setAttribute("thumbHeight", Integer.toString(jfif.yThumbnail));
jpegVariety.appendChild(app0JFIF);
// Due to format oddity, add JFXX and app2ICC as subnodes here...
// ...and ignore them below, if added...
apendJFXX(app0JFIF);
appendICCProfile(app0JFIF);
}
root.appendChild(jpegVariety);
appendMarkerSequence(root, segments, isJFIF);
return root;
}
private void appendMarkerSequence(IIOMetadataNode root, List<Segment> segments, boolean isJFIF) {
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
root.appendChild(markerSequence); root.appendChild(markerSequence);
for (Segment segment : segments) for (Segment segment : segments)
switch (segment.marker) { switch (segment.marker) {
// SOF3 is the only one supported by now case JPEG.SOF0:
case JPEG.SOF1:
case JPEG.SOF2:
case JPEG.SOF3: case JPEG.SOF3:
case JPEG.SOF5:
case JPEG.SOF6:
case JPEG.SOF7:
case JPEG.SOF9:
case JPEG.SOF10:
case JPEG.SOF11:
case JPEG.SOF13:
case JPEG.SOF14:
case JPEG.SOF15:
Frame sofSegment = (Frame) segment; Frame sofSegment = (Frame) segment;
IIOMetadataNode sof = new IIOMetadataNode("sof"); IIOMetadataNode sof = new IIOMetadataNode("sof");
@@ -96,13 +160,13 @@ class JPEGImage10Metadata extends AbstractMetadata {
HuffmanTable huffmanTable = (HuffmanTable) segment; HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dht = new IIOMetadataNode("dht"); IIOMetadataNode dht = new IIOMetadataNode("dht");
// Uses fixed tables...
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) { for (int c = 0; c < 2; c++) {
if (huffmanTable.tc[i][j] != 0) { if (huffmanTable.isPresent(i, c)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable"); IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(j)); dhtable.setAttribute("class", String.valueOf(c));
dhtable.setAttribute("htableId", String.valueOf(i)); dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, c));
dht.appendChild(dhtable); dht.appendChild(dhtable);
} }
} }
@@ -112,8 +176,28 @@ class JPEGImage10Metadata extends AbstractMetadata {
break; break;
case JPEG.DQT: case JPEG.DQT:
markerSequence.appendChild(new IIOMetadataNode("dqt")); QuantizationTable quantizationTable = (QuantizationTable) segment;
// TODO: IIOMetadataNode dqt = new IIOMetadataNode("dqt");
for (int i = 0; i < 4; i++) {
if (quantizationTable.isPresent(i)) {
IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
dqtable.setAttribute("elementPrecision", quantizationTable.precision(i) != 16 ? "0" : "1"); // 0 = 8 bits, 1 = 16 bits
dqtable.setAttribute("qtableId", Integer.toString(i));
dqtable.setUserObject(quantizationTable.toNativeTable(i));
dqt.appendChild(dqtable);
}
}
markerSequence.appendChild(dqt);
break;
case JPEG.DRI:
RestartInterval restartInterval = (RestartInterval) segment;
IIOMetadataNode dri = new IIOMetadataNode("dri");
dri.setAttribute("interval", Integer.toString(restartInterval.interval));
markerSequence.appendChild(dri);
break; break;
case JPEG.SOS: case JPEG.SOS:
@@ -144,6 +228,25 @@ class JPEGImage10Metadata extends AbstractMetadata {
break; break;
case JPEG.APP0:
if (segment instanceof JFIF) {
// Either already added, or we'll ignore it anyway...
break;
}
else if (isJFIF && segment instanceof JFXX) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP2:
if (isJFIF && segment instanceof ICCProfile) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP14: case JPEG.APP14:
if (segment instanceof AdobeDCT) { if (segment instanceof AdobeDCT) {
AdobeDCT adobe = (AdobeDCT) segment; AdobeDCT adobe = (AdobeDCT) segment;
@@ -165,32 +268,149 @@ class JPEGImage10Metadata extends AbstractMetadata {
break; break;
} }
}
return root; private void appendICCProfile(IIOMetadataNode app0JFIF) {
if (embeddedICCProfile != null) {
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
app2ICC.setUserObject(embeddedICCProfile);
app0JFIF.appendChild(app2ICC);
}
}
private void apendJFXX(IIOMetadataNode app0JFIF) {
if (jfxx != null) {
IIOMetadataNode jfxxNode = new IIOMetadataNode("JFXX");
app0JFIF.appendChild(jfxxNode);
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
app0JFXX.setAttribute("extensionCode", Integer.toString(jfxx.extensionCode));
jfxxNode.appendChild(app0JFXX);
switch (jfxx.extensionCode) {
case JFXX.JPEG:
IIOMetadataNode thumbJPEG = new IIOMetadataNode("JFIFthumbJPEG");
thumbJPEG.appendChild(new IIOMetadataNode("markerSequence"));
// TODO: Insert segments in marker sequence...
// List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(new ByteArrayImageInputStream(jfxx.thumbnail), JPEGSegmentUtil.ALL_SEGMENTS);
// Convert to Segment as in JPEGImageReader...
// appendMarkerSequence(thumbJPEG, segments, false);
app0JFXX.appendChild(thumbJPEG);
break;
case JFXX.INDEXED:
IIOMetadataNode thumbPalette = new IIOMetadataNode("JFIFthumbPalette");
thumbPalette.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbPalette.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbPalette);
break;
case JFXX.RGB:
IIOMetadataNode thumbRGB = new IIOMetadataNode("JFIFthumbRGB");
thumbRGB.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbRGB.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbRGB);
break;
}
}
} }
@Override @Override
protected IIOMetadataNode getStandardChromaNode() { protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
for (Segment segment : segments) { IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
if (segment instanceof Frame) { colorSpaceType.setAttribute("name", getColorSpaceType());
Frame sofSegment = (Frame) segment; chroma.appendChild(colorSpaceType);
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "GRAY" : "RGB"); // TODO YCC, YCCK, CMYK etc
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame())); numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame()));
chroma.appendChild(numChannels); chroma.appendChild(numChannels);
break;
}
}
return chroma; 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 @Override
protected IIOMetadataNode getStandardCompressionNode() { protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression"); IIOMetadataNode compression = new IIOMetadataNode("Compression");
@@ -200,7 +420,7 @@ class JPEGImage10Metadata extends AbstractMetadata {
compression.appendChild(compressionTypeName); compression.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE");
compression.appendChild(lossless); compression.appendChild(lossless);
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans"); IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
@@ -215,12 +435,67 @@ class JPEGImage10Metadata extends AbstractMetadata {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "normal"); // TODO imageOrientation.setAttribute("value", getExifOrientation(exif));
dimension.appendChild(imageOrientation); dimension.appendChild(imageOrientation);
if (jfif != null) {
// Aspect ratio
float xDensity = Math.max(1, jfif.xDensity);
float yDensity = Math.max(1, jfif.yDensity);
float aspectRatio = jfif.units == 0 ? xDensity / yDensity : yDensity / xDensity;
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", Float.toString(aspectRatio));
dimension.insertBefore(pixelAspectRatio, imageOrientation); // Keep order
if (jfif.units != 0) {
// Pixel size
float scale = jfif.units == 1 ? 25.4F : 10.0F; // DPI or DPcm
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
horizontalPixelSize.setAttribute("value", Float.toString(scale / xDensity));
dimension.appendChild(horizontalPixelSize);
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
verticalPixelSize.setAttribute("value", Float.toString(scale / yDensity));
dimension.appendChild(verticalPixelSize);
}
}
return dimension; return dimension;
} }
private String getExifOrientation(Directory exif) {
if (exif != null) {
Entry orientationEntry = exif.getEntryById(TIFF.TAG_ORIENTATION);
if (orientationEntry != null) {
switch (((Number) orientationEntry.getValue()).intValue()) {
case 2:
return "FlipH";
case 3:
return "Rotate180";
case 4:
return "FlipV";
case 5:
return "FlipVRotate90";
case 6:
return "Rotate270";
case 7:
return "FlipHRotate90";
case 8:
return "Rotate90";
case 0:
case 1:
default:
// Fall-through
}
}
}
return "Normal";
}
@Override @Override
protected IIOMetadataNode getStandardTextNode() { protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text"); IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -235,6 +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; return text.hasChildNodes() ? text : null;
} }
} }
@@ -52,11 +52,6 @@ import java.util.List;
*/ */
final class JPEGImage10MetadataCleaner { 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; private final JPEGImageReader reader;
JPEGImage10MetadataCleaner(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.) 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 jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0); IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
@@ -145,7 +140,7 @@ final class JPEGImage10MetadataCleaner {
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG"); jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc... // Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
IIOMetadata thumbMeta = thumbnailReader.readMetadata(); 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()); jfifThumb.appendChild(thumbTree.getLastChild());
app0JFXX.appendChild(jfifThumb); app0JFXX.appendChild(jfifThumb);
break; break;
@@ -307,11 +302,11 @@ final class JPEGImage10MetadataCleaner {
} }
try { try {
imageMetadata.setFromTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree); imageMetadata.setFromTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree);
} }
catch (IIOInvalidTreeException e) { catch (IIOInvalidTreeException e) {
if (JPEGImageReader.DEBUG) { 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< --"); System.out.println("-- 8< --");
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false); 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 */ /** Internal constant for referring all APP segments */
static final int ALL_APP_MARKERS = -1; static final int ALL_APP_MARKERS = -1;
/** Segment identifiers for the JPEG segments we care about reading. */
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
/** Our JPEG reading delegate */ /** Our JPEG reading delegate */
private final ImageReader delegate; private final ImageReader delegate;
@@ -534,7 +531,7 @@ public final class JPEGImageReader extends ImageReaderBase {
return image; return image;
} }
private JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException { static JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
// Adapted from libjpeg jdapimin.c: // Adapted from libjpeg jdapimin.c:
// Guess the input colorspace // Guess the input colorspace
// (Wish JPEG committee had provided a real way to specify this...) // (Wish JPEG committee had provided a real way to specify this...)
@@ -717,10 +714,11 @@ public final class JPEGImageReader extends ImageReaderBase {
private void initHeader(final int imageIndex) throws IOException { private void initHeader(final int imageIndex) throws IOException {
if (imageIndex < 0) { if (imageIndex < 0) {
throw new IllegalArgumentException("imageIndex < 0: " + imageIndex); throw new IndexOutOfBoundsException("imageIndex < 0: " + imageIndex);
} }
if (imageIndex == currentStreamIndex) { if (imageIndex == currentStreamIndex) {
initHeader();
return; return;
} }
@@ -837,7 +835,7 @@ public final class JPEGImageReader extends ImageReaderBase {
try { try {
imageInput.seek(streamOffsets.get(currentStreamIndex)); imageInput.seek(streamOffsets.get(currentStreamIndex));
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS); return JPEGSegmentUtil.readSegments(imageInput, JPEGSegmentUtil.ALL_SEGMENTS);
} }
catch (IIOException | IllegalArgumentException ignore) { catch (IIOException | IllegalArgumentException ignore) {
if (DEBUG) { if (DEBUG) {
@@ -1218,38 +1216,9 @@ public final class JPEGImageReader extends ImageReaderBase {
@Override @Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException { public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
checkBounds(imageIndex);
initHeader(imageIndex); initHeader(imageIndex);
IIOMetadata imageMetadata; return new JPEGImage10Metadata(segments, getSOF(), getJFIF(), getJFXX(), getEmbeddedICCProfile(true), getAdobeDCT(), getExif());
if (isLossless()) {
return new JPEGImage10Metadata(segments);
}
else {
try {
imageMetadata = delegate.getImageMetadata(0);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
}
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) {
metadataCleaner = new JPEGImage10MetadataCleaner(this);
}
return metadataCleaner.cleanMetadata(imageMetadata);
}
}
return imageMetadata;
} }
@Override @Override
@@ -51,7 +51,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Locale; 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 * JPEGImageWriter
@@ -159,6 +159,17 @@ public final class JPEGImageWriter extends ImageWriterBase {
writeCMYK(streamMetadata, image, param); writeCMYK(streamMetadata, image, param);
} }
else { 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); delegate.write(streamMetadata, image, param);
} }
} }
@@ -39,6 +39,7 @@ import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
final class JPEGLosslessDecoder { final class JPEGLosslessDecoder {
@@ -51,12 +52,12 @@ final class JPEGLosslessDecoder {
private final QuantizationTable quantTable; private final QuantizationTable quantTable;
private Scan scan; private Scan scan;
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256]; private final int[][][] HuffTab = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
private final int IDCT_Source[] = new int[64]; private final int[] IDCT_Source = new int[64];
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan private final int[] nBlock = new int[10]; // number of blocks in the i-th Comp in a scan
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan private final int[][] acTab = new int[10][]; // ac HuffTab for the i-th Comp in a scan
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan private final int[][] dcTab = new int[10][]; // dc HuffTab for the i-th Comp in a scan
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan private final int[][] qTab = new int[10][]; // quantization table for the i-th Comp in a scan
private boolean restarting; private boolean restarting;
private int marker; private int marker;
@@ -70,7 +71,7 @@ final class JPEGLosslessDecoder {
private int mask; private int mask;
private int[][] outputData; private int[][] outputData;
private static final int IDCT_P[] = { private static final int[] IDCT_P = {
0, 5, 40, 16, 45, 2, 7, 42, 0, 5, 40, 16, 45, 2, 7, 42,
21, 56, 8, 61, 18, 47, 1, 4, 21, 56, 8, 61, 18, 47, 1, 4,
41, 23, 58, 13, 32, 24, 37, 10, 41, 23, 58, 13, 32, 24, 37, 10,
@@ -80,16 +81,6 @@ final class JPEGLosslessDecoder {
50, 55, 25, 36, 11, 62, 14, 35, 50, 55, 25, 36, 11, 62, 14, 35,
28, 49, 52, 27, 38, 30, 51, 54 28, 49, 52, 27, 38, 30, 51, 54
}; };
private static final int TABLE[] = {
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
private static final int RESTART_MARKER_BEGIN = 0xFFD0; private static final int RESTART_MARKER_BEGIN = 0xFFD0;
private static final int RESTART_MARKER_END = 0xFFD7; private static final int RESTART_MARKER_END = 0xFFD7;
@@ -158,7 +149,7 @@ final class JPEGLosslessDecoder {
huffTable.buildHuffTables(HuffTab); huffTable.buildHuffTables(HuffTab);
} }
quantTable.enhanceTables(TABLE); quantTable.enhanceTables();
current = input.readUnsignedShort(); current = input.readUnsignedShort();
@@ -185,11 +176,10 @@ final class JPEGLosslessDecoder {
selection = scan.spectralSelStart; selection = scan.spectralSelStart;
final Scan.Component[] scanComps = scan.components; final Scan.Component[] scanComps = scan.components;
final int[][] quantTables = quantTable.quantTables;
for (int i = 0; i < numComp; i++) { for (int i = 0; i < numComp; i++) {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel); Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTables[component.qtSel]; qTab[i] = quantTable.qTable(component.qtSel);
nBlock[i] = component.vSub * component.hSub; nBlock[i] = component.vSub * component.hSub;
int dcTabSel = scanComps[i].dcTabSel; int dcTabSel = scanComps[i].dcTabSel;
@@ -220,18 +210,18 @@ final class JPEGLosslessDecoder {
outputData[componentIndex] = new int[xDim * yDim]; outputData[componentIndex] = new int[xDim * yDim];
} }
final int firstValue[] = new int[numComp]; final int[] firstValue = new int[numComp];
for (int i = 0; i < numComp; i++) { for (int i = 0; i < numComp; i++) {
firstValue[i] = (1 << (precision - 1)); firstValue[i] = (1 << (precision - 1));
} }
final int pred[] = new int[numComp]; final int[] pred = new int[numComp];
scanNum++; scanNum++;
while (true) { // Decode one scan while (true) { // Decode one scan
int temp[] = new int[1]; // to store remainder bits int[] temp = new int[1]; // to store remainder bits
int index[] = new int[1]; int[] index = new int[1];
System.arraycopy(firstValue, 0, pred, 0, numComp); System.arraycopy(firstValue, 0, pred, 0, numComp);
@@ -288,7 +278,7 @@ final class JPEGLosslessDecoder {
private boolean useACForDC(final int dcTabSel) { private boolean useACForDC(final int dcTabSel) {
if (isLossless()) { if (isLossless()) {
for (HuffmanTable huffTable : huffTables) { for (HuffmanTable huffTable : huffTables) {
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) { if (!huffTable.isPresent(dcTabSel, 0) && huffTable.isPresent(dcTabSel, 1)) {
return true; return true;
} }
} }
@@ -324,7 +314,7 @@ final class JPEGLosslessDecoder {
return Scan.read(input, length); return Scan.read(input, length);
} }
private int decode(final int prev[], final int temp[], final int index[]) throws IOException { private int decode(final int[] prev, final int[] temp, final int[] index) throws IOException {
if (numComp == 1) { if (numComp == 1) {
return decodeSingle(prev, temp, index); return decodeSingle(prev, temp, index);
} }
@@ -336,7 +326,7 @@ final class JPEGLosslessDecoder {
} }
} }
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException { private int decodeSingle(final int[] prev, final int[] temp, final int[] index) throws IOException {
// At the beginning of the first line and // At the beginning of the first line and
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision. // at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
if (restarting) { if (restarting) {
@@ -390,7 +380,7 @@ final class JPEGLosslessDecoder {
return 0; return 0;
} }
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException { private int decodeRGB(final int[] prev, final int[] temp, final int[] index) throws IOException {
final int[] outputRedData = outputData[0]; final int[] outputRedData = outputData[0];
final int[] outputGreenData = outputData[1]; final int[] outputGreenData = outputData[1];
final int[] outputBlueData = outputData[2]; final int[] outputBlueData = outputData[2];
@@ -435,7 +425,7 @@ final class JPEGLosslessDecoder {
return decode0(prev, temp, index); return decode0(prev, temp, index);
} }
private int decodeAny(final int prev[], final int temp[], final int index[]) throws IOException { private int decodeAny(final int[] prev, final int[] temp, final int[] index) throws IOException {
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) { for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
final int[] outputData = this.outputData[componentIndex]; final int[] outputData = this.outputData[componentIndex];
final int previous; final int previous;
@@ -469,17 +459,17 @@ final class JPEGLosslessDecoder {
} }
private int decode0(int[] prev, int[] temp, int[] index) throws IOException { private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
int value, actab[], dctab[]; int value;
int qtab[]; int[] actab;
int[] dctab;
int[] qtab;
for (int ctrC = 0; ctrC < numComp; ctrC++) { for (int ctrC = 0; ctrC < numComp; ctrC++) {
qtab = qTab[ctrC]; qtab = qTab[ctrC];
actab = acTab[ctrC]; actab = acTab[ctrC];
dctab = dcTab[ctrC]; dctab = dcTab[ctrC];
for (int i = 0; i < nBlock[ctrC]; i++) { for (int i = 0; i < nBlock[ctrC]; i++) {
for (int k = 0; k < IDCT_Source.length; k++) { Arrays.fill(IDCT_Source, 0);
IDCT_Source[k] = 0;
}
value = getHuffmanValue(dctab, temp, index); value = getHuffmanValue(dctab, temp, index);
@@ -545,7 +535,7 @@ final class JPEGLosslessDecoder {
// and marker_index=9 // and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue() // If marker_index=9 then index is always > 8, or HuffmanValue()
// will not be called // will not be called
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException { private int getHuffmanValue(final int[] table, final int[] temp, final int[] index) throws IOException {
int code, input; int code, input;
final int mask = 0xFFFF; final int mask = 0xFFFF;
@@ -603,7 +593,7 @@ final class JPEGLosslessDecoder {
return code & 0xFF; return code & 0xFF;
} }
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException { private int getn(final int[] pred, final int n, final int[] temp, final int[] index) throws IOException {
int result; int result;
final int one = 1; final int one = 1;
final int n_one = -1; final int n_one = -1;
@@ -688,7 +678,7 @@ final class JPEGLosslessDecoder {
return result; return result;
} }
private int getPreviousX(final int data[]) { private int getPreviousX(final int[] data) {
if (xLoc > 0) { if (xLoc > 0) {
return data[((yLoc * xDim) + xLoc) - 1]; return data[((yLoc * xDim) + xLoc) - 1];
} }
@@ -700,7 +690,7 @@ final class JPEGLosslessDecoder {
} }
} }
private int getPreviousXY(final int data[]) { private int getPreviousXY(final int[] data) {
if ((xLoc > 0) && (yLoc > 0)) { if ((xLoc > 0) && (yLoc > 0)) {
return data[(((yLoc - 1) * xDim) + xLoc) - 1]; return data[(((yLoc - 1) * xDim) + xLoc) - 1];
} }
@@ -709,7 +699,7 @@ final class JPEGLosslessDecoder {
} }
} }
private int getPreviousY(final int data[]) { private int getPreviousY(final int[] data) {
if (yLoc > 0) { if (yLoc > 0) {
return data[((yLoc - 1) * xDim) + xLoc]; return data[((yLoc - 1) * xDim) + xLoc];
} }
@@ -722,7 +712,7 @@ final class JPEGLosslessDecoder {
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1)); return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
} }
private void output(final int pred[]) { private void output(final int[] pred) {
if (numComp == 1) { if (numComp == 1) {
outputSingle(pred); outputSingle(pred);
} }
@@ -734,7 +724,7 @@ final class JPEGLosslessDecoder {
} }
} }
private void outputSingle(final int pred[]) { private void outputSingle(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) { if ((xLoc < xDim) && (yLoc < yDim)) {
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0]; outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
xLoc++; xLoc++;
@@ -746,7 +736,7 @@ final class JPEGLosslessDecoder {
} }
} }
private void outputRGB(final int pred[]) { private void outputRGB(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) { if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc; final int index = (yLoc * xDim) + xLoc;
outputData[0][index] = pred[0]; outputData[0][index] = pred[0];
@@ -761,7 +751,7 @@ final class JPEGLosslessDecoder {
} }
} }
private void outputAny(final int pred[]) { private void outputAny(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) { if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc; final int index = (yLoc * xDim) + xLoc;
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) { for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
@@ -35,35 +35,42 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGQTable;
import java.io.DataInput; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
final class QuantizationTable extends Segment { final class QuantizationTable extends Segment {
private final int precision[] = new int[4]; // Quantization precision 8 or 16 private static final int[] ZIGZAG = {
private final int[] tq = new int[4]; // 1: this table is presented 0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
final int quantTables[][] = new int[4][64]; // Tables private final int[] precision = new int[4]; // Quantization precision 8 or 16
private final boolean[] tq = new boolean[4]; // 1: this table is present
private final int[][] quantTables = new int[4][64]; // Tables
QuantizationTable() { QuantizationTable() {
super(JPEG.DQT); super(JPEG.DQT);
tq[0] = 0;
tq[1] = 0;
tq[2] = 0;
tq[3] = 0;
} }
// TODO: Get rid of table param, make it a member? // TODO: Consider creating a copy for the decoder here, as we need to keep the original values for the metadata
void enhanceTables(final int[] table) throws IOException { void enhanceTables() {
for (int t = 0; t < 4; t++) { for (int t = 0; t < 4; t++) {
if (tq[t] != 0) { if (tq[t]) {
enhanceQuantizationTable(quantTables[t], table); enhanceQuantizationTable(quantTables[t], ZIGZAG);
} }
} }
} }
private void enhanceQuantizationTable(final int qtab[], final int[] table) { private void enhanceQuantizationTable(final int[] qtab, final int[] table) {
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
qtab[table[ i]] *= 90; qtab[table[ i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90; qtab[table[(4 * 8) + i]] *= 90;
@@ -122,7 +129,7 @@ final class QuantizationTable extends Segment {
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]); throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
} }
table.tq[t] = 1; table.tq[t] = true;
if (table.precision[t] == 8) { if (table.precision[t] == 8) {
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
@@ -152,4 +159,28 @@ final class QuantizationTable extends Segment {
return table; return table;
} }
public boolean isPresent(int tabelId) {
return tq[tabelId];
}
int precision(int tableId) {
return precision[tableId];
}
int[] qTable(int tabelId) {
return quantTables[tabelId];
}
JPEGQTable toNativeTable(int tableId) {
// TODO: Should de-zigzag (ie. "natural order") while reading
// TODO: ...and make sure the table isn't "enhanced"...
int[] qTable = new int[quantTables[tableId].length];
for (int i = 0; i < qTable.length; i++) {
qTable[i] = quantTables[tableId][ZIGZAG[i]];
}
return new JPEGQTable(qTable);
}
} }
@@ -85,12 +85,12 @@ public class JPEGImage10MetadataCleanerTest {
reader.setInput(input); reader.setInput(input);
IIOMetadata original = origReader.getImageMetadata(0); 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); JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0)); 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"); NodeList origDHT = origTree.getElementsByTagName("dht");
assertEquals(1, origDHT.getLength()); assertEquals(1, origDHT.getLength());
@@ -31,8 +31,8 @@
package com.twelvemonkeys.imageio.plugins.jpeg; package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.lang.StringUtil;
import org.hamcrest.core.IsInstanceOf; import org.hamcrest.core.IsInstanceOf;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mockito.internal.matchers.GreaterThan; import org.mockito.internal.matchers.GreaterThan;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@@ -182,23 +182,27 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
public void testICCProfileCMYKClassOutputColors() throws IOException { public void testICCProfileCMYKClassOutputColors() throws IOException {
// Make sure ICC profile with class output isn't converted to too bright values // Make sure ICC profile with class output isn't converted to too bright values
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg")));
ImageReadParam param = reader.getDefaultReadParam(); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"))) {
param.setSourceRegion(new Rectangle(800, 800, 64, 8)); reader.setInput(stream);
param.setSourceSubsampling(8, 8, 2, 2);
BufferedImage image = reader.read(0, param); ImageReadParam param = reader.getDefaultReadParam();
assertNotNull(image); param.setSourceRegion(new Rectangle(800, 800, 64, 8));
param.setSourceSubsampling(8, 8, 2, 2);
byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); BufferedImage image = reader.read(0, param);
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}; 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) { 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 { public void testICCDuplicateSequence() throws IOException {
// Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1 // Variation of the above, file contains multiple ICC chunks, with all counts and sequence numbers == 1
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg"))) {
reader.setInput(stream);
assertEquals(345, reader.getWidth(0)); assertEquals(345, reader.getWidth(0));
assertEquals(540, reader.getHeight(0)); assertEquals(540, reader.getHeight(0));
BufferedImage image = reader.read(0); BufferedImage image = reader.read(0);
assertNotNull(image); assertNotNull(image);
assertEquals(345, image.getWidth()); assertEquals(345, image.getWidth());
assertEquals(540, image.getHeight()); assertEquals(540, image.getHeight());
}
reader.dispose(); finally {
reader.dispose();
}
} }
@Test @Test
public void testICCDuplicateSequenceZeroBased() throws IOException { public void testICCDuplicateSequenceZeroBased() throws IOException {
// File contains multiple ICC chunks, with all counts and sequence numbers == 0 // File contains multiple ICC chunks, with all counts and sequence numbers == 0
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-xerox-dc250-heavyweight-1-progressive-jfif.jpg"))) {
reader.setInput(stream);
assertEquals(3874, reader.getWidth(0)); assertEquals(3874, reader.getWidth(0));
assertEquals(5480, reader.getHeight(0)); assertEquals(5480, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory param.setSourceRegion(new Rectangle(0, 0, 3874, 16)); // Save some memory
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(3874, image.getWidth()); assertEquals(3874, image.getWidth());
assertEquals(16, image.getHeight()); assertEquals(16, image.getHeight());
}
reader.dispose(); finally {
reader.dispose();
}
} }
@Test @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 // Profile should have been about 550 000 bytes, split into multiple chunks. Written by GIMP 2.6.11
// See: https://bugzilla.redhat.com/show_bug.cgi?id=695246 // See: https://bugzilla.redhat.com/show_bug.cgi?id=695246
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-invalid-icc-profile-data.jpg"))) {
reader.setInput(stream);
assertEquals(1993, reader.getWidth(0)); assertEquals(1993, reader.getWidth(0));
assertEquals(1038, reader.getHeight(0)); assertEquals(1038, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(1993, image.getWidth()); assertEquals(1993, image.getWidth());
assertEquals(8, image.getHeight()); assertEquals(8, image.getHeight());
}
reader.dispose(); finally {
reader.dispose();
}
} }
@Test @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 // File contains CMYK ICC profile ("Coated FOGRA27 (ISO 12647-2:2004)"), but image data is 3 channel YCC/RGB
// JFIF 1.1 with unknown origin. // JFIF 1.1 with unknown origin.
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cco-illegalargument-rgb-coated-fogra27.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cco-illegalargument-rgb-coated-fogra27.jpg"))) {
reader.setInput(stream);
assertEquals(281, reader.getWidth(0)); assertEquals(281, reader.getWidth(0));
assertEquals(449, reader.getHeight(0)); assertEquals(449, reader.getHeight(0));
BufferedImage image = reader.read(0); BufferedImage image = reader.read(0);
assertNotNull(image); assertNotNull(image);
assertEquals(281, image.getWidth()); assertEquals(281, image.getWidth());
assertEquals(449, image.getHeight()); assertEquals(449, image.getHeight());
// TODO: Need to test colors! // TODO: Need to test colors!
reader.dispose(); }
finally {
reader.dispose();
}
} }
@Test @Test
@@ -293,22 +310,27 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// but image data is plain 3 channel YCC/RGB. // but image data is plain 3 channel YCC/RGB.
// EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"... // EXIF/TIFF metadata says Software: "Microsoft Windows Photo Gallery 6.0.6001.18000"...
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/no-image-types-rgb-us-web-coated-v2-ms-photogallery-exif.jpg"))) {
reader.setInput(stream);
assertEquals(1743, reader.getWidth(0)); assertEquals(1743, reader.getWidth(0));
assertEquals(2551, reader.getHeight(0)); assertEquals(2551, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 1743, 16)); // Save some memory param.setSourceRegion(new Rectangle(0, 0, 1743, 16)); // Save some memory
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(1743, image.getWidth()); assertEquals(1743, image.getWidth());
assertEquals(16, image.getHeight()); 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 @Test
@@ -316,107 +338,131 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// File contains JFIF (!), RGB ICC profile AND Adobe App14 specifying unknown conversion, // File contains JFIF (!), RGB ICC profile AND Adobe App14 specifying unknown conversion,
// but image data is 4 channel CMYK (from SOF0 channel Ids 'C', 'M', 'Y', 'K'). // but image data is 4 channel CMYK (from SOF0 channel Ids 'C', 'M', 'Y', 'K').
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-cmyk-invalid-icc-profile-srgb.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-cmyk-invalid-icc-profile-srgb.jpg"))) {
reader.setInput(stream);
assertEquals(493, reader.getWidth(0)); assertEquals(493, reader.getWidth(0));
assertEquals(500, reader.getHeight(0)); assertEquals(500, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(0, 0, 493, 16)); // Save some memory param.setSourceRegion(new Rectangle(0, 0, 493, 16)); // Save some memory
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(493, image.getWidth()); assertEquals(493, image.getWidth());
assertEquals(16, image.getHeight()); 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 @Test
public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException { public void testWarningEmbeddedColorProfileInvalidIgnored() throws IOException {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/warning-embedded-color-profile-invalid-ignored-cmyk.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/warning-embedded-color-profile-invalid-ignored-cmyk.jpg"))) {
reader.setInput(stream);
assertEquals(183, reader.getWidth(0)); assertEquals(183, reader.getWidth(0));
assertEquals(283, reader.getHeight(0)); assertEquals(283, reader.getHeight(0));
BufferedImage image = reader.read(0); BufferedImage image = reader.read(0);
assertNotNull(image); assertNotNull(image);
assertEquals(183, image.getWidth()); assertEquals(183, image.getWidth());
assertEquals(283, image.getHeight()); assertEquals(283, image.getHeight());
// TODO: Need to test colors! // TODO: Need to test colors!
}
finally {
reader.dispose();
}
} }
@Test @Test
public void testEOFSOSSegment() throws IOException { public void testEOFSOSSegment() throws IOException {
// Regression... // Regression...
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/eof-sos-segment-bug.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/eof-sos-segment-bug.jpg"))) {
reader.setInput(stream);
assertEquals(266, reader.getWidth(0)); assertEquals(266, reader.getWidth(0));
assertEquals(400, reader.getHeight(0)); assertEquals(400, reader.getHeight(0));
BufferedImage image = reader.read(0); BufferedImage image = reader.read(0);
assertNotNull(image); assertNotNull(image);
assertEquals(266, image.getWidth()); assertEquals(266, image.getWidth());
assertEquals(400, image.getHeight()); assertEquals(400, image.getHeight());
}
finally {
reader.dispose();
}
} }
@Test @Test
public void testInvalidICCSingleChunkBadSequence() throws IOException { public void testInvalidICCSingleChunkBadSequence() throws IOException {
// Regression // Regression
// Single segment ICC profile, with chunk index/count == 0 // Single segment ICC profile, with chunk index/count == 0
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-single-chunk-bad-sequence-number.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-single-chunk-bad-sequence-number.jpg"))) {
reader.setInput(stream);
assertEquals(1772, reader.getWidth(0)); assertEquals(1772, reader.getWidth(0));
assertEquals(2126, reader.getHeight(0)); assertEquals(2126, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); param.setSourceRegion(new Rectangle(reader.getWidth(0), 8));
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class); IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener); reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(1772, image.getWidth()); assertEquals(1772, image.getWidth());
assertEquals(8, image.getHeight()); assertEquals(8, image.getHeight());
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString()); verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
}
finally {
reader.dispose();
}
} }
@Test @Test
public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException { public void testYCbCrNotSubsampledNonstandardChannelIndexes() throws IOException {
// Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes // Regression: Make sure 3 channel, non-subsampled JFIF, defaults to YCbCr, even if unstandard channel indexes
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-ycbcr-no-subsampling-intel.jpg"))) {
reader.setInput(stream);
assertEquals(600, reader.getWidth(0)); assertEquals(600, reader.getWidth(0));
assertEquals(600, reader.getHeight(0)); assertEquals(600, reader.getHeight(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(8, 8)); param.setSourceRegion(new Rectangle(8, 8));
BufferedImage image = reader.read(0, param); BufferedImage image = reader.read(0, param);
assertNotNull(image); assertNotNull(image);
assertEquals(8, image.getWidth()); assertEquals(8, image.getWidth());
assertEquals(8, image.getHeight()); assertEquals(8, image.getHeight());
// QnD test: Make sure all pixels are white (if treated as RGB, they will be pink-ish) // 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 y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) { for (int x = 0; x < image.getWidth(); x++) {
assertEquals(0xffffff, image.getRGB(x, y) & 0xffffff); assertEquals(0xffffff, image.getRGB(x, y) & 0xffffff);
}
} }
} }
finally {
reader.dispose();
}
} }
@Test @Test
@@ -424,36 +470,87 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// Special case, throws exception below without special treatment // Special case, throws exception below without special treatment
// java.awt.color.CMMException: General CMM error517 // java.awt.color.CMMException: General CMM error517
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-corbis-rgb.jpg"))); try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/cmm-exception-corbis-rgb.jpg"))) {
reader.setInput(stream);
assertEquals(512, reader.getWidth(0)); assertEquals(512, reader.getWidth(0));
assertEquals(384, reader.getHeight(0)); assertEquals(384, reader.getHeight(0));
BufferedImage image = reader.read(0); BufferedImage image = reader.read(0);
assertNotNull(image); assertNotNull(image);
assertEquals(512, image.getWidth()); assertEquals(512, image.getWidth());
assertEquals(384, image.getHeight()); assertEquals(384, image.getHeight());
}
reader.dispose(); finally {
} 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");
} }
@Test @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 // See issue #107, from PDFBox team
JPEGImageReader reader = createReader(); 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 // TODO: Consider wrapping the delegate in JPEGImageReader with methods that don't throw
// runtime exceptions, and instead throw IIOException? // runtime exceptions, and instead throw IIOException?
@Test @Test
public void testBrokenGetRawImageType() throws IOException { public void testBrokenGetRawImageType() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -523,7 +620,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test(timeout = 200) @Test(timeout = 200)
public void testBrokenGetRawImageTypeIgnoreMetadata() throws IOException { public void testBrokenGetRawImageTypeIgnoreMetadata() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -549,7 +646,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test @Test
public void testBrokenGetImageTypes() throws IOException { public void testBrokenGetImageTypes() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -575,7 +672,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test(timeout = 200) @Test(timeout = 200)
public void testBrokenGetImageTypesIgnoreMetadata() throws IOException { public void testBrokenGetImageTypesIgnoreMetadata() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -601,7 +698,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test @Test
public void testBrokenRead() throws IOException { public void testBrokenRead() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -627,7 +724,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test @Test
public void testBrokenGetDimensions() throws IOException { public void testBrokenGetDimensions() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -656,7 +753,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
@Test @Test
public void testBrokenGetImageMetadata() throws IOException { public void testBrokenGetImageMetadata() {
JPEGImageReader reader = createReader(); JPEGImageReader reader = createReader();
try { try {
@@ -1284,6 +1381,9 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
// Assume that the aspect ratio is 1 if both x/y density is 0. // Assume that the aspect ratio is 1 if both x/y density is 0.
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false);
NodeList dimensions = tree.getElementsByTagName("Dimension"); NodeList dimensions = tree.getElementsByTagName("Dimension");
assertEquals(1, dimensions.getLength()); assertEquals(1, dimensions.getLength());
assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName()); assertEquals("PixelAspectRatio", dimensions.item(0).getFirstChild().getNodeName());
@@ -1321,7 +1421,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
NodeList markerSequences = iioTree.getElementsByTagName("markerSequence"); NodeList markerSequences = iioTree.getElementsByTagName("markerSequence");
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2 assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(0); IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
assertNotNull(markerSequence); assertNotNull(markerSequence);
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0)); assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
@@ -1379,6 +1479,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
for (TestData testData : getTestData()) { for (TestData testData : getTestData()) {
reader.setInput(testData.getInputStream()); reader.setInput(testData.getInputStream());
assert referenceReader != null;
referenceReader.setInput(testData.getInputStream()); referenceReader.setInput(testData.getInputStream());
for (int i = 0; i < reader.getNumImages(true); i++) { for (int i = 0; i < reader.getNumImages(true); i++) {
@@ -1393,6 +1494,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
Node referenceTree = reference.getAsTree(formatName); Node referenceTree = reference.getAsTree(formatName);
Node actualTree = metadata.getAsTree(formatName); Node actualTree = metadata.getAsTree(formatName);
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(referenceTree, false);
// System.out.println("--------");
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(actualTree, false); // new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(actualTree, false);
assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree); assertTreesEquals(String.format("Metadata differs for %s image %s ", testData, i), referenceTree, actualTree);
} }
@@ -1432,8 +1535,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
if (expectedTree == null) { if (expectedTree == null) {
assertNull(actualTree); fail("Expected tree is null, actual tree is non-null");
return;
} }
assertEquals(String.format("%s: Node names differ", message), expectedTree.getNodeName(), actualTree.getNodeName()); assertEquals(String.format("%s: Node names differ", message), expectedTree.getNodeName(), actualTree.getNodeName());
@@ -1443,7 +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()); assertEquals(String.format("%s: Number of attributes for <%s> differ", message, expectedTree.getNodeName()), expectedAttributes.getLength(), actualAttributes.getLength());
for (int i = 0; i < expectedAttributes.getLength(); i++) { for (int i = 0; i < expectedAttributes.getLength(); i++) {
Node item = expectedAttributes.item(i); Node item = expectedAttributes.item(i);
assertEquals(String.format("%s: \"%s\" attribute for <%s> differ", message, item.getNodeName(), expectedTree.getNodeName()), item.getNodeValue(), actualAttributes.getNamedItem(item.getNodeName()).getNodeValue()); String nodeValue = item.getNodeValue();
// NOTE: com.sun...JPEGMetadata javax_imageio_1.0 format bug: Uses "normal" instead of "Normal" ImageOrientation
if ("ImageOrientation".equals(expectedTree.getNodeName()) && "value".equals(item.getNodeName())) {
nodeValue = StringUtil.capitalize(nodeValue);
}
assertEquals(String.format("%s: \"%s\" attribute for <%s> differ", message, item.getNodeName(), expectedTree.getNodeName()), nodeValue, actualAttributes.getNamedItem(item.getNodeName()).getNodeValue());
} }
// Test for equal user objects. // Test for equal user objects.
@@ -1460,6 +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 // Sort nodes to make sure that sequence of equally named tags does not matter
List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes()); List<IIOMetadataNode> expectedChildren = sortNodes(expectedTree.getChildNodes());
List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes()); List<IIOMetadataNode> actualChildren = sortNodes(actualTree.getChildNodes());
@@ -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> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
@@ -60,10 +60,10 @@ public abstract class AbstractEntry implements Entry {
} }
/** /**
* Returns a format-native identifier. * Returns a format-native identifier.
* For example {@code "2:00"} for IPTC "Record Version" field, or {@code "0x040c"} for PSD "Thumbnail" resource. * 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())}. * This default implementation simply returns {@code String.valueOf(getIdentifier())}.
* *
* @return a format-native identifier. * @return a format-native identifier.
*/ */
protected String getNativeIdentifier() { protected String getNativeIdentifier() {
@@ -174,7 +174,7 @@ public abstract class AbstractEntry implements Entry {
} }
AbstractEntry other = (AbstractEntry) pOther; AbstractEntry other = (AbstractEntry) pOther;
return identifier.equals(other.identifier) && ( return identifier.equals(other.identifier) && (
value == null && other.value == null || value != null && valueEquals(other) value == null && other.value == null || value != null && valueEquals(other)
); );
@@ -144,7 +144,7 @@ public final class XMPReader extends MetadataReader {
parseAttributesForKnownElements(subsubdirs, node); parseAttributesForKnownElements(subsubdirs, node);
if (!subsubdirs.isEmpty()) { 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()) { for (Map.Entry<String, List<Entry>> entry : subsubdirs.entrySet()) {
entries.addAll(entry.getValue()); 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? // TODO: Should we still allow asking for a subdirectory by item id?
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) { for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-pcx</artifactId> <artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name> <name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-pdf</artifactId> <artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name> <name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-pict</artifactId> <artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name> <name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-pnm</artifactId> <artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name> <name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-psd</artifactId> <artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name> <name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-reference</artifactId> <artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: reference test cases</name> <name>TwelveMonkeys :: ImageIO :: reference test cases</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-sgi</artifactId> <artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name> <name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-tga</artifactId> <artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name> <name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
@@ -89,16 +89,10 @@ final class TGAImageWriter extends ImageWriterBase {
processImageStarted(0); processImageStarted(0);
WritableRaster rowRaster = header.getPixelDepth() == 32 WritableRaster rowRaster = header.getPixelDepth() == 32
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false) ? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
.createBufferedImage(renderedImage.getWidth(), 1) : renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT
.getRaster() ? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
: renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT : ImageTypeSpecifier.createFromRenderedImage(renderedImage).createBufferedImage(renderedImage.getWidth(), 1).getRaster();
? 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(); DataBuffer buffer = rowRaster.getDataBuffer();
@@ -142,17 +136,14 @@ final class TGAImageWriter extends ImageWriterBase {
} }
// Vi kan lage en DataBuffer wrapper-klasse, // TODO: Refactor to common util
// som gjør TYPE_INT_RGB/INT_ARGB/INT_ARGB_PRE/INT_BGR til tilsvarende TYPE_xBYTE-klasser. // TODO: Implement WritableRaster too, for use in reading
// 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?
private Raster asByteRaster(final Raster raster, ColorModel colorModel) { private Raster asByteRaster(final Raster raster, ColorModel colorModel) {
switch (raster.getTransferType()) { switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_BYTE:
return raster; return raster;
case DataBuffer.TYPE_USHORT: 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: case DataBuffer.TYPE_INT:
final int bands = colorModel.getNumComponents(); final int bands = colorModel.getNumComponents();
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-thumbsdb</artifactId> <artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name> <name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
@@ -340,7 +340,7 @@ public final class ThumbsDBImageReader extends ImageReaderBase {
// TODO: Rethink this... // TODO: Rethink this...
// Seems to be up to Windows and the installed programs what formats // Seems to be up to Windows and the installed programs what formats
// are supported... // 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 // At least this seems fine for now
String extension = FileUtil.getExtension(pFileName); String extension = FileUtil.getExtension(pFileName);
if (StringUtil.isEmpty(extension)) { 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; 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 { public static void main(String[] pArgs) throws IOException {
ThumbsDBImageReader reader = new ThumbsDBImageReader(); ThumbsDBImageReader reader = new ThumbsDBImageReader();
@@ -421,7 +421,7 @@ public final class ThumbsDBImageReader extends ImageReaderBase {
return SIZE; return SIZE;
} }
}); });
label.setText("" + image.getWidth() + "x" + image.getHeight() + ": " + pName); label.setText(image.getWidth() + "x" + image.getHeight() + ": " + pName);
label.setToolTipText(image.toString()); label.setToolTipText(image.toString());
pParent.add(label); pParent.add(label);
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name> <name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -91,11 +91,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
super(Validate.notNull(stream, "stream")); super(Validate.notNull(stream, "stream"));
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0"); this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
this.type = Validate.isTrue( this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE || type == TIFFExtension.COMPRESSION_CCITT_T4 ||
type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6, type == TIFFExtension.COMPRESSION_CCITT_T6,
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s" type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s");
);
this.fillOrder = Validate.isTrue( this.fillOrder = Validate.isTrue(
fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT, fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
fillOrder, "Expected fill order 1 or 2: %s" 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); 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 { private void fetch() throws IOException {
if (decodedPos >= decodedLength) { if (decodedPos >= decodedLength) {
decodedLength = 0; decodedLength = 0;
@@ -101,5 +101,8 @@ interface TIFFExtension {
int GROUP3OPT_2DENCODING = 1; int GROUP3OPT_2DENCODING = 1;
int GROUP3OPT_UNCOMPRESSED = 2; int GROUP3OPT_UNCOMPRESSED = 2;
int GROUP3OPT_FILLBITS = 4; int GROUP3OPT_FILLBITS = 4;
int GROUP3OPT_BYTEALIGNED = 8;
int GROUP4OPT_UNCOMPRESSED = 2; int GROUP4OPT_UNCOMPRESSED = 2;
int GROUP4OPT_BYTEALIGNED = 4;
} }
@@ -144,7 +144,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
IIOMetadataNode valueNode = new IIOMetadataNode("TIFFUndefined"); IIOMetadataNode valueNode = new IIOMetadataNode("TIFFUndefined");
tagNode.appendChild(valueNode); tagNode.appendChild(valueNode);
if (count == 1) { if (count == 1 && (value == null || !value.getClass().isArray())) {
valueNode.setAttribute("value", String.valueOf(value)); valueNode.setAttribute("value", String.valueOf(value));
} }
else { else {
@@ -160,7 +160,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
String typeName = getMetadataType(tag); String typeName = getMetadataType(tag);
// NOTE: ASCII/Strings have count 1, always. This seems consistent with the JAI ImageIO version. // 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); IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
valueNode.appendChild(elementNode); valueNode.appendChild(elementNode);
@@ -915,7 +915,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException { public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException {
// Standard validation // Standard validation
super.mergeTree(formatName, root); super.setFromTree(formatName, root);
// Set by "merging" with empty map // Set by "merging" with empty map
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>(); LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>();
@@ -1144,10 +1144,11 @@ public final class TIFFImageMetadata extends AbstractMetadata {
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode); throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode);
} }
List<Entry> entries = new ArrayList<>();
NodeList nodes = ifdNode.getChildNodes(); 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))); entries.add(toEntry(nodes.item(i)));
} }
@@ -1293,21 +1293,22 @@ public final class TIFFImageReader extends ImageReaderBase {
imageInput.seek(stripTileOffsets[0]); imageInput.seek(stripTileOffsets[0]);
if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) { 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; stripTileOffsets[0] += len;
stripTileByteCounts[0] -= len; stripTileByteCounts[0] -= len;
} }
// We'll prepend each tile with a JFIF "header" (SOI...SOS) // We'll prepend each tile with a JFIF "header" (SOI...SOS)
imageInput.seek(realJPEGOffset); imageInput.seek(realJPEGOffset);
jpegHeader = new byte[(int) (stripTileOffsets[0] - realJPEGOffset)]; jpegHeader = new byte[Math.max(0, (int) (stripTileOffsets[0] - realJPEGOffset))];
imageInput.readFully(jpegHeader); imageInput.readFully(jpegHeader);
} }
// In case of single tile, make sure we read the entire JFIF stream // In case of single tile, make sure we read the entire JFIF stream
if (stripTileByteCounts != null && stripTileByteCounts.length == 1) { if (stripTileByteCounts != null && stripTileByteCounts.length == 1 && stripTileByteCounts[0] < jpegLength) {
// TODO: Consider issue warning here! processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead.");
stripTileByteCounts[0] = Math.max(stripTileByteCounts[0], jpegLength); stripTileByteCounts[0] = jpegLength;
} }
// Read data // Read data
@@ -2307,12 +2308,22 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
case TIFFExtension.COMPRESSION_CCITT_T4: case TIFFExtension.COMPRESSION_CCITT_T4:
case TIFFExtension.COMPRESSION_CCITT_T6: 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: default:
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression); 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) { private InputStream createFillOrderStream(final int fillOrder, final InputStream stream) {
switch (fillOrder) { switch (fillOrder) {
case TIFFBaseline.FILL_LEFT_TO_RIGHT: 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, 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 }; (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 // 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 }; 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); 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 @Test
public void testDecodeType3_2D() throws IOException { public void testDecodeType3_2D() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6, InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6,
@@ -308,6 +308,27 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
assertNotNull(image); assertNotNull(image);
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight())); 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("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 @Test
public void testReadMultipleExtraSamples() throws IOException { public void testReadMultipleExtraSamples() throws IOException {
ImageReader reader = createReader(); ImageReader reader = createReader();
@@ -1072,6 +1072,45 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest {
assertArrayEquals(new byte[] {'I', 'I', 42, 0}, Arrays.copyOf(bytes, 4)); 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 @Test
public void testRewrite() throws IOException { public void testRewrite() throws IOException {
ImageWriter writer = createImageWriter(); ImageWriter writer = createImageWriter();
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Twelvemonkeys</name> <name>Twelvemonkeys</name>
@@ -86,7 +86,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection> <connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection> <developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url> <url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.5</tag> <tag>twelvemonkeys-3.6</tag>
</scm> </scm>
<properties> <properties>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.5</version> <version>3.6</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>