Compare commits

..

56 Commits

Author SHA1 Message Date
Harald Kuhr c61e47b3e7 [maven-release-plugin] prepare for next development iteration 2021-12-11 22:54:21 +01:00
Harald Kuhr 1d47d2ef90 [maven-release-plugin] prepare release twelvemonkeys-3.7.1 2021-12-11 22:54:18 +01:00
Harald Kuhr 94554b0660 #631 New way of forcing profile activation + guarding all invocations of ICC_Profile.getInstance()
(cherry picked from commit b2c5915db8)
2021-12-11 18:59:19 +01:00
Harald Kuhr a39bca4d2f #645 AAIOBE in CCITTFaxDecoderStream now wrapped in IOException
(cherry picked from commit 3911191b04)
2021-12-11 18:59:18 +01:00
Harald Kuhr fdbbcc54a8 Preparing for next release. 2021-12-10 17:02:41 +01:00
Harald Kuhr 00cd9471dd #483: Rollback 2021-12-10 17:00:58 +01:00
Snyk bot eb6a3bde39 fix: upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4 (#642)
Snyk has created this PR to upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/3eb50c00-e586-4f4c-a39c-90c80f89cc60?utm_source=github&utm_medium=referral&page=upgrade-pr

(cherry picked from commit bc328419ac)
2021-12-10 16:57:00 +01:00
Harald Kuhr d3dd4c29d5 Avoid fetching external resources in XMPReader.
(cherry picked from commit da4efe98bf)
2021-12-10 16:57:00 +01:00
Harald Kuhr 3355acfa03 Minor clean-up.
(cherry picked from commit 6653f4a85d)
2021-12-10 16:56:58 +01:00
Harald Kuhr b70e52561a Minor AffineTransformOp clean-up + removed test output.
(cherry picked from commit 511a29beb9)
2021-12-10 16:56:56 +01:00
Gauthier 49ddbfa85e Make tests pass on JDK 16 and 17 (#635)
* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

(cherry picked from commit 5617b4323c)
2021-12-10 16:56:56 +01:00
Harald Kuhr 215db21569 #629: Fixed build
(cherry picked from commit 16d0af357d)
2021-12-10 16:56:54 +01:00
Harald Kuhr 0ac8011053 #629: Preliminary WebP animation (ANIM/ANMF) support
(cherry picked from commit 74927d5396)
2021-12-10 16:56:52 +01:00
Harald Kuhr abbbca9be3 Readme improvements
(cherry picked from commit 7e809dd834)
2021-12-10 16:56:52 +01:00
Harald Kuhr bc5473aec6 Fixed Travis link + bonus project summary updates
(cherry picked from commit 7aa95a08bc)
2021-12-10 16:56:52 +01:00
Harald Kuhr aca8b1256a Minor servlet clean-up.
(cherry picked from commit c28963ae49)
2021-12-10 16:56:51 +01:00
Harald Kuhr 18c1edf0e5 Servlet deprecation
(cherry picked from commit 0327f5fc1a)
2021-12-10 16:56:51 +01:00
Harald Kuhr 6a01466ebb #628: Stabilized build + better dependency scopes and module names in interop modules
(cherry picked from commit 1c59057c30)
2021-12-10 16:56:31 +01:00
Harald Kuhr b19df1640b #626 Clean up + fill order support for all compression types
(cherry picked from commit 3e1f85c4dc)
2021-12-10 16:53:27 +01:00
Harald Kuhr e8e3e0f1d6 #628 TIFF metadata fix, now always outputs denominator for rationals.
+ Bonus: Added JAI TIFF interop module with test and other minor fixes.

(cherry picked from commit 11227a68a0)
2021-12-10 16:53:23 +01:00
Oliver Schmidtmer c249a21c8c #626: Handle fillOrder in TIFFImageReader, not in CCITTFaxDecoderStream (#627)
(cherry picked from commit 62ba73a30e)
2021-12-10 16:53:23 +01:00
Harald Kuhr 182b2fdfa9 Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading.
(cherry picked from commit 1f33afb5a1)
2021-12-10 16:53:22 +01:00
Harald Kuhr df068e350d #626 TIFF CCITT detection only once per IFD
(cherry picked from commit 9d3f271867)
2021-12-10 16:53:20 +01:00
Harald Kuhr b55c623b87 #623: TGAImageReader, PCXImageReader and SGIImageReader now return more standard image types as default, for better AffineTransformOp compatibility. Added tests.
Bonus: Subsampling fix for TGAImageReader and BMPImageReader.

(cherry picked from commit 812e12acb0)
2021-12-10 16:53:16 +01:00
Harald Kuhr 09573b52ac #624: Added metadata support for 16 bit USHORT gray.
(cherry picked from commit 060b6cf852)
2021-12-10 16:53:15 +01:00
Koen De Groote ff7edbd223 Certain pixeldepth-16 TGA files fail to process, classcast exception (#624)
* Added fixed for monochrome tga16 bit. Uncertain if that description is complete.
Test files added. Without the changed code, the tests fail.

* Fix suggested by HaraldK

Co-authored-by: Koen De Groote <koen.degroote@limecraft.com>
(cherry picked from commit e68ce7ffd1)
2021-12-10 16:53:15 +01:00
Harald Kuhr d8f0cd97a2 Fix typo in TIFFImageMetadataFormat mk II.
(cherry picked from commit 778cdef69c)
2021-12-10 16:52:21 +01:00
Harald Kuhr f6971a9bce Fix typo in TIFFImageMetadataFormat.
(cherry picked from commit d46a76fca8)
2021-12-10 16:52:20 +01:00
Harald Kuhr 821965df0d #621 Don't add ICC profile for default gray images
(cherry picked from commit 105a1ee466)
2021-12-10 16:52:12 +01:00
Harald Kuhr ed46305d31 #619: Fix WebP Y'CbCr->RGB conversion (now uses rec 601)
(cherry picked from commit 976e5d6210)
2021-12-10 16:51:49 +01:00
Harald Kuhr ca3adb7c45 Minor clean-up.
(cherry picked from commit 6daca00fcd)
2021-12-10 16:51:45 +01:00
Harald Kuhr 59f76209bc Some more minor clean-up.
(cherry picked from commit ce997a6951)
2021-12-10 16:51:31 +01:00
Harald Kuhr 2076235313 Minor clean-up.
(cherry picked from commit 23bf5cb7b2)
2021-12-10 16:51:31 +01:00
Harald Kuhr 88bebf31f8 #616: Remove dependency on old xmlgraphics-commons (no longer needed)
(cherry picked from commit 564778f415)
2021-12-10 16:51:21 +01:00
Harald Kuhr 37beb21c29 Fix WebP ICC handling for images with alpha.
(cherry picked from commit e28bf8fb44)
2021-12-10 16:51:20 +01:00
Harald Kuhr 88c0d27516 Add WebP to BOM.
(cherry picked from commit cf8d630d01)
2021-12-10 16:51:20 +01:00
Harald Kuhr dcd4ffccf4 Switch build from travis.ci.org to com
(cherry picked from commit 0ff7224912)
2021-12-10 16:51:12 +01:00
Koen De Groote f35f5c6b24 Documentation cleanup (#612)
* Added the `@Deprecated` tag to instances where is was mentioned in documentation, but not for the actual code itself.

Changed one documentation link pointing at a non-existing item.

* As per PR suggestion.

(cherry picked from commit 196081a317)
2021-12-10 16:51:12 +01:00
Harald Kuhr 9dcf53d985 #609 Fixed ICC Profile handling in WebP.
(cherry picked from commit ff50180d86)
2021-12-10 16:51:11 +01:00
Harald Kuhr 44a2066b79 Minor code clean-up.
(cherry picked from commit 8f2c482167)
2021-12-10 16:51:06 +01:00
Oliver Schmidtmer 382c51db7c Invert EOF check
(cherry picked from commit cd42d81817)
2021-12-10 16:50:59 +01:00
Oliver Schmidtmer 660f3f7e86 #579 Deeper EOL search in the CCITT stream
(cherry picked from commit ba5c667b6c)
2021-12-10 16:50:59 +01:00
Harald Kuhr b4c30872c4 XXX: Remove another old servlet class.
(cherry picked from commit 94eac2d6e5)
2021-12-10 16:50:49 +01:00
Harald Kuhr 402d4d466d HTTPS links in README.md
(cherry picked from commit f63a33d541)
2021-12-10 16:50:49 +01:00
Harald Kuhr 7860bf7e17 XXX: Remove old servlet class.
(cherry picked from commit 2f9768a1d4)
2021-12-10 16:50:49 +01:00
Harald Kuhr d52522fb80 #483 Minor optimization
(cherry picked from commit 06bcf22242)
2021-12-10 16:41:24 +01:00
Harald Kuhr 8a8c6b1931 #483 Add license headers.
(cherry picked from commit 20c7f8e60e)
2021-12-10 16:41:24 +01:00
Harald Kuhr 2930708a54 #483 Initial PSD Write support
(cherry picked from commit 15a9ad0a9b)
2021-12-10 16:41:23 +01:00
Harald Kuhr 5661e7459c #606: Fix bug introduced by more aggressive readDirect.
(cherry picked from commit 4e2bf131d2)
2021-12-10 16:39:20 +01:00
Harald Kuhr 4d45ea4966 #606: Workaround for broken JDK WBMPImageReader
(cherry picked from commit d0c4a07556)
2021-12-10 16:39:19 +01:00
Harald Kuhr 34852f7be5 Cleaner tests for Java 6 or later... A little late. :-)
(cherry picked from commit 21059c8d5a)
2021-12-10 16:39:17 +01:00
Harald Kuhr abc929a531 Adding GitHub sponsors link.
(cherry picked from commit fa7b530809)
2021-12-10 16:39:16 +01:00
Harald Kuhr 247a09ca61 Test clean-up.
(cherry picked from commit 790cf3b32e)
2021-12-10 16:39:15 +01:00
Harald Kuhr c2880fe793 Rename file, add missing file extension.
(cherry picked from commit b1baaad23b)
2021-12-10 16:39:15 +01:00
Harald Kuhr c9e522475b Bump plugins and stop deploying useless (internal) artifacts.
(cherry picked from commit 7fa704ace5)
2021-12-10 16:39:15 +01:00
Harald Kuhr 1da16e4d7a Updated versions to 3.7.0.
(cherry picked from commit 8d07f4fe90)
2021-12-10 16:33:02 +01:00
65 changed files with 613 additions and 2321 deletions
-53
View File
@@ -1,53 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: Reported bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Version information**
1. The version of the TwelveMonkeys ImageIO library in use.
For example: 4.0.0
2. The *exact* output of `java --version` (or `java -version` for older Java releases).
For example:
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable.
**To Reproduce**
Steps to reproduce the behavior:
1. Compile the below sample code
2. Download the sample image file
3. Run the code with the sample file
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Example code**
Preferably as a failing JUnit test, or a standalone program with a `main` method that showcases the problem.
Less is more. Don't add your entire project, only the code required to reproduce the problem. 😀
**Sample file(s)**
Attach any sample files needed to reproduce the problem. Use a ZIP-file if the format is not directly supported by GitHub.
**Stak trace**
Always include the stack trace you experience.
**Screenshots**
If applicable, add screenshots to help explain your problem.
Do not add screenshots of code or stack traces. 😀
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: New feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem or use case is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
@@ -1,13 +0,0 @@
---
name: Trouble shooting and programming help
about: "General programming issues will reach a wider audience at StackOverflow. Tag
questions with javax-imageio and/or twelvemonkeys \U0001F600 "
title: ''
labels: Trouble-shooting
assignees: ''
---
General programming issues and problems will reach a much wider audience at StackOverflow, we suggest you ask them there. This will offload our work with maintaining the library, and make sure you get better help sooner.
Tag the question with `javax-imageio` and/or `twelvemonkeys` and we'll find them there.
@@ -1,11 +0,0 @@
**What is fixed** Add link to the issue this PR fixes.
Example: Fixes #42.
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
**What is changed** Briefly describe the changes proposed in this pull request:
* Fixed rare exception happening in `x >= 42` case
* Small optimization of `decompress()` method
* Corrected API doc for `compress()` method to reflect current implementation
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -1,563 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
/**
* A helper class for working with ICC color profiles.
* <p>
* Standard ICC color profiles are read from system-specific locations
* for known operating systems.
* </p>
* <p>
* Color profiles may be configured by placing a property-file
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
* on the classpath, specifying the full path to the profiles.
* ICC color profiles are probably already present on your system, or
* can be downloaded from
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
* * </p>
* <p>
* Example property file:
* </p>
* <pre>
* # icc_profiles.properties
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
* GENERIC_CMYK=/path/to/Generic CMYK.icc
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/
public final class ColorProfiles {
/**
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
*/
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
static final int ICC_PROFILE_HEADER_SIZE = 128;
static {
// In case we didn't activate through SPI already
ProfileDeferralActivator.activateProfiles();
}
private ColorProfiles() {
}
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
return getProfileHeaderWithProfileId(profile.getData());
}
static byte[] getProfileHeaderWithProfileId(byte[] data) {
// ICC profile header is the first 128 bytes
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(header, data);
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
return header;
}
private static byte[] computeMD5(byte[] header, byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
return digest.digest();
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
/**
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
*/
public static boolean isCS_sRGB(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
}
/**
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_GRAY
*/
public static boolean isCS_GRAY(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_Profile validateProfile(final ICC_Profile profile) {
// Fix profile before validation
profileCleaner.fixProfile(profile);
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
return profile;
}
/**
* Reads an ICC Profile from the given input stream, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
* @see #readProfile(InputStream)
*/
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
Validate.notNull(input, "input");
return ICC_Profile.getInstance(input);
}
/**
* Reads an ICC Profile from the given input stream, with extra validation.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
*/
public static ICC_Profile readProfile(final InputStream input) throws IOException {
Validate.notNull(input, "input");
DataInputStream dataInput = new DataInputStream(input);
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
try {
dataInput.readFully(header);
int size = validateHeaderAndGetSize(header);
byte[] data = Arrays.copyOf(header, size);
dataInput.readFully(data, header.length, size - header.length);
return createProfile(data);
}
catch (EOFException e) {
throw new IllegalArgumentException("Truncated ICC Profile data", e);
}
}
/**
* Creates an ICC Profile from the given byte array, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
* except that extraneous bytes at the end of the array is ignored.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
* @see #createProfile(byte[])
*/
public static ICC_Profile createProfileRaw(final byte[] input) {
int size = validateHeaderAndGetSize(input);
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
// does not discard extra bytes at the end. We'll chop them off here for convenience
return ICC_Profile.getInstance(limit(input, size));
}
/**
* Reads an ICC Profile from the given byte array, with extra validation.
* Extraneous bytes at the end of the array are ignored.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
*/
public static ICC_Profile createProfile(final byte[] input) {
int size = validateAndGetSize(input);
// Look up in cache before returning, these are already validated
byte[] profileHeader = getProfileHeaderWithProfileId(input);
ICC_Profile internal = getInternalProfile(profileHeader);
if (internal != null) {
return internal;
}
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
if (cached != null) {
return cached.getProfile();
}
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
// We'll validate & cache by creating a color space and returning its profile...
// TODO: Rewrite with separate cache for profiles...
return ColorSpaces.createColorSpace(profile).getProfile();
}
private static byte[] limit(byte[] input, int size) {
return input.length == size ? input : Arrays.copyOf(input, size);
}
private static int validateAndGetSize(byte[] input) {
int size = validateHeaderAndGetSize(input);
if (size < 0 || size > input.length) {
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
}
return size;
}
private static int validateHeaderAndGetSize(byte[] input) {
Validate.notNull(input, "input");
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
}
int size = intBigEndian(input, ICC_Profile.icHdrSize);
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
}
return size;
}
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
int profileCSType = getCsType(profileHeader);
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
}
return null;
}
private static int intBigEndian(byte[] data, int index) {
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
}
private static int getCsType(byte[] profileHeader) {
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
switch (csSig) {
case ICC_Profile.icSigXYZData:
return ColorSpace.TYPE_XYZ;
case ICC_Profile.icSigLabData:
return ColorSpace.TYPE_Lab;
case ICC_Profile.icSigLuvData:
return ColorSpace.TYPE_Luv;
case ICC_Profile.icSigYCbCrData:
return ColorSpace.TYPE_YCbCr;
case ICC_Profile.icSigYxyData:
return ColorSpace.TYPE_Yxy;
case ICC_Profile.icSigRgbData:
return ColorSpace.TYPE_RGB;
case ICC_Profile.icSigGrayData:
return ColorSpace.TYPE_GRAY;
case ICC_Profile.icSigHsvData:
return ColorSpace.TYPE_HSV;
case ICC_Profile.icSigHlsData:
return ColorSpace.TYPE_HLS;
case ICC_Profile.icSigCmykData:
return ColorSpace.TYPE_CMYK;
// Note: There is no TYPE_* 10...
case ICC_Profile.icSigCmyData:
return ColorSpace.TYPE_CMY;
case ICC_Profile.icSigSpace2CLR:
return ColorSpace.TYPE_2CLR;
case ICC_Profile.icSigSpace3CLR:
return ColorSpace.TYPE_3CLR;
case ICC_Profile.icSigSpace4CLR:
return ColorSpace.TYPE_4CLR;
case ICC_Profile.icSigSpace5CLR:
return ColorSpace.TYPE_5CLR;
case ICC_Profile.icSigSpace6CLR:
return ColorSpace.TYPE_6CLR;
case ICC_Profile.icSigSpace7CLR:
return ColorSpace.TYPE_7CLR;
case ICC_Profile.icSigSpace8CLR:
return ColorSpace.TYPE_8CLR;
case ICC_Profile.icSigSpace9CLR:
return ColorSpace.TYPE_9CLR;
case ICC_Profile.icSigSpaceACLR:
return ColorSpace.TYPE_ACLR;
case ICC_Profile.icSigSpaceBCLR:
return ColorSpace.TYPE_BCLR;
case ICC_Profile.icSigSpaceCCLR:
return ColorSpace.TYPE_CCLR;
case ICC_Profile.icSigSpaceDCLR:
return ColorSpace.TYPE_DCLR;
case ICC_Profile.icSigSpaceECLR:
return ColorSpace.TYPE_ECLR;
case ICC_Profile.icSigSpaceFCLR:
return ColorSpace.TYPE_FCLR;
default:
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
}
}
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
static void fixProfile(ICC_Profile profile) {
profileCleaner.fixProfile(profile);
}
static boolean validationAltersProfileHeader() {
return profileCleaner.validationAltersProfileHeader();
}
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
static class sRGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
static class CIEXYZ {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
static class PYCC {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
static class GRAY {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
static class LINEAR_RGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -30,17 +30,24 @@
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.LRUHashMap;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
import java.util.Properties;
/**
* A helper class for working with ICC color profiles and color spaces.
@@ -77,6 +84,9 @@ public final class ColorSpaces {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
@@ -87,13 +97,14 @@ public final class ColorSpaces {
@SuppressWarnings("WeakerAccess")
public static final int CS_GENERIC_CMYK = 5001;
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
static final int ICC_PROFILE_HEADER_SIZE = 128;
// Weak references to hold the color spaces while cached
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
// Cache for the latest used color spaces
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
static {
// In case we didn't activate through SPI already
@@ -118,7 +129,7 @@ public final class ColorSpaces {
Validate.notNull(profile, "profile");
// Fix profile before lookup/create
fixProfile(profile);
profileCleaner.fixProfile(profile);
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
@@ -130,20 +141,55 @@ public final class ColorSpaces {
return getCachedOrCreateCS(profile, profileHeader);
}
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
return getProfileHeaderWithProfileId(profile.getData());
}
private static byte[] getProfileHeaderWithProfileId(byte[] data) {
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(data);
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
// ICC profile header is the first 128 bytes
return Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
}
private static byte[] computeMD5(byte[] data) {
try {
return MessageDigest.getInstance("MD5").digest(data);
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
}
@@ -164,7 +210,7 @@ public final class ColorSpaces {
cache.put(key, cs);
// On LCMS, validation *alters* the profile header, need to re-generate key
if (ColorProfiles.validationAltersProfileHeader()) {
if (profileCleaner.validationAltersProfileHeader()) {
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
}
}
@@ -179,11 +225,11 @@ public final class ColorSpaces {
}
}
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
private static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
return getCachedCS(new Key(profileHeader));
}
static void validateColorSpace(final ICC_ColorSpace cs) {
private static void validateColorSpace(final ICC_ColorSpace cs) {
// Validate the color space, to avoid caching bad profiles/color spaces
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
@@ -194,27 +240,220 @@ public final class ColorSpaces {
}
/**
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
*/
@Deprecated
public static boolean isCS_sRGB(final ICC_Profile profile) {
return ColorProfiles.isCS_sRGB(profile);
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
}
/**
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_GRAY
*/
@Deprecated
public static boolean isCS_GRAY(final ICC_Profile profile) {
return ColorProfiles.isCS_GRAY(profile);
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
@Deprecated
public static ICC_Profile validateProfile(final ICC_Profile profile) {
return ColorProfiles.validateProfile(profile);
// Fix profile before validation
profileCleaner.fixProfile(profile);
validateColorSpace(new ICC_ColorSpace(profile));
return profile;
}
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
return ICC_Profile.getInstance(input);
}
public static ICC_Profile readProfile(final InputStream input) throws IOException {
// TODO: Implement this smarter?
// Could read the header 128 bytes, get size + magic, then read read rest into array and feed the byte[] method...
ICC_Profile profile = ICC_Profile.getInstance(input);
if (profile == null) {
throw new IllegalArgumentException("Invalid ICC Profile Data");
}
return createProfile(profile.getData());
}
public static ICC_Profile createProfileRaw(final byte[] input) {
try {
return readProfileRaw(new ByteArrayInputStream(input));
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid ICC Profile Data", e);
}
}
public static ICC_Profile createProfile(final byte[] input) {
Validate.notNull(input, "input");
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
throw new IllegalArgumentException("Truncated ICC profile, length < 128: " + input.length);
}
int size = intBigEndian(input, 0);
if (size < 0 || size > input.length) {
throw new IllegalArgumentException("Truncated ICC profile, length < " + size + ": " + input.length);
}
if (input[36] != 'a' || input[37] != 'c' || input[38] != 's' || input[39] != 'p') {
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
}
// Look up in cache before returning, these are already validated
byte[] profileHeader = getProfileHeaderWithProfileId(input);
int csType = getCsType(profileHeader);
ICC_ColorSpace internal = getInternalCS(csType, profileHeader);
if (internal != null) {
return internal.getProfile();
}
ICC_ColorSpace cached = getCachedCS(profileHeader);
if (cached != null) {
return cached.getProfile();
}
// WEIRDNESS: Unlike the InputStream version, the byte version
// of ICC_Profile.getInstance() does not discard extra bytes at the end.
// We'll chop them off here for convenience
byte[] profileBytes = input.length == size ? input : Arrays.copyOf(input, size);
ICC_Profile profile = ICC_Profile.getInstance(profileBytes);
// We'll validate & cache by creating a color space and returning its profile...
// TODO: Rewrite with separate cache for profiles...
return createColorSpace(profile).getProfile();
}
private static int intBigEndian(byte[] data, int index) {
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
}
private static int getCsType(byte[] profileHeader) {
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
// TODO: Wonder why they didn't just use the sig as type, when there is obviously a 1:1 mapping...
switch (csSig) {
case ICC_Profile.icSigXYZData:
return ColorSpace.TYPE_XYZ;
case ICC_Profile.icSigLabData:
return ColorSpace.TYPE_Lab;
case ICC_Profile.icSigLuvData:
return ColorSpace.TYPE_Luv;
case ICC_Profile.icSigYCbCrData:
return ColorSpace.TYPE_YCbCr;
case ICC_Profile.icSigYxyData:
return ColorSpace.TYPE_Yxy;
case ICC_Profile.icSigRgbData:
return ColorSpace.TYPE_RGB;
case ICC_Profile.icSigGrayData:
return ColorSpace.TYPE_GRAY;
case ICC_Profile.icSigHsvData:
return ColorSpace.TYPE_HSV;
case ICC_Profile.icSigHlsData:
return ColorSpace.TYPE_HLS;
case ICC_Profile.icSigCmykData:
return ColorSpace.TYPE_CMYK;
// Note: There is no TYPE_* 10...
case ICC_Profile.icSigCmyData:
return ColorSpace.TYPE_CMY;
case ICC_Profile.icSigSpace2CLR:
return ColorSpace.TYPE_2CLR;
case ICC_Profile.icSigSpace3CLR:
return ColorSpace.TYPE_3CLR;
case ICC_Profile.icSigSpace4CLR:
return ColorSpace.TYPE_4CLR;
case ICC_Profile.icSigSpace5CLR:
return ColorSpace.TYPE_5CLR;
case ICC_Profile.icSigSpace6CLR:
return ColorSpace.TYPE_6CLR;
case ICC_Profile.icSigSpace7CLR:
return ColorSpace.TYPE_7CLR;
case ICC_Profile.icSigSpace8CLR:
return ColorSpace.TYPE_8CLR;
case ICC_Profile.icSigSpace9CLR:
return ColorSpace.TYPE_9CLR;
case ICC_Profile.icSigSpaceACLR:
return ColorSpace.TYPE_ACLR;
case ICC_Profile.icSigSpaceBCLR:
return ColorSpace.TYPE_BCLR;
case ICC_Profile.icSigSpaceCCLR:
return ColorSpace.TYPE_CCLR;
case ICC_Profile.icSigSpaceDCLR:
return ColorSpace.TYPE_DCLR;
case ICC_Profile.icSigSpaceECLR:
return ColorSpace.TYPE_ECLR;
case ICC_Profile.icSigSpaceFCLR:
return ColorSpace.TYPE_FCLR;
default:
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
}
}
/**
@@ -297,6 +536,50 @@ public final class ColorSpaces {
}
}
@SuppressWarnings("SameParameterValue")
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
private static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
private static final class Key {
private final byte[] data;
@@ -319,4 +602,78 @@ public final class ColorSpaces {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
}
}
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
private static class sRGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
private static class CIEXYZ {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
private static class PYCC {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
private static class GRAY {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
private static class LINEAR_RGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
private static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
);
}
catch (SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.color;
import javax.imageio.spi.ImageInputStreamSpi;
@@ -1,4 +1,2 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
# Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -1,247 +0,0 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.Assert.*;
public class ColorProfilesTest {
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
}
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorProfiles.isCS_sRGB(null);
}
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorProfiles.isCS_GRAY(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileNull() {
ColorProfiles.createProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileNull() throws IOException {
ColorProfiles.readProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawNull() {
ColorProfiles.createProfileRaw(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawNull() throws IOException {
ColorProfiles.readProfileRaw(null);
}
@Test
public void testCreateProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
assertArrayEquals(data, profileRaw.getData());
}
@Test
public void testReadProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertArrayEquals(data, profileRaw.getData());
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawBadData() {
ColorProfiles.createProfileRaw(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawBadData() throws IOException {
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileBadData() {
ColorProfiles.createProfile(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileBadData() throws IOException {
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
}
@Test
public void testCreateProfileBytesSame() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSame() throws IOException {
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileDifferent() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
assertNotSame(profile1, profile2);
}
@Test
public void testCreateProfileBytesSameAsCached() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileInputStreamSameAsCached() throws IOException {
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testCreateProfileBytesSameAsInternal() {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSameAsInternal() throws IOException {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
}
@@ -35,6 +35,7 @@ import org.junit.Test;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.Assert.*;
@@ -90,6 +91,34 @@ public class ColorSpacesTest {
assertTrue(created.isCS_sRGB());
}
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
}
@Test
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
@@ -138,13 +167,11 @@ public class ColorSpacesTest {
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
@@ -153,19 +180,16 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorSpaces.isCS_sRGB(null);
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
@@ -174,7 +198,6 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorSpaces.isCS_GRAY(null);
@@ -193,4 +216,69 @@ public class ColorSpacesTest {
assertNotSame(cs1, cs2);
}
@Test
public void testReadProfileBytesSame() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile1 = ColorSpaces.createProfile(profile.getData());
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSame() throws IOException {
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileDifferent() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
assertNotSame(profile1, profile2);
}
@Test
public void testReadProfileBytesSameAsCached() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileInputStreamSameAsCached() throws IOException {
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileBytesSameAsInternal() {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorSpaces.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSameAsInternal() throws IOException {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorSpaces.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
}
@@ -1,28 +0,0 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import static org.mockito.Mockito.*;
public class ProfileDeferralActivatorTest {
@Test
public void testActivateProfiles() {
// Should just run with no exceptions...
ProfileDeferralActivator.activateProfiles();
}
@Test
public void testSpiRegistration() {
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
ServiceRegistry registry = mock(ServiceRegistry.class);
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
spi.onRegistration(registry, category);
verify(registry, only()).deregisterServiceProvider(spi, category);
}
}
@@ -78,7 +78,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
protected abstract ImageWriterSpi createProvider();
protected final T createWriter() throws IOException {
return writerClass.cast(provider.createWriterInstance());
return writerClass.cast(provider.createWriterInstance(null));
}
protected abstract List<? extends RenderedImage> getTestData();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -31,7 +31,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -336,7 +335,7 @@ public final class JPEGImageReader extends ImageReaderBase {
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
else if (bogusAdobeDCT
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
if (DEBUG) {
@@ -631,7 +630,7 @@ public final class JPEGImageReader extends ImageReaderBase {
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
return ColorProfiles.createProfile(profileData);
return ColorSpaces.createProfile(profileData);
}
}
@@ -951,6 +950,10 @@ public final class JPEGImageReader extends ImageReaderBase {
return null;
}
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
int iccChunkDataSize = segment.data.length - segmentDataStart;
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
return readICCProfileSafe(stream, allowBadIndexes);
}
else if (!segments.isEmpty()) {
@@ -986,6 +989,9 @@ public final class JPEGImageReader extends ImageReaderBase {
InputStream[] streams = new InputStream[count];
streams[badICC ? 0 : chunkNumber - 1] = stream;
int iccChunkDataSize = 0;
int iccSize = 0;
for (int i = 1; i < count; i++) {
Application segment = segments.get(i);
stream = new DataInputStream(segment.data());
@@ -998,6 +1004,12 @@ public final class JPEGImageReader extends ImageReaderBase {
int index = badICC ? i : chunkNumber - 1;
streams[index] = stream;
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
iccChunkDataSize += segment.data.length - segmentDataStart;
if (index == 0) {
iccSize = intFromBigEndian(segment.data, segmentDataStart);
}
}
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
@@ -1008,8 +1020,10 @@ public final class JPEGImageReader extends ImageReaderBase {
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
try {
ICC_Profile profile = ColorSpaces.readProfileRaw(stream);
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
}
catch (IOException | RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -30,7 +30,7 @@
package com.twelvemonkeys.imageio.metadata.jpeg;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.psd.PSD;
@@ -353,7 +353,7 @@ public final class JPEGSegmentUtil {
Directory psd = new PSDReader().read(stream);
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
if (iccEntry != null) {
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
ICC_Profile profile = ColorSpaces.createProfileRaw((byte[]) iccEntry.getValue());
System.err.println("ICC Profile: " + profile);
}
System.err.println("PSD: " + psd);
@@ -30,22 +30,27 @@
package com.twelvemonkeys.imageio.metadata.tiff;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageOutputStream;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
/**
* TIFFWriter
*
@@ -57,24 +62,7 @@ public final class TIFFWriter extends MetadataWriter {
private static final int WORD_LENGTH = 2;
private static final int LONGWORD_LENGTH = 4;
// TODO: We probably want to gloss over client code writing IFDs in BigTIFF (or vice versa) somehow... Silently convert IFD -> IFD8
private final boolean longOffsets;
private final int offsetSize;
private final long entryLength;
private final int directoryCountLength;
public TIFFWriter() {
this(LONGWORD_LENGTH);
}
public TIFFWriter(int offsetSize) {
this.offsetSize = Validate.isTrue(offsetSize == 4 || offsetSize == 8, offsetSize, "offsetSize must be 4 for TIFF or 8 for BigTIFF");
longOffsets = offsetSize == 8;
directoryCountLength = longOffsets ? 8 : WORD_LENGTH;
entryLength = 2 * WORD_LENGTH + 2 * offsetSize;
}
private static final long ENTRY_LENGTH = 12;
public boolean write(final Collection<? extends Entry> entries, final ImageOutputStream stream) throws IOException {
return write(new IFD(entries), stream);
@@ -103,7 +91,7 @@ public final class TIFFWriter extends MetadataWriter {
}
// Offset to next IFD (EOF)
writeOffset(stream, 0);
stream.writeInt(0);
return true;
}
@@ -112,12 +100,7 @@ public final class TIFFWriter extends MetadataWriter {
// Header
ByteOrder byteOrder = stream.getByteOrder();
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
stream.writeShort(longOffsets ? TIFF.BIGTIFF_MAGIC : TIFF.TIFF_MAGIC);
if (longOffsets) {
stream.writeShort(offsetSize); // Always 8 in this case
stream.writeShort(0);
}
stream.writeShort(42);
}
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
@@ -139,42 +122,37 @@ public final class TIFFWriter extends MetadataWriter {
long dataSize = computeDataSize(ordered);
// Offset to this IFD
final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize;
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
if (!isSubIFD) {
writeOffset(stream, ifdOffset);
dataOffset += offsetSize;
stream.writeInt(assertIntegerOffset(ifdOffset));
dataOffset += LONGWORD_LENGTH;
// Seek to offset
stream.seek(ifdOffset);
}
else {
dataOffset += directoryCountLength + ordered.size() * entryLength;
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
}
// Write directory
writeDirectoryCount(stream, ordered.size());
stream.writeShort(ordered.size());
for (Entry entry : ordered) {
// Write tag id, type & value count
// Write tag id
stream.writeShort((Integer) entry.getIdentifier());
// Write tag type
stream.writeShort(getType(entry));
writeValueCount(stream, getCount(entry));
// Write value count
stream.writeInt(getCount(entry));
// Write value
Object value = entry.getValue();
if (value instanceof Directory) {
if (value instanceof CompoundDirectory) {
// Can't have both nested and linked IFDs
throw new AssertionError("SubIFD cannot contain linked IFDs");
}
// We can't write offset here, we need to write value, as both LONG/IFD and LONG8/IFD8 is allowed
// TODO: Or possibly gloss over, by always writing IFD8 for BigTIFF?
long streamPosition = stream.getStreamPosition() + offsetSize;
writeValueInline(dataOffset, getType(entry), stream);
if (entry.getValue() instanceof Directory) {
// TODO: This could possibly be a compound directory, in which case the count should be > 1
stream.writeInt(assertIntegerOffset(dataOffset));
long streamPosition = stream.getStreamPosition();
stream.seek(dataOffset);
Directory subIFD = (Directory) value;
Directory subIFD = (Directory) entry.getValue();
writeIFD(subIFD, stream, true);
dataOffset += computeDataSize(subIFD);
stream.seek(streamPosition);
@@ -187,26 +165,8 @@ public final class TIFFWriter extends MetadataWriter {
return ifdOffset;
}
private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException {
if (longOffsets) {
stream.writeLong(count);
}
else {
stream.writeShort(count);
}
}
private void writeValueCount(ImageOutputStream stream, int count) throws IOException {
if (longOffsets) {
stream.writeLong(count);
}
else {
stream.writeInt(count);
}
}
public long computeIFDSize(final Collection<? extends Entry> directory) {
return directoryCountLength + computeDataSize(new IFD(directory)) + directory.size() * entryLength;
public long computeIFDSize(final Collection<Entry> directory) {
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
}
private long computeDataSize(final Directory directory) {
@@ -219,13 +179,13 @@ public final class TIFFWriter extends MetadataWriter {
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
}
if (length > offsetSize) {
if (length > LONGWORD_LENGTH) {
dataSize += length;
}
if (entry.getValue() instanceof Directory) {
Directory subIFD = (Directory) entry.getValue();
long subIFDSize = directoryCountLength + computeDataSize(subIFD) + subIFD.size() * entryLength;
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
dataSize += subIFDSize;
}
}
@@ -273,11 +233,11 @@ public final class TIFFWriter extends MetadataWriter {
short type = getType(entry);
long valueLength = getValueLength(type, getCount(entry));
if (valueLength <= offsetSize) {
if (valueLength <= LONGWORD_LENGTH) {
writeValueInline(entry.getValue(), type, stream);
// Pad
for (long i = valueLength; i < offsetSize; i++) {
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
stream.write(0);
}
@@ -388,28 +348,12 @@ public final class TIFFWriter extends MetadataWriter {
doubles = (double[]) value;
}
else {
throw new IllegalArgumentException("Unsupported type for TIFF DOUBLE: " + value.getClass());
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
}
stream.writeDoubles(doubles, 0, doubles.length);
break;
case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8:
if (longOffsets) {
long[] longs;
if (value instanceof long[]) {
longs = (long[]) value;
}
else {
throw new IllegalArgumentException("Unsupported type for TIFF LONG8: " + value.getClass());
}
stream.writeLongs(longs, 0, longs.length);
break;
}
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -433,7 +377,6 @@ public final class TIFFWriter extends MetadataWriter {
break;
case TIFF.TYPE_LONG:
case TIFF.TYPE_SLONG:
case TIFF.TYPE_IFD:
stream.writeInt(((Number) value).intValue());
break;
case TIFF.TYPE_RATIONAL:
@@ -448,13 +391,6 @@ public final class TIFFWriter extends MetadataWriter {
case TIFF.TYPE_DOUBLE:
stream.writeDouble(((Number) value).doubleValue());
break;
case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8:
case TIFF.TYPE_IFD8:
if (longOffsets) {
stream.writeLong(((Number) value).longValue());
break;
}
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -463,39 +399,18 @@ public final class TIFFWriter extends MetadataWriter {
}
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
writeOffset(stream, dataOffset);
stream.writeInt(assertIntegerOffset(dataOffset));
long position = stream.getStreamPosition();
stream.seek(dataOffset);
writeValueInline(value, type, stream);
stream.seek(position);
}
public void writeOffset(final ImageOutputStream output, long offset) throws IOException {
if (longOffsets) {
output.writeLong(assertLongOffset(offset));
}
else {
output.writeInt(assertIntegerOffset(offset)); // Treated as unsigned
}
}
public int offsetSize() {
return offsetSize;
}
private int assertIntegerOffset(final long offset) throws IIOException {
if (offset < 0 || offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
private int assertIntegerOffset(long offset) throws IIOException {
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
throw new IIOException("Integer overflow for TIFF stream");
}
return (int) offset;
}
private long assertLongOffset(final long offset) throws IIOException {
if (offset < 0) {
throw new IIOException("Long overflow for BigTIFF stream");
}
return offset;
}
}
@@ -1,345 +0,0 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.metadata.tiff;
import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.ImageOutputStreamImpl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* TIFFWriterTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
*/
public class BigTIFFWriterTest extends MetadataWriterAbstractTest {
@Override
protected InputStream getData() throws IOException {
// TODO: Replace with BigTIFF resource
return getResource("/exif/exif-jpeg-segment.bin").openStream();
}
protected TIFFReader createReader() {
return new TIFFReader();
}
@Override
protected TIFFWriter createWriter() {
return new TIFFWriter(8);
}
@Test
public void testWriteReadSimple() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(164, data.length);
assertEquals('M', data[0]);
assertEquals('M', data[1]);
assertEquals(0, data[2]);
assertEquals(43, data[3]);
Directory read = createReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(5, read.size());
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
}
@Test
public void testWriteMotorola() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(94, data.length);
assertEquals('M', data[0]);
assertEquals('M', data[1]);
assertEquals(0, data[2]);
assertEquals(43, data[3]);
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(2, read.size());
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
}
@Test
public void testWriteIntel() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(94, data.length);
assertEquals('I', data[0]);
assertEquals('I', data[1]);
assertEquals(43, data[2]);
assertEquals(0, data[3]);
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(2, read.size());
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
}
@Test
public void testNestingIFD8Long8() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertNotNull(read);
assertEquals(1, read.size());
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
}
@Test
public void testNestingIFDLong() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertNotNull(read);
assertEquals(1, read.size());
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
}
@Test
public void testReadWriteRead() throws IOException {
Directory original = createReader().read(getDataAsIIS());
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
try {
createWriter().write(original, imageOutput);
}
finally {
imageOutput.close();
}
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertEquals(original, read);
}
@Test
public void testComputeIFDSize() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(140, writer.computeIFDSize(entries));
assertEquals(148, stream.getStreamPosition());
}
@Test
public void testComputeIFDSizeNestedIFD8Long8() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(162, writer.computeIFDSize(entries));
assertEquals(170, stream.getStreamPosition());
}
@Test
public void testComputeIFDSizeNestedIFDLong() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(162, writer.computeIFDSize(entries)); // 162 = 5 * (8 + 20) + 22
assertEquals(170, stream.getStreamPosition()); // 170 = 8 + 5 * (8 + 20) + 22
}
private static class NullImageOutputStream extends ImageOutputStreamImpl {
@Override
public void write(int b) throws IOException {
streamPos++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
streamPos += len;
}
@Override
public int read() throws IOException {
throw new UnsupportedOperationException("Method read not implemented");
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
throw new UnsupportedOperationException("Method read not implemented");
}
}
}
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.metadata.tiff;
import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
@@ -85,7 +84,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -133,7 +132,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -168,7 +167,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -205,7 +204,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -248,10 +247,9 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
writer.write(new IFD(entries), stream);
assertEquals(94, writer.computeIFDSize(entries));
assertEquals(98, stream.getStreamPosition());
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
}
@Test
@@ -268,10 +266,9 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
writer.write(new IFD(entries), stream);
assertEquals(92, writer.computeIFDSize(entries)); // 92 = 5 * (2 + 12) + 22
assertEquals(96, stream.getStreamPosition()); // 96 = 4 + 5 * (2 + 12) + 22
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
}
private static class NullImageOutputStream extends ImageOutputStreamImpl {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -30,7 +30,7 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.stream.ImageInputStream;
@@ -56,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
profile = ColorProfiles.readProfileRaw(stream);
profile = ColorSpaces.readProfile(stream);
}
}
@@ -1,69 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.ImageWriteParam;
import java.util.Locale;
/**
* PSDImageWriteParam
*/
public final class PSDImageWriteParam extends ImageWriteParam {
PSDImageWriteParam() {
this(Locale.getDefault());
}
PSDImageWriteParam(final Locale locale) {
super(locale);
compressionTypes = new String[] {
"None",
"PackBits",
// Two ZIP compression types are defined in spec, never seen in the wild...
// "ZIP",
// "ZIP+Predictor",
};
compressionType = compressionTypes[1];
canWriteCompressed = true;
}
static int getCompressionType(final ImageWriteParam param) {
if (param == null || param.getCompressionMode() != MODE_EXPLICIT || param.getCompressionType() == null || param.getCompressionType().equals("None")) {
return PSD.COMPRESSION_NONE;
}
else if (param.getCompressionType().equals("PackBits")) {
return PSD.COMPRESSION_RLE;
}
throw new IllegalArgumentException(String.format("Unsupported compression type: %s", param.getCompressionType()));
}
}
@@ -1,400 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.RasterUtils;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.Collections;
/**
* Minimal ImageWriter for Adobe Photoshop Document (PSD) format.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
*/
public final class PSDImageWriter extends ImageWriterBase {
PSDImageWriter(ImageWriterSpi provider) {
super(provider);
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
// TODO: Implement
return null;
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
// TODO: Implement
return null;
}
@Override
public ImageWriteParam getDefaultWriteParam() {
return new PSDImageWriteParam(getLocale());
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage iioImage, ImageWriteParam param) throws IOException {
assertOutput();
imageOutput.setByteOrder(ByteOrder.BIG_ENDIAN);
RenderedImage image = iioImage.getRenderedImage();
SampleModel sampleModel = image.getSampleModel();
int colorComponents = image.getColorModel().getColorSpace().getNumComponents();
int channels = sampleModel.getNumBands();
int width = image.getWidth();
int height = image.getHeight();
int bits = getBitsPerSample(sampleModel);
int mode = getColorMode(image.getColorModel());
// TODO: Allow stream metadata or param to force PSD/PSB (version 1/2)?
boolean largeFormat = width > PSDHeader.PSD_MAX_SIZE || height > PSDHeader.PSD_MAX_SIZE;
new PSDHeader(channels, width, height, bits, mode, largeFormat).write(imageOutput);
writeColorModeData(image, mode);
writeImageResources(image, mode);
// Length of the layer and mask information section. (**PSB** length is 8 bytes.)
// TODO: Write an empty dummy layer here, if there's alpha? See below... Or see if Photoshop handles alpha if no layers at all...
if (largeFormat) {
imageOutput.writeLong(0);
}
else {
imageOutput.writeInt(0);
}
processImageStarted(0);
// Image Data Section (composite layer only).
// The last section of a Photoshop file contains the image pixel data.
// Image data is stored in planar order: first all the red data, then all the green data, etc.
// Each plane is stored in scan-line order, with no pad bytes,
final int compression = PSDImageWriteParam.getCompressionType(param);
imageOutput.writeShort(compression);
long byteCountPos = imageOutput.getStreamPosition();
// PSB (large format) byte counts are actually 32 bit offsets, not 16 bit as described in spec
int[] byteCounts = new int[compression == PSD.COMPRESSION_RLE ? height * channels : 0];
imageOutput.skipBytes(byteCounts.length * (largeFormat ? 4 : 2));
// TODO: Loop over tiles?
Raster tile = sampleModel.getTransferType() == DataBuffer.TYPE_INT && sampleModel instanceof SinglePixelPackedSampleModel
? RasterUtils.asByteRaster(image.getTile(0, 0))
: image.getTile(0, 0);
for (int channel = 0; channel < channels; channel++) {
// TODO: Alpha issues:
// 1. Alpha channel is written (but not read, because there are no layers, and alpha is considered present only if layer count is negative)
// - Can we write a small hidden layer, just to have -1 layers?
// 2. Alpha needs to be premultiplied against white background (to avoid inverse halo)
Raster channelRaster = tile.createChild(0, 0, width, height, 0, 0, new int[] {channel});
switch (bits) {
case 1:
// TODO: Figure out why we can't write multi-pixel packed 1 bit samples as bytes...
case 8:
write8BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
case 16:
write16BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
case 32:
write32BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
default:
throw new AssertionError(); // Should be guarded against already
}
processImageProgress(channel * 100f / channels);
}
updateByteCounts(byteCountPos, byteCounts, largeFormat);
processImageComplete();
}
private void updateByteCounts(long byteCountPos, int[] byteCounts, boolean largeFormat) throws IOException {
if (byteCounts.length == 0) {
return;
}
// Update byte counts for RLE
long pos = imageOutput.getStreamPosition();
imageOutput.seek(byteCountPos);
if (largeFormat) {
imageOutput.writeInts(byteCounts, 0, byteCounts.length);
}
else {
for (int byteCount : byteCounts) {
imageOutput.writeShort(byteCount);
}
}
imageOutput.seek(pos);
}
private void writeColorModeData(RenderedImage image, int mode) throws IOException {
if (mode == PSD.COLOR_MODE_INDEXED) {
IndexColorModel icm = (IndexColorModel) image.getColorModel();
// Indexed color images: length is 768; color data contains the color table for the image, in non-interleaved order.
imageOutput.writeInt(768);
byte[] colors = new byte[256];
icm.getReds(colors);
imageOutput.write(colors);
icm.getGreens(colors);
imageOutput.write(colors);
icm.getBlues(colors);
imageOutput.write(colors);
}
else {
imageOutput.writeInt(0);
}
}
private void writeImageResources(RenderedImage image, int mode) throws IOException {
// Length of image resource section. The length may be zero
imageOutput.writeInt(0);
long startImageResources = imageOutput.getStreamPosition();
// Write ICC color profile if not "native" sRGB or gray (or bitmap/indexed)
if (mode != PSD.COLOR_MODE_BITMAP && mode != PSD.COLOR_MODE_INDEXED) {
ColorSpace colorSpace = image.getColorModel().getColorSpace();
if (!colorSpace.isCS_sRGB() && colorSpace instanceof ICC_ColorSpace) {
ICC_Profile profile = ((ICC_ColorSpace) colorSpace).getProfile();
ICCProfile.writeData(imageOutput, profile);
}
}
// Write creator software (Exif)
Entry software = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO PSD writer " + originatingProvider.getVersion());
PSDEXIF1Data.writeData(imageOutput, Collections.singleton(software));
long endImageResources = imageOutput.getStreamPosition();
// Update image resources length
imageOutput.seek(startImageResources - 4);
imageOutput.writeInt((int) (endImageResources - startImageResources));
imageOutput.seek(endImageResources);
}
private void write8BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rowBytes = null;
for (int y = 0; y < height; y++) {
rowBytes = (byte[]) raster.getDataElements(0, y, width, 1, rowBytes);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < rowBytes.length; i++) {
rowBytes[i] = (byte) (0xff - rowBytes[i] & 0xff);
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.write(rowBytes);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (OutputStream stream = new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder())) {
stream.write(rowBytes);
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
private void write16BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
short[] row = null;
for (int y = 0; y < height; y++) {
row = (short[]) raster.getDataElements(0, y, width, 1, row);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < row.length; i++) {
row[i] = (short) (0xffff - row[i] & 0xffff);
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.writeShorts(row, 0, row.length);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
for (short sample : row) {
stream.writeShort(sample);
}
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
private void write32BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
int[] row = null;
for (int y = 0; y < height; y++) {
row = (int[]) raster.getDataElements(0, y, width, 1, row);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < row.length; i++) {
row[i] = 0xffffffff - row[i];
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.writeInts(row, 0, row.length);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
for (int sample : row) {
stream.writeInt(sample);
}
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
static int getColorMode(ColorModel colorModel) {
if (colorModel instanceof IndexColorModel) {
if (colorModel.getPixelSize() == 1) {
return PSD.COLOR_MODE_BITMAP;
}
else {
return PSD.COLOR_MODE_INDEXED;
}
}
int csType = colorModel.getColorSpace().getType();
switch (csType) {
case ColorSpace.TYPE_GRAY:
if (colorModel.getPixelSize() == 1) {
return PSD.COLOR_MODE_BITMAP;
}
else {
return PSD.COLOR_MODE_GRAYSCALE;
}
case ColorSpace.TYPE_RGB:
return PSD.COLOR_MODE_RGB;
case ColorSpace.TYPE_CMYK:
return PSD.COLOR_MODE_CMYK;
default:
throw new IllegalArgumentException("Unsupported color space type for PSD: " + csType);
}
}
static int getBitsPerSample(SampleModel sampleModel) {
int bits = sampleModel.getSampleSize(0);
for (int i = 1; i < sampleModel.getNumBands(); i++) {
if (bits != sampleModel.getSampleSize(i)) {
throw new IllegalArgumentException("All samples must be of equal size for PSD: " + bits);
}
}
switch (bits) {
case 1:
case 8:
case 16:
case 32:
return (short) bits;
default:
throw new IllegalArgumentException("Unsupported sample size for PSD (expected 1, 8, 16 or 32): " + bits);
}
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File(args[0]));
ImageIO.write(image, "PSD", new File("test.psd"));
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import java.util.Locale;
import static com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter.getBitsPerSample;
import static com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter.getColorMode;
/**
* PSDImageWriterSpi
*/
public final class PSDImageWriterSpi extends ImageWriterSpiBase {
public PSDImageWriterSpi() {
super(new PSDProviderInfo());
}
@Override
public boolean canEncodeImage(ImageTypeSpecifier type) {
// PSD supports:
// - 1, 8, 16 or 32 bit/sample
// - Number of samples <= 56
// - RGB, CMYK, Gray, Indexed color
try {
getBitsPerSample(type.getSampleModel());
getColorMode(type.getColorModel());
}
catch (IllegalArgumentException ignore) {
// We can't write this type
return false;
}
return type.getNumBands() <= 56; // Can't be negative
}
@Override
public ImageWriter createWriterInstance(Object extension) {
return new PSDImageWriter(this);
}
public String getDescription(final Locale pLocale) {
return "Adobe Photoshop Document (PSD) image writer";
}
}
@@ -54,8 +54,8 @@ final class PSDProviderInfo extends ReaderWriterProviderInfo {
},
"com.twelvemonkeys.imageio.plugins.psd.PSDImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter",
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
null,
null,
false, null, null, null, null,
true, PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, null, null
);
@@ -1 +0,0 @@
com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi
@@ -1,37 +0,0 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.List;
/**
* PSDImageWriterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageWriterTest.java,v 1.0 05/05/2021 haraldk Exp$
*/
public class PSDImageWriterTest extends ImageWriterAbstractTest<PSDImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new PSDImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_INDEXED)
);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -1,67 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import java.util.Locale;
/**
* BigTIFFImageWriterSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BigTIFFImageWriterSpi.java,v 1.0 18.09.13 12:46 haraldk Exp$
*/
public final class BigTIFFImageWriterSpi extends ImageWriterSpiBase {
// TODO: Implement canEncodeImage better
public BigTIFFImageWriterSpi() {
super(new BigTIFFProviderInfo());
}
@Override
public boolean canEncodeImage(final ImageTypeSpecifier type) {
// TODO: Test bit depths compatibility
return true;
}
@Override
public TIFFImageWriter createWriterInstance(final Object extension) {
return new TIFFImageWriter(this);
}
@Override
public String getDescription(final Locale locale) {
return "BigTIFF image writer";
}
}
@@ -50,8 +50,8 @@ final class BigTIFFProviderInfo extends ReaderWriterProviderInfo {
},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"},
null,
null,
false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
);
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -2494,12 +2493,12 @@ public final class TIFFImageReader extends ImageReaderBase {
return value;
}
private ICC_Profile getICCProfile() {
private ICC_Profile getICCProfile() throws IOException {
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
if (entry != null) {
try {
return ColorProfiles.createProfile((byte[]) entry.getValue());
return ColorSpaces.createProfile((byte[]) entry.getValue());
}
catch (CMMException | IllegalArgumentException e) {
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
@@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
@@ -204,26 +204,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
long streamPosition = imageOutput.getStreamPosition();
long ifdSize = tiffWriter.computeIFDSize(entries.values());
long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize();
long stripOffset = streamPosition + 4 + ifdSize + 4;
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
nextIFDPointerOffset = imageOutput.getStreamPosition();
// If we have a previous IFD, update pointer
if (streamPosition > lastIFDPointerOffset) {
imageOutput.seek(lastIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, ifdPointer);
imageOutput.writeInt((int) ifdPointer);
imageOutput.seek(nextIFDPointerOffset);
}
tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later
imageOutput.writeInt(0); // Update next IFD pointer later
}
else {
tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later
imageOutput.writeInt(0); // Update current IFD pointer later
}
long stripOffset = imageOutput.getStreamPosition();
@@ -262,7 +262,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
nextIFDPointerOffset = imageOutput.getStreamPosition();
@@ -270,10 +270,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
// However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset
// TODO: Refactor writeIFD to take an offset
imageOutput.seek(lastIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, ifdPointer);
imageOutput.writeInt((int) ifdPointer);
imageOutput.seek(nextIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later
imageOutput.writeInt(0); // Next IFD pointer updated later
}
return nextIFDPointerOffset;
@@ -855,7 +855,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
// * is not sRGB (assuming sRGB to be the default RGB interpretation), and
// * is not gray scale (assuming photometric either BlackIsZero or WhiteIsZero)
ColorSpace colorSpace = colorModel.getColorSpace();
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorProfiles.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorSpaces.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
}
}
@@ -959,15 +959,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
configureStreamByteOrder(streamMetadata, imageOutput);
writingSequence = true;
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
sequenceTIFFWriter = new TIFFWriter();
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
sequenceLastIFDPos = imageOutput.getStreamPosition();
}
private boolean isBigTIFF() throws IOException {
return "bigtiff".equalsIgnoreCase(getFormatName());
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
if (!writingSequence) {
@@ -1023,7 +1019,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
BufferedImage original;
// BufferedImage original = ImageIO.read(file);
try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) {
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
try {
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
if (!readers.hasNext()) {
@@ -1053,6 +1050,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
original = reader.read(0, param);
}
finally {
inputStream.close();
}
System.err.println("original: " + original);
@@ -1088,11 +1088,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
// output.deleteOnExit();
System.err.println("output: " + output);
TIFFImageWriter writer = new TIFFImageWriter(new TIFFImageWriterSpi());
TIFFImageWriter writer = new TIFFImageWriter(null);
// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
ImageOutputStream stream = ImageIO.createImageOutputStream(output);
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
try {
writer.setOutput(stream);
ImageWriteParam param = writer.getDefaultWriteParam();
@@ -1110,6 +1111,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
writer.write(null, new IIOImage(image, null, null), param);
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
}
finally {
stream.close();
}
System.err.println("output.length: " + output.length());
@@ -1,2 +1 @@
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi
@@ -1,117 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
import static org.junit.Assert.assertArrayEquals;
/**
* BigTIFFImageWriterTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BigTIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$
*/
public class BigTIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new BigTIFFImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY)
);
}
@Test
public void roundrtip() throws IOException {
TIFFImageWriter writer = createWriter();
ImageReader reader = ImageIO.getImageReader(writer);
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/bigtiff/BigTIFF.tif"))) {
reader.setInput(input);
BufferedImage image = reader.read(0);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(temp)) {
writer.setOutput(output);
writer.write(image);
}
finally {
writer.dispose();
}
// Validate we actually write BigTIFF
byte[] data = temp.toByteArray();
assertArrayEquals(new byte[] { 'M', 'M', 0, TIFF.BIGTIFF_MAGIC}, Arrays.copyOf(data, 4));
// Read image back and see that it is the same
try (ImageInputStream stream = new ByteArrayImageInputStream(data)) {
reader.setInput(stream);
BufferedImage after = reader.read(0);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertRGBEquals("Pixel values differ: ", image.getRGB(x, y), after.getRGB(x, y), 0);
}
}
}
}
finally {
reader.dispose();
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -32,7 +32,6 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
@@ -288,7 +287,7 @@ final class WebPImageReader extends ImageReaderBase {
long chunkStart = imageInput.getStreamPosition();
if (nextChunk == WebP.CHUNK_ICCP) {
iccProfile = ColorProfiles.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
iccProfile = ColorSpaces.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
}
else {
processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk)));
@@ -366,7 +365,7 @@ final class WebPImageReader extends ImageReaderBase {
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
readHeader(imageIndex);
if (iccProfile != null && !ColorProfiles.isCS_sRGB(iccProfile)) {
if (iccProfile != null && !ColorSpaces.isCS_sRGB(iccProfile)) {
ICC_ColorSpace colorSpace = ColorSpaces.createColorSpace(iccProfile);
int[] bandOffsets = header.containsALPH ? new int[] {0, 1, 2, 3} : new int[] {0, 1, 2};
return ImageTypeSpecifiers.createInterleaved(colorSpace, bandOffsets, DataBuffer.TYPE_BYTE, header.containsALPH, false);
@@ -526,7 +525,7 @@ final class WebPImageReader extends ImageReaderBase {
if (!iccProfile.equals(destinationProfile)) {
if (DEBUG) {
System.err.println("Converting from " + iccProfile + " to " + (ColorProfiles.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
System.err.println("Converting from " + iccProfile + " to " + (ColorSpaces.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
}
WritableRaster raster = colorModel.hasAlpha()
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Twelvemonkeys</name>
@@ -86,7 +86,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.8.0</tag>
<tag>twelvemonkeys-3.7.0</tag>
</scm>
<properties>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>