mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 488d6da71a | |||
| b19e45a911 | |||
| a5b6cf898d | |||
| ce597c437d | |||
| fa4b3787d0 | |||
| 3c18e8a510 | |||
| 206481038e | |||
| cff4d88991 | |||
| 8ea8e061a9 | |||
| 101ad18f71 | |||
| 08b441a17e | |||
| b6c76d8566 | |||
| 3f74b2ddf3 | |||
| 46b48f32c3 | |||
| a07d0285fe | |||
| 98de4ad4ec | |||
| aa82612765 | |||
| 9213da3184 | |||
| a5e2226a5a | |||
| 773bedccca | |||
| 6bcc17a020 | |||
| 37d1da9b9d | |||
| 8cf1405dfc | |||
| 8c37d19928 | |||
| 87cd506fdd | |||
| e0c7edebbd | |||
| 5d13bd653f | |||
| 2d974874a9 | |||
| f625622b10 | |||
| dbdd7ae3f1 | |||
| 73883ebf99 | |||
| 970b238066 | |||
| 6cb8ac4b68 | |||
| 1a2a4edfe8 | |||
| a12a1f73b5 | |||
| 46bfdd93d8 | |||
| 447ef6b8eb | |||
| c19b6ede0a | |||
| 401c6355a1 | |||
| be0cf16124 | |||
| 47b0cd6e9a | |||
| b52ab149b3 | |||
| 900c26a5ac | |||
| 7233c593ac | |||
| 8159ba1245 | |||
| 92581a077b | |||
| f87b4d6748 | |||
| 0495f6e266 | |||
| bc7c8ba20c | |||
| 0c270f8343 | |||
| e1db332dca | |||
| 016977e382 | |||
| 134eecc59f | |||
| 16c78052ee | |||
| b51e8ccf6e | |||
| 76a9ff1122 | |||
| e9996f096f | |||
| 93d42e1c24 | |||
| 5824167600 | |||
| 126956ebd0 | |||
| d54ceba3ff | |||
| 11ee7e5e23 | |||
| 954dffd213 | |||
| b7b2a61c93 | |||
| 3cf6a4b836 | |||
| a93be99933 | |||
| b19bd1441f | |||
| 482af60534 | |||
| f55a6d30dd | |||
| e5f6227479 | |||
| 01d6fc8b49 | |||
| f133ea7d61 | |||
| 3d5cf0eecd | |||
| 975e23c28f | |||
| be60f307f7 | |||
| 3e783fba92 | |||
| 35ffe29e03 | |||
| 55155aa61c | |||
| 9e9decd5dd | |||
| 3c0b78549e | |||
| 46db5a2fbf | |||
| f6a9477279 | |||
| b7192ae857 | |||
| c0b2769e3b | |||
| 6c27ec6b30 | |||
| 0c90196357 | |||
| 48f82a159f | |||
| 7105738811 | |||
| 10aa4ba41e | |||
| 6fb06da4d7 | |||
| a963e1c355 | |||
| 966a9da45d | |||
| 319b2c4e18 | |||
| e9bf7d080c | |||
| fb3691e2ee | |||
| 25f9cc5c55 | |||
| 94777ddc96 | |||
| a4c12d0d64 | |||
| 08a69886b1 | |||
| ab85ff0ec8 |
@@ -9,11 +9,11 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
java: [ 8, 11, 17, 18 ]
|
||||
java: [ 8, 11, 17 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ matrix.java }}
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Run Tests
|
||||
run: mvn test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
uses: mikepenz/action-junit-report@v2
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
@@ -35,11 +35,11 @@ jobs:
|
||||
matrix:
|
||||
kcms: [ true, false ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- run: |
|
||||
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
|
||||
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'jdkfile'
|
||||
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Run Tests
|
||||
run: mvn test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
uses: mikepenz/action-junit-report@v2
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
@@ -66,9 +66,9 @@ jobs:
|
||||
if: github.ref == 'refs/heads/master' # only perform on latest master
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Maven Central
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v2
|
||||
with: # running setup-java again overwrites the settings.xml
|
||||
distribution: 'temurin'
|
||||
java-version: '8'
|
||||
|
||||
@@ -50,7 +50,7 @@ As there is lots of legacy data out there, we see the need for open implementati
|
||||
|
||||
|
||||
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
|
||||
and make sure you use an updated and secure version.*
|
||||
and make sure you use version 1.14 or later.*
|
||||
|
||||
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
|
||||
[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html).
|
||||
@@ -523,7 +523,7 @@ q: What about JAI? Several of the formats are already supported by JAI.
|
||||
|
||||
a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues.
|
||||
The most obvious being:
|
||||
- It's not actively developed. No issue has been fixed for years.
|
||||
- It's not actively developed. No issues has been fixed for years.
|
||||
- To get full format support, you need native libs.
|
||||
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
|
||||
Some environments may also prevent deployment of native libs, which brings us back to square one.
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
|
||||
<batik.version>1.15</batik.version>
|
||||
<batik.version>1.14</batik.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
-627
@@ -1,627 +0,0 @@
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* Base class for easy read-only implementation of the standard image metadata format.
|
||||
* Chroma, Data and Transparency nodes values are based on the required
|
||||
* {@link ImageTypeSpecifier}.
|
||||
* Other values or overrides may be specified using the builder.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
public class StandardImageMetadataSupport extends AbstractMetadata {
|
||||
|
||||
// The only required field, most standard metadata can be extracted from the type
|
||||
private final ImageTypeSpecifier type;
|
||||
protected final ColorSpaceType colorSpaceType;
|
||||
protected final boolean blackIsZero;
|
||||
private final IndexColorModel palette;
|
||||
protected final String compressionName;
|
||||
protected final boolean compressionLossless;
|
||||
protected final PlanarConfiguration planarConfiguration;
|
||||
private final int[] bitsPerSample;
|
||||
private final int[] significantBits;
|
||||
private final int[] sampleMSB;
|
||||
protected final Double pixelAspectRatio;
|
||||
protected final ImageOrientation orientation;
|
||||
protected final String formatVersion;
|
||||
protected final SubimageInterpretation subimageInterpretation;
|
||||
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
|
||||
private final Collection<TextEntry> textEntries;
|
||||
|
||||
protected StandardImageMetadataSupport(Builder builder) {
|
||||
notNull(builder, "builder");
|
||||
|
||||
// Baseline
|
||||
type = builder.type;
|
||||
|
||||
// Chroma
|
||||
colorSpaceType = builder.colorSpaceType;
|
||||
blackIsZero = builder.blackIsZero;
|
||||
palette = builder.palette;
|
||||
|
||||
// Compression
|
||||
compressionName = builder.compressionName;
|
||||
compressionLossless = builder.compressionLossless;
|
||||
|
||||
// Data
|
||||
planarConfiguration = builder.planarConfiguration;
|
||||
bitsPerSample = builder.bitsPerSample;
|
||||
significantBits = builder.significantBits;
|
||||
sampleMSB = builder.sampleMSB;
|
||||
|
||||
// Dimension
|
||||
orientation = builder.orientation;
|
||||
pixelAspectRatio = builder.pixelAspectRatio;
|
||||
|
||||
// Document
|
||||
formatVersion = builder.formatVersion;
|
||||
documentCreationTime = builder.documentCreationTime;
|
||||
subimageInterpretation = builder.subimageInterpretation;
|
||||
|
||||
// Text
|
||||
textEntries = builder.textEntries;
|
||||
}
|
||||
|
||||
public static Builder builder(ImageTypeSpecifier type) {
|
||||
return new Builder(type);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final ImageTypeSpecifier type;
|
||||
private ColorSpaceType colorSpaceType;
|
||||
private boolean blackIsZero = true;
|
||||
private IndexColorModel palette;
|
||||
private String compressionName;
|
||||
private boolean compressionLossless = true;
|
||||
private PlanarConfiguration planarConfiguration;
|
||||
public int[] bitsPerSample;
|
||||
private int[] significantBits;
|
||||
private int[] sampleMSB;
|
||||
private Double pixelAspectRatio;
|
||||
private ImageOrientation orientation = ImageOrientation.Normal;
|
||||
private String formatVersion;
|
||||
private SubimageInterpretation subimageInterpretation;
|
||||
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
|
||||
private final Collection<TextEntry> textEntries = new ArrayList<>();
|
||||
|
||||
protected Builder(ImageTypeSpecifier type) {
|
||||
this.type = notNull(type, "type");
|
||||
}
|
||||
|
||||
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
|
||||
this.colorSpaceType = colorSpaceType;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBlackIsZero(boolean blackIsZero) {
|
||||
this.blackIsZero = blackIsZero;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPalette(IndexColorModel palette) {
|
||||
this.palette = palette;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCompressionTypeName(String compressionName) {
|
||||
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCompressionLossless(boolean lossless) {
|
||||
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
|
||||
this.planarConfiguration = planarConfiguration;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBitsPerSample(int... bitsPerSample) {
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSignificantBitsPerSample(int... significantBits) {
|
||||
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
|
||||
significantBits,
|
||||
String.format("single value or %d values expected", type.getNumBands()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSampleMSB(int... sampleMSB) {
|
||||
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
|
||||
sampleMSB,
|
||||
String.format("single value or %d values expected", type.getNumBands()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
|
||||
this.pixelAspectRatio = pixelAspectRatio;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOrientation(ImageOrientation orientation) {
|
||||
this.orientation = notNull(orientation, "orientation");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFormatVersion(String formatVersion) {
|
||||
this.formatVersion = notNull(formatVersion, "formatVersion");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
|
||||
this.subimageInterpretation = interpretation;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDocumentCreationTime(Calendar creationTime) {
|
||||
this.documentCreationTime = creationTime;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTextEntries(Map<String, String> entries) {
|
||||
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
|
||||
}
|
||||
|
||||
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
|
||||
TextEntry[] result = new TextEntry[entries.size()];
|
||||
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> entry : entries) {
|
||||
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return Arrays.asList(result);
|
||||
}
|
||||
|
||||
public Builder withTextEntries(Collection<TextEntry> entries) {
|
||||
this.textEntries.addAll(notNull(entries, "entries"));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTextEntry(String keyword, String value) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIOMetadata build() {
|
||||
return new StandardImageMetadataSupport(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum ColorSpaceType {
|
||||
XYZ(3),
|
||||
Lab(3),
|
||||
Luv(3),
|
||||
YCbCr(3),
|
||||
Yxy(3),
|
||||
YCCK(4),
|
||||
PhotoYCC(3),
|
||||
RGB(3),
|
||||
GRAY(1),
|
||||
HSV(3),
|
||||
HLS(3),
|
||||
CMYK(3),
|
||||
CMY(3),
|
||||
|
||||
// Generic types (so much extra work, because Java names can't start with a number, phew...)
|
||||
GENERIC_2CLR(2, "2CLR"),
|
||||
GENERIC_3CLR(3, "3CLR"),
|
||||
GENERIC_4CLR(4, "4CLR"),
|
||||
GENERIC_5CLR(5, "5CLR"),
|
||||
GENERIC_6CLR(6, "6CLR"),
|
||||
GENERIC_7CLR(7, "7CLR"),
|
||||
GENERIC_8CLR(8, "8CLR"),
|
||||
GENERIC_9CLR(9, "9CLR"),
|
||||
GENERIC_ACLR(0xA, "ACLR"),
|
||||
GENERIC_BCLR(0xB, "BCLR"),
|
||||
GENERIC_CCLR(0xC, "CCLR"),
|
||||
GENERIC_DCLR(0xD, "DCLR"),
|
||||
GENERIC_ECLR(0xE, "ECLR"),
|
||||
GENERIC_FCLR(0xF, "FCLR");
|
||||
|
||||
final int numChannels;
|
||||
private final String nameOverride;
|
||||
|
||||
ColorSpaceType(int numChannels) {
|
||||
this(numChannels, null);
|
||||
}
|
||||
ColorSpaceType(int numChannels, String nameOverride) {
|
||||
this.numChannels = numChannels;
|
||||
this.nameOverride = nameOverride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return nameOverride != null ? nameOverride : super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
protected enum PlanarConfiguration {
|
||||
PixelInterleaved,
|
||||
PlaneInterleaved,
|
||||
LineInterleaved,
|
||||
TileInterleaved
|
||||
}
|
||||
|
||||
protected enum ImageOrientation {
|
||||
Normal,
|
||||
Rotate90,
|
||||
Rotate180,
|
||||
Rotate270,
|
||||
FlipH,
|
||||
FlipV,
|
||||
FlipHRotate90,
|
||||
FlipVRotate90
|
||||
}
|
||||
|
||||
protected enum SubimageInterpretation {
|
||||
Standalone,
|
||||
SinglePage,
|
||||
FullResolution,
|
||||
ReducedResolution,
|
||||
PyramidLayer,
|
||||
Preview,
|
||||
VolumeSlice,
|
||||
ObjectView,
|
||||
Panorama,
|
||||
AnimationFrame,
|
||||
TransparencyMask,
|
||||
CompositingLayer,
|
||||
SpectralSlice,
|
||||
Unknown
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
|
||||
|
||||
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
|
||||
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
|
||||
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
|
||||
|
||||
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
|
||||
chromaNode.appendChild(colorSpaceTypeNode);
|
||||
colorSpaceTypeNode.setAttribute("name", csType.toString());
|
||||
|
||||
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
|
||||
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
|
||||
chromaNode.appendChild(numChannelsNode);
|
||||
|
||||
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
|
||||
chromaNode.appendChild(blackIsZeroNode);
|
||||
|
||||
if (colorModel instanceof IndexColorModel || palette != null) {
|
||||
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
|
||||
|
||||
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
|
||||
chromaNode.appendChild(paletteNode);
|
||||
|
||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
|
||||
paletteNode.appendChild(paletteEntryNode);
|
||||
|
||||
paletteEntryNode.setAttribute("index", Integer.toString(i));
|
||||
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
|
||||
// Assumption: BITMASK transparency will use single transparent pixel
|
||||
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
|
||||
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
|
||||
}
|
||||
}
|
||||
|
||||
if (colorMap.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
|
||||
chromaNode.appendChild(backgroundIndexNode);
|
||||
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: BackgroundColor?
|
||||
|
||||
return chromaNode;
|
||||
}
|
||||
|
||||
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
|
||||
switch (colorSpace.getType()) {
|
||||
case ColorSpace.TYPE_XYZ:
|
||||
return XYZ;
|
||||
case ColorSpace.TYPE_Lab:
|
||||
return Lab;
|
||||
case ColorSpace.TYPE_Luv:
|
||||
return Luv;
|
||||
case ColorSpace.TYPE_YCbCr:
|
||||
return YCbCr;
|
||||
case ColorSpace.TYPE_Yxy:
|
||||
return Yxy;
|
||||
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
|
||||
case ColorSpace.TYPE_RGB:
|
||||
return RGB;
|
||||
case ColorSpace.TYPE_GRAY:
|
||||
return GRAY;
|
||||
case ColorSpace.TYPE_HSV:
|
||||
return HSV;
|
||||
case ColorSpace.TYPE_HLS:
|
||||
return HLS;
|
||||
case ColorSpace.TYPE_CMYK:
|
||||
return CMYK;
|
||||
case ColorSpace.TYPE_CMY:
|
||||
return CMY;
|
||||
default:
|
||||
int numComponents = colorSpace.getNumComponents();
|
||||
if (numComponents == 1) {
|
||||
return GRAY;
|
||||
}
|
||||
else if (numComponents < 16) {
|
||||
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
|
||||
}
|
||||
|
||||
protected static final class TextEntry {
|
||||
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
|
||||
|
||||
final String keyword;
|
||||
final String value;
|
||||
final String language;
|
||||
final String encoding;
|
||||
final String compression;
|
||||
|
||||
public TextEntry(final String keyword, final String value) {
|
||||
this(keyword, value, null, null, null);
|
||||
}
|
||||
|
||||
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
|
||||
this.keyword = keyword;
|
||||
this.value = notNull(value, "value");
|
||||
this.language = language;
|
||||
this.encoding = encoding;
|
||||
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (compressionName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", compressionName);
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", booleanString(compressionLossless));
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
protected static String booleanString(boolean booleanValue) {
|
||||
return booleanValue ? "TRUE" : "FALSE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
|
||||
dataNode.appendChild(planarConfigurationNode);
|
||||
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
|
||||
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
|
||||
|
||||
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
|
||||
? "Index"
|
||||
: sampleFormat(type.getSampleModel());
|
||||
|
||||
if (sampleFormatValue != null) {
|
||||
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormatNode.setAttribute("value", sampleFormatValue);
|
||||
dataNode.appendChild(sampleFormatNode);
|
||||
}
|
||||
|
||||
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
|
||||
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
|
||||
dataNode.appendChild(bitsPerSampleNode);
|
||||
|
||||
if (significantBits != null) {
|
||||
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
|
||||
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
|
||||
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
|
||||
dataNode.appendChild(significantBitsPerSampleNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleMSB != null) {
|
||||
// TODO: Only if different from default!
|
||||
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
|
||||
dataNode.appendChild(sampleMSBNode);
|
||||
}
|
||||
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
private static String createListValue(final int itemCount, final int... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static String sampleFormat(SampleModel sampleModel) {
|
||||
switch (sampleModel.getDataType()) {
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
case DataBuffer.TYPE_INT:
|
||||
if (sampleModel instanceof ComponentSampleModel) {
|
||||
return "SignedIntegral";
|
||||
}
|
||||
// Otherwise fall-through, most likely a *PixelPackedSampleModel
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
return "UnsignedIntegral";
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
case DataBuffer.TYPE_DOUBLE:
|
||||
return "Real";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
|
||||
if (pixelAspectRatio != null) {
|
||||
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
|
||||
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
|
||||
dimensionNode.appendChild(pixelAspectRatioNode);
|
||||
}
|
||||
|
||||
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientationNode.setAttribute("value", orientation.toString());
|
||||
dimensionNode.appendChild(imageOrientationNode);
|
||||
|
||||
return dimensionNode.hasChildNodes() ? dimensionNode : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
|
||||
|
||||
if (formatVersion != null) {
|
||||
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
|
||||
documentNode.appendChild(formatVersionNode);
|
||||
formatVersionNode.setAttribute("value", formatVersion);
|
||||
}
|
||||
|
||||
if (subimageInterpretation != null) {
|
||||
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
|
||||
documentNode.appendChild(subImageInterpretationNode);
|
||||
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
|
||||
}
|
||||
|
||||
if (documentCreationTime != null) {
|
||||
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
|
||||
documentNode.appendChild(imageCreationTimeNode);
|
||||
|
||||
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
|
||||
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
|
||||
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
|
||||
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
|
||||
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
|
||||
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
|
||||
}
|
||||
|
||||
return documentNode.hasChildNodes() ? documentNode : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (textEntries.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||
|
||||
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||
|
||||
for (TextEntry entry : textEntries) {
|
||||
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
|
||||
textNode.appendChild(textEntryNode);
|
||||
if (entry.keyword != null) {
|
||||
textEntryNode.setAttribute("keyword", entry.keyword);
|
||||
}
|
||||
textEntryNode.setAttribute("value", entry.value);
|
||||
if (entry.language != null) {
|
||||
textEntryNode.setAttribute("language", entry.language);
|
||||
}
|
||||
if (entry.encoding != null) {
|
||||
textEntryNode.setAttribute("encoding", entry.encoding);
|
||||
}
|
||||
if (entry.compression != null) {
|
||||
textEntryNode.setAttribute("compression", entry.compression);
|
||||
}
|
||||
}
|
||||
|
||||
return textNode;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
|
||||
|
||||
ColorModel colorModel = type.getColorModel();
|
||||
|
||||
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
|
||||
transparencyNode.appendChild(alphaNode);
|
||||
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
|
||||
|
||||
if (colorModel instanceof IndexColorModel) {
|
||||
IndexColorModel icm = (IndexColorModel) colorModel;
|
||||
if (icm.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
|
||||
transparencyNode.appendChild(transparentIndexNode);
|
||||
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
return transparencyNode;
|
||||
}
|
||||
}
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.stream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
/**
|
||||
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
|
||||
* and provides greatly improved performance
|
||||
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
|
||||
* for shorter reads, like single byte or bit reads.
|
||||
*/
|
||||
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
|
||||
private static final Closeable CLOSEABLE_STUB = new Closeable() {
|
||||
@Override public void close() {}
|
||||
};
|
||||
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
|
||||
private byte[] buffer = byteBuffer.array();
|
||||
private int bufferPos;
|
||||
private int bufferLimit;
|
||||
|
||||
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||
private final byte[] integralCacheArray = integralCache.array();
|
||||
|
||||
private SeekableByteChannel channel;
|
||||
private Closeable closeable;
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
|
||||
*
|
||||
* @param file a {@code File} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||
* @throws IOException if an I/O error occurs while opening the file.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final File file) throws IOException {
|
||||
this(notNull(file, "file").toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
|
||||
*
|
||||
* @param file a {@code Path} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
|
||||
* @throws IOException if an I/O error occurs while opening the file.
|
||||
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final Path file) throws IOException {
|
||||
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
|
||||
*
|
||||
* @param file a {@code RandomAccessFile} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final RandomAccessFile file) {
|
||||
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
|
||||
this(notNull(file, "file").getChannel(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
|
||||
* <p>
|
||||
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
|
||||
* </p>
|
||||
*
|
||||
* @param inputStream a {@code FileInputStream} to read from.
|
||||
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
|
||||
this(notNull(inputStream, "inputStream").getChannel(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
|
||||
* <p>
|
||||
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
|
||||
* </p>
|
||||
*
|
||||
* @param channel a {@code SeekableByteChannel} to read from.
|
||||
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
|
||||
this(notNull(channel, "channel"), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
|
||||
* <p>
|
||||
* Closing this stream will close the {@code Cache}.
|
||||
* </p>
|
||||
*
|
||||
* @param cache a {@code SeekableByteChannel} to read from.
|
||||
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||
*/
|
||||
BufferedChannelImageInputStream(final Cache cache) {
|
||||
this(notNull(cache, "cache"), true);
|
||||
}
|
||||
|
||||
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
|
||||
this.channel = notNull(channel, "channel");
|
||||
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean fillBuffer() throws IOException {
|
||||
byteBuffer.rewind();
|
||||
int length = channel.read(byteBuffer);
|
||||
bufferPos = 0;
|
||||
bufferLimit = max(length, 0);
|
||||
|
||||
return bufferLimit > 0;
|
||||
}
|
||||
|
||||
private boolean bufferEmpty() {
|
||||
return bufferPos >= bufferLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByteOrder(ByteOrder byteOrder) {
|
||||
super.setByteOrder(byteOrder);
|
||||
integralCache.order(byteOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (bufferEmpty() && !fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
|
||||
return buffer[bufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
checkClosed();
|
||||
bitOffset = 0;
|
||||
|
||||
if (bufferEmpty()) {
|
||||
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||
if (length >= buffer.length) {
|
||||
return readDirect(bytes, offset, length);
|
||||
}
|
||||
else if (!fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int fromBuffer = readBuffered(bytes, offset, length);
|
||||
|
||||
if (length > fromBuffer) {
|
||||
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||
// we'll read as much as possible from the buffer, and the rest directly after
|
||||
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||
}
|
||||
|
||||
return fromBuffer;
|
||||
}
|
||||
|
||||
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||
bufferLimit = 0;
|
||||
|
||||
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
|
||||
int read = 0;
|
||||
while (wrapped.hasRemaining()) {
|
||||
int count = channel.read(wrapped);
|
||||
if (count == -1) {
|
||||
if (read == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
read += count;
|
||||
}
|
||||
|
||||
streamPos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||
// Read as much as possible from buffer
|
||||
int available = Math.min(bufferLimit - bufferPos, length);
|
||||
|
||||
if (available > 0) {
|
||||
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||
bufferPos += available;
|
||||
streamPos += available;
|
||||
}
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
public long length() {
|
||||
// WTF?! This method is allowed to throw IOException in the interface...
|
||||
try {
|
||||
checkClosed();
|
||||
return channel.size();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
buffer = null;
|
||||
byteBuffer = null;
|
||||
|
||||
channel = null;
|
||||
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
finally {
|
||||
closeable = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to override the readShort(), readInt() and readLong() methods,
|
||||
// because the implementations in ImageInputStreamImpl expects the
|
||||
// read(byte[], int, int) to always read the expected number of bytes,
|
||||
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||
// random places...
|
||||
// Notes:
|
||||
// * readUnsignedXx() is covered by their signed counterparts
|
||||
// * readChar() is covered by readShort()
|
||||
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||
// respectively.
|
||||
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
readFully(integralCacheArray, 0, 2);
|
||||
|
||||
return integralCache.getShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
readFully(integralCacheArray, 0, 4);
|
||||
|
||||
return integralCache.getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
readFully(integralCacheArray, 0, 8);
|
||||
|
||||
return integralCache.getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (position < flushedPos) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
|
||||
if (streamPos == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimized to not invalidate buffer if new position is within current buffer
|
||||
long newBufferPos = bufferPos + position - streamPos;
|
||||
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||
bufferPos = (int) newBufferPos;
|
||||
}
|
||||
else {
|
||||
// Will invalidate buffer
|
||||
bufferLimit = 0;
|
||||
channel.position(position);
|
||||
}
|
||||
|
||||
streamPos = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBefore(final long pos) throws IOException {
|
||||
super.flushBefore(pos);
|
||||
|
||||
if (channel instanceof Cache) {
|
||||
// In case of memory cache, free up memory
|
||||
((Cache) channel).flushBefore(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -49,7 +49,6 @@ import static java.lang.Math.max;
|
||||
* {@link File} or {@link RandomAccessFile} can be used as input.
|
||||
*
|
||||
* @see javax.imageio.stream.FileImageInputStream
|
||||
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
|
||||
*/
|
||||
// TODO: Create a memory-mapped version?
|
||||
// Or not... From java.nio.channels.FileChannel.map:
|
||||
@@ -58,7 +57,6 @@ import static java.lang.Math.max;
|
||||
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||
// standpoint of performance it is generally only worth mapping relatively
|
||||
// large files into memory.
|
||||
@Deprecated
|
||||
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
@@ -192,10 +190,10 @@ public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
buffer = null;
|
||||
|
||||
raf.close();
|
||||
|
||||
raf = null;
|
||||
buffer = null;
|
||||
}
|
||||
|
||||
// Need to override the readShort(), readInt() and readLong() methods,
|
||||
|
||||
+6
-9
@@ -37,19 +37,18 @@ import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamSpi
|
||||
* Experimental
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedFileImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
@@ -70,13 +69,12 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
||||
if (input instanceof File) {
|
||||
try {
|
||||
return new BufferedChannelImageInputStream((File) input);
|
||||
return new BufferedFileImageInputStream((File) input);
|
||||
}
|
||||
catch (FileNotFoundException | NoSuchFileException e) {
|
||||
catch (FileNotFoundException e) {
|
||||
// For consistency with the JRE bundled SPIs, we'll return null here,
|
||||
// even though the spec does not say that's allowed.
|
||||
// The problem is that the SPIs can only declare that they support an input type like a File,
|
||||
@@ -93,8 +91,7 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a File";
|
||||
}
|
||||
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BufferedInputStreamImageInputStreamSpi.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
|
||||
*/
|
||||
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedInputStreamImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
|
||||
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
ImageInputStreamSpi provider = providers.next();
|
||||
if (provider != this) {
|
||||
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
if (input instanceof InputStream) {
|
||||
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
|
||||
|
||||
if (channel instanceof SeekableByteChannel) {
|
||||
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
|
||||
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
|
||||
}
|
||||
|
||||
// Otherwise, create a cache for backwards seeking
|
||||
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseCacheFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Service provider that instantiates an ImageInputStream from an InputStream";
|
||||
}
|
||||
|
||||
private static class InputStreamFilter implements ServiceRegistry.Filter {
|
||||
@Override
|
||||
public boolean filter(final Object provider) {
|
||||
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -48,7 +48,7 @@ import java.util.Locale;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedRAFImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
@@ -69,10 +69,9 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
||||
if (input instanceof RandomAccessFile) {
|
||||
return new BufferedChannelImageInputStream((RandomAccessFile) input);
|
||||
return new BufferedFileImageInputStream((RandomAccessFile) input);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
||||
@@ -83,8 +82,7 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
||||
private final int dataOffset;
|
||||
private final int dataLength;
|
||||
|
||||
public ByteArrayImageInputStream(final byte[] data) {
|
||||
this(data, 0, data != null ? data.length : -1);
|
||||
public ByteArrayImageInputStream(final byte[] pData) {
|
||||
this(pData, 0, pData != null ? pData.length : -1);
|
||||
}
|
||||
|
||||
public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
|
||||
this.data = notNull(data, "data");
|
||||
dataOffset = isMax(data.length, offset, "offset");
|
||||
dataLength = isMax(data.length - offset, length, "length");
|
||||
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
|
||||
data = notNull(pData, "data");
|
||||
dataOffset = isBetween(0, pData.length, offset, "offset");
|
||||
dataLength = isBetween(0, pData.length - offset, length, "length");
|
||||
}
|
||||
|
||||
private static int isMax(final int high, final int value, final String name) {
|
||||
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
|
||||
private static int isBetween(final int low, final int high, final int value, final String name) {
|
||||
return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, high, value));
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
||||
return data[((int) streamPos++) + dataOffset] & 0xff;
|
||||
}
|
||||
|
||||
public int read(byte[] buffer, int offset, int len) throws IOException {
|
||||
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
if (streamPos >= dataLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int length = (int) Math.min(dataLength - streamPos, len);
|
||||
int length = (int) Math.min(this.dataLength - streamPos, pLength);
|
||||
bitOffset = 0;
|
||||
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
|
||||
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
|
||||
streamPos += length;
|
||||
|
||||
return length;
|
||||
|
||||
+8
-9
@@ -45,7 +45,7 @@ import java.util.Locale;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
|
||||
*/
|
||||
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
|
||||
public ByteArrayImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
@@ -55,17 +55,16 @@ public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
|
||||
if (input instanceof byte[]) {
|
||||
return new ByteArrayImageInputStream((byte[]) input);
|
||||
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
|
||||
if (pInput instanceof byte[]) {
|
||||
return new ByteArrayImageInputStream((byte[]) pInput);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale locale) {
|
||||
public String getDescription(Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a byte array";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
interface Cache extends SeekableByteChannel {
|
||||
void flushBefore(long pos);
|
||||
}
|
||||
+1
-1
@@ -125,7 +125,7 @@ public final class DirectImageInputStream extends ImageInputStreamImpl {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
|
||||
// We could seek to EOF here, but the usual case
|
||||
|
||||
stream.close();
|
||||
super.close();
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
|
||||
import static java.nio.file.StandardOpenOption.READ;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
// Note: We could consider creating a memory-mapped version...
|
||||
// But, from java.nio.channels.FileChannel.map:
|
||||
// For most operating systems, mapping a file into memory is more
|
||||
// expensive than reading or writing a few tens of kilobytes of data via
|
||||
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||
// standpoint of performance it is generally only worth mapping relatively
|
||||
// large files into memory.
|
||||
final class FileCache implements Cache {
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private final FileChannel cache;
|
||||
private final ReadableByteChannel channel;
|
||||
|
||||
// TODO: Perhaps skip this constructor?
|
||||
FileCache(InputStream stream, File cacheDir) throws IOException {
|
||||
// Stream will be closed with channel, documented behavior
|
||||
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
|
||||
}
|
||||
|
||||
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
|
||||
this.channel = notNull(channel, "channel");
|
||||
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
|
||||
|
||||
// Create a temp file to hold our cache,
|
||||
// will be deleted when this channel is closed, as we close the cache
|
||||
Path cacheFile = cacheDir == null
|
||||
? Files.createTempFile("imageio", ".tmp")
|
||||
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
|
||||
|
||||
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
void fetch() throws IOException {
|
||||
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
|
||||
// Continue transfer...
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return channel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
cache.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dest) throws IOException {
|
||||
fetch();
|
||||
|
||||
if (cache.position() >= cache.size()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cache.read(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException {
|
||||
return cache.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||
cache.position(newPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate(long size) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override public void flushBefore(long pos) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
final class MemoryCache implements Cache {
|
||||
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private final List<byte[]> cache = new ArrayList<>();
|
||||
private final ReadableByteChannel channel;
|
||||
private long length;
|
||||
private long position;
|
||||
private long start;
|
||||
|
||||
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
|
||||
MemoryCache(InputStream stream) {
|
||||
this(Channels.newChannel(notNull(stream, "stream")));
|
||||
}
|
||||
|
||||
public MemoryCache(ReadableByteChannel channel) {
|
||||
this.channel = notNull(channel, "channel");
|
||||
}
|
||||
|
||||
byte[] fetchBlock() throws IOException {
|
||||
long currPos = position;
|
||||
|
||||
long index = currPos / BLOCK_SIZE;
|
||||
|
||||
if (index >= Integer.MAX_VALUE) {
|
||||
throw new IOException("Memory cache max size exceeded");
|
||||
}
|
||||
|
||||
while (index >= cache.size()) {
|
||||
byte[] block;
|
||||
try {
|
||||
block = new byte[BLOCK_SIZE];
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
|
||||
}
|
||||
|
||||
cache.add(block);
|
||||
length += readBlock(block);
|
||||
}
|
||||
|
||||
return cache.get((int) index);
|
||||
}
|
||||
|
||||
private int readBlock(final byte[] block) throws IOException {
|
||||
ByteBuffer wrapped = ByteBuffer.wrap(block);
|
||||
|
||||
while (wrapped.hasRemaining()) {
|
||||
int count = channel.read(wrapped);
|
||||
if (count == -1) {
|
||||
// Last block
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return channel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dest) throws IOException {
|
||||
byte[] buffer = fetchBlock();
|
||||
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||
|
||||
if (position >= length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
|
||||
dest.put(buffer, bufferPos, len);
|
||||
|
||||
position += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||
if (newPosition < start) {
|
||||
throw new IOException("Seek before flush position");
|
||||
}
|
||||
|
||||
this.position = newPosition;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate(long size) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBefore(long pos) {
|
||||
if (pos < start) {
|
||||
throw new IndexOutOfBoundsException("pos < flushed position");
|
||||
}
|
||||
if (pos > position) {
|
||||
throw new IndexOutOfBoundsException("pos > current position");
|
||||
}
|
||||
|
||||
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
|
||||
|
||||
// Clear blocks no longer needed
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
cache.set(i, null);
|
||||
}
|
||||
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
|
||||
+41
-14
@@ -33,7 +33,9 @@ package com.twelvemonkeys.imageio.stream;
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.stream.FileCacheImageInputStream;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -50,7 +52,7 @@ import java.util.Locale;
|
||||
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
// TODO: URI instead of URL?
|
||||
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public URLImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
@@ -62,28 +64,53 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
|
||||
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
|
||||
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
if (input instanceof URL) {
|
||||
URL url = (URL) input;
|
||||
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
|
||||
if (pInput instanceof URL) {
|
||||
URL url = (URL) pInput;
|
||||
|
||||
// Special case for file protocol, a lot faster than FileCacheImageInputStream
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
try {
|
||||
return new BufferedChannelImageInputStream(new File(url.toURI()));
|
||||
return new BufferedFileImageInputStream(new File(url.toURI()));
|
||||
}
|
||||
catch (URISyntaxException shouldNeverHappen) {
|
||||
// This should never happen, but if it does, we'll fall back to using the stream
|
||||
shouldNeverHappen.printStackTrace();
|
||||
catch (URISyntaxException ignore) {
|
||||
// This should never happen, but if it does, we'll fall back to using the stream
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise revert to cached
|
||||
InputStream urlStream = url.openStream();
|
||||
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
|
||||
final InputStream urlStream = url.openStream();
|
||||
if (pUseCache) {
|
||||
return new FileCacheImageInputStream(urlStream, pCacheDir) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
}
|
||||
finally {
|
||||
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return new MemoryCacheImageInputStream(urlStream) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
}
|
||||
finally {
|
||||
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type URL: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,7 +118,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDescription(final Locale locale) {
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a URL";
|
||||
}
|
||||
}
|
||||
|
||||
+39
-90
@@ -30,15 +30,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.MultiPixelPackedSampleModel;
|
||||
import java.awt.image.SampleModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
||||
|
||||
/**
|
||||
* Factory class for creating {@code ImageTypeSpecifier}s.
|
||||
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
|
||||
@@ -51,52 +58,28 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
*/
|
||||
public final class ImageTypeSpecifiers {
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
|
||||
0xFF0000,
|
||||
0x00FF00,
|
||||
0x0000FF,
|
||||
0x0,
|
||||
DataBuffer.TYPE_INT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
|
||||
0x0000FF,
|
||||
0x00FF00,
|
||||
0xFF0000,
|
||||
0x0,
|
||||
DataBuffer.TYPE_INT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
|
||||
0xF800,
|
||||
0x07E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
|
||||
0x7C00,
|
||||
0x03E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
|
||||
private ImageTypeSpecifiers() {}
|
||||
|
||||
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
|
||||
switch (bufferedImageType) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
|
||||
case BufferedImage.TYPE_INT_RGB:
|
||||
return TYPE_INT_RGB;
|
||||
|
||||
case BufferedImage.TYPE_INT_BGR:
|
||||
return TYPE_INT_BGR;
|
||||
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types
|
||||
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||
return TYPE_USHORT_565_RGB;
|
||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
0xF800,
|
||||
0x07E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
|
||||
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||
return TYPE_USHORT_555_RGB;
|
||||
|
||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
0x7C00,
|
||||
0x03E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -107,41 +90,23 @@ public final class ImageTypeSpecifiers {
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask);
|
||||
if (bits != 32) {
|
||||
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
||||
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
private static int calculateRequiredBits(int mask) {
|
||||
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
|
||||
int r = 1;
|
||||
|
||||
while ((mask >>>= 1) != 0) {
|
||||
r++;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
|
||||
final int[] bandOffsets,
|
||||
final int dataType,
|
||||
@@ -270,20 +235,4 @@ public final class ImageTypeSpecifiers {
|
||||
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("image == null!");
|
||||
}
|
||||
|
||||
if (image instanceof BufferedImage) {
|
||||
int bufferedImageType = ((BufferedImage) image).getType();
|
||||
|
||||
if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
|
||||
return createFromBufferedImageType(bufferedImageType);
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageTypeSpecifier(image);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
||||
com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi
|
||||
# Use SPI loading as a hook for early profile activation
|
||||
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
||||
|
||||
-332
@@ -1,332 +0,0 @@
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ImageOrientation;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.PlanarConfiguration;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.SubimageInterpretation;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.TextEntry;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.builder;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class StandardImageMetadataSupportTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void createNullBuilder() {
|
||||
new StandardImageMetadataSupport(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void createNullType() {
|
||||
new StandardImageMetadataSupport(builder(null));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderNullType() {
|
||||
builder(null).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createValid() {
|
||||
IIOMetadata metadata = new StandardImageMetadataSupport(builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
|
||||
assertNotNull(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderValid() {
|
||||
IIOMetadata metadata = builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB))
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesUnspecified() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
assertNull(metadata.getStandardCompressionNode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesNone() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("nOnE") // Case-insensitive
|
||||
.build();
|
||||
|
||||
assertNull(metadata.getStandardCompressionNode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesName() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("foo")
|
||||
.build();
|
||||
|
||||
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compressionNode);
|
||||
|
||||
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
|
||||
assertEquals("foo", compressionName.getAttribute("value"));
|
||||
|
||||
// Defaults to lossless true
|
||||
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
|
||||
assertEquals("TRUE", compressionLossless.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void withCompressionLossyIllegal() {
|
||||
builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionLossless(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesLossy() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("bar")
|
||||
.withCompressionLossless(false)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compressionNode);
|
||||
|
||||
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
|
||||
assertEquals("bar", compressionName.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
|
||||
assertEquals("FALSE", compressionLossless.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDocumentValuesDefault() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNull(documentNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDocumentValues() {
|
||||
Calendar creationTime = Calendar.getInstance();
|
||||
creationTime.set(2022, Calendar.SEPTEMBER, 8, 14, 5, 0);
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withFormatVersion("42")
|
||||
.withDocumentCreationTime(creationTime)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode formatVersion = (IIOMetadataNode) documentNode.getElementsByTagName("FormatVersion").item(0);
|
||||
assertEquals("42", formatVersion.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageCreationTime = (IIOMetadataNode) documentNode.getElementsByTagName("ImageCreationTime").item(0);
|
||||
assertEquals("2022", imageCreationTime.getAttribute("year"));
|
||||
assertEquals("9", imageCreationTime.getAttribute("month"));
|
||||
assertEquals("8", imageCreationTime.getAttribute("day"));
|
||||
assertEquals("14", imageCreationTime.getAttribute("hour"));
|
||||
assertEquals("5", imageCreationTime.getAttribute("minute"));
|
||||
assertEquals("0", imageCreationTime.getAttribute("second"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesDefault() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNull(textNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesSingle() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntry("foo", "bar")
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textNode.getElementsByTagName("TextEntry").item(0);
|
||||
assertEquals("foo", textEntry.getAttribute("keyword"));
|
||||
assertEquals("bar", textEntry.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesMap() {
|
||||
Map<String, String> entries = new HashMap<>();
|
||||
entries.put("foo", "bar");
|
||||
entries.put("bar", "xyzzy");
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntries(entries)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
|
||||
assertEquals(entries.size(), textEntries.getLength());
|
||||
|
||||
int i = 0;
|
||||
for (Entry<String, String> entry : entries.entrySet()) {
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
|
||||
assertEquals(entry.getKey(), textEntry.getAttribute("keyword"));
|
||||
assertEquals(entry.getValue(), textEntry.getAttribute("value"));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesList() {
|
||||
List<TextEntry> entries = Arrays.asList(
|
||||
new TextEntry(null, "foo"), // No key allowed
|
||||
new TextEntry("foo", "bar"),
|
||||
new TextEntry("bar", "xyzzy"),
|
||||
new TextEntry("bar", "nothing happens..."), // Duplicates allowed
|
||||
new TextEntry("everything", "válüè", "unknown", "UTF-8", "zip")
|
||||
);
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntries(entries)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
|
||||
assertEquals(entries.size(), textEntries.getLength());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
TextEntry entry = entries.get(i);
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
|
||||
|
||||
assertAttributeEqualOrAbsent(entry.keyword, textEntry, "keyword");
|
||||
|
||||
assertEquals(entry.value, textEntry.getAttribute("value"));
|
||||
|
||||
assertAttributeEqualOrAbsent(entry.language, textEntry, "language");
|
||||
assertAttributeEqualOrAbsent(entry.encoding, textEntry, "encoding");
|
||||
assertAttributeEqualOrAbsent(entry.compression, textEntry, "compression");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertAttributeEqualOrAbsent(final String expectedValue, IIOMetadataNode node, final String attribute) {
|
||||
if (expectedValue != null) {
|
||||
assertEquals(expectedValue, node.getAttribute(attribute));
|
||||
}
|
||||
else {
|
||||
assertFalse(node.hasAttribute(attribute));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPlanarColorspaceType() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList(
|
||||
"XYZ", "Lab", "Luv", "YCbCr", "Yxy", "YCCK", "PhotoYCC",
|
||||
"RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY",
|
||||
"2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR",
|
||||
"9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR"
|
||||
);
|
||||
|
||||
for (ColorSpaceType value : ColorSpaceType.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withColorSpaceType(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardChromaNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ColorSpaceType").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("name")); // Format oddity: Why is this not "value"?
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPlanarConfiguration() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList("PixelInterleaved", "PlaneInterleaved", "LineInterleaved", "TileInterleaved");
|
||||
|
||||
for (PlanarConfiguration value : PlanarConfiguration.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR))
|
||||
.withPlanarConfiguration(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDataNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("PlanarConfiguration").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withImageOrientation() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList("Normal", "Rotate90", "Rotate180", "Rotate270", "FlipH", "FlipV", "FlipHRotate90", "FlipVRotate90");
|
||||
|
||||
for (ImageOrientation value : ImageOrientation.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withOrientation(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDimensionNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ImageOrientation").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSubimageInterpretation() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList(
|
||||
"Standalone", "SinglePage", "FullResolution", "ReducedResolution", "PyramidLayer",
|
||||
"Preview", "VolumeSlice", "ObjectView", "Panorama", "AnimationFrame",
|
||||
"TransparencyMask", "CompositingLayer", "SpectralSlice", "Unknown"
|
||||
);
|
||||
|
||||
for (SubimageInterpretation value : SubimageInterpretation.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB))
|
||||
.withSubimageInterpretation(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("SubimageInterpretation").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
// TODO: Remove this test, and instead test the disk cache directly!
|
||||
public class BufferedChannelImageInputStreamFileCacheTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private InputStream randomDataToInputStream(byte[] data) {
|
||||
random.nextBytes(data);
|
||||
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullStream() throws IOException {
|
||||
try {
|
||||
new FileCache((InputStream) null, null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullChannel() throws IOException {
|
||||
try {
|
||||
new FileCache((ReadableByteChannel) null, null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = data.length - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws IOException {
|
||||
// Create wrapper stream
|
||||
Cache cache = mock(Cache.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
|
||||
|
||||
stream.close();
|
||||
verify(cache, only()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
// TODO: Remove this test, and instead test the memory cache directly!
|
||||
public class BufferedChannelImageInputStreamMemoryCacheTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private InputStream randomDataToInputStream(byte[] data) {
|
||||
random.nextBytes(data);
|
||||
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(new ByteArrayInputStream(new byte[0])))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullStream() {
|
||||
try {
|
||||
new MemoryCache((InputStream) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullChannel() {
|
||||
try {
|
||||
new MemoryCache((ReadableByteChannel) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = data.length - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws IOException {
|
||||
// Create wrapper stream
|
||||
Cache cache = mock(Cache.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
|
||||
|
||||
stream.close();
|
||||
verify(cache, only()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
-431
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
public class BufferedChannelImageInputStreamTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private File randomDataToFile(byte[] data) throws IOException {
|
||||
random.nextBytes(data);
|
||||
|
||||
File file = File.createTempFile("read", ".tmp");
|
||||
Files.write(file.toPath(), data);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(File.createTempFile("empty", ".tmp")))) {
|
||||
assertEquals("Data length should be same as stream length", 0, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullFileInputStream() {
|
||||
try {
|
||||
new BufferedChannelImageInputStream((FileInputStream) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("inputstream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullByteChannel() {
|
||||
try {
|
||||
new BufferedChannelImageInputStream((SeekableByteChannel) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = stream.length() - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
File file = randomDataToFile(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseChannel() throws IOException {
|
||||
SeekableByteChannel channel = mock(SeekableByteChannel.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(channel);
|
||||
|
||||
stream.close();
|
||||
verify(channel, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
File file = randomDataToFile(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -45,9 +45,7 @@ import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
@@ -56,7 +54,6 @@ import static org.mockito.Mockito.verify;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
@Deprecated
|
||||
public class BufferedFileImageInputStreamTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
@@ -75,7 +72,6 @@ public class BufferedFileImageInputStreamTest {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Test
|
||||
public void testCreateNullFile() throws IOException {
|
||||
try {
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
/**
|
||||
* BufferedInputStreamImageInputStreamSpiTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
|
||||
*/
|
||||
public class BufferedFileInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
|
||||
@Override
|
||||
protected ImageInputStreamSpi createProvider() {
|
||||
return new BufferedInputStreamImageInputStreamSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream createInput() throws IOException {
|
||||
return Files.newInputStream(File.createTempFile("test-", ".tst").toPath());
|
||||
}
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* BufferedInputStreamImageInputStreamSpiTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
|
||||
*/
|
||||
public class BufferedInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
|
||||
@Override
|
||||
protected ImageInputStreamSpi createProvider() {
|
||||
return new BufferedInputStreamImageInputStreamSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream createInput() {
|
||||
return new ByteArrayInputStream(new byte[0]);
|
||||
}
|
||||
}
|
||||
+37
-71
@@ -30,15 +30,20 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
public class ImageTypeSpecifiersTest {
|
||||
|
||||
@@ -65,19 +70,12 @@ public class ImageTypeSpecifiersTest {
|
||||
ImageTypeSpecifier expected;
|
||||
|
||||
switch (type) {
|
||||
// Special handling for INT_RGB and BGR, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
|
||||
case BufferedImage.TYPE_INT_RGB:
|
||||
expected = createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false);
|
||||
break;
|
||||
case BufferedImage.TYPE_INT_BGR:
|
||||
expected = createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false);
|
||||
break;
|
||||
// Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
|
||||
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||
expected = createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
||||
expected = createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
||||
break;
|
||||
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||
expected = createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
||||
expected = createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
||||
break;
|
||||
default:
|
||||
expected = ImageTypeSpecifier.createFromBufferedImageType(type);
|
||||
@@ -88,24 +86,12 @@ public class ImageTypeSpecifiersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked24() {
|
||||
public void testCreatePacked32() {
|
||||
// TYPE_INT_RGB
|
||||
assertEquals(
|
||||
createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false)
|
||||
);
|
||||
// TYPE_INT_BGR
|
||||
assertEquals(
|
||||
createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
|
||||
);
|
||||
|
||||
// Extra: Make sure color models bits is actually 24 (ImageTypeSpecifier equivalent returns 32)
|
||||
assertEquals(24, ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked32() {
|
||||
// TYPE_INT_ARGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false),
|
||||
@@ -116,36 +102,35 @@ public class ImageTypeSpecifiersTest {
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked15() {
|
||||
// TYPE_USHORT_555_RGB
|
||||
// TYPE_INT_BGR
|
||||
assertEquals(
|
||||
createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
|
||||
);
|
||||
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
|
||||
|
||||
// Extra: Make sure color models bits is actually 15 (ImageTypeSpecifier equivalent returns 32)
|
||||
assertEquals(15, ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked16() {
|
||||
// TYPE_USHORT_555_RGB
|
||||
assertEquals(
|
||||
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
|
||||
|
||||
// TYPE_USHORT_565_RGB
|
||||
assertEquals(
|
||||
createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 16,0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB PRE"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 16, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
|
||||
);
|
||||
|
||||
@@ -157,17 +142,17 @@ public class ImageTypeSpecifiersTest {
|
||||
public void testCreatePacked8() {
|
||||
// "BYTE 332 RGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 8, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
|
||||
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB PRE"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
|
||||
);
|
||||
|
||||
@@ -175,12 +160,15 @@ public class ImageTypeSpecifiersTest {
|
||||
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, final int bits,
|
||||
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
|
||||
final int redMask, final int greenMask, final int blueMask, final int alphaMask,
|
||||
final int transferType, final boolean isAlphaPremultiplied) {
|
||||
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT || transferType == DataBuffer.TYPE_INT, transferType, "transferType: %s");
|
||||
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel =
|
||||
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
@@ -728,28 +716,6 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateFromBufferedImageTypeShouldEqualConstructor() {
|
||||
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
|
||||
BufferedImage image = new BufferedImage(1, 1, type);
|
||||
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
|
||||
ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type);
|
||||
|
||||
assertEquals(fromConstructor.getColorModel(), fromType.getColorModel());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateFromRenderedImageShouldEqualConstructor() {
|
||||
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
|
||||
BufferedImage image = new BufferedImage(1, 1, type);
|
||||
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
|
||||
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
|
||||
|
||||
assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel());
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] createByteLut(final int count) {
|
||||
byte[] lut = new byte[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
|
||||
+9
-3
@@ -40,8 +40,11 @@ import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
@@ -241,7 +244,10 @@ public final class HDRImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
return new HDRMetadata(getRawImageType(imageIndex), header);
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new HDRMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException {
|
||||
|
||||
+107
-9
@@ -1,19 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
public class HDRMetadata extends StandardImageMetadataSupport {
|
||||
public HDRMetadata(ImageTypeSpecifier type, HDRHeader header) {
|
||||
super(builder(type)
|
||||
.withCompressionTypeName("RLE")
|
||||
.withTextEntry("Software", header.getSoftware()));
|
||||
final class HDRMetadata extends AbstractMetadata {
|
||||
private final HDRHeader header;
|
||||
|
||||
HDRMetadata(final HDRHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
csType.setAttribute("name", "RGB");
|
||||
// TODO: Support XYZ
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", "3");
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", "RLE");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// For HDR, the stored sample data is UnsignedIntegral and data is 4 channels (RGB+Exp),
|
||||
// but decoded to Real (float) 3 chanel RGB
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
@@ -28,4 +92,38 @@ public class HDRMetadata extends StandardImageMetadataSupport {
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
// TODO: Support other orientations
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (header.getSoftware() != null) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "Software");
|
||||
textEntry.setAttribute("value", header.getSoftware());
|
||||
text.appendChild(textEntry);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
// No transparency
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
final class ICNSImageMetadata extends StandardImageMetadataSupport {
|
||||
ICNSImageMetadata(ImageTypeSpecifier type, String compressionName) {
|
||||
super(builder(type).withCompressionTypeName(compressionName));
|
||||
}
|
||||
}
|
||||
+15
-29
@@ -35,16 +35,11 @@ import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
@@ -66,9 +61,10 @@ import java.util.List;
|
||||
* @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
|
||||
*/
|
||||
public final class ICNSImageReader extends ImageReaderBase {
|
||||
// TODO: Support ToC resource for faster parsing/faster determine number of icons?
|
||||
// TODO: Subsampled reading for completeness, even if never used?
|
||||
private final List<IconResource> icons = new ArrayList<>();
|
||||
private final List<IconResource> masks = new ArrayList<>();
|
||||
private List<IconResource> icons = new ArrayList<IconResource>();
|
||||
private List<IconResource> masks = new ArrayList<IconResource>();
|
||||
private IconResource lastResourceRead;
|
||||
|
||||
private int length;
|
||||
@@ -140,7 +136,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
IconResource resource = readIconResource(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
||||
|
||||
switch (resource.depth()) {
|
||||
case 1:
|
||||
@@ -234,9 +230,14 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
packedSize -= 4;
|
||||
}
|
||||
|
||||
try (InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize)) {
|
||||
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
|
||||
|
||||
try {
|
||||
ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE];
|
||||
@@ -490,7 +491,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
|
||||
String format;
|
||||
|
||||
if (Arrays.equals(ICNS.PNG_MAGIC, Arrays.copyOfRange(magic, 0, ICNS.PNG_MAGIC.length))) {
|
||||
if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
|
||||
format = "PNG";
|
||||
}
|
||||
else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
|
||||
@@ -526,6 +527,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
IconResource resource = IconResource.read(imageInput);
|
||||
|
||||
if (resource.isTOC()) {
|
||||
// TODO: IconResource.readTOC()?
|
||||
int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE;
|
||||
long pos = resource.start + resource.length;
|
||||
|
||||
@@ -568,23 +570,6 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
IconResource resource = readIconResource(imageIndex);
|
||||
|
||||
String compressionName;
|
||||
if (resource.isForeignFormat()) {
|
||||
// Special handling of PNG/JPEG 2000 icons
|
||||
imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE);
|
||||
compressionName = getForeignFormat(imageInput);
|
||||
}
|
||||
else {
|
||||
compressionName = resource.isCompressed() ? "RLE" : "None";
|
||||
}
|
||||
|
||||
return new ICNSImageMetadata(getRawImageType(imageIndex), compressionName);
|
||||
}
|
||||
|
||||
private static final class ICNSBitMaskColorModel extends IndexColorModel {
|
||||
static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel();
|
||||
|
||||
@@ -593,6 +578,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedAssignment"})
|
||||
public static void main(String[] args) throws IOException {
|
||||
int argIndex = 0;
|
||||
|
||||
|
||||
+1
-8
@@ -34,13 +34,7 @@ import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.event.IIOWriteWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
@@ -110,7 +104,6 @@ public final class ICNSImageWriter extends ImageWriterBase {
|
||||
sequenceIndex = 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Override
|
||||
public void endWriteSequence() throws IOException {
|
||||
assertOutput();
|
||||
|
||||
+15
-9
@@ -38,13 +38,8 @@ import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@@ -145,12 +140,17 @@ final class SipsJP2Reader {
|
||||
}
|
||||
|
||||
private static String checkErrorMessage(final Process process) throws IOException {
|
||||
try (InputStream stream = process.getErrorStream()) {
|
||||
InputStream stream = process.getErrorStream();
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String message = reader.readLine();
|
||||
|
||||
return message != null && message.startsWith("Error: ") ? message.substring(7) : null;
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] buildCommand(final File sipsCommand, final File tempFile) {
|
||||
@@ -159,13 +159,19 @@ final class SipsJP2Reader {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static File dumpToFile(final ImageInputStream stream) throws IOException {
|
||||
File tempFile = File.createTempFile("imageio-icns-", ".png");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(tempFile)) {
|
||||
FileOutputStream out = new FileOutputStream(tempFile);
|
||||
|
||||
try {
|
||||
FileUtil.copy(IIOUtil.createStreamAdapter(stream), out);
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
|
||||
/**
|
||||
* Form.
|
||||
@@ -27,7 +27,7 @@ abstract class Form {
|
||||
|
||||
abstract int width();
|
||||
abstract int height();
|
||||
abstract double aspect();
|
||||
abstract float aspect();
|
||||
abstract int bitplanes();
|
||||
abstract int compressionType();
|
||||
|
||||
@@ -118,7 +118,7 @@ abstract class Form {
|
||||
}
|
||||
|
||||
private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) {
|
||||
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"));
|
||||
super(formType);
|
||||
this.bitmapHeader = bitmapHeader;
|
||||
this.viewMode = viewMode;
|
||||
this.colorMap = colorMap;
|
||||
@@ -127,19 +127,6 @@ abstract class Form {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
private static boolean validFormType(int formType) {
|
||||
switch (formType) {
|
||||
case TYPE_ACBM:
|
||||
case TYPE_ILBM:
|
||||
case TYPE_PBM:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_RGBN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int width() {
|
||||
return bitmapHeader.width;
|
||||
@@ -161,8 +148,8 @@ abstract class Form {
|
||||
}
|
||||
|
||||
@Override
|
||||
double aspect() {
|
||||
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (double) bitmapHeader.yAspect);
|
||||
float aspect() {
|
||||
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (float) bitmapHeader.yAspect);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -310,7 +297,7 @@ abstract class Form {
|
||||
}
|
||||
|
||||
private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) {
|
||||
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"));
|
||||
super(formType);
|
||||
this.deepGlobal = deepGlobal;
|
||||
this.deepLocation = deepLocation;
|
||||
this.deepPixel = deepPixel;
|
||||
@@ -318,15 +305,6 @@ abstract class Form {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
private static boolean validFormType(int formType) {
|
||||
switch (formType) {
|
||||
case TYPE_DEEP:
|
||||
case TYPE_TVPP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int width() {
|
||||
@@ -359,8 +337,8 @@ abstract class Form {
|
||||
}
|
||||
|
||||
@Override
|
||||
double aspect() {
|
||||
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (double) deepGlobal.yAspect;
|
||||
float aspect() {
|
||||
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (float) deepGlobal.yAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+235
-86
@@ -1,83 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.image.*;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
final class IFFImageMetadata extends StandardImageMetadataSupport {
|
||||
IFFImageMetadata(ImageTypeSpecifier type, Form header, IndexColorModel palette) {
|
||||
this(builder(type), notNull(header, "header"), palette);
|
||||
final class IFFImageMetadata extends AbstractMetadata {
|
||||
private final Form header;
|
||||
private final IndexColorModel colorMap;
|
||||
private final List<GenericChunk> meta;
|
||||
|
||||
IFFImageMetadata(Form header, IndexColorModel colorMap) {
|
||||
this.header = notNull(header, "header");
|
||||
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
|
||||
this.colorMap = colorMap;
|
||||
this.meta = header.meta;
|
||||
}
|
||||
|
||||
private IFFImageMetadata(Builder builder, Form header, IndexColorModel palette) {
|
||||
super(builder.withPalette(palette)
|
||||
.withCompressionTypeName(compressionName(header))
|
||||
.withBitsPerSample(bitsPerSample(header))
|
||||
.withPlanarConfiguration(planarConfiguration(header))
|
||||
.withPixelAspectRatio(header.aspect() != 0 ? header.aspect() : null)
|
||||
.withFormatVersion("1.0")
|
||||
.withTextEntries(textEntries(header)));
|
||||
}
|
||||
|
||||
private static String compressionName(Form header) {
|
||||
switch (header.compressionType()) {
|
||||
case BMHDChunk.COMPRESSION_NONE:
|
||||
return "None";
|
||||
case BMHDChunk.COMPRESSION_BYTE_RUN:
|
||||
return "RLE";
|
||||
case 4:
|
||||
// Compression type 4 means different things for different FORM types, we support
|
||||
// Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count
|
||||
if (header.formType == TYPE_RGB8) {
|
||||
return "RGB8";
|
||||
}
|
||||
private boolean validFormType(int formType) {
|
||||
switch (formType) {
|
||||
default:
|
||||
return "Unknown";
|
||||
return false;
|
||||
case TYPE_ACBM:
|
||||
case TYPE_DEEP:
|
||||
case TYPE_ILBM:
|
||||
case TYPE_PBM:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_RGBN:
|
||||
case TYPE_TVPP:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] bitsPerSample(Form header) {
|
||||
int bitplanes = header.bitplanes();
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
|
||||
switch (header.bitplanes()) {
|
||||
case 8:
|
||||
if (colorMap == null) {
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 24:
|
||||
case 25:
|
||||
case 32:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
default:
|
||||
csType.setAttribute("name", "Unknown");
|
||||
}
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
if (colorMap == null && header.bitplanes() == 8) {
|
||||
numChannels.setAttribute("value", Integer.toString(1));
|
||||
}
|
||||
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
}
|
||||
else {
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
}
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
|
||||
// NOTE: TGA files may contain a color map, even if true color...
|
||||
// Not sure if this is a good idea to expose to the meta data,
|
||||
// as it might be unexpected... Then again...
|
||||
if (colorMap != null) {
|
||||
IIOMetadataNode palette = new IIOMetadataNode("Palette");
|
||||
chroma.appendChild(palette);
|
||||
|
||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||
palette.appendChild(paletteEntry);
|
||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||
|
||||
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
}
|
||||
|
||||
if (colorMap.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex");
|
||||
chroma.appendChild(backgroundIndex);
|
||||
backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
|
||||
// and also a BGP1 (background pen 1?) and BGP2 chunks
|
||||
// if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||
// Color background = new Color(extensions.getBackgroundColor(), true);
|
||||
//
|
||||
// IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
|
||||
// chroma.appendChild(backgroundColor);
|
||||
//
|
||||
// backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
|
||||
// backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
|
||||
// backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
|
||||
// }
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
|
||||
return null; // All defaults
|
||||
}
|
||||
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", "RLE");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode data = new IIOMetadataNode("Data");
|
||||
|
||||
// PlanarConfiguration
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
switch (header.formType) {
|
||||
case TYPE_DEEP:
|
||||
case TYPE_TVPP:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_PBM:
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
break;
|
||||
case TYPE_ILBM:
|
||||
planarConfiguration.setAttribute("value", "PlaneInterleaved");
|
||||
break;
|
||||
default:
|
||||
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
|
||||
break;
|
||||
}
|
||||
data.appendChild(planarConfiguration);
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral");
|
||||
data.appendChild(sampleFormat);
|
||||
|
||||
// BitsPerSample
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
String value = bitsPerSampleValue(header.bitplanes());
|
||||
bitsPerSample.setAttribute("value", value);
|
||||
data.appendChild(bitsPerSample);
|
||||
|
||||
// SignificantBitsPerSample not in format
|
||||
// SampleMSB not in format
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private String bitsPerSampleValue(int bitplanes) {
|
||||
switch (bitplanes) {
|
||||
case 1:
|
||||
case 2:
|
||||
@@ -87,47 +192,91 @@ final class IFFImageMetadata extends StandardImageMetadataSupport {
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
return new int[] {bitplanes};
|
||||
return Integer.toString(bitplanes);
|
||||
case 24:
|
||||
return new int[] {8, 8, 8};
|
||||
return "8 8 8";
|
||||
case 25:
|
||||
if (header.formType != TYPE_RGB8) {
|
||||
throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType)));
|
||||
}
|
||||
|
||||
return new int[] {8, 8, 8, 1};
|
||||
return "8 8 8 1";
|
||||
case 32:
|
||||
return new int[] {8, 8, 8, 8};
|
||||
return "8 8 8 8";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
|
||||
}
|
||||
}
|
||||
|
||||
private static PlanarConfiguration planarConfiguration(Form header) {
|
||||
switch (header.formType) {
|
||||
case TYPE_DEEP:
|
||||
case TYPE_TVPP:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_PBM:
|
||||
return PlanarConfiguration.PixelInterleaved;
|
||||
case TYPE_ILBM:
|
||||
return PlanarConfiguration.PlaneInterleaved;
|
||||
default:
|
||||
return null;
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
if (header.aspect() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
// PixelAspectRatio
|
||||
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect()));
|
||||
dimension.appendChild(pixelAspectRatio);
|
||||
|
||||
// TODO: HorizontalScreenSize?
|
||||
// TODO: VerticalScreenSize?
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private static List<TextEntry> textEntries(Form header) {
|
||||
if (header.meta.isEmpty()) {
|
||||
return emptyList();
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", "1.0");
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (meta.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<TextEntry> text = new ArrayList<>();
|
||||
for (GenericChunk chunk : header.meta) {
|
||||
text.add(new TextEntry(toChunkStr(chunk.chunkId),
|
||||
new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8:StandardCharsets.US_ASCII)));
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||
for (GenericChunk chunk : meta) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("TextEntry");
|
||||
node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId));
|
||||
node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII));
|
||||
text.appendChild(node);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
}
|
||||
|
||||
if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) {
|
||||
IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex");
|
||||
transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||
transparency.appendChild(transparentIndex);
|
||||
}
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-8
@@ -38,19 +38,14 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@@ -355,7 +350,9 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
return new IFFImageMetadata(getRawImageType(imageIndex), header, header.colorMap());
|
||||
init(imageIndex);
|
||||
|
||||
return new IFFImageMetadata(header, header.colorMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+79
-146
@@ -1,37 +1,24 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static java.awt.image.BufferedImage.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class IFFImageMetadataTest {
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_8_BIT_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_GRAY);
|
||||
private static final ImageTypeSpecifier TYPE_8_BIT_PALETTE = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_INDEXED);
|
||||
private static final ImageTypeSpecifier TYPE_24_BIT_RGB = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_3BYTE_BGR);
|
||||
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB_DEEP = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE);
|
||||
|
||||
@Test
|
||||
public void testStandardFeatures() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
final IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Standard metadata format
|
||||
assertTrue(metadata.isStandardMetadataFormatSupported());
|
||||
@@ -64,9 +51,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -91,9 +78,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -119,10 +106,9 @@ public class IFFImageMetadataTest {
|
||||
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
|
||||
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(5, chroma.getLength());
|
||||
@@ -133,7 +119,7 @@ public class IFFImageMetadataTest {
|
||||
|
||||
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
|
||||
assertEquals("NumChannels", numChannels.getNodeName());
|
||||
assertEquals("4", numChannels.getAttribute("value"));
|
||||
assertEquals("3", numChannels.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
|
||||
assertEquals("BlackIsZero", blackIsZero.getNodeName());
|
||||
@@ -167,9 +153,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode compression = getStandardNode(metadata, "Compression");
|
||||
IIOMetadataNode compression = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compression);
|
||||
assertEquals("Compression", compression.getNodeName());
|
||||
assertEquals(2, compression.getLength());
|
||||
@@ -190,9 +176,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
assertNull(getStandardNode(metadata, "Compression")); // No compression, all default...
|
||||
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -200,9 +186,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -227,9 +213,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -254,9 +240,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -283,10 +269,9 @@ public class IFFImageMetadataTest {
|
||||
.with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here
|
||||
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -312,9 +297,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_PBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -339,9 +324,9 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_PBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -371,20 +356,10 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(bitmapHeader);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
|
||||
if (dimension != null) {
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
}
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNull(dimension);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -393,24 +368,20 @@ public class IFFImageMetadataTest {
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(new CAMGChunk(4));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
|
||||
// No Dimension node is okay, or one with an aspect ratio of 1.0
|
||||
if (dimension != null) {
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,22 +398,18 @@ public class IFFImageMetadataTest {
|
||||
.with(bitmapHeader)
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("2.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -458,22 +425,18 @@ public class IFFImageMetadataTest {
|
||||
.with(bitmapHeader)
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("0.5", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -484,22 +447,18 @@ public class IFFImageMetadataTest {
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -507,33 +466,32 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode document = getStandardNode(metadata, "Document");
|
||||
IIOMetadataNode document = metadata.getStandardDocumentNode();
|
||||
assertNotNull(document);
|
||||
assertEquals("Document", document.getNodeName());
|
||||
assertEquals(1, document.getLength());
|
||||
|
||||
IIOMetadataNode formatVersion = (IIOMetadataNode) document.getFirstChild();
|
||||
assertEquals("FormatVersion", formatVersion.getNodeName());
|
||||
assertEquals("1.0", formatVersion.getAttribute("value"));
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild();
|
||||
assertEquals("FormatVersion", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
assertNull(formatVersion.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardText() throws IIOException {
|
||||
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
|
||||
String[] texts = {"annotation", "dupe", "äñnótâtïøñ"};
|
||||
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
|
||||
String[] texts = {"annotation", "äñnótâtïøñ"};
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII)))
|
||||
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.US_ASCII)))
|
||||
.with(new GenericChunk(chunks[2], texts[2].getBytes(StandardCharsets.UTF_8)));
|
||||
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode text = getStandardNode(metadata, "Text");
|
||||
IIOMetadataNode text = metadata.getStandardTextNode();
|
||||
assertNotNull(text);
|
||||
assertEquals("Text", text.getNodeName());
|
||||
assertEquals(texts.length, text.getLength());
|
||||
@@ -551,21 +509,10 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
|
||||
if (transparency != null) {
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("none", alpha.getAttribute("value"));
|
||||
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
}
|
||||
// Otherwise, no transparency, just defaults
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNull(transparency); // No transparency, just defaults
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -573,18 +520,18 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", pixelAspectRatio.getNodeName());
|
||||
assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -593,33 +540,28 @@ public class IFFImageMetadataTest {
|
||||
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
|
||||
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(2, transparency.getLength());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("TransparentIndex", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling();
|
||||
assertEquals("TransparentIndex", transparentIndex.getNodeName());
|
||||
assertEquals("1", transparentIndex.getAttribute("value"));
|
||||
|
||||
assertNull(transparentIndex.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardRGB8() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_RGB8)
|
||||
.with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Chroma
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -639,7 +581,7 @@ public class IFFImageMetadataTest {
|
||||
assertNull(blackIsZero.getNextSibling()); // No more children
|
||||
|
||||
// Data
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -659,7 +601,7 @@ public class IFFImageMetadataTest {
|
||||
assertNull(bitsPerSample.getNextSibling()); // No more children
|
||||
|
||||
// Transparency
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
@@ -682,10 +624,10 @@ public class IFFImageMetadataTest {
|
||||
Form header = Form.ofType(IFF.TYPE_DEEP)
|
||||
.with(new DGBLChunk(8))
|
||||
.with(dpel);
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB_DEEP, header, header.colorMap());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Chroma
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -707,7 +649,7 @@ public class IFFImageMetadataTest {
|
||||
assertNull(blackIsZero.getNextSibling()); // No more children
|
||||
|
||||
// Data
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -727,7 +669,7 @@ public class IFFImageMetadataTest {
|
||||
assertNull(bitsPerSample.getNextSibling()); // No more children
|
||||
|
||||
// Transparency
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
@@ -738,13 +680,4 @@ public class IFFImageMetadataTest {
|
||||
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
// TODO: Test RGB8 + ColorMap
|
||||
|
||||
private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) {
|
||||
IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
NodeList nodes = asTree.getElementsByTagName(nodeName);
|
||||
|
||||
return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pcx</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||
|
||||
+5
-7
@@ -45,7 +45,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
@@ -377,12 +377,10 @@ public final class PCXImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
// checkBounds(imageIndex);
|
||||
// readHeader();
|
||||
//
|
||||
// return new PCXMetadata(header, getVGAPalette());
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
return new PCXMetadata(rawType, header);
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new PCXMetadata(header, getVGAPalette());
|
||||
}
|
||||
|
||||
private IndexColorModel getVGAPalette() throws IOException {
|
||||
|
||||
+225
-18
@@ -1,29 +1,236 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.pcx;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
final class PCXMetadata extends StandardImageMetadataSupport {
|
||||
public PCXMetadata(ImageTypeSpecifier type, PCXHeader header) {
|
||||
super(builder(type)
|
||||
.withPlanarConfiguration(planarConfiguration(header))
|
||||
.withCompressionTypeName(compressionName(header))
|
||||
.withFormatVersion(String.valueOf(header.getVersion())));
|
||||
final class PCXMetadata extends AbstractMetadata {
|
||||
private final PCXHeader header;
|
||||
private final IndexColorModel vgaPalette;
|
||||
|
||||
PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
|
||||
this.header = header;
|
||||
this.vgaPalette = vgaPalette;
|
||||
}
|
||||
|
||||
private static PlanarConfiguration planarConfiguration(PCXHeader header) {
|
||||
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
|
||||
}
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
private static String compressionName(PCXHeader header) {
|
||||
switch (header.getCompression()) {
|
||||
case PCX.COMPRESSION_NONE:
|
||||
return "None";
|
||||
case PCX.COMPRESSION_RLE:
|
||||
return "RLE";
|
||||
IndexColorModel palette = null;
|
||||
boolean gray = false;
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
switch (header.getBitsPerPixel()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
palette = header.getEGAPalette();
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
case 8:
|
||||
// We may have IndexColorModel here for 1 channel images
|
||||
if (header.getChannels() == 1 && vgaPalette != null) {
|
||||
palette = vgaPalette;
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
}
|
||||
if (header.getChannels() == 1) {
|
||||
csType.setAttribute("name", "GRAY");
|
||||
gray = true;
|
||||
break;
|
||||
}
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
|
||||
case 24:
|
||||
// Some sources says this is possible... Untested.
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
|
||||
default:
|
||||
csType.setAttribute("name", "Unknown");
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
chroma.appendChild(csType);
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model, not data! (see data node)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", gray ? "1" : "3");
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
if (palette != null) {
|
||||
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
|
||||
chroma.appendChild(paletteNode);
|
||||
|
||||
for (int i = 0; i < palette.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||
|
||||
paletteEntry.setAttribute("red", Integer.toString(palette.getRed(i)));
|
||||
paletteEntry.setAttribute("green", Integer.toString(palette.getGreen(i)));
|
||||
paletteEntry.setAttribute("blue", Integer.toString(palette.getBlue(i)));
|
||||
|
||||
paletteNode.appendChild(paletteEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.getCompression() != PCX.COMPRESSION_NONE) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", header.getCompression() == PCX.COMPRESSION_RLE ? "RLE" : "Uknown");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
// Planar configuration only makes sense for multi-channel images
|
||||
if (header.getChannels() > 1) {
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
planarConfiguration.setAttribute("value", "LineInterleaved");
|
||||
node.appendChild(planarConfiguration);
|
||||
}
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
|
||||
switch (header.getBitsPerPixel()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
sampleFormat.setAttribute("value", "Index");
|
||||
break;
|
||||
case 8:
|
||||
if (header.getChannels() == 1 && vgaPalette != null) {
|
||||
sampleFormat.setAttribute("value", "Index");
|
||||
break;
|
||||
}
|
||||
// Else fall through for GRAY
|
||||
default:
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
break;
|
||||
}
|
||||
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("FormatVersion");
|
||||
imageOrientation.setAttribute("value", String.valueOf(header.getVersion()));
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No text node
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pdf</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pict</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
||||
|
||||
+9
-21
@@ -67,28 +67,18 @@ import com.twelvemonkeys.io.enc.Decoder;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Reader for Apple Mac Paint Picture (PICT) format.
|
||||
@@ -2621,9 +2611,7 @@ public final class PICTImageReader extends ImageReaderBase {
|
||||
return getYPtCoord(getPICTFrame().height);
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) {
|
||||
// TODO: The images look slightly different in Preview.. Could indicate the color space is wrong...
|
||||
return Collections.singletonList(
|
||||
ImageTypeSpecifiers.createPacked(
|
||||
@@ -2635,10 +2623,10 @@ public final class PICTImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
checkBounds(imageIndex);
|
||||
getPICTFrame(); // TODO: Would probably be better to use readPictHeader here, but it isn't cached
|
||||
|
||||
return new PICTMetadata(rawType, version, screenImageXRatio, screenImageYRatio);
|
||||
return new PICTMetadata(version, screenImageXRatio, screenImageYRatio);
|
||||
}
|
||||
|
||||
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
||||
|
||||
+79
-40
@@ -1,38 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.pict;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
/**
|
||||
* PICTMetadata.
|
||||
@@ -41,13 +11,82 @@ import javax.imageio.ImageTypeSpecifier;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PICTMetadata.java,v 1.0 23/03/2021 haraldk Exp$
|
||||
*/
|
||||
final class PICTMetadata extends StandardImageMetadataSupport {
|
||||
PICTMetadata(final ImageTypeSpecifier type, final int version, final double screenImageXRatio, final double screenImageYRatio) {
|
||||
super(builder(type)
|
||||
.withPixelAspectRatio(screenImageXRatio > 0.0d && screenImageYRatio > 0.0d ? screenImageXRatio / screenImageYRatio : 1)
|
||||
.withFormatVersion(Integer.toString(version))
|
||||
);
|
||||
// As this is a vector-ish format, some of the data makes no sense... :-P
|
||||
// It is, however, consistent with the getRawImageTyp/getImageTypes
|
||||
public class PICTMetadata extends AbstractMetadata {
|
||||
|
||||
private final int version;
|
||||
private final double screenImageXRatio;
|
||||
private final double screenImageYRatio;
|
||||
|
||||
PICTMetadata(final int version, final double screenImageXRatio, final double screenImageYRatio) {
|
||||
this.version = version;
|
||||
this.screenImageXRatio = screenImageXRatio;
|
||||
this.screenImageYRatio = screenImageYRatio;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
csType.setAttribute("name", "RGB");
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
numChannels.setAttribute("value", "3");
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
if (screenImageXRatio > 0.0d && screenImageYRatio > 0.0d) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Dimension");
|
||||
double ratio = screenImageXRatio / screenImageYRatio;
|
||||
IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio");
|
||||
subNode.setAttribute("value", "" + ratio);
|
||||
node.appendChild(subNode);
|
||||
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode data = new IIOMetadataNode("Data");
|
||||
|
||||
// As this is a vector-ish format, with possibly multiple regions of pixel data, this makes no sense... :-P
|
||||
// This is, however, consistent with the getRawImageTyp/getImageTypes
|
||||
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
data.appendChild(planarConfiguration);
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
data.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", "32");
|
||||
data.appendChild(bitsPerSample);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", Integer.toString(version));
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-32
@@ -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.plugins.pntg;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
@@ -64,7 +34,7 @@ public final class PNTGImageReader extends ImageReaderBase {
|
||||
private static final Set<ImageTypeSpecifier> IMAGE_TYPES =
|
||||
Collections.singleton(ImageTypeSpecifiers.createIndexed(new int[] {-1, 0}, false, -1, 1, DataBuffer.TYPE_BYTE));
|
||||
|
||||
PNTGImageReader(final ImageReaderSpi provider) {
|
||||
protected PNTGImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@@ -153,7 +123,9 @@ public final class PNTGImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
return new PNTGMetadata(getRawImageType(imageIndex));
|
||||
checkBounds(imageIndex);
|
||||
|
||||
return new PNTGMetadata();
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
|
||||
-30
@@ -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.plugins.pntg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
+73
-38
@@ -1,38 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.pntg;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
/**
|
||||
* PNTGMetadata.
|
||||
@@ -41,11 +11,76 @@ import javax.imageio.ImageTypeSpecifier;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PNTGMetadata.java,v 1.0 23/03/2021 haraldk Exp$
|
||||
*/
|
||||
final class PNTGMetadata extends StandardImageMetadataSupport {
|
||||
public PNTGMetadata(ImageTypeSpecifier type) {
|
||||
super(builder(type)
|
||||
.withBlackIsZero(false)
|
||||
.withCompressionTypeName("PackBits")
|
||||
.withFormatVersion("1.0"));
|
||||
public class PNTGMetadata extends AbstractMetadata {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
csType.setAttribute("name", "GRAY");
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
numChannels.setAttribute("value", "1");
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "FALSE");
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compressionNode = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", "PackBits"); // RLE?
|
||||
compressionNode.appendChild(compressionTypeName);
|
||||
compressionNode.appendChild(new IIOMetadataNode("Lossless"));
|
||||
// "value" defaults to TRUE
|
||||
|
||||
return compressionNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode data = new IIOMetadataNode("Data");
|
||||
|
||||
// PlanarConfiguration
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
data.appendChild(planarConfiguration);
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
data.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", "1");
|
||||
data.appendChild(bitsPerSample);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", "1.0");
|
||||
|
||||
// TODO: We could get the file creation time from MacBinary header here...
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: We could get the file name from MacBinary header here...
|
||||
return super.getStandardTextNode();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
||||
+1
-5
@@ -1,11 +1,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pntg;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* PNTGMetadataTest.
|
||||
*
|
||||
@@ -16,6 +12,6 @@ import java.awt.image.*;
|
||||
public class PNTGMetadataTest {
|
||||
@Test
|
||||
public void testCreate() {
|
||||
new PNTGMetadata(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY));
|
||||
new PNTGMetadata();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
||||
|
||||
+5
-2
@@ -43,7 +43,7 @@ import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
@@ -468,7 +468,10 @@ public final class PNMImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
return new PNMMetadata(getRawImageType(imageIndex), header);
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new PNMMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
+118
-40
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -30,35 +30,92 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* PNMMetadata.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
final class PNMMetadata extends StandardImageMetadataSupport {
|
||||
final class PNMMetadata extends AbstractMetadata {
|
||||
private final PNMHeader header;
|
||||
|
||||
PNMMetadata(ImageTypeSpecifier type, PNMHeader header) {
|
||||
super(builder(type)
|
||||
.withColorSpaceType(colorSpace(header))
|
||||
// TODO: Might make sense to set gamma?
|
||||
.withBlackIsZero(header.getTupleType() != TupleType.BLACKANDWHITE_WHITE_IS_ZERO)
|
||||
.withSignificantBitsPerSample(significantBits(header))
|
||||
.withSampleMSB(header.getByteOrder() == ByteOrder.BIG_ENDIAN ? 0 : header.getBitsPerSample() - 1)
|
||||
.withOrientation(orientation(header))
|
||||
);
|
||||
|
||||
PNMMetadata(final PNMHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
private static int significantBits(PNMHeader header) {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
switch (header.getTupleType()) {
|
||||
case BLACKANDWHITE:
|
||||
case BLACKANDWHITE_ALPHA:
|
||||
case BLACKANDWHITE_WHITE_IS_ZERO:
|
||||
case GRAYSCALE:
|
||||
case GRAYSCALE_ALPHA:
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
case RGB:
|
||||
case RGB_ALPHA:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
case CMYK:
|
||||
case CMYK_ALPHA:
|
||||
csType.setAttribute("name", "CMYK");
|
||||
break;
|
||||
}
|
||||
|
||||
if (csType.getAttribute("name") != null) {
|
||||
chroma.appendChild(csType);
|
||||
}
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", Integer.toString(header.getSamplesPerPixel()));
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
// TODO: Might make sense to set gamma?
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO
|
||||
? "FALSE"
|
||||
: "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT
|
||||
? "Real"
|
||||
: "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(header.getBitsPerSample())));
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||
? "0"
|
||||
: Integer.toString(header.getBitsPerSample() - 1);
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private int computeSignificantBits() {
|
||||
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
|
||||
return header.getBitsPerSample();
|
||||
}
|
||||
@@ -75,30 +132,38 @@ final class PNMMetadata extends StandardImageMetadataSupport {
|
||||
return significantBits;
|
||||
}
|
||||
|
||||
private static ColorSpaceType colorSpace(PNMHeader header) {
|
||||
switch (header.getTupleType()) {
|
||||
case BLACKANDWHITE:
|
||||
case BLACKANDWHITE_ALPHA:
|
||||
case BLACKANDWHITE_WHITE_IS_ZERO:
|
||||
case GRAYSCALE:
|
||||
case GRAYSCALE_ALPHA:
|
||||
return ColorSpaceType.GRAY;
|
||||
default:
|
||||
return null; // Fall back to color model's type
|
||||
}
|
||||
}
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
private static ImageOrientation orientation(PNMHeader header) {
|
||||
// For some reason, the float values are stored bottom-up
|
||||
return header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB
|
||||
? ImageOrientation.FlipH
|
||||
: ImageOrientation.Normal;
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value",
|
||||
header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB
|
||||
? "FlipH"
|
||||
: "Normal");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: Could avoid this override, by changing the StandardImageMetadataSupport to
|
||||
// use List<Entry<String, String>> instead of Map<String, String> (we use duplicate "comment"s).
|
||||
if (!header.getComments().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
@@ -114,4 +179,17 @@ final class PNMMetadata extends StandardImageMetadataSupport {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getTransparency() == Transparency.OPAQUE ? "none" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-psd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-reference</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
final class SGIHeader {
|
||||
private int compression;
|
||||
@@ -157,6 +157,6 @@ final class SGIHeader {
|
||||
}
|
||||
}
|
||||
|
||||
return new String(bytes, 0, len, StandardCharsets.US_ASCII);
|
||||
return new String(bytes, 0, len, Charset.forName("ASCII"));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -45,7 +45,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
@@ -378,10 +378,11 @@ public final class SGIImageReader extends ImageReaderBase {
|
||||
imageInput.seek(imageInput.getFlushedPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
return new SGIMetadata(rawType, header);
|
||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new SGIMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
+191
-21
@@ -1,39 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.sgi;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
final class SGIMetadata extends StandardImageMetadataSupport {
|
||||
public SGIMetadata(ImageTypeSpecifier type, SGIHeader header) {
|
||||
super(builder(type)
|
||||
.withSignificantBitsPerSample(computeSignificantBits(header))
|
||||
.withCompressionTypeName(compressionName(header))
|
||||
.withOrientation(ImageOrientation.FlipV)
|
||||
.withTextEntry("DocumentName", header.getName())
|
||||
);
|
||||
final class SGIMetadata extends AbstractMetadata {
|
||||
private final SGIHeader header;
|
||||
|
||||
SGIMetadata(final SGIHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
private static int computeSignificantBits(SGIHeader header) {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
switch (header.getColorMode()) {
|
||||
case SGI.COLORMODE_NORMAL:
|
||||
switch (header.getChannels()) {
|
||||
case 1:
|
||||
case 2:
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
default:
|
||||
csType.setAttribute("name", Integer.toHexString(header.getChannels()).toUpperCase() + "CLR");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// SGIIMAGE.TXT describes these as RGB
|
||||
case SGI.COLORMODE_DITHERED:
|
||||
case SGI.COLORMODE_SCREEN:
|
||||
case SGI.COLORMODE_COLORMAP:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
}
|
||||
|
||||
if (csType.getAttribute("name") != null) {
|
||||
chroma.appendChild(csType);
|
||||
}
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", Integer.toString(header.getChannels()));
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.getCompression() != SGI.COMPRESSION_NONE) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE
|
||||
? "RLE"
|
||||
: "Uknown");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBytesPerPixel() * 8)));
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(computeSignificantBits())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private int computeSignificantBits() {
|
||||
int significantBits = 0;
|
||||
|
||||
int maxSample = header.getMaxValue();
|
||||
|
||||
int significantBits = 1;
|
||||
|
||||
while ((maxSample >>>= 1) != 0) {
|
||||
while (maxSample > 0) {
|
||||
maxSample >>>= 1;
|
||||
significantBits++;
|
||||
}
|
||||
|
||||
return significantBits;
|
||||
}
|
||||
|
||||
private static String compressionName(SGIHeader header) {
|
||||
switch (header.getCompression()) {
|
||||
case SGI.COMPRESSION_NONE:
|
||||
return "None";
|
||||
case SGI.COMPRESSION_RLE:
|
||||
return "RLE";
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return "Uknown";
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "FlipV");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (!header.getName().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "name");
|
||||
textEntry.setAttribute("value", header.getName());
|
||||
text.appendChild(textEntry);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3
|
||||
? "none"
|
||||
: "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tga</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||
|
||||
+10
-12
@@ -31,16 +31,18 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.awt.color.ColorSpace.*;
|
||||
import static java.awt.color.ColorSpace.TYPE_GRAY;
|
||||
import static java.awt.color.ColorSpace.TYPE_RGB;
|
||||
|
||||
final class TGAHeader {
|
||||
|
||||
@@ -116,14 +118,10 @@ final class TGAHeader {
|
||||
'}';
|
||||
}
|
||||
|
||||
static TGAHeader from(final ImageTypeSpecifier type, final boolean compressed) {
|
||||
return from(type, 0, 0, compressed);
|
||||
}
|
||||
static TGAHeader from(final RenderedImage image, final boolean compressed) {
|
||||
notNull(image, "image");
|
||||
|
||||
static TGAHeader from(final ImageTypeSpecifier type, int width, int height, final boolean compressed) {
|
||||
notNull(type, "type");
|
||||
|
||||
ColorModel colorModel = type.getColorModel();
|
||||
ColorModel colorModel = image.getColorModel();
|
||||
IndexColorModel colorMap = colorModel instanceof IndexColorModel ? (IndexColorModel) colorModel : null;
|
||||
|
||||
TGAHeader header = new TGAHeader();
|
||||
@@ -137,8 +135,8 @@ final class TGAHeader {
|
||||
header.x = 0;
|
||||
header.y = 0;
|
||||
|
||||
header.width = width;
|
||||
header.height = height;
|
||||
header.width = image.getWidth(); // TODO: Param source region/subsampling might affect this
|
||||
header.height = image.getHeight(); // // TODO: Param source region/subsampling might affect this
|
||||
header.pixelDepth = colorModel.getPixelSize() == 15 ? 16 : colorModel.getPixelSize();
|
||||
|
||||
header.origin = TGA.ORIGIN_UPPER_LEFT; // TODO: Allow parameter to control this?
|
||||
|
||||
+5
-3
@@ -47,7 +47,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.File;
|
||||
@@ -538,8 +538,10 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
return new TGAMetadata(rawType, header, extensions);
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new TGAMetadata(header, extensions);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
+9
-13
@@ -37,16 +37,12 @@ import com.twelvemonkeys.io.LittleEndianDataOutputStream;
|
||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataOutput;
|
||||
import java.io.File;
|
||||
@@ -69,7 +65,8 @@ final class TGAImageWriter extends ImageWriterBase {
|
||||
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
Validate.notNull(imageType, "imageType");
|
||||
|
||||
return new TGAMetadata(imageType, TGAHeader.from(imageType, isRLE(param, null)), null);
|
||||
TGAHeader header = TGAHeader.from(imageType.createBufferedImage(1, 1), isRLE(param, null));
|
||||
return new TGAMetadata(header, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,8 +107,7 @@ final class TGAImageWriter extends ImageWriterBase {
|
||||
|
||||
final boolean compressed = isRLE(param, image.getMetadata());
|
||||
RenderedImage renderedImage = image.getRenderedImage();
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromRenderedImage(renderedImage);
|
||||
TGAHeader header = TGAHeader.from(type, renderedImage.getWidth(), renderedImage.getHeight(), compressed);
|
||||
TGAHeader header = TGAHeader.from(renderedImage, compressed);
|
||||
|
||||
header.write(imageOutput);
|
||||
|
||||
@@ -121,7 +117,7 @@ final class TGAImageWriter extends ImageWriterBase {
|
||||
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
|
||||
: renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT
|
||||
? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster()
|
||||
: type.createBufferedImage(renderedImage.getWidth(), 1).getRaster();
|
||||
: ImageTypeSpecifier.createFromRenderedImage(renderedImage).createBufferedImage(renderedImage.getWidth(), 1).getRaster();
|
||||
|
||||
final DataBuffer buffer = rowRaster.getDataBuffer();
|
||||
|
||||
@@ -139,7 +135,7 @@ final class TGAImageWriter extends ImageWriterBase {
|
||||
break;
|
||||
}
|
||||
|
||||
DataOutput imageOutput = compressed ? createRLEStream(this.imageOutput, header.getPixelDepth()) : this.imageOutput;
|
||||
DataOutput imageOutput = compressed ? createRLEStream(header, this.imageOutput) : this.imageOutput;
|
||||
|
||||
switch (buffer.getDataType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
@@ -178,8 +174,8 @@ final class TGAImageWriter extends ImageWriterBase {
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
private static LittleEndianDataOutputStream createRLEStream(final ImageOutputStream stream, int pixelDepth) {
|
||||
return new LittleEndianDataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(stream), new RLEEncoder(pixelDepth)));
|
||||
private static LittleEndianDataOutputStream createRLEStream(final TGAHeader header, final ImageOutputStream stream) {
|
||||
return new LittleEndianDataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(stream), new RLEEncoder(header.getPixelDepth())));
|
||||
}
|
||||
|
||||
// TODO: Refactor to common util
|
||||
|
||||
+300
-43
@@ -1,83 +1,340 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 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.tga;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class TGAMetadata extends StandardImageMetadataSupport {
|
||||
TGAMetadata(ImageTypeSpecifier type, TGAHeader header, TGAExtensions extensions) {
|
||||
super(builder(type)
|
||||
.withCompressionTypeName(compressionName(header))
|
||||
.withPixelAspectRatio(pixelAspectRatio(extensions))
|
||||
.withOrientation(orientation(header))
|
||||
.withFormatVersion(extensions == null ? "1.0" : "2.0")
|
||||
.withDocumentCreationTime(documentCreationTime(extensions))
|
||||
.withTextEntries(textEntries(header, extensions))
|
||||
);
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
final class TGAMetadata extends AbstractMetadata {
|
||||
private final TGAHeader header;
|
||||
private final TGAExtensions extensions;
|
||||
|
||||
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
||||
this.header = notNull(header, "header");
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
private static String compressionName(TGAHeader header) {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_NONE:
|
||||
case TGA.IMAGETYPE_COLORMAPPED:
|
||||
case TGA.IMAGETYPE_TRUECOLOR:
|
||||
case TGA.IMAGETYPE_MONOCHROME:
|
||||
return "None";
|
||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
|
||||
case TGA.IMAGETYPE_TRUECOLOR:
|
||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||
case TGA.IMAGETYPE_COLORMAPPED:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
default:
|
||||
csType.setAttribute("name", "Unknown");
|
||||
}
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
if (header.getImageType() == TGA.IMAGETYPE_MONOCHROME || header.getImageType() == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||
numChannels.setAttribute("value", Integer.toString(1));
|
||||
}
|
||||
else {
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
}
|
||||
break;
|
||||
case 16:
|
||||
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
}
|
||||
else {
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
}
|
||||
break;
|
||||
case 24:
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
break;
|
||||
case 32:
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
break;
|
||||
}
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
|
||||
// NOTE: TGA files may contain a color map, even if true color...
|
||||
// Not sure if this is a good idea to expose to the meta data,
|
||||
// as it might be unexpected... Then again...
|
||||
IndexColorModel colorMap = header.getColorMap();
|
||||
if (colorMap != null) {
|
||||
IIOMetadataNode palette = new IIOMetadataNode("Palette");
|
||||
chroma.appendChild(palette);
|
||||
|
||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||
palette.appendChild(paletteEntry);
|
||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||
|
||||
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||
Color background = new Color(extensions.getBackgroundColor(), true);
|
||||
|
||||
IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
|
||||
chroma.appendChild(backgroundColor);
|
||||
|
||||
backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
|
||||
backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
|
||||
backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
|
||||
}
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||
return "RLE";
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
node.appendChild(compressionTypeName);
|
||||
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||
? "Unknown" : "RLE";
|
||||
compressionTypeName.setAttribute("value", value);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
node.appendChild(lossless);
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
|
||||
return node;
|
||||
default:
|
||||
return "Unknown";
|
||||
// No compression
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static double pixelAspectRatio(TGAExtensions extensions) {
|
||||
return extensions != null ? extensions.getPixelAspectRatio() : 1f;
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
node.appendChild(planarConfiguration);
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_COLORMAPPED:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||
sampleFormat.setAttribute("value", "Index");
|
||||
break;
|
||||
default:
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
break;
|
||||
}
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
bitsPerSample.setAttribute("value", createListValue(1, "8"));
|
||||
break;
|
||||
case 16:
|
||||
if (header.getImageType() == TGA.IMAGETYPE_MONOCHROME || header.getImageType() == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||
bitsPerSample.setAttribute("value", "16");
|
||||
}
|
||||
else if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
||||
}
|
||||
else {
|
||||
bitsPerSample.setAttribute("value", createListValue(3, "5"));
|
||||
}
|
||||
break;
|
||||
case 24:
|
||||
bitsPerSample.setAttribute("value", createListValue(3, "8"));
|
||||
break;
|
||||
case 32:
|
||||
bitsPerSample.setAttribute("value", createListValue(4, "8"));
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static ImageOrientation orientation(TGAHeader header) {
|
||||
switch (header.origin) {
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
switch (header.getOrigin()) {
|
||||
case TGA.ORIGIN_LOWER_LEFT:
|
||||
return ImageOrientation.FlipH;
|
||||
imageOrientation.setAttribute("value", "FlipH");
|
||||
break;
|
||||
case TGA.ORIGIN_LOWER_RIGHT:
|
||||
return ImageOrientation.Rotate180;
|
||||
imageOrientation.setAttribute("value", "Rotate180");
|
||||
break;
|
||||
case TGA.ORIGIN_UPPER_LEFT:
|
||||
return ImageOrientation.Normal;
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
break;
|
||||
case TGA.ORIGIN_UPPER_RIGHT:
|
||||
return ImageOrientation.FlipV;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown orientation: " + header.origin);
|
||||
imageOrientation.setAttribute("value", "FlipV");
|
||||
break;
|
||||
}
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
dimension.appendChild(pixelAspectRatio);
|
||||
pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0");
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private static Calendar documentCreationTime(TGAExtensions extensions) {
|
||||
return extensions != null ? extensions.creationDate : null;
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0");
|
||||
|
||||
// ImageCreationTime from extensions date
|
||||
if (extensions != null && extensions.getCreationDate() != null) {
|
||||
IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
|
||||
document.appendChild(imageCreationTime);
|
||||
|
||||
Calendar date = extensions.getCreationDate();
|
||||
|
||||
imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
|
||||
imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
|
||||
imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
|
||||
imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
|
||||
imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
|
||||
imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private static Map<String, String> textEntries(TGAHeader header, TGAExtensions extensions) {
|
||||
LinkedHashMap<String, String> textEntries = new LinkedHashMap<>();
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
// NOTE: Keywords follow TIFF standard naming
|
||||
putIfValue(textEntries, "DocumentName", header.getIdentification());
|
||||
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||
|
||||
if (extensions != null) {
|
||||
putIfValue(textEntries, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : (extensions.getSoftware() + " " + extensions.getSoftwareVersion()));
|
||||
putIfValue(textEntries, "Artist", extensions.getAuthorName());
|
||||
putIfValue(textEntries, "UserComment", extensions.getAuthorComments());
|
||||
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : (extensions.getSoftware() + " " + extensions.getSoftwareVersion()));
|
||||
appendTextEntry(text, "Artist", extensions.getAuthorName());
|
||||
appendTextEntry(text, "UserComment", extensions.getAuthorComments());
|
||||
}
|
||||
|
||||
return textEntries;
|
||||
return text.hasChildNodes() ? text : null;
|
||||
}
|
||||
|
||||
private static void putIfValue(final Map<String, String> textEntries, final String keyword, final String value) {
|
||||
private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
textEntries.put(keyword, value);
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
parent.appendChild(textEntry);
|
||||
textEntry.setAttribute("keyword", keyword);
|
||||
textEntry.setAttribute("value", value);
|
||||
}
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
if (extensions != null) {
|
||||
if (extensions.hasAlpha()) {
|
||||
alpha.setAttribute("value", extensions.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||
}
|
||||
else {
|
||||
alpha.setAttribute("value", "none");
|
||||
}
|
||||
}
|
||||
else if (header.getAttributeBits() == 8) {
|
||||
alpha.setAttribute("value", "nonpremultiplied");
|
||||
}
|
||||
else {
|
||||
alpha.setAttribute("value", "none");
|
||||
}
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-18
@@ -30,17 +30,12 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
|
||||
/**
|
||||
@@ -51,9 +46,6 @@ import static org.junit.Assume.assumeFalse;
|
||||
* @version $Id: TGAImageWriteParamTest.java,v 1.0 08/04/2021 haraldk Exp$
|
||||
*/
|
||||
public class TGAImageWriteParamTest {
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_3BYTE_BGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
|
||||
@Test
|
||||
public void testDefaultCopyFromMetadata() {
|
||||
TGAImageWriteParam param = new TGAImageWriteParam();
|
||||
@@ -115,8 +107,8 @@ public class TGAImageWriteParamTest {
|
||||
ImageWriteParam param = new ImageWriteParam(null);
|
||||
assumeFalse(param.canWriteCompressed());
|
||||
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,7 +116,7 @@ public class TGAImageWriteParamTest {
|
||||
ImageWriteParam param = new TGAImageWriteParam();
|
||||
param.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
|
||||
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null)));
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,7 +124,7 @@ public class TGAImageWriteParamTest {
|
||||
ImageWriteParam param = new TGAImageWriteParam();
|
||||
param.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
|
||||
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null)));
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -140,12 +132,12 @@ public class TGAImageWriteParamTest {
|
||||
TGAImageWriteParam param = new TGAImageWriteParam();
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null)));
|
||||
assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null)));
|
||||
|
||||
param.setCompressionType("RLE");
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null)));
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null)));
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null)));
|
||||
assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null)));
|
||||
}
|
||||
|
||||
}
|
||||
+67
-94
@@ -30,18 +30,14 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.Calendar;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -54,15 +50,10 @@ import static org.junit.Assert.*;
|
||||
* @version $Id: TGAMetadataTest.java,v 1.0 08/04/2021 haraldk Exp$
|
||||
*/
|
||||
public class TGAMetadataTest {
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_BYTE_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_3BYTE_BGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
|
||||
@Test
|
||||
public void testStandardFeatures() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false);
|
||||
final TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false);
|
||||
final TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
// Standard metadata format
|
||||
assertTrue(metadata.isStandardMetadataFormatSupported());
|
||||
@@ -92,10 +83,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardChromaGray() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, false);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), false);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -117,10 +108,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardChromaRGB() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
@@ -144,11 +135,10 @@ public class TGAMetadataTest {
|
||||
public void testStandardChromaPalette() {
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
IndexColorModel indexColorModel = new IndexColorModel(8, bw.length, bw, bw, bw, -1);
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel);
|
||||
TGAHeader header = TGAHeader.from(type, false);
|
||||
TGAMetadata metadata = new TGAMetadata(type, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), false);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(4, chroma.getLength());
|
||||
@@ -184,10 +174,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardCompressionRLE() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode compression = getStandardNode(metadata, "Compression");
|
||||
IIOMetadataNode compression = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compression);
|
||||
assertEquals("Compression", compression.getNodeName());
|
||||
assertEquals(2, compression.getLength());
|
||||
@@ -205,18 +195,18 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardCompressionNone() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
assertNull(getStandardNode(metadata, "Compression")); // No compression, all default...
|
||||
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataGray() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -238,10 +228,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardDataRGB() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -263,11 +253,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardDataRGBA() {
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||
TGAHeader header = TGAHeader.from(type, true);
|
||||
TGAMetadata metadata = new TGAMetadata(type, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -291,11 +280,10 @@ public class TGAMetadataTest {
|
||||
public void testStandardDataPalette() {
|
||||
byte[] rgb = new byte[1 << 8]; // Colors doesn't really matter here
|
||||
IndexColorModel indexColorModel = new IndexColorModel(8, rgb.length, rgb, rgb, rgb, 0);
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel);
|
||||
TGAHeader header = TGAHeader.from(type, true);
|
||||
TGAMetadata metadata = new TGAMetadata(type, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = getStandardNode(metadata, "Data");
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
@@ -317,56 +305,53 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionNormal() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("Normal", imageOrientation.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionFlipH() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
header.origin = TGA.ORIGIN_LOWER_LEFT;
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(2, dimension.getLength());
|
||||
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
|
||||
IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("ImageOrientation", imageOrientation.getNodeName());
|
||||
assertEquals("FlipH", imageOrientation.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
assertNull(imageOrientation.getNextSibling()); // No more children
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDocument() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode document = getStandardNode(metadata, "Document");
|
||||
IIOMetadataNode document = metadata.getStandardDocumentNode();
|
||||
assertNotNull(document);
|
||||
assertEquals("Document", document.getNodeName());
|
||||
assertEquals(1, document.getLength());
|
||||
@@ -380,13 +365,13 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardDocumentExtensions() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
TGAExtensions extensions = new TGAExtensions();
|
||||
extensions.creationDate = Calendar.getInstance();
|
||||
extensions.creationDate.set(2021, Calendar.APRIL, 8, 18, 55, 0);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, extensions);
|
||||
TGAMetadata metadata = new TGAMetadata(header, extensions);
|
||||
|
||||
IIOMetadataNode document = getStandardNode(metadata, "Document");
|
||||
IIOMetadataNode document = metadata.getStandardDocumentNode();
|
||||
assertNotNull(document);
|
||||
assertEquals("Document", document.getNodeName());
|
||||
assertEquals(2, document.getLength());
|
||||
@@ -409,7 +394,7 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardText() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true);
|
||||
header.identification = "MY_FILE.TGA";
|
||||
|
||||
TGAExtensions extensions = new TGAExtensions();
|
||||
@@ -417,9 +402,9 @@ public class TGAMetadataTest {
|
||||
extensions.authorName = "Harald K";
|
||||
extensions.authorComments = "Comments, comments... ";
|
||||
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, extensions);
|
||||
TGAMetadata metadata = new TGAMetadata(header, extensions);
|
||||
|
||||
IIOMetadataNode text = getStandardNode(metadata, "Text");
|
||||
IIOMetadataNode text = metadata.getStandardTextNode();
|
||||
assertNotNull(text);
|
||||
assertEquals("Text", text.getNodeName());
|
||||
assertEquals(4, text.getLength());
|
||||
@@ -447,10 +432,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardTransparencyRGB() {
|
||||
TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true);
|
||||
TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
@@ -464,11 +449,10 @@ public class TGAMetadataTest {
|
||||
|
||||
@Test
|
||||
public void testStandardTransparencyRGBA() {
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
TGAHeader header = TGAHeader.from(type, true);
|
||||
TGAMetadata metadata = new TGAMetadata(type, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
@@ -484,30 +468,19 @@ public class TGAMetadataTest {
|
||||
public void testStandardTransparencyPalette() {
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
IndexColorModel indexColorModel = new IndexColorModel(8, bw.length, bw, bw, bw, 1);
|
||||
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel);
|
||||
TGAHeader header = TGAHeader.from(type, true);
|
||||
TGAMetadata metadata = new TGAMetadata(type, header, null);
|
||||
TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), true);
|
||||
TGAMetadata metadata = new TGAMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(2, transparency.getLength());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling();
|
||||
assertEquals("TransparentIndex", transparentIndex.getNodeName());
|
||||
assertEquals("1", transparentIndex.getAttribute("value"));
|
||||
|
||||
assertNull(transparentIndex.getNextSibling()); // No more children
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) {
|
||||
IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
NodeList nodes = asTree.getElementsByTagName(nodeName);
|
||||
|
||||
return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-thumbsdb</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
||||
|
||||
+3
-11
@@ -39,6 +39,7 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.NullOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -556,7 +557,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
IIOWriteProgressListener progress = mock(IIOWriteProgressListener.class, "progress");
|
||||
writer.addIIOWriteProgressListener(progress);
|
||||
|
||||
try (ImageOutputStream output = new NullImageOutputStream()) {
|
||||
try (ImageOutputStream output = ImageIO.createImageOutputStream(new NullOutputStream())) {
|
||||
writer.setOutput(output);
|
||||
|
||||
try {
|
||||
@@ -614,7 +615,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
public void testWriteParamJPEGQuality() throws IOException {
|
||||
ImageWriter writer = createWriter();
|
||||
|
||||
try (ImageOutputStream output = new NullImageOutputStream()) {
|
||||
try (ImageOutputStream output = ImageIO.createImageOutputStream(new NullOutputStream())) {
|
||||
writer.setOutput(output);
|
||||
|
||||
try {
|
||||
@@ -1365,30 +1366,21 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
private static final class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||
@Override
|
||||
public void write(int b) {
|
||||
streamPos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
streamPos += len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
streamPos++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) {
|
||||
streamPos += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return streamPos;
|
||||
}
|
||||
}
|
||||
|
||||
// Special purpose data buffer that does not require memory, to allow very large images
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
interface AlphaFiltering {
|
||||
int NONE = 0;
|
||||
int HORIZONTAL = 1;
|
||||
int VERTICAL = 2;
|
||||
int GRADIENT = 3;
|
||||
}
|
||||
+33
-146
@@ -1,169 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.webp;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* LSBBitReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
public final class LSBBitReader {
|
||||
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
|
||||
|
||||
private final ImageInputStream imageInput;
|
||||
private int bitOffset = 64;
|
||||
private long streamPosition = -1;
|
||||
|
||||
/**
|
||||
* Pre-buffers up to the next 8 Bytes in input.
|
||||
* Contains valid bits in bits 63 to {@code bitOffset} (inclusive).
|
||||
*/
|
||||
private long buffer;
|
||||
int bitOffset = 0;
|
||||
|
||||
public LSBBitReader(ImageInputStream imageInput) {
|
||||
this.imageInput = notNull(imageInput);
|
||||
this.imageInput = imageInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified number of bits from the stream in an LSB-first way and advances the bitOffset.
|
||||
* The underlying ImageInputStream will be advanced to the first not (completely) read byte.
|
||||
* Requesting more than 64 bits will advance the reader by the correct amount and return the lowest 64 bits of
|
||||
* the read number
|
||||
*
|
||||
* @param bits the number of bits to read
|
||||
* @return a signed long built from the requested bits (truncated to the low 64 bits)
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see LSBBitReader#peekBits
|
||||
*/
|
||||
// TODO: Optimize this... Read many bits at once!
|
||||
public long readBits(int bits) throws IOException {
|
||||
return readBits(bits, false);
|
||||
long result = 0;
|
||||
for (int i = 0; i < bits; i++) {
|
||||
result |= (long) readBit() << i;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified number of bits from the buffer in an LSB-first way.
|
||||
* Does not advance the bitOffset or the underlying input stream.
|
||||
* As only 56 bits are buffered (in the worst case) peeking more is not possible without advancing the reader and
|
||||
* as such disallowed.
|
||||
*
|
||||
* @param bits the number of bits to peek (max 56)
|
||||
* @return a signed long built from the requested bits
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see LSBBitReader#readBits
|
||||
*/
|
||||
public long peekBits(int bits) throws IOException {
|
||||
if (bits > 56) {
|
||||
throw new IllegalArgumentException("Tried peeking over 56");
|
||||
}
|
||||
|
||||
return readBits(bits, true);
|
||||
}
|
||||
|
||||
private long readBits(int bits, boolean peek) throws IOException {
|
||||
if (bits <= 56) {
|
||||
// Could eliminate if we never read from the underlying InputStream
|
||||
// outside this class after the object is created
|
||||
if (streamPosition != imageInput.getStreamPosition()) {
|
||||
// Need to reset buffer as stream was read in the meantime
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
long ret = (buffer >>> bitOffset) & ((1L << bits) - 1);
|
||||
|
||||
if (!peek) {
|
||||
bitOffset += bits;
|
||||
|
||||
if (bitOffset >= 8) {
|
||||
refillBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
// Peek always false in this case
|
||||
long lower = readBits(56);
|
||||
return (readBits(bits - 56) << (56)) | lower;
|
||||
}
|
||||
}
|
||||
|
||||
private void refillBuffer() throws IOException {
|
||||
// Set to stream position consistent with buffered bytes
|
||||
imageInput.readLong(); // Don't replace with skipBytes(8) or seek(+8), this will invalidate stream buffer... TODO: Fix streams to cope...
|
||||
|
||||
for (; bitOffset >= 8; bitOffset -= 8) {
|
||||
try {
|
||||
byte b = imageInput.readByte();
|
||||
buffer = ((long) b << 56) | buffer >>> 8;
|
||||
streamPosition++;
|
||||
}
|
||||
catch (EOFException e) {
|
||||
imageInput.seek(streamPosition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset to guarantee stream position consistent with returned bytes
|
||||
// Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
|
||||
// this class after the object is created.
|
||||
imageInput.seek(streamPosition);
|
||||
}
|
||||
|
||||
private void resetBuffer() throws IOException {
|
||||
long inputStreamPosition = imageInput.getStreamPosition();
|
||||
|
||||
try {
|
||||
buffer = imageInput.readLong();
|
||||
bitOffset = 0;
|
||||
streamPosition = inputStreamPosition;
|
||||
imageInput.seek(inputStreamPosition);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// Retry byte by byte
|
||||
streamPosition = inputStreamPosition - 8;
|
||||
bitOffset = 64;
|
||||
refillBuffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Left for backwards compatibility / Compatibility with ImageInputStream interface
|
||||
// TODO: Optimize this...
|
||||
// TODO: Consider not reading value over and over....
|
||||
public int readBit() throws IOException {
|
||||
return (int) readBits(1);
|
||||
int bit = 7 - bitOffset;
|
||||
|
||||
imageInput.setBitOffset(bit);
|
||||
|
||||
// Compute final bit offset before we call read() and seek()
|
||||
int newBitOffset = (bitOffset + 1) & 0x7;
|
||||
|
||||
int val = imageInput.read();
|
||||
if (val == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
if (newBitOffset != 0) {
|
||||
// Move byte position back if in the middle of a byte
|
||||
// NOTE: RESETS bit offset!
|
||||
imageInput.seek(imageInput.getStreamPosition() - 1);
|
||||
}
|
||||
|
||||
bitOffset = newBitOffset;
|
||||
|
||||
// Shift the bit to be read to the rightmost position
|
||||
return (val >> (7 - bit)) & 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
+176
-11
@@ -1,19 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.webp;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
final class WebPImageMetadata extends StandardImageMetadataSupport {
|
||||
WebPImageMetadata(ImageTypeSpecifier type, VP8xChunk header) {
|
||||
super(builder(type)
|
||||
.withCompressionTypeName(notNull(header, "header").isLossless ? "VP8L" : "VP8")
|
||||
.withCompressionLossless(header.isLossless)
|
||||
.withPixelAspectRatio(1.0)
|
||||
.withFormatVersion("1.0")
|
||||
// TODO: Get useful text nodes from EXIF or XMP
|
||||
);
|
||||
/**
|
||||
* WebPMetadata
|
||||
*/
|
||||
final class WebPImageMetadata extends AbstractMetadata {
|
||||
private final VP8xChunk header;
|
||||
|
||||
WebPImageMetadata(final VP8xChunk header) {
|
||||
this.header = notNull(header, "header");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
csType.setAttribute("name", "RGB");
|
||||
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
numChannels.setAttribute("value", Integer.toString(header.containsALPH ? 4 : 3));
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
String value = header.isLossless ? "VP8L" : "VP8"; // TODO: Naming: VP8L and VP8 or WebP and WebP Lossless?
|
||||
compressionTypeName.setAttribute("value", value);
|
||||
|
||||
// TODO: VP8 + lossless alpha!
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
node.appendChild(lossless);
|
||||
lossless.setAttribute("value", header.isLossless ? "TRUE" : "FALSE");
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
// TODO: WebP seems to support planar as well?
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
node.appendChild(planarConfiguration);
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
node.appendChild(sampleFormat);
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
bitsPerSample.setAttribute("value", createListValue(header.containsALPH ? 4 : 3, Integer.toString(8)));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
dimension.appendChild(imageOrientation);
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
dimension.appendChild(pixelAspectRatio);
|
||||
pixelAspectRatio.setAttribute("value", "1.0");
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", "1.0");
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
// TODO: Get useful text nodes from EXIF or XMP
|
||||
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||
|
||||
return text.hasChildNodes() ? text : null;
|
||||
}
|
||||
|
||||
// private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||
// if (value != null) {
|
||||
// IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
// parent.appendChild(textEntry);
|
||||
// textEntry.setAttribute("keyword", keyword);
|
||||
// textEntry.setAttribute("value", value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
if (header.containsALPH) {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
transparency.appendChild(alpha);
|
||||
alpha.setAttribute("value", "nonpremultiplied");
|
||||
return transparency;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Define native WebP metadata format (probably use RIFF structure)
|
||||
}
|
||||
|
||||
+71
-143
@@ -31,6 +31,28 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.webp;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorConvertOp;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
@@ -45,25 +67,6 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.imageio.util.RasterUtils;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
/**
|
||||
* WebPImageReader
|
||||
*/
|
||||
@@ -96,9 +99,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||
|
||||
if (imageInput != null) {
|
||||
lsbBitReader = new LSBBitReader(imageInput);
|
||||
}
|
||||
lsbBitReader = new LSBBitReader(imageInput);
|
||||
}
|
||||
|
||||
private void readHeader(int imageIndex) throws IOException {
|
||||
@@ -140,16 +141,6 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
case WebP.CHUNK_ANIM:
|
||||
// TODO: 32 bit bg color (hint!) + 16 bit loop count
|
||||
// + expose bg color in std image metadata...
|
||||
|
||||
/*
|
||||
int b = (int) lsbBitReader.readBits(8);
|
||||
int g = (int) lsbBitReader.readBits(8);
|
||||
int r = (int) lsbBitReader.readBits(8);
|
||||
int a = (int) lsbBitReader.readBits(8);
|
||||
|
||||
Color bg = new Color(r, g, b, a);
|
||||
short loopCount = (short) lsbBitReader.readBits(16);
|
||||
*/
|
||||
break;
|
||||
|
||||
case WebP.CHUNK_ANMF:
|
||||
@@ -162,7 +153,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
Rectangle bounds = new Rectangle(x, y, w, h);
|
||||
|
||||
// TODO: Expose duration/flags in image metadata
|
||||
int duration = (int) lsbBitReader.readBits(24);
|
||||
int duration = (int) imageInput.readBits(24);
|
||||
int flags = imageInput.readUnsignedByte(); // 6 bit reserved + blend mode + disposal mode
|
||||
|
||||
frames.add(new AnimationFrame(chunkLength, chunkStart, bounds, duration, flags));
|
||||
@@ -214,7 +205,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
switch (chunk) {
|
||||
case WebP.CHUNK_VP8_:
|
||||
// https://tools.ietf.org/html/rfc6386#section-9.1
|
||||
//https://tools.ietf.org/html/rfc6386#section-9.1
|
||||
int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = inter frame (not used in WebP)
|
||||
|
||||
if (frameType != 0) {
|
||||
@@ -274,19 +265,19 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// RsV|I|L|E|X|A|R
|
||||
int reserved = lsbBitReader.readBit();
|
||||
int reserved = (int) imageInput.readBits(2);
|
||||
if (reserved != 0) {
|
||||
// Spec says SHOULD be 0
|
||||
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
|
||||
}
|
||||
|
||||
header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
|
||||
header.containsXMP_ = lsbBitReader.readBit() == 1;
|
||||
header.containsEXIF = lsbBitReader.readBit() == 1;
|
||||
header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
|
||||
header.containsICCP = lsbBitReader.readBit() == 1;
|
||||
header.containsICCP = imageInput.readBit() == 1;
|
||||
header.containsALPH = imageInput.readBit() == 1; // L -> aLpha
|
||||
header.containsEXIF = imageInput.readBit() == 1;
|
||||
header.containsXMP_ = imageInput.readBit() == 1;
|
||||
header.containsANIM = imageInput.readBit() == 1; // A -> Anim
|
||||
|
||||
reserved = (int) lsbBitReader.readBits(26); // 2 + 24 bits reserved
|
||||
reserved = (int) imageInput.readBits(25); // 1 + 24 bits reserved
|
||||
if (reserved != 0) {
|
||||
// Spec says SHOULD be 0
|
||||
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
|
||||
@@ -434,7 +425,8 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
if (header.containsANIM) {
|
||||
AnimationFrame frame = frames.get(imageIndex);
|
||||
imageInput.seek(frame.offset + 16);
|
||||
readVP8Extended(destination, param, frame.offset + frame.length, frame.bounds.width, frame.bounds.height);
|
||||
opaqueAlpha(destination.getAlphaRaster());
|
||||
readVP8Extended(destination, param, frame.offset + frame.length);
|
||||
}
|
||||
else {
|
||||
imageInput.seek(header.offset + header.length);
|
||||
@@ -460,10 +452,6 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd) throws IOException {
|
||||
readVP8Extended(destination, param, streamEnd, header.width, header.height);
|
||||
}
|
||||
|
||||
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width, final int height) throws IOException {
|
||||
while (imageInput.getStreamPosition() < streamEnd) {
|
||||
int nextChunk = imageInput.readInt();
|
||||
long chunkLength = imageInput.readUnsignedInt();
|
||||
@@ -477,16 +465,45 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
switch (nextChunk) {
|
||||
case WebP.CHUNK_ALPH:
|
||||
readAlpha(destination, param, width, height);
|
||||
int reserved = (int) imageInput.readBits(2);
|
||||
if (reserved != 0) {
|
||||
// Spec says SHOULD be 0
|
||||
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
||||
}
|
||||
|
||||
int preProcessing = (int) imageInput.readBits(2);
|
||||
int filtering = (int) imageInput.readBits(2);
|
||||
int compression = (int) imageInput.readBits(2);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("preProcessing: " + preProcessing);
|
||||
System.out.println("filtering: " + filtering);
|
||||
System.out.println("compression: " + compression);
|
||||
}
|
||||
|
||||
switch (compression) {
|
||||
case 0:
|
||||
readUncompressedAlpha(destination.getAlphaRaster());
|
||||
break;
|
||||
case 1:
|
||||
opaqueAlpha(destination.getAlphaRaster()); // TODO: Remove when correctly implemented!
|
||||
// readVP8Lossless(destination.getAlphaRaster(), param);
|
||||
break;
|
||||
default:
|
||||
processWarningOccurred("Unknown WebP alpha compression: " + compression);
|
||||
opaqueAlpha(destination.getAlphaRaster());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WebP.CHUNK_VP8_:
|
||||
readVP8(RasterUtils.asByteRaster(destination.getRaster())
|
||||
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[] {0, 1, 2}), param);
|
||||
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[]{ 0, 1, 2}), param);
|
||||
break;
|
||||
|
||||
case WebP.CHUNK_VP8L:
|
||||
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param, width, height);
|
||||
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param);
|
||||
break;
|
||||
|
||||
case WebP.CHUNK_ANIM:
|
||||
@@ -510,92 +527,6 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
|
||||
int reserved = (int) lsbBitReader.readBits(2);
|
||||
if (reserved != 0) {
|
||||
// Spec says SHOULD be 0
|
||||
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
||||
}
|
||||
|
||||
int preProcessing = (int) lsbBitReader.readBits(2);
|
||||
int filtering = (int) lsbBitReader.readBits(2);
|
||||
int compression = (int) lsbBitReader.readBits(2);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("preProcessing: " + preProcessing);
|
||||
System.out.println("filtering: " + filtering);
|
||||
System.out.println("compression: " + compression);
|
||||
}
|
||||
|
||||
WritableRaster alphaRaster = destination.getAlphaRaster();
|
||||
switch (compression) {
|
||||
case 0:
|
||||
readUncompressedAlpha(alphaRaster);
|
||||
break;
|
||||
case 1:
|
||||
// Simulate header
|
||||
imageInput.seek(imageInput.getStreamPosition() - 5);
|
||||
|
||||
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, destination.getWidth(), destination.getHeight(), 4, null);
|
||||
readVP8Lossless(tempRaster, param, width, height);
|
||||
|
||||
// Copy from green (band 1) in temp to alpha in destination
|
||||
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[] {1}));
|
||||
break;
|
||||
default:
|
||||
processWarningOccurred("Unknown WebP alpha compression: " + compression);
|
||||
opaqueAlpha(alphaRaster);
|
||||
break;
|
||||
}
|
||||
|
||||
if (filtering != AlphaFiltering.NONE) {
|
||||
for (int y = 0; y < destination.getHeight(); y++) {
|
||||
for (int x = 0; x < destination.getWidth(); x++) {
|
||||
int predictorAlpha = getPredictorAlpha(alphaRaster, filtering, y, x);
|
||||
alphaRaster.setSample(x, y, 0, alphaRaster.getSample(x, y, 0) + predictorAlpha % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getPredictorAlpha(WritableRaster alphaRaster, int filtering, int y, int x) {
|
||||
switch (filtering) {
|
||||
case AlphaFiltering.NONE:
|
||||
return 0;
|
||||
case AlphaFiltering.HORIZONTAL:
|
||||
if (x == 0) {
|
||||
return y == 0 ? 0 : alphaRaster.getSample(0, y - 1, 0);
|
||||
}
|
||||
else {
|
||||
return alphaRaster.getSample(x - 1, y, 0);
|
||||
}
|
||||
case AlphaFiltering.VERTICAL:
|
||||
if (y == 0) {
|
||||
return x == 0 ? 0 : alphaRaster.getSample(x - 1, 0, 0);
|
||||
}
|
||||
else {
|
||||
return alphaRaster.getSample(x, y - 1, 0);
|
||||
}
|
||||
case AlphaFiltering.GRADIENT:
|
||||
if (x == 0) {
|
||||
return y == 0 ? 0 : alphaRaster.getSample(0, y - 1, 0);
|
||||
}
|
||||
else if (y == 0) {
|
||||
return alphaRaster.getSample(x - 1, 0, 0);
|
||||
}
|
||||
else {
|
||||
int left = alphaRaster.getSample(x - 1, y, 0);
|
||||
int top = alphaRaster.getSample(x, y - 1, 0);
|
||||
int topLeft = alphaRaster.getSample(x - 1, y - 1, 0);
|
||||
|
||||
return max(0, min(left + top - topLeft, 255));
|
||||
}
|
||||
default:
|
||||
processWarningOccurred("Unknown WebP alpha filtering: " + filtering);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyICCProfileIfNeeded(final BufferedImage destination) {
|
||||
if (iccProfile != null) {
|
||||
ColorModel colorModel = destination.getColorModel();
|
||||
@@ -627,7 +558,6 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
private void readUncompressedAlpha(final WritableRaster alphaRaster) throws IOException {
|
||||
// Hardly used in practice, need to find a sample file
|
||||
processWarningOccurred("Uncompressed WebP alpha not implemented");
|
||||
@@ -635,13 +565,8 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
private void readVP8Lossless(final WritableRaster raster, final ImageReadParam param) throws IOException {
|
||||
readVP8Lossless(raster, param, header.width, header.height);
|
||||
}
|
||||
|
||||
private void readVP8Lossless(final WritableRaster raster, final ImageReadParam param,
|
||||
final int width, final int height) throws IOException {
|
||||
VP8LDecoder decoder = new VP8LDecoder(imageInput, DEBUG);
|
||||
decoder.readVP8Lossless(raster, true, param, width, height);
|
||||
decoder.readVP8Lossless(raster, true);
|
||||
}
|
||||
|
||||
private void readVP8(final WritableRaster raster, final ImageReadParam param) throws IOException {
|
||||
@@ -663,7 +588,10 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
return new WebPImageMetadata(getRawImageType(imageIndex), header);
|
||||
readHeader(imageIndex);
|
||||
readMeta();
|
||||
|
||||
return new WebPImageMetadata(header);
|
||||
}
|
||||
|
||||
private void readMeta() throws IOException {
|
||||
|
||||
+27
-1
@@ -75,8 +75,10 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
|
||||
int chunk = stream.readInt();
|
||||
|
||||
switch (chunk) {
|
||||
case WebP.CHUNK_VP8L:
|
||||
// TODO. Support lossless
|
||||
// case WebP.CHUNK_VP8L:
|
||||
case WebP.CHUNK_VP8X:
|
||||
return containsSupportedChunk(stream, chunk);
|
||||
case WebP.CHUNK_VP8_:
|
||||
return true;
|
||||
default:
|
||||
@@ -89,6 +91,30 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsSupportedChunk(ImageInputStream stream, int chunk) throws IOException {
|
||||
// Temporary: Seek for VP8_, either first or second (after ICCP), or inside ANMF...
|
||||
try {
|
||||
while (chunk != WebP.CHUNK_VP8L && chunk != WebP.CHUNK_ALPH) {
|
||||
long length = stream.readUnsignedInt();
|
||||
stream.seek(stream.getStreamPosition() + length);
|
||||
chunk = stream.readInt();
|
||||
|
||||
// Look inside ANMF chunks...
|
||||
if (chunk == WebP.CHUNK_ANMF) {
|
||||
stream.seek(stream.getStreamPosition() + 4 + 16);
|
||||
chunk = stream.readInt();
|
||||
}
|
||||
|
||||
if (chunk == WebP.CHUNK_VP8_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EOFException ignore) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(final Object extension) {
|
||||
return new WebPImageReader(this);
|
||||
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class ColorIndexingTransform implements Transform {
|
||||
|
||||
private final byte[] colorTable;
|
||||
private final byte bits;
|
||||
|
||||
public ColorIndexingTransform(byte[] colorTable, byte bits) {
|
||||
this.colorTable = colorTable;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyInverse(WritableRaster raster) {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
byte[] rgba = new byte[4];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
// Reversed so no used elements are overridden (in case of packing)
|
||||
for (int x = width - 1; x >= 0; x--) {
|
||||
int componentSize = 8 >> bits;
|
||||
int packed = 1 << bits;
|
||||
int xC = x / packed;
|
||||
int componentOffset = componentSize * (x % packed);
|
||||
|
||||
int sample = raster.getSample(xC, y, 1);
|
||||
|
||||
int index = sample >> componentOffset & ((1 << componentSize) - 1);
|
||||
|
||||
// Arraycopy for 4 elements might not be beneficial
|
||||
System.arraycopy(colorTable, index * 4, rgba, 0, 4);
|
||||
raster.setDataElements(x, y, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class ColorTransform implements Transform {
|
||||
private final Raster data;
|
||||
private final byte bits;
|
||||
|
||||
public ColorTransform(Raster raster, byte bits) {
|
||||
this.data = raster;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyInverse(WritableRaster raster) {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
byte[] rgba = new byte[4];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
|
||||
data.getDataElements(x >> bits, y >> bits, rgba);
|
||||
ColorTransformElement trans = new ColorTransformElement(rgba);
|
||||
|
||||
raster.getDataElements(x, y, rgba);
|
||||
|
||||
trans.inverseTransform(rgba);
|
||||
|
||||
raster.setDataElements(x, y, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: For encoding!
|
||||
private static void colorTransform(final int red, final int blue, final int green,
|
||||
final ColorTransformElement trans,
|
||||
final int[] newRedBlue) {
|
||||
// Transformed values of red and blue components
|
||||
int tmp_red = red;
|
||||
int tmp_blue = blue;
|
||||
|
||||
// Applying transform is just adding the transform deltas
|
||||
tmp_red += colorTransformDelta((byte) trans.green_to_red, (byte) green);
|
||||
tmp_blue += colorTransformDelta((byte) trans.green_to_blue, (byte) green);
|
||||
tmp_blue += colorTransformDelta((byte) trans.red_to_blue, (byte) red);
|
||||
|
||||
// No pointer dereferences in Java...
|
||||
// TODO: Consider passing an offset too, so we can modify in-place
|
||||
newRedBlue[0] = tmp_red & 0xff;
|
||||
newRedBlue[1] = tmp_blue & 0xff;
|
||||
}
|
||||
|
||||
// A conversion from the 8-bit unsigned representation (uint8) to the 8-bit
|
||||
// signed one (int8) is required before calling ColorTransformDelta(). It
|
||||
// should be performed using 8-bit two's complement (that is: uint8 range
|
||||
// [128-255] is mapped to the [-128, -1] range of its converted int8
|
||||
// value).
|
||||
private static byte colorTransformDelta(final byte t, final byte c) {
|
||||
return (byte) ((t * c) >> 5);
|
||||
}
|
||||
|
||||
private static final class ColorTransformElement {
|
||||
|
||||
final int green_to_red;
|
||||
final int green_to_blue;
|
||||
final int red_to_blue;
|
||||
|
||||
ColorTransformElement(final byte[] rgba) {
|
||||
this.green_to_red = rgba[2];
|
||||
this.green_to_blue = rgba[1];
|
||||
this.red_to_blue = rgba[0];
|
||||
}
|
||||
|
||||
private void inverseTransform(final byte[] rgb) {
|
||||
// Applying inverse transform is just adding (!, different from specification) the
|
||||
// color transform deltas 3
|
||||
|
||||
// Transformed values of red and blue components
|
||||
int tmp_red = rgb[0];
|
||||
int tmp_blue = rgb[2];
|
||||
|
||||
tmp_red += colorTransformDelta((byte) this.green_to_red, rgb[1]);
|
||||
tmp_blue += colorTransformDelta((byte) this.green_to_blue, rgb[1]);
|
||||
tmp_blue += colorTransformDelta((byte) this.red_to_blue, (byte) tmp_red); // Spec has red & 0xff
|
||||
|
||||
rgb[0] = (byte) (tmp_red & 0xff);
|
||||
rgb[2] = (byte) (tmp_blue & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.webp.LSBBitReader;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class HuffmanCodeGroup {
|
||||
/**
|
||||
* Used for green, backward reference length and color cache
|
||||
*/
|
||||
public final HuffmanTable mainCode;
|
||||
|
||||
public final HuffmanTable redCode;
|
||||
public final HuffmanTable blueCode;
|
||||
public final HuffmanTable alphaCode;
|
||||
public final HuffmanTable distanceCode;
|
||||
|
||||
public HuffmanCodeGroup(LSBBitReader lsbBitReader, int colorCacheBits) throws IOException {
|
||||
mainCode = new HuffmanTable(lsbBitReader, 256 + 24 + (colorCacheBits > 0 ? 1 << colorCacheBits : 0));
|
||||
redCode = new HuffmanTable(lsbBitReader, 256);
|
||||
blueCode = new HuffmanTable(lsbBitReader, 256);
|
||||
alphaCode = new HuffmanTable(lsbBitReader, 256);
|
||||
distanceCode = new HuffmanTable(lsbBitReader, 40);
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class HuffmanInfo {
|
||||
public final Raster huffmanMetaCodes; // Raster allows intuitive lookup by x and y
|
||||
|
||||
public final int metaCodeBits;
|
||||
|
||||
public final HuffmanCodeGroup[] huffmanGroups;
|
||||
|
||||
public HuffmanInfo(Raster huffmanMetaCodes, int metaCodeBits, HuffmanCodeGroup[] huffmanGroups) {
|
||||
this.huffmanMetaCodes = huffmanMetaCodes;
|
||||
this.metaCodeBits = metaCodeBits;
|
||||
this.huffmanGroups = huffmanGroups;
|
||||
}
|
||||
}
|
||||
-351
@@ -1,351 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.webp.LSBBitReader;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a single huffman tree as a table.
|
||||
* <p>
|
||||
* Decoding a symbol just involves reading bits from the input stream and using that read value to index into the
|
||||
* lookup table.
|
||||
* <p>
|
||||
* Code length and the corresponding symbol are packed into one array element (int).
|
||||
* This is done to avoid the overhead and the fragmentation over the whole heap involved with creating objects
|
||||
* of a custom class. The upper 16 bits of each element are the code length and lower 16 bits are the symbol.
|
||||
* <p>
|
||||
* The max allowed code length by the WEBP specification is 15, therefore this would mean the table needs to have
|
||||
* 2^15 elements. To keep a reasonable memory usage, instead the lookup table only directly holds symbols with code
|
||||
* length up to {@code LEVEL1_BITS} (Currently 8 bits). For longer codes the lookup table stores a reference to a
|
||||
* second level lookup table. This reference consists of an element with length as the max length of the level 2
|
||||
* table and value as the index of the table in the list of level 2 tables.
|
||||
* <p>
|
||||
* Reading bits from the input is done in a least significant bit first way (LSB) way, therefore the prefix of the
|
||||
* read value of length i is the lowest i bits in inverse order.
|
||||
* The lookup table is directly indexed by the {@code LEVEL1_BITS} next bits read from the input (i.e. the bits
|
||||
* corresponding to next code are inverse suffix of the read value/index).
|
||||
* So for a code length of l all values with the lowest l bits the same need to decode to the same symbol
|
||||
* regardless of the {@code (LEVEL1_BITS - l)} higher bits. So the lookup table needs to have the entry of this symbol
|
||||
* repeated every 2^(l + 1) spots starting from the bitwise inverse of the code.
|
||||
*
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class HuffmanTable {
|
||||
|
||||
private static final int LEVEL1_BITS = 8;
|
||||
/**
|
||||
* Symbols of the L-code in the order they need to be read
|
||||
*/
|
||||
private static final int[] L_CODE_ORDER = {17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private final int[] level1 = new int[1 << LEVEL1_BITS];
|
||||
private final List<int[]> level2 = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Build a Huffman table by reading the encoded symbol lengths from the reader
|
||||
*
|
||||
* @param lsbBitReader the reader to read from
|
||||
* @param alphabetSize the number of symbols in the alphabet to be decoded by this huffman table
|
||||
* @throws IOException when reading produces an exception
|
||||
*/
|
||||
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
|
||||
boolean simpleLengthCode = lsbBitReader.readBit() == 1;
|
||||
|
||||
if (simpleLengthCode) {
|
||||
int symbolNum = lsbBitReader.readBit() + 1;
|
||||
boolean first8Bits = lsbBitReader.readBit() == 1;
|
||||
short symbol1 = (short) lsbBitReader.readBits(first8Bits ? 8 : 1);
|
||||
|
||||
if (symbolNum == 2) {
|
||||
short symbol2 = (short) lsbBitReader.readBits(8);
|
||||
|
||||
for (int i = 0; i < (1 << LEVEL1_BITS); i += 2) {
|
||||
level1[i] = 1 << 16 | symbol1;
|
||||
level1[i + 1] = 1 << 16 | symbol2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Arrays.fill(level1, symbol1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// code lengths also huffman coded
|
||||
// first read the "first stage" code lengths
|
||||
// In the following this is called the L-Code (for length code)
|
||||
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
|
||||
short[] lCodeLengths = new short[L_CODE_ORDER.length];
|
||||
int numPosCodeLens = 0;
|
||||
|
||||
for (int i = 0; i < numLCodeLengths; i++) {
|
||||
short len = (short) lsbBitReader.readBits(3);
|
||||
lCodeLengths[L_CODE_ORDER[i]] = len;
|
||||
|
||||
if (len > 0) {
|
||||
numPosCodeLens++;
|
||||
}
|
||||
}
|
||||
|
||||
// Use L-Code to read the actual code lengths
|
||||
short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
|
||||
|
||||
buildFromLengths(codeLengths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Huffman table by using already given code lengths to generate the codes from
|
||||
*
|
||||
* @param codeLengths the array specifying the bit length of the code for a symbol (i.e. {@code codeLengths[i]}
|
||||
* is the bit length of the code for the symbol i)
|
||||
* @param numPosCodeLens the number of positive (i.e. non-zero) codeLengths in the array (allows more efficient
|
||||
* table generation)
|
||||
*/
|
||||
private HuffmanTable(short[] codeLengths, int numPosCodeLens) {
|
||||
buildFromLengths(codeLengths, numPosCodeLens);
|
||||
}
|
||||
|
||||
// Helper methods to allow reusing in different constructors
|
||||
private void buildFromLengths(short[] codeLengths) {
|
||||
int numPosCodeLens = 0;
|
||||
|
||||
for (short codeLength : codeLengths) {
|
||||
if (codeLength != 0) {
|
||||
numPosCodeLens++;
|
||||
}
|
||||
}
|
||||
|
||||
buildFromLengths(codeLengths, numPosCodeLens);
|
||||
}
|
||||
|
||||
private void buildFromLengths(short[] codeLengths, int numPosCodeLens) {
|
||||
// Pack code length and corresponding symbols as described above
|
||||
int[] lengthsAndSymbols = new int[numPosCodeLens];
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < codeLengths.length; i++) {
|
||||
if (codeLengths[i] != 0) {
|
||||
lengthsAndSymbols[index++] = codeLengths[i] << 16 | i;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: Only 1 code value
|
||||
if (numPosCodeLens == 1) {
|
||||
// Length is 0 so mask to clear length bits
|
||||
Arrays.fill(level1, lengthsAndSymbols[0] & 0xffff);
|
||||
}
|
||||
|
||||
// Due to the layout of the elements this effectively first sorts by length and then symbol.
|
||||
Arrays.sort(lengthsAndSymbols);
|
||||
|
||||
// The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
|
||||
// Only the lowest bits (corresponding to the bit length of the code) are considered.
|
||||
// Example: code 0..010 (length 2) would appear as 0..001.
|
||||
int code = 0;
|
||||
|
||||
// Used for level2 lookup
|
||||
int rootEntry = -1;
|
||||
int[] currentTable = null;
|
||||
|
||||
for (int i = 0; i < lengthsAndSymbols.length; i++) {
|
||||
int lengthAndSymbol = lengthsAndSymbols[i];
|
||||
|
||||
int length = lengthAndSymbol >>> 16;
|
||||
|
||||
if (length <= LEVEL1_BITS) {
|
||||
for (int j = code; j < level1.length; j += 1 << length) {
|
||||
level1[j] = lengthAndSymbol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Existing level2 table not fitting
|
||||
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
|
||||
// Figure out needed table size.
|
||||
// Start at current symbol and length.
|
||||
// Every symbol uses 1 slot at the current bit length.
|
||||
// Going up 1 bit in length multiplies the slots by 2.
|
||||
// No more open slots indicate the table size to be big enough.
|
||||
int maxLength = length;
|
||||
|
||||
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
|
||||
j < lengthsAndSymbols.length && openSlots > 0;
|
||||
j++, openSlots--) {
|
||||
|
||||
int innerLength = lengthsAndSymbols[j] >>> 16;
|
||||
|
||||
while (innerLength != maxLength) {
|
||||
maxLength++;
|
||||
openSlots <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
int level2Size = maxLength - LEVEL1_BITS;
|
||||
|
||||
currentTable = new int[1 << level2Size];
|
||||
rootEntry = code & ((1 << LEVEL1_BITS) - 1);
|
||||
level2.add(currentTable);
|
||||
|
||||
// Set root table indirection
|
||||
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1);
|
||||
}
|
||||
|
||||
// Add to existing (or newly generated) 2nd level table
|
||||
for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
|
||||
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
code = nextCode(code, length);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the next code
|
||||
*
|
||||
* @param code the current code
|
||||
* @param length the currently valid length
|
||||
* @return {@code reverse(reverse(code, length) + 1, length)} where {@code reverse(a, b)} is the lowest b bits of
|
||||
* a in inverted order
|
||||
*/
|
||||
private int nextCode(int code, int length) {
|
||||
int a = (~code) & ((1 << length) - 1);
|
||||
|
||||
// This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
|
||||
// I.e. the lowest 0-bit in the value code represents
|
||||
int step = Integer.highestOneBit(a);
|
||||
|
||||
// In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
|
||||
// This corresponds to adding 1 to the value
|
||||
return (code & (step - 1)) | step;
|
||||
}
|
||||
|
||||
private static short[] readCodeLengths(LSBBitReader lsbBitReader, short[] aCodeLengths, int alphabetSize,
|
||||
int numPosCodeLens) throws IOException {
|
||||
|
||||
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens);
|
||||
|
||||
// Not sure where this comes from. Just adapted from the libwebp implementation
|
||||
int codedSymbols;
|
||||
if (lsbBitReader.readBit() == 1) {
|
||||
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
|
||||
codedSymbols = (int) (2 + lsbBitReader.readBits(maxSymbolBitLength));
|
||||
}
|
||||
else {
|
||||
codedSymbols = alphabetSize;
|
||||
}
|
||||
|
||||
short[] codeLengths = new short[alphabetSize];
|
||||
|
||||
// Default code for repeating
|
||||
short prevLength = 8;
|
||||
|
||||
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
|
||||
short len = huffmanTable.readSymbol(lsbBitReader);
|
||||
|
||||
if (len < 16) { // Literal length
|
||||
codeLengths[i] = len;
|
||||
if (len != 0) {
|
||||
prevLength = len;
|
||||
}
|
||||
}
|
||||
else {
|
||||
short repeatSymbol = 0;
|
||||
int extraBits;
|
||||
int repeatOffset;
|
||||
|
||||
switch (len) {
|
||||
case 16: // Repeat previous
|
||||
repeatSymbol = prevLength;
|
||||
extraBits = 2;
|
||||
repeatOffset = 3;
|
||||
break;
|
||||
case 17: // Repeat 0 short
|
||||
extraBits = 3;
|
||||
repeatOffset = 3;
|
||||
break;
|
||||
case 18: // Repeat 0 long
|
||||
extraBits = 7;
|
||||
repeatOffset = 11;
|
||||
break;
|
||||
default:
|
||||
throw new IIOException("Huffman: Unreachable: Decoded Code Length > 18.");
|
||||
}
|
||||
|
||||
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
|
||||
|
||||
if (i + repeatCount > alphabetSize) {
|
||||
throw new IIOException(
|
||||
String.format(
|
||||
"Huffman: Code length repeat count overflows alphabet: Start index: %d, count: " +
|
||||
"%d, alphabet size: %d", i, repeatCount, alphabetSize)
|
||||
);
|
||||
}
|
||||
|
||||
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
|
||||
i += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return codeLengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next code symbol from the streaming and decode it using the Huffman table
|
||||
*
|
||||
* @param lsbBitReader the reader to read a symbol from (will be advanced accordingly)
|
||||
* @return the decoded symbol
|
||||
* @throws IOException when the reader throws one reading a symbol
|
||||
*/
|
||||
public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
|
||||
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
|
||||
int lengthAndSymbol = level1[index];
|
||||
|
||||
int length = lengthAndSymbol >>> 16;
|
||||
|
||||
if (length > LEVEL1_BITS) {
|
||||
// Lvl2 lookup
|
||||
lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
|
||||
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
|
||||
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
|
||||
length = lengthAndSymbol >>> 16;
|
||||
}
|
||||
|
||||
lsbBitReader.readBits(length); // Consume bits
|
||||
|
||||
return (short) (lengthAndSymbol & 0xffff);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ package com.twelvemonkeys.imageio.plugins.webp.lossless;
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
interface PredictorMode {
|
||||
public interface PredictorMode {
|
||||
// Special rules:
|
||||
// Top-left pixel of image is predicted BLACK
|
||||
// Rest of top pixels is predicted L
|
||||
|
||||
-270
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class PredictorTransform implements Transform {
|
||||
private final Raster data;
|
||||
private final byte bits;
|
||||
|
||||
public PredictorTransform(Raster raster, byte bits) {
|
||||
this.data = raster;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyInverse(WritableRaster raster) {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
byte[] rgba = new byte[4];
|
||||
|
||||
// Handle top and left border separately
|
||||
|
||||
// (0,0) Black (0x000000ff) predict
|
||||
raster.getDataElements(0, 0, rgba);
|
||||
rgba[3] += 0xff;
|
||||
raster.setDataElements(0, 0, rgba);
|
||||
|
||||
byte[] predictor = new byte[4];
|
||||
byte[] predictor2 = new byte[4];
|
||||
byte[] predictor3 = new byte[4];
|
||||
|
||||
// (x,0) L predict
|
||||
for (int x = 1; x < width; x++) {
|
||||
raster.getDataElements(x, 0, rgba);
|
||||
raster.getDataElements(x - 1, 0, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
|
||||
raster.setDataElements(x, 0, rgba);
|
||||
}
|
||||
|
||||
// (0,y) T predict
|
||||
for (int y = 1; y < height; y++) {
|
||||
raster.getDataElements(0, y, rgba);
|
||||
raster.getDataElements(0, y - 1, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
|
||||
raster.setDataElements(0, y, rgba);
|
||||
}
|
||||
|
||||
for (int y = 1; y < height; y++) {
|
||||
for (int x = 1; x < width; x++) {
|
||||
int transformType = data.getSample(x >> bits, y >> bits, 1);
|
||||
|
||||
raster.getDataElements(x, y, rgba);
|
||||
|
||||
int lX = x - 1; // x for left
|
||||
int tY = y - 1; // y for top
|
||||
|
||||
// top right is not (x+1, tY) if last pixel in line instead (0, y)
|
||||
int trX = x == width - 1 ? 0 : x + 1;
|
||||
int trY = x == width - 1 ? y : tY;
|
||||
|
||||
switch (transformType) {
|
||||
case PredictorMode.BLACK:
|
||||
rgba[3] += 0xff;
|
||||
break;
|
||||
case PredictorMode.L:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.T:
|
||||
raster.getDataElements(x, tY, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.TR:
|
||||
raster.getDataElements(trX, trY, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.TL:
|
||||
raster.getDataElements(lX, tY, predictor);
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_L_TR_T:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(trX, trY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_L_TL:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(lX, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_L_T:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_TL_T:
|
||||
raster.getDataElements(lX, tY, predictor);
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_T_TR:
|
||||
raster.getDataElements(x, tY, predictor);
|
||||
raster.getDataElements(trX, trY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.AVG_L_TL_T_TR:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(lX, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
raster.getDataElements(trX, trY, predictor3);
|
||||
average2(predictor2, predictor3);
|
||||
|
||||
average2(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.SELECT:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
raster.getDataElements(lX, tY, predictor3);
|
||||
|
||||
|
||||
addPixels(rgba, select(predictor, predictor2, predictor3));
|
||||
break;
|
||||
case PredictorMode.CLAMP_ADD_SUB_FULL:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
raster.getDataElements(lX, tY, predictor3);
|
||||
clampAddSubtractFull(predictor, predictor2, predictor3);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
case PredictorMode.CLAMP_ADD_SUB_HALF:
|
||||
raster.getDataElements(lX, y, predictor);
|
||||
raster.getDataElements(x, tY, predictor2);
|
||||
average2(predictor, predictor2);
|
||||
|
||||
raster.getDataElements(lX, tY, predictor2);
|
||||
clampAddSubtractHalf(predictor, predictor2);
|
||||
|
||||
addPixels(rgba, predictor);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
raster.setDataElements(x, y, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] select(final byte[] l, final byte[] t, final byte[] tl) {
|
||||
// l = left pixel, t = top pixel, tl = top left pixel.
|
||||
|
||||
// ARGB component estimates for prediction.
|
||||
|
||||
int pAlpha = addSubtractFull(l[3], t[3], tl[3]);
|
||||
int pRed = addSubtractFull(l[0], t[0], tl[0]);
|
||||
int pGreen = addSubtractFull(l[1], t[1], tl[1]);
|
||||
int pBlue = addSubtractFull(l[2], t[2], tl[2]);
|
||||
|
||||
// Manhattan distances to estimates for left and top pixels.
|
||||
int pL = manhattanDistance(l, pAlpha, pRed, pGreen, pBlue);
|
||||
int pT = manhattanDistance(t, pAlpha, pRed, pGreen, pBlue);
|
||||
|
||||
// Return either left or top, the one closer to the prediction.
|
||||
return pL < pT ? l : t;
|
||||
}
|
||||
|
||||
private static int manhattanDistance(byte[] rgba, int pAlpha, int pRed, int pGreen, int pBlue) {
|
||||
return abs(pAlpha - (rgba[3] & 0xff)) + abs(pRed - (rgba[0] & 0xff)) +
|
||||
abs(pGreen - (rgba[1] & 0xff)) + abs(pBlue - (rgba[2] & 0xff));
|
||||
}
|
||||
|
||||
private static void average2(final byte[] rgba1, final byte[] rgba2) {
|
||||
rgba1[0] = (byte) (((rgba1[0] & 0xff) + (rgba2[0] & 0xff)) / 2);
|
||||
rgba1[1] = (byte) (((rgba1[1] & 0xff) + (rgba2[1] & 0xff)) / 2);
|
||||
rgba1[2] = (byte) (((rgba1[2] & 0xff) + (rgba2[2] & 0xff)) / 2);
|
||||
rgba1[3] = (byte) (((rgba1[3] & 0xff) + (rgba2[3] & 0xff)) / 2);
|
||||
}
|
||||
|
||||
// Clamp the input value between 0 and 255.
|
||||
private static int clamp(final int a) {
|
||||
return max(0, min(a, 255));
|
||||
}
|
||||
|
||||
private static void clampAddSubtractFull(final byte[] a, final byte[] b, final byte[] c) {
|
||||
a[0] = (byte) clamp(addSubtractFull(a[0], b[0], c[0]));
|
||||
a[1] = (byte) clamp(addSubtractFull(a[1], b[1], c[1]));
|
||||
a[2] = (byte) clamp(addSubtractFull(a[2], b[2], c[2]));
|
||||
a[3] = (byte) clamp(addSubtractFull(a[3], b[3], c[3]));
|
||||
}
|
||||
|
||||
private static void clampAddSubtractHalf(final byte[] a, final byte[] b) {
|
||||
a[0] = (byte) clamp(addSubtractHalf(a[0], b[0]));
|
||||
a[1] = (byte) clamp(addSubtractHalf(a[1], b[1]));
|
||||
a[2] = (byte) clamp(addSubtractHalf(a[2], b[2]));
|
||||
a[3] = (byte) clamp(addSubtractHalf(a[3], b[3]));
|
||||
}
|
||||
|
||||
private static int addSubtractFull(byte a, byte b, byte c) {
|
||||
return (a & 0xff) + (b & 0xff) - (c & 0xff);
|
||||
}
|
||||
|
||||
private static int addSubtractHalf(byte a, byte b) {
|
||||
return (a & 0xff) + ((a & 0xff) - (b & 0xff)) / 2;
|
||||
}
|
||||
|
||||
private static void addPixels(byte[] rgba, byte[] predictor) {
|
||||
rgba[0] += predictor[0];
|
||||
rgba[1] += predictor[1];
|
||||
rgba[2] += predictor[2];
|
||||
rgba[3] += predictor[3];
|
||||
}
|
||||
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* @author Simon Kammermeier
|
||||
*/
|
||||
final class SubtractGreenTransform implements Transform {
|
||||
|
||||
private static void addGreenToBlueAndRed(byte[] rgb) {
|
||||
rgb[0] = (byte) ((rgb[0] + rgb[1]) & 0xff);
|
||||
rgb[2] = (byte) ((rgb[2] + rgb[1]) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyInverse(WritableRaster raster) {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
byte[] rgba = new byte[4];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
raster.getDataElements(x, y, rgba);
|
||||
addGreenToBlueAndRed(rgba);
|
||||
raster.setDataElements(x, y, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-4
@@ -31,14 +31,25 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.webp.lossless;
|
||||
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* Transform.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
interface Transform {
|
||||
final class Transform {
|
||||
final int type;
|
||||
final Object data;
|
||||
|
||||
void applyInverse(WritableRaster raster);
|
||||
Transform(final int type, final Object data) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
byte[] getData() {
|
||||
return (byte[]) data;
|
||||
}
|
||||
|
||||
int[] getColorMap() {
|
||||
return (int[]) data;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user