From 263fb75d1d657788cf5e99f2b0424d899ce37392 Mon Sep 17 00:00:00 2001
From: Harald Kuhr
Date: Wed, 11 Mar 2026 21:09:26 +0100
Subject: [PATCH] DDS cleanup (#1262)
* Refactorings and code clean-up
* Major rework/standardization:
* DDSEncoderType, DX10DXGIFormat merged with DDSType for a single way to describe a DDS format
* Added constants for DXGI formats
* DDSImageWriteParam is now mutable and supports standard way of setting compression type
* DDSImageMetadata now supports more of the format
Performance:
* DDSReader now use seek() to jump to correct mipmap instead of reading all bytes
* DDSImageWriter now uses getTile(0, 0) instead of getData() for better performance
* Fix JavaDoc :tada:
* Sonar issues + roll back accidental check-in
* More clean-up: Removed optional flags from param, header size validation, metadata now reports compresion as lossy
* More clean-up: Now keeps stream byte order consistent (LE), support for Raster, more tests
* Mipmap support using ImageIO sequence API
* Added raster write test
+ fixed a small issue for PAM
* Sonar issues
---
.../imageio/StandardImageMetadataSupport.java | 5 +-
.../twelvemonkeys/imageio/util/IIOUtil.java | 119 ++++++-
.../imageio/util/IIOUtilTest.java | 229 +++++++++++++
.../imageio/util/ImageWriterAbstractTest.java | 54 ++-
.../imageio/plugins/dds/BlockCompression.java | 11 +
.../imageio/plugins/dds/DDS.java | 15 +-
.../imageio/plugins/dds/DDSEncoderType.java | 64 ----
.../imageio/plugins/dds/DDSHeader.java | 134 ++++++--
.../plugins/dds/DDSImageDataEncoder.java | 52 +--
...DDSMetadata.java => DDSImageMetadata.java} | 49 ++-
.../imageio/plugins/dds/DDSImageReader.java | 20 +-
.../plugins/dds/DDSImageReaderSpi.java | 10 +-
.../plugins/dds/DDSImageWriteParam.java | 62 ++++
.../imageio/plugins/dds/DDSImageWriter.java | 315 +++++++++++++-----
.../plugins/dds/DDSImageWriterSpi.java | 10 +-
.../imageio/plugins/dds/DDSProviderInfo.java | 22 +-
.../imageio/plugins/dds/DDSReader.java | 92 +----
.../imageio/plugins/dds/DDSType.java | 167 ++++++++--
.../imageio/plugins/dds/DDSWriterParam.java | 139 --------
.../imageio/plugins/dds/DX10DXGIFormat.java | 50 ---
.../imageio/plugins/dds/DX10Header.java | 33 --
.../imageio/plugins/dds/DXGI.java | 129 +++++++
.../imageio/plugins/dds/DXT10Header.java | 45 +++
.../plugins/dds/DDSImageMetadataTest.java | 101 ++++++
.../plugins/dds/DDSImageReaderTest.java | 76 +++++
.../plugins/dds/DDSImageWriteParamTest.java | 55 +++
.../plugins/dds/DDSImageWriterTest.java | 172 +++++++++-
.../imageio/plugins/pnm/TupleType.java | 7 +-
28 files changed, 1653 insertions(+), 584 deletions(-)
create mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/BlockCompression.java
delete mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSEncoderType.java
rename imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/{DDSMetadata.java => DDSImageMetadata.java} (61%)
create mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParam.java
delete mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSWriterParam.java
delete mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10DXGIFormat.java
delete mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10Header.java
create mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXGI.java
create mode 100644 imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXT10Header.java
create mode 100644 imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadataTest.java
create mode 100644 imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParamTest.java
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java
index 9b6793ec..bac40138 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java
@@ -23,6 +23,7 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* {@link ImageTypeSpecifier}.
* Other values or overrides may be specified using the builder.
*
+ * @see Standard (Plug-in Neutral) Metadata Format Specification
* @author Harald Kuhr
*/
public class StandardImageMetadataSupport extends AbstractMetadata {
@@ -79,11 +80,11 @@ public class StandardImageMetadataSupport extends AbstractMetadata {
textEntries = builder.textEntries;
}
- public static Builder builder(ImageTypeSpecifier type) {
+ protected static Builder builder(ImageTypeSpecifier type) {
return new Builder(type);
}
- public static class Builder {
+ protected static class Builder {
private final ImageTypeSpecifier type;
private ColorSpaceType colorSpaceType;
private boolean blackIsZero = true;
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java
index fded5f04..85ad435e 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java
@@ -35,6 +35,8 @@ import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOParam;
import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageWriteParam;
import javax.imageio.spi.IIOServiceProvider;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
@@ -45,7 +47,9 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -68,7 +72,7 @@ public final class IIOUtil {
*/
public static InputStream createStreamAdapter(final ImageInputStream pStream) {
// TODO: Include stream start pos?
- // TODO: Skip buffering for known in-memory implementations?
+ // TODO: Skip buffering for known in-memory implementations? pStream.isCachedMemory
return new BufferedInputStream(new IIOInputStreamAdapter(pStream));
}
@@ -82,7 +86,7 @@ public final class IIOUtil {
*/
public static InputStream createStreamAdapter(final ImageInputStream pStream, final long pLength) {
// TODO: Include stream start pos?
- // TODO: Skip buffering for known in-memory implementations?
+ // TODO: Skip buffering for known in-memory implementations? pStream.isCachedMemory
return new BufferedInputStream(new IIOInputStreamAdapter(pStream, pLength));
}
@@ -359,4 +363,115 @@ public final class IIOUtil {
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
}
}
+
+ /**
+ * Copies all the standard param values from source to destination.
+ *
+ * Typical use (in some imaginary {@code FooImageWriter} class):
+ *
+ *
+ *
+ * ImageWriteParam param = ...
+ * FooImageWriteparam fooParam = param instanceof FooImageWriteParam
+ * ? (FooImageWriteParam) param
+ * : copyStandardParams(param, getDefaultWriteParam());
+ *
+ *
+ * May also be useful for {@code ImageReader}s that delegate reading to other plugins
+ * (like a TIFF plugin delegating JPEG format decoding to a {@code JPEGImageReader}).
+ *
+ * @param source the source parameter, may be {@code null}
+ * @param destination the destination parameter
+ * @return destination
+ *
+ * @param the plugin specific subclass of {@code IIOParam}
+ *
+ * @throws NullPointerException if destination is {@code null}
+ */
+ public static T copyStandardParams(IIOParam source, T destination) {
+ Objects.requireNonNull(destination);
+ Validate.isTrue(source != destination, "source must be different from destination");
+
+ if (source != null) {
+ copyIIOParams(source, destination);
+
+ // TODO: API & usage... Is it ever useful to copy from a read to a write param or vice versa?
+ // If not, maybe throw an IllegalArgumentException instead
+
+ if (source instanceof ImageReadParam && destination instanceof ImageReadParam) {
+ copyImageReadParams((ImageReadParam) source, (ImageReadParam) destination);
+ }
+
+ if (source instanceof ImageWriteParam && destination instanceof ImageWriteParam) {
+ copyImageWriteParams((ImageWriteParam) source, (ImageWriteParam) destination);
+ }
+ }
+
+ return destination;
+ }
+
+ private static void copyImageWriteParams(ImageWriteParam source, ImageWriteParam destination) {
+ // TODO: Usage... It's very unlikely that compression settings of one plugin is compatible with another...
+ // Is the the below useful?
+ // Also, is it okay to just silently ignore settings from one format that isn't compatible with another?
+
+ // Quirky API, we can't query for compression mode, unless source.canWriteCompressed is true...
+ if (source.canWriteCompressed() && destination.canWriteCompressed()) {
+ int compressionMode = source.getCompressionMode();
+ destination.setCompressionMode(compressionMode);
+
+ if (compressionMode == ImageWriteParam.MODE_EXPLICIT
+ && source.getCompressionType() != null
+ && Arrays.asList(destination.getCompressionTypes()).contains(source.getCompressionType())) {
+ destination.setCompressionType(source.getCompressionType());
+ destination.setCompressionQuality(source.getCompressionQuality());
+ }
+ }
+
+ if (source.canWriteProgressive() && destination.canWriteProgressive()) {
+ destination.setProgressiveMode(source.getProgressiveMode());
+ }
+
+ if (source.canWriteTiles() && destination.canWriteTiles()) {
+ int tilingMode = source.getTilingMode();
+ destination.setTilingMode(tilingMode);
+
+ if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
+ // TODO: What if source can offset (and has offsets) and dest can't? Is it ok to just ignore the setting?
+ boolean canWriteOffsetTiles = source.canOffsetTiles() && destination.canOffsetTiles();
+
+ destination.setTiling(
+ source.getTileWidth(), source.getTileHeight(),
+ canWriteOffsetTiles ? source.getTileGridXOffset() : 0,
+ canWriteOffsetTiles ? source.getTileGridYOffset() : 0
+ );
+ }
+ }
+ }
+
+ private static void copyImageReadParams(ImageReadParam source, ImageReadParam destination) {
+ destination.setDestination(source.getDestination());
+ destination.setDestinationBands(source.getDestinationBands());
+
+ if (destination.canSetSourceRenderSize()) {
+ destination.setSourceRenderSize(source.getSourceRenderSize());
+ }
+
+ destination.setSourceProgressivePasses(
+ source.getSourceMinProgressivePass(),
+ source.getSourceMaxProgressivePass()
+ );
+ }
+
+ private static void copyIIOParams(IIOParam source, IIOParam destination) {
+ destination.setController(source.getController());
+ destination.setSourceSubsampling(
+ source.getSourceXSubsampling(), source.getSourceYSubsampling(),
+ source.getSubsamplingXOffset(), source.getSubsamplingYOffset()
+ );
+ destination.setSourceRegion(source.getSourceRegion());
+ destination.setSourceBands(source.getSourceBands());
+ destination.setDestinationOffset(source.getDestinationOffset());
+ destination.setDestinationType(source.getDestinationType());
+ }
}
\ No newline at end of file
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java
index 01f32d62..f9aa4f08 100644
--- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/IIOUtilTest.java
@@ -1,8 +1,19 @@
package com.twelvemonkeys.imageio.util;
import org.junit.jupiter.api.Test;
+
import static org.junit.jupiter.api.Assertions.*;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.plugins.bmp.BMPImageWriteParam;
+import javax.imageio.plugins.jpeg.JPEGImageReadParam;
+
/**
* IIOUtilTest
*/
@@ -204,4 +215,222 @@ public class IIOUtilTest {
private int divCeil(int numerator, int denominator) {
return (numerator + denominator - 1) / denominator;
}
+
+ @Test
+ void copyStandardParamsDestinationNull() {
+ ImageReadParam param = new ImageReadParam();
+
+ assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(null, null));
+ assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(param, null));
+ }
+
+ @Test
+ void copyStandardParamsSame() {
+ ImageReadParam param = new ImageReadParam();
+ assertThrows(IllegalArgumentException.class, () -> IIOUtil.copyStandardParams(param, param));
+ }
+
+ @Test
+ void copyStandardParamsSourceNull() {
+ ImageReadParam param = new ImageReadParam() {
+ @Override
+ public void setSourceRegion(Rectangle sourceRegion) {
+ fail("Should not be invoked");
+ }
+ };
+
+ assertSame(param, IIOUtil.copyStandardParams(null, param));
+ }
+
+ @Test
+ void copyStandardParamsImageReadParam() {
+ int sourceXSubsampling = 3;
+ int sourceYSubsampling = 4;
+ int subsamplingXOffset = 1;
+ int subsamplingYOffset = 2;
+ Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
+ int[] sourceBands = { 0, 1, 2 };
+
+ Point destinationOffset = new Point(7, 9);
+ int[] destinationBands = { 2, 1, 0 };
+
+ ImageReadParam sourceParam = new ImageReadParam();
+ sourceParam.setSourceRegion(sourceRegion);
+ sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
+ sourceParam.setSourceBands(sourceBands);
+
+ sourceParam.setDestinationOffset(destinationOffset);
+ sourceParam.setDestinationBands(destinationBands);
+
+ JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
+
+ assertEquals(sourceRegion, jpegParam.getSourceRegion());
+ assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
+ assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
+ assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
+ assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
+ assertArrayEquals(sourceBands, jpegParam.getSourceBands());
+
+ assertEquals(destinationOffset, jpegParam.getDestinationOffset());
+ assertArrayEquals(destinationBands, jpegParam.getDestinationBands());
+ }
+
+ @Test
+ void copyStandardParamsImageReadParamDestination() {
+ // Destination and destination type is mutually exclusive
+ BufferedImage destination = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
+
+ ImageReadParam sourceParam = new ImageReadParam();
+ sourceParam.setDestination(destination);
+
+ assertEquals(destination, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestination());
+ }
+
+ @Test
+ void copyStandardParamsImageReadParamDestinationType() {
+ // Destination and destination type is mutually exclusive
+ ImageTypeSpecifier destinationType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
+
+ ImageReadParam sourceParam = new ImageReadParam();
+ sourceParam.setDestinationType(destinationType);
+
+ assertEquals(destinationType, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestinationType());
+ }
+
+ @Test
+ void copyStandardParamsReadToWrite() {
+ int sourceXSubsampling = 3;
+ int sourceYSubsampling = 4;
+ int subsamplingXOffset = 1;
+ int subsamplingYOffset = 2;
+ Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
+ int[] sourceBands = { 0, 1, 2 };
+
+ Point destinationOffset = new Point(7, 9);
+
+ ImageWriteParam sourceParam = new ImageWriteParam(null);
+ sourceParam.setSourceRegion(sourceRegion);
+ sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
+ sourceParam.setSourceBands(sourceBands);
+
+ sourceParam.setDestinationOffset(destinationOffset);
+
+ JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
+
+ assertEquals(sourceRegion, jpegParam.getSourceRegion());
+ assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
+ assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
+ assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
+ assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
+ assertArrayEquals(sourceBands, jpegParam.getSourceBands());
+
+ assertEquals(destinationOffset, jpegParam.getDestinationOffset());
+ assertNull(jpegParam.getDestinationBands()); // Only in read param
+ }
+
+ @Test
+ void copyStandardParamsImageWriteParam() {
+ int sourceXSubsampling = 3;
+ int sourceYSubsampling = 4;
+ int subsamplingXOffset = 1;
+ int subsamplingYOffset = 2;
+ Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
+ int[] sourceBands = { 0, 1, 2 };
+
+ Point destinationOffset = new Point(7, 9);
+
+ ImageWriteParam sourceParam = new ImageWriteParam(null);
+ sourceParam.setSourceRegion(sourceRegion);
+ sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
+ sourceParam.setSourceBands(sourceBands);
+
+ sourceParam.setDestinationOffset(destinationOffset);
+
+ BMPImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new BMPImageWriteParam());
+
+ assertEquals(sourceRegion, fooParam.getSourceRegion());
+ assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
+ assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
+ assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
+ assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
+ assertArrayEquals(sourceBands, fooParam.getSourceBands());
+
+ assertEquals(destinationOffset, fooParam.getDestinationOffset());
+ }
+
+ @Test
+ void copyStandardParamsImageWriteParamEverything() {
+ int sourceXSubsampling = 3;
+ int sourceYSubsampling = 4;
+ int subsamplingXOffset = 1;
+ int subsamplingYOffset = 2;
+ Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
+ int[] sourceBands = { 0, 1, 2 };
+
+ Point destinationOffset = new Point(7, 9);
+
+ String compressionType = "Foo";
+ float quality = 0.42f;
+
+ ImageWriteParam sourceParam = new ImageWriteParam() {
+ {
+ canWriteProgressive = true;
+
+ canWriteTiles = true;
+ canOffsetTiles = true;
+
+ canWriteCompressed = true;
+ compressionTypes = new String[] { "Foo", "Bar" };
+ }
+ };
+ sourceParam.setSourceRegion(sourceRegion);
+ sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
+ sourceParam.setSourceBands(sourceBands);
+
+ sourceParam.setDestinationOffset(destinationOffset);
+
+ sourceParam.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); // Default is COPY_FROM_METADATA...
+ sourceParam.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
+ sourceParam.setTiling(1, 2, 3, 4);
+ sourceParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ sourceParam.setCompressionType(compressionType);
+ sourceParam.setCompressionQuality(quality);
+
+ FooImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new FooImageWriteParam());
+
+ assertEquals(sourceRegion, fooParam.getSourceRegion());
+ assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
+ assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
+ assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
+ assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
+ assertArrayEquals(sourceBands, fooParam.getSourceBands());
+
+ assertEquals(destinationOffset, fooParam.getDestinationOffset());
+
+ assertEquals(ImageWriteParam.MODE_DEFAULT, fooParam.getProgressiveMode());
+
+ assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getTilingMode());
+ assertEquals(1, fooParam.getTileWidth());
+ assertEquals(2, fooParam.getTileHeight());
+ assertEquals(3, fooParam.getTileGridXOffset());
+ assertEquals(4, fooParam.getTileGridYOffset());
+
+ assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getCompressionMode());
+ assertEquals(compressionType, fooParam.getCompressionType());
+ assertEquals(quality, fooParam.getCompressionQuality());
+ }
+
+ // A basic param that supports "everything"
+ static class FooImageWriteParam extends ImageWriteParam {
+ FooImageWriteParam() {
+ canWriteProgressive = true;
+
+ canWriteTiles = true;
+ canOffsetTiles = true;
+
+ canWriteCompressed = true;
+ compressionType = "Unset";
+ compressionTypes = new String[] { "Bar", "Foo" };
+ }
+ }
}
\ No newline at end of file
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java
index 4fbc57aa..c564dc06 100755
--- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java
@@ -34,6 +34,7 @@ import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.mockito.InOrder;
+import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
@@ -84,6 +85,7 @@ public abstract class ImageWriterAbstractTest {
protected static BufferedImage drawSomething(final BufferedImage image) {
Graphics2D g = image.createGraphics();
+
try {
int width = image.getWidth();
int height = image.getHeight();
@@ -131,18 +133,54 @@ public abstract class ImageWriterAbstractTest {
public void testWrite() throws IOException {
ImageWriter writer = createWriter();
- for (RenderedImage testData : getTestData()) {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ try {
+ for (RenderedImage testData : getTestData()) {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
- writer.setOutput(stream);
- writer.write(drawSomething((BufferedImage) testData));
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+ writer.write(drawSomething((BufferedImage) testData));
+ }
+ catch (IOException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+
+ assertTrue(buffer.size() > 0, "No image data written");
}
- catch (IOException e) {
- throw new AssertionError(e.getMessage(), e);
+ }
+ finally {
+ writer.dispose();
+ }
+ }
+
+ @Test
+ public void testWriteRaster() throws IOException {
+ ImageWriter writer = createWriter();
+
+ try {
+ if (!writer.canWriteRasters()) {
+ return;
}
- assertTrue(buffer.size() > 0, "No image data written");
+ ImageWriteParam param = writer.getDefaultWriteParam();
+
+ for (RenderedImage testData : getTestData()) {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ writer.write(null, new IIOImage(testData.getTile(0, 0), null, null), param);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+
+ assertTrue(buffer.size() > 0, "No image data written");
+ }
+ }
+ finally {
+ writer.dispose();
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/BlockCompression.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/BlockCompression.java
new file mode 100644
index 00000000..ffcf3945
--- /dev/null
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/BlockCompression.java
@@ -0,0 +1,11 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+enum BlockCompression {
+ BC1,
+ BC2,
+ BC3,
+ BC4,
+ BC5,
+// BC6H,
+// BC7
+}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java
index 7dbb9716..a2a6b6a5 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java
@@ -33,8 +33,10 @@ package com.twelvemonkeys.imageio.plugins.dds;
@SuppressWarnings("unused")
interface DDS {
- int MAGIC = ('D' << 24) + ('D' << 16) + ('S' << 8) + ' '; //Big-Endian
+ int MAGIC = 'D' + ('D' << 8) + ('S' << 16) + (' ' << 24); // Little-Endian
+
int HEADER_SIZE = 124;
+ int PIXELFORMAT_SIZE = 32;
// Header Flags
int FLAG_CAPS = 1; // Required in every .dds file.
@@ -47,7 +49,6 @@ interface DDS {
int FLAG_DEPTH = 1 << 23; // Required in a depth texture.
// Pixel Format Flags
- int DDSPF_SIZE = 32;
int PIXEL_FORMAT_FLAG_ALPHAPIXELS = 0x1;
int PIXEL_FORMAT_FLAG_ALPHA = 0x2;
int PIXEL_FORMAT_FLAG_FOURCC = 0x04;
@@ -56,16 +57,6 @@ interface DDS {
//DX10 Resource Dimensions
int D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3;
- //DXGI Formats (DX10)
- int DXGI_FORMAT_BC1_UNORM = 71;
- int DXGI_FORMAT_BC2_UNORM = 72;
- int DXGI_FORMAT_BC3_UNORM = 77;
- int DXGI_FORMAT_BC4_UNORM = 80;
- int DXGI_FORMAT_BC5_UNORM = 83;
- int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91;
- int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93;
- int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29;
-
//dwCaps
int DDSCAPS_COMPLEX = 0x8;
int DDSCAPS_MIPMAP = 0x400000;
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSEncoderType.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSEncoderType.java
deleted file mode 100644
index d780dd92..00000000
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSEncoderType.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.twelvemonkeys.imageio.plugins.dds;
-
-/**
- * Lists a number of supported encoders for block compressors and uncompressed types.
- * DXGI Format List
- * Compression Algorithms
- * An extended Non-DX10 FourCC list
- */
-public enum DDSEncoderType {
- BC1(DDSType.DXT1.value(), DDS.DXGI_FORMAT_BC1_UNORM, 8),
- BC2(DDSType.DXT2.value(), DDS.DXGI_FORMAT_BC2_UNORM, 16),
- BC3(DDSType.DXT5.value(), DDS.DXGI_FORMAT_BC3_UNORM, 16),
- BC4(0x31495441, DDS.DXGI_FORMAT_BC4_UNORM, 8),
- BC5(0x32495441, DDS.DXGI_FORMAT_BC5_UNORM, 16);
-
- private final int fourCC;
- private final int dx10DxgiFormat;
- private final int bitCountOrBlockSize;
- private final int[] rgbaMask;
-
- //fourCC constructor
- DDSEncoderType(int fourCC, int dx10DxgiFormat, int blockSize) {
- this.fourCC = fourCC;
- this.dx10DxgiFormat = dx10DxgiFormat;
- bitCountOrBlockSize = blockSize;
- rgbaMask = null;
- }
-
- //non-fourCC constructor (e.g. A8R8G8B8)
- DDSEncoderType(int dx10DxgiFormat, int bitCount, int[] masks) {
- fourCC = 0;
- this.dx10DxgiFormat = dx10DxgiFormat;
- bitCountOrBlockSize = bitCount;
- rgbaMask = masks;
- }
-
- boolean isFourCC() {
- return fourCC != 0;
- }
-
- int getFourCC() {
- return fourCC;
- }
-
- boolean isAlphaMaskSupported() {
- return !isFourCC() && rgbaMask[3] > 0;
- }
-
- boolean isBlockCompression() {
- return this.isFourCC();
- }
-
- int getBitsOrBlockSize() {
- return bitCountOrBlockSize;
- }
-
- public int[] getRGBAMask() {
- return rgbaMask;
- }
-
- public int getDx10Format() {
- return dx10DxgiFormat;
- }
-}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java
index 6fe4e2b4..a9a7898a 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java
@@ -30,11 +30,22 @@
package com.twelvemonkeys.imageio.plugins.dds;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A1R5G5B5_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A4R4G4B4_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8B8G8R8_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8R8G8B8_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R5G6B5_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R8G8B8_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X1R5G5B5_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X4R4G4B4_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8B8G8R8_MASKS;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8R8G8B8_MASKS;
+
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.Dimension;
import java.io.IOException;
-import java.nio.ByteOrder;
+import java.util.Arrays;
final class DDSHeader {
@@ -52,17 +63,17 @@ final class DDSHeader {
private int blueMask;
private int alphaMask;
+ DXT10Header dxt10Header;
+
@SuppressWarnings("unused")
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
DDSHeader header = new DDSHeader();
// Read MAGIC bytes [0,3]
- imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
int magic = imageInput.readInt();
if (magic != DDS.MAGIC) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
}
- imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// DDS_HEADER structure
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
@@ -97,6 +108,9 @@ final class DDSHeader {
// DDS_PIXELFORMAT structure
int px_dwSize = imageInput.readInt(); // [76,79]
+ if (px_dwSize != DDS.PIXELFORMAT_SIZE) {
+ throw new IIOException(String.format("Invalid DDS PIXELFORMAT size (expected %d): %d", DDS.PIXELFORMAT_SIZE, dwSize));
+ }
header.pixelFormatFlags = imageInput.readInt(); // [80,83]
header.fourCC = imageInput.readInt(); // [84,87]
@@ -113,6 +127,10 @@ final class DDSHeader {
int dwReserved2 = imageInput.readInt(); // [124,127]
+ if (header.fourCC == DDSType.DXT10.fourCC()) {
+ header.dxt10Header = DXT10Header.read(imageInput);
+ }
+
return header;
}
@@ -146,31 +164,101 @@ final class DDSHeader {
return mipMapCount;
}
- int getBitCount() {
- return bitCount;
+ DDSType getType() throws IIOException {
+ if (dxt10Header != null) {
+ return dxt10Header.getType();
+ }
+
+ return getRawType();
}
- int getFourCC() {
- return fourCC;
+ DDSType getRawType() throws IIOException {
+ if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
+ // DXT
+ return DDSType.fromFourCC(fourCC);
+ }
+ else if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
+ // RGB
+ int alphaMask = ((pixelFormatFlags & 0x01) != 0) ? this.alphaMask : 0; // 0x01 alpha
+
+ if (bitCount == 16) {
+ if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
+ // A1R5G5B5
+ return DDSType.A1R5G5B5;
+ }
+ else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
+ // X1R5G5B5
+ return DDSType.X1R5G5B5;
+ }
+ else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
+ // A4R4G4B4
+ return DDSType.A4R4G4B4;
+ }
+ else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
+ // X4R4G4B4
+ return DDSType.X4R4G4B4;
+ }
+ else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
+ // R5G6B5
+ return DDSType.R5G6B5;
+ }
+
+ throw new IIOException("Unsupported 16bit RGB image.");
+ }
+ else if (bitCount == 24) {
+ if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
+ // R8G8B8
+ return DDSType.R8G8B8;
+ }
+
+ throw new IIOException("Unsupported 24bit RGB image.");
+ }
+ else if (bitCount == 32) {
+ if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
+ // A8B8G8R8
+ return DDSType.A8B8G8R8;
+ }
+ else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
+ // X8B8G8R8
+ return DDSType.X8B8G8R8;
+ }
+ else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
+ // A8R8G8B8
+ return DDSType.A8R8G8B8;
+ }
+ else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
+ // X8R8G8B8
+ return DDSType.X8R8G8B8;
+ }
+
+ throw new IIOException("Unsupported 32bit RGB image.");
+ }
+
+ throw new IIOException("Unsupported bit count: " + bitCount);
+ }
+
+ throw new IIOException("Unsupported YUV or LUMINANCE image.");
}
- int getPixelFormatFlags() {
- return pixelFormatFlags;
+ @Override
+ public String toString() {
+ return "DDSHeader{" +
+ "flags=" + Integer.toBinaryString(flags) +
+ ", mipMapCount=" + mipMapCount +
+ ", dimensions=" + Arrays.toString(Arrays.stream(dimensions)
+ .map(DDSHeader::dimensionToString)
+ .toArray(String[]::new)) +
+ ", pixelFormatFlags=" + Integer.toBinaryString(pixelFormatFlags) +
+ ", fourCC=" + fourCC +
+ ", bitCount=" + bitCount +
+ ", redMask=" + redMask +
+ ", greenMask=" + greenMask +
+ ", blueMask=" + blueMask +
+ ", alphaMask=" + alphaMask +
+ '}';
}
- int getRedMask() {
- return redMask;
- }
-
- int getGreenMask() {
- return greenMask;
- }
-
- int getBlueMask() {
- return blueMask;
- }
-
- int getAlphaMask() {
- return alphaMask;
+ private static String dimensionToString(Dimension dimension) {
+ return String.format("%dx%d", dimension.width, dimension.height);
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageDataEncoder.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageDataEncoder.java
index a8ed5f44..d993fa83 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageDataEncoder.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageDataEncoder.java
@@ -3,7 +3,6 @@ package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.stream.ImageOutputStream;
import java.awt.Color;
import java.awt.image.Raster;
-import java.awt.image.RenderedImage;
import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.ARGB_ORDER;
@@ -13,15 +12,11 @@ import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.RGB_16_ORDER;
/**
* A designated class to encode image data to binary.
- *
- * References:
- *
- * [1] GPU DXT Decompression.
- * [2] TEXTURE COMPRESSION TECHNIQUES.
- * [3] Real-Time DXT Compression by J.M.P. van Waveren
- * [4] Khronos Data Format Specification v1.4 by Andrew Garrard
- *
- *
+ *
+ * @see GPU DXT Decompression.
+ * @see TEXTURE COMPRESSION TECHNIQUES.
+ * @see Real-Time DXT Compression by J.M.P. van Waveren
+ * @see Khronos Data Format Specification v1.4 by Andrew Garrard
*/
class DDSImageDataEncoder {
private DDSImageDataEncoder() {}
@@ -31,25 +26,27 @@ class DDSImageDataEncoder {
private static final int BC4_CHANNEL_ALPHA = 3; //BC3 reuses algorithm from BC4 but uses alpha channelIndex for sampling.
private static final int BC4_CHANNEL_GREEN = 1; //same re-usage as BC3 but for green channel BC5 uses
- static void writeImageData(ImageOutputStream imageOutput, RenderedImage renderedImage, DDSEncoderType type) throws IOException {
- switch (type) {
+ static void writeImageData(ImageOutputStream imageOutput, Raster raster, BlockCompression compression) throws IOException {
+ // TODO: Support compression == null for uncompressed RGB(A/X) data?
+
+ switch (compression) {
case BC1:
- new BlockCompressor1(false).encode(imageOutput, renderedImage);
+ new BlockCompressor1(false).encode(imageOutput, raster);
break;
case BC2:
- new BlockCompressor2().encode(imageOutput, renderedImage);
+ new BlockCompressor2().encode(imageOutput, raster);
break;
case BC3:
- new BlockCompressor3().encode(imageOutput, renderedImage);
+ new BlockCompressor3().encode(imageOutput, raster);
break;
case BC4:
- new BlockCompressor4(BC4_CHANNEL_RED).encode(imageOutput, renderedImage);
+ new BlockCompressor4(BC4_CHANNEL_RED).encode(imageOutput, raster);
break;
case BC5:
- new BlockCompressor5().encode(imageOutput, renderedImage);
+ new BlockCompressor5().encode(imageOutput, raster);
break;
default:
- throw new IllegalArgumentException("DDS Type is not supported for encoder yet : " + type);
+ throw new IllegalArgumentException("DDS block compression is not supported yet: " + compression);
}
}
@@ -173,8 +170,14 @@ class DDSImageDataEncoder {
boolean getBlockEndpoints(int[] sampledColors, int[] paletteBuffer) {
if (sampledColors.length != 64)
throw new IllegalStateException("Unintended behaviour, expecting sampled colors of block to be 64, got " + sampledColors.length);
- int minR = 0xff; int minG = 0xff; int minB = 0xff;
- int maxR = 0; int maxG = 0; int maxB = 0;
+ int minR = 0xff;
+ int minG = 0xff;
+ int minB = 0xff;
+
+ int maxR = 0;
+ int maxG = 0;
+ int maxB = 0;
+
boolean alphaMode = false;
int i = 0;
while (i < 64) {
@@ -209,7 +212,6 @@ class DDSImageDataEncoder {
return alphaMode;
}
-
//Reference [3] Page 7
boolean getBlockEndpoints2(int[] sampled, int[] paletteBuffer) {
int maxDistance = -1;
@@ -415,10 +417,10 @@ class DDSImageDataEncoder {
}
}
- void encode(ImageOutputStream imageOutput, RenderedImage image) throws IOException {
- int blocksXCount = (image.getWidth() + 3) / 4;
- int blocksYCount = (image.getHeight() + 3) / 4;
- Raster raster = image.getData();
+ void encode(ImageOutputStream imageOutput, Raster raster) throws IOException {
+ int blocksXCount = (raster.getWidth() + 3) / 4;
+ int blocksYCount = (raster.getHeight() + 3) / 4;
+
for (int blockY = 0; blockY < blocksYCount; blockY++) {
for (int blockX = 0; blockX < blocksXCount; blockX++) {
raster.getPixels(blockX * 4, blockY * 4, 4, 4, samples);
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSMetadata.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
similarity index 61%
rename from imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSMetadata.java
rename to imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
index 37e469ff..ff77fef9 100755
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSMetadata.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
@@ -34,25 +34,48 @@ import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import javax.imageio.ImageTypeSpecifier;
-final class DDSMetadata extends StandardImageMetadataSupport {
- DDSMetadata(ImageTypeSpecifier type, DDSHeader header) {
- super(builder(type)
- .withCompressionTypeName(compressionName(header))
- .withFormatVersion("1.0")
+final class DDSImageMetadata extends StandardImageMetadataSupport {
+
+ DDSImageMetadata(ImageTypeSpecifier specifier, DDSType type) {
+ super(builder(specifier)
+ .withCompressionTypeName(compressionName(type))
+ .withCompressionLossless(!type.isBlockCompression())
+ .withBitsPerSample(bitsPerSample(type))
+ .withFormatVersion("1.0")
);
}
- private static String compressionName(DDSHeader header) {
- // If the fourCC is valid, compression is one of the DXTn versions, otherwise None
- int flags = header.getPixelFormatFlags();
-
- if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
- // DXTn
- DDSType type = DDSType.valueOf(header.getFourCC());
-
+ private static String compressionName(DDSType type) {
+ if (type != null && type.isFourCC()) {
return type.name();
}
return "None";
}
+
+ private static int[] bitsPerSample(DDSType type) {
+ if (type.isBlockCompression()) {
+ return null; // Use defaults
+ }
+
+ int[] bitsPerSample = new int[4];
+
+ for (int i = 0; i < bitsPerSample.length; i++) {
+ bitsPerSample[i] = countMaskBits(type.rgbaMasks[i]);
+ }
+
+ return bitsPerSample;
+ }
+
+ private static int countMaskBits(int mask) {
+ // See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
+ int count;
+
+ for (count = 0; mask != 0; count++) {
+ mask &= mask - 1; // clear the least significant bit set
+ }
+
+ return count;
+ }
+
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java
index e6a40af2..bef6f42e 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java
@@ -48,6 +48,12 @@ import java.util.Iterator;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
+/**
+ * ImageReader implementation for Microsoft DirectDraw Surface (DDS) format.
+ *
+ * @author Paul Allen
+ * @author Harald Kuhr
+ */
public final class DDSImageReader extends ImageReaderBase {
private DDSHeader header;
@@ -90,7 +96,16 @@ public final class DDSImageReader extends ImageReaderBase {
checkBounds(imageIndex);
readHeader();
- // TODO: Implement for the specific formats...
+ DDSType type = header.getType();
+ if (!type.isBlockCompression() && type.rgbaMasks[3] == 0) {
+ return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
+ }
+
+ // TODO: DXT1 can have 1 bit alpha, usually don't...
+ // DXT3/5 have alpha
+ // DXT2/4 ...?
+
+
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
}
@@ -146,14 +161,13 @@ public final class DDSImageReader extends ImageReaderBase {
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
ImageTypeSpecifier imageType = getRawImageType(imageIndex);
- return new DDSMetadata(imageType, header);
+ return new DDSImageMetadata(imageType, header.getType());
}
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
header = DDSHeader.read(imageInput);
-
imageInput.flushBefore(imageInput.getStreamPosition());
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java
index 28af3768..b6ade378 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java
@@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
+import java.nio.ByteOrder;
import java.util.Locale;
public final class DDSImageReaderSpi extends ImageReaderSpiBase {
@@ -52,10 +53,15 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
ImageInputStream stream = (ImageInputStream) source;
stream.mark();
+ ByteOrder byteOrder = stream.getByteOrder();
try {
+ stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+
return stream.readInt() == DDS.MAGIC;
- } finally {
+ }
+ finally {
+ stream.setByteOrder(byteOrder);
stream.reset();
}
}
@@ -67,6 +73,6 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
@Override
public String getDescription(Locale locale) {
- return "Direct DrawSurface (DDS) Image Reader";
+ return "DirectDraw Surface (DDS) Image Reader";
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParam.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParam.java
new file mode 100644
index 00000000..6b0ea889
--- /dev/null
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParam.java
@@ -0,0 +1,62 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+import javax.imageio.ImageWriteParam;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class DDSImageWriteParam extends ImageWriteParam {
+
+ static final DDSType DEFAULT_TYPE = DDSType.DXT5;
+ private static final String[] COMPRESSION_TYPES = compressionTypes();
+
+ private static String[] compressionTypes() {
+ // TODO: Maybe hardcode subset of values that we actually support writing?
+ List compressionTypes = Arrays.stream(DDSType.values())
+ .filter(DDSType::isBlockCompression)
+ .map(Enum::name)
+ .collect(Collectors.toList());
+ compressionTypes.add(0, "None");
+
+ return compressionTypes.toArray(new String[0]);
+ }
+
+ private boolean writeDXT10;
+
+ DDSImageWriteParam() {
+ canWriteCompressed = true;
+ compressionTypes = COMPRESSION_TYPES;
+ compressionType = DEFAULT_TYPE.name();
+ }
+
+ public void setWriteDX10() {
+ writeDXT10 = true;
+ }
+
+ public void clearWriteDX10() {
+ writeDXT10 = false;
+ }
+
+ public boolean isWriteDXT10() {
+ return writeDXT10;
+ }
+
+ DDSType type() {
+ if (compressionType == null || compressionType.equals("None")) {
+ return null;
+ }
+
+ return DDSType.valueOf(compressionType);
+ }
+
+ int getDxgiFormat() {
+ DDSType type = type();
+
+ if (type != null) {
+ return type.dxgiFormat();
+ }
+
+ return DXGI.DXGI_FORMAT_UNKNOWN;
+ }
+}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriter.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriter.java
index 2c4b1f40..690fa371 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriter.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriter.java
@@ -1,7 +1,9 @@
package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.ImageWriterBase;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
@@ -9,6 +11,8 @@ import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.MemoryCacheImageOutputStream;
+
+import java.awt.Dimension;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.File;
@@ -18,79 +22,191 @@ import java.nio.file.Files;
import java.nio.file.Paths;
/**
- * A designated class to begin writing DDS file with headers, class {@link DDSImageDataEncoder} will handle image data encoding process
+ * ImageWriter implementation for Microsoft DirectDraw Surface (DDS) format.
+ *
+ * @author KhanTypo
+ * @author Harald Kuhr
*/
class DDSImageWriter extends ImageWriterBase {
+
+ private long startPos;
+ // TODO: Create a SequenceSupport class that handles sequence prepare/write/end
+ private int mipmapIndex = -1;
+ private DDSType mipmapType;
+ private Dimension mipmapDimension;
+
protected DDSImageWriter(ImageWriterSpi provider) {
super(provider);
}
@Override
- public DDSWriterParam getDefaultWriteParam() {
- return DDSWriterParam.DEFAULT_PARAM;
+ public DDSImageWriteParam getDefaultWriteParam() {
+ return new DDSImageWriteParam();
+ }
+
+ @Override
+ protected void resetMembers() {
+ mipmapIndex = -1;
+ mipmapType = null;
+ mipmapDimension = null;
+ }
+
+ @Override
+ public boolean canWriteRasters() {
+ return true;
+ }
+
+ @Override
+ public boolean canWriteSequence() {
+ return true;
+ }
+
+ @Override
+ public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
+ assertOutput();
+
+ if (mipmapIndex >= 0) {
+ throw new IllegalStateException("writeSequence already started");
+ }
+ mipmapIndex = 0;
+
+ startPos = imageOutput.getStreamPosition();
+ imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ imageOutput.writeInt(DDS.MAGIC);
+ imageOutput.flush();
+ }
+
+ @Override
+ public void endWriteSequence() throws IOException {
+ assertOutput();
+
+ if (mipmapIndex < 0) {
+ throw new IllegalStateException("prepareWriteSequence not called");
+ }
+
+ // Go back and update hader
+ updateHeader(mipmapIndex);
+
+ mipmapIndex = -1;
+ mipmapType = null;
+ mipmapDimension = null;
+
+ imageOutput.flush();
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
- assertOutput();
- RenderedImage renderedImage = image.getRenderedImage();
- ensureTextureSize(renderedImage);
- ensureImageChannels(renderedImage);
+ prepareWriteSequence(streamMetadata);
+ writeToSequence(image, param);
+ endWriteSequence();
+ }
- DDSWriterParam ddsParam = param instanceof DDSWriterParam ? ((DDSWriterParam) param) : this.getDefaultWriteParam();
+ @Override
+ public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
+ if (mipmapIndex < 0) {
+ throw new IllegalStateException("prepareWriteSequence not called");
+ }
- processImageStarted(0);
- imageOutput.setByteOrder(ByteOrder.BIG_ENDIAN);
- imageOutput.writeInt(DDS.MAGIC);
- imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ Raster raster = getRaster(image);
+ ensureImageChannels(raster);
+ ensureTextureDimension(raster);
- writeHeader(image, ddsParam);
- writeDXT10Header(ddsParam);
+ DDSImageWriteParam ddsParam = param instanceof DDSImageWriteParam
+ ? ((DDSImageWriteParam) param)
+ : IIOUtil.copyStandardParams(param, getDefaultWriteParam());
- //image data encoding
+ DDSType type = ddsParam.type();
+ if (mipmapType == null) {
+ mipmapType = type;
+ }
+ else if (type != mipmapType) {
+ processWarningOccurred(mipmapIndex, "All images in DDS MipMap must use same pixel format and compression");
+ }
+ if (mipmapType == null) {
+ throw new IIOException("Only compressed DDS using DXT1-5 or DXT10 with block compression is currently supported");
+ }
+
+ if (mipmapIndex == 0) {
+ writeHeader(raster.getWidth(), raster.getHeight(), mipmapType, ddsParam.isWriteDXT10());
+ if (ddsParam.isWriteDXT10()) {
+ writeDXT10Header(ddsParam.getDxgiFormat());
+ }
+ }
+
+ processImageStarted(mipmapIndex);
processImageProgress(0f);
- DDSImageDataEncoder.writeImageData(imageOutput, renderedImage, ddsParam.getEncoderType());
- processImageProgress(100f);
- imageOutput.flush();
+ DDSImageDataEncoder.writeImageData(imageOutput, raster, mipmapType.compression);
+
+ processImageProgress(100f);
processImageComplete();
+
+ mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight());
+ mipmapIndex++;
+ }
+
+ private static Raster getRaster(IIOImage image) throws IIOException {
+ if (image.hasRaster()) {
+ return image.getRaster();
+ }
+ else {
+ RenderedImage renderedImage = image.getRenderedImage();
+
+ if (renderedImage.getNumXTiles() != 1 || renderedImage.getNumYTiles() != 1) {
+ throw new IIOException("Only single tile images supported");
+ }
+
+ return renderedImage.getTile(0, 0);
+ }
}
/**
* Checking if the image has 3 channels (RGB) or 4 channels (RGBA) and if image has 8 bits/channel.
+ *
+ * @see DDSImageWriterSpi#canEncodeImage(ImageTypeSpecifier)
*/
-
- private void ensureImageChannels(RenderedImage renderedImage) {
- Raster data = renderedImage.getData();
+ private void ensureImageChannels(Raster data) throws IIOException {
int numBands = data.getNumBands();
- if (numBands < 3)
- throw new IllegalStateException("Only image with 3 channels (RGB) or 4 channels (RGBA) is supported, got " + numBands + " channels");
+ if (numBands < 3 || numBands > 4) {
+ throw new IIOException(
+ "Only image with 3 channels (RGB) or 4 channels (RGBA) is supported, got " + numBands + " channels");
+ }
+
int sampleSize = data.getSampleModel().getSampleSize(0);
- if (sampleSize != 8)
- throw new IllegalStateException("Only image with 8 bits/channel is supported, got " + sampleSize);
+ if (sampleSize != 8) {
+ throw new IIOException("Only image with 8 bits/channel is supported, got " + sampleSize);
+ }
}
/**
* Checking if an image can be evenly divided into blocks of 4x4, ideally a power of 2.
- * e.g. 16x16, 32x32, 512x128, 512x512, 1024x512, 1024x1024, 2048x1024, ...
+ * e.g. 16x16, 32x32, 512x128, 512x512, 1024x512, 1024x1024, 2048x1024...
*/
- private void ensureTextureSize(RenderedImage renderedImage) {
- int w = renderedImage.getWidth();
- int h = renderedImage.getHeight();
- if (w % 4 != 0 || h % 4 != 0)
- throw new IllegalStateException(String.format("Image size must be dividable by 4, ideally a power of 2; got (%d x %d)", w, h));
+ private void ensureTextureDimension(Raster raster) throws IIOException {
+ int width = raster.getWidth();
+ int height = raster.getHeight();
+
+ // Should also allow mipmaps 2x2 and 1x1?
+ if (width % 4 != 0 || height % 4 != 0) {
+ throw new IIOException(String.format("Image dimensions must be dividable by 4, ideally a power of 2; got %dx%d", width, height));
+ }
+
+ if (mipmapDimension != null && (mipmapDimension.width != width * 2|| mipmapDimension.height != height * 2)) {
+ throw new IIOException(
+ String.format("For mipmap, image dimensions must be exactly half of previous (%dx%d); got %dx%d",
+ mipmapDimension.width, mipmapDimension.height, width, height)
+ );
+ }
}
-
- private void writeHeader(IIOImage image, DDSWriterParam param) throws IOException {
+ private void writeHeader(int width, int height, DDSType type, boolean writeDXT10) throws IOException {
imageOutput.writeInt(DDS.HEADER_SIZE);
- imageOutput.writeInt(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT | param.getOptionalBitFlags());
- RenderedImage renderedImage = image.getRenderedImage();
- int height = renderedImage.getHeight();
+ int linearSizeOrPitch = type.isBlockCompression() ? DDS.FLAG_LINEARSIZE : DDS.FLAG_PITCH;
+ imageOutput.writeInt(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT | linearSizeOrPitch);
+
imageOutput.writeInt(height);
- int width = renderedImage.getWidth();
imageOutput.writeInt(width);
- writePitchOrLinearSize(height, width, param);
+ writePitchOrLinearSize(height, width, type);
//dwDepth
imageOutput.writeInt(0);
//dwMipmapCount
@@ -98,97 +214,126 @@ class DDSImageWriter extends ImageWriterBase {
//reserved
imageOutput.write(new byte[44]);
//pixFmt
- writePixelFormat(param);
+ writePixelFormat(type, writeDXT10);
//dwCaps, right now we keep it simple by only using DDSCAP_TEXTURE as it is required.
imageOutput.writeInt(DDS.DDSCAPS_TEXTURE);
//dwCaps2, unused for now as we are not working with cube maps
imageOutput.writeInt(0);
//dwCaps3, dwCaps4, dwReserved2 : 3 unused integers
imageOutput.write(new byte[12]);
+ }
+ private void updateHeader(int mipmapCount) throws IOException {
+ if (mipmapCount == 1) {
+ // Fast case, nothing to do
+ return;
+ }
+
+ long streamPosition = imageOutput.getStreamPosition();
+ imageOutput.seek(startPos + 8); // Seek back to start + 4 magic + 4 header size
+
+ int flags = imageOutput.readInt();
+ imageOutput.seek(imageOutput.getStreamPosition() - 4);
+ imageOutput.writeInt(flags | DDS.FLAG_MIPMAPCOUNT);
+
+ imageOutput.seek(imageOutput.getStreamPosition() + 16);
+ imageOutput.writeInt(mipmapCount);
+
+ imageOutput.seek(streamPosition); // Restore pos
}
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat
- private void writePixelFormat(DDSWriterParam param) throws IOException {
- imageOutput.writeInt(DDS.DDSPF_SIZE);
- writePixelFormatFlags(param);
- writeFourCC(param);
- writeRGBAData(param);
+ private void writePixelFormat(DDSType type, boolean writeDXT10) throws IOException {
+ imageOutput.writeInt(DDS.PIXELFORMAT_SIZE);
+ writePixelFormatFlags(type, writeDXT10);
+ writeFourCC(type, writeDXT10);
+ writeRGBAData(type, writeDXT10);
}
- private void writeDXT10Header(DDSWriterParam param) throws IOException {
- if (param.isUsingDxt10()) {
- //dxgiFormat
- imageOutput.writeInt(param.getDxgiFormat());
- //resourceDimension
- imageOutput.writeInt(DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D);
- //miscFlag
- imageOutput.writeInt(0);
- //arraySize
- imageOutput.writeInt(1);
- //miscFlag2
- imageOutput.writeInt(0);
- }
+ private void writeDXT10Header(int dxgiFormat) throws IOException {
+ //dxgiFormat
+ imageOutput.writeInt(dxgiFormat);
+ //resourceDimension
+ imageOutput.writeInt(DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D);
+ //miscFlag
+ imageOutput.writeInt(0);
+ //arraySize
+ imageOutput.writeInt(1);
+ //miscFlag2
+ imageOutput.writeInt(0);
}
- private void writeRGBAData(DDSWriterParam param) throws IOException {
- if (!param.isUsingDxt10() && !param.getEncoderType().isFourCC()) {
+ private void writeRGBAData(DDSType type, boolean writeDXT10) throws IOException {
+ if (!writeDXT10 && !type.isFourCC()) {
//dwRGBBitCount
- imageOutput.writeInt(param.getEncoderType().getBitsOrBlockSize());
+ imageOutput.writeInt(type.blockSize() * 8); // TODO: Is bitcount always a multiple of 8?
- int[] mask = param.getEncoderType().getRGBAMask();
+ int[] mask = type.rgbaMasks;
//dwRBitMask
imageOutput.writeInt(mask[0]);
//dwGBitMask
imageOutput.writeInt(mask[1]);
- //dwBitMask
+ //dwBBitMask
imageOutput.writeInt(mask[2]);
//dwABitMask
imageOutput.writeInt(mask[3]);
- } else {
+ }
+ else {
//write 5 zero integers as fourCC is used
imageOutput.write(new byte[20]);
}
}
- private void writeFourCC(DDSWriterParam param) throws IOException {
- if (param.isUsingDxt10()) {
- imageOutput.writeInt(DDSType.DXT10.value());
- } else if (param.getEncoderType().isFourCC())
- imageOutput.writeInt(param.getEncoderType().getFourCC());
-
- }
-
- private void writePixelFormatFlags(DDSWriterParam param) throws IOException {
- if (param.isUsingDxt10() || param.getEncoderType().isFourCC()) {
- imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
- } else {
- imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (param.getEncoderType().isAlphaMaskSupported() ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
+ private void writeFourCC(DDSType type, boolean writeDXT10) throws IOException {
+ if (writeDXT10) {
+ imageOutput.writeInt(DDSType.DXT10.fourCC());
+ }
+ else if (type.isFourCC()) {
+ imageOutput.writeInt(type.fourCC());
+ }
+ else {
+ // No fourCC, custom format...
+ imageOutput.writeInt(0);
}
}
- private void writePitchOrLinearSize(int height, int width, DDSWriterParam param) throws IOException {
- DDSEncoderType type = param.getEncoderType();
- int bitsOrBlockSize = type.getBitsOrBlockSize();
+ private void writePixelFormatFlags(DDSType type, boolean writeDXT10) throws IOException {
+ if (writeDXT10 || type.isFourCC()) {
+ imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
+ }
+ else {
+ imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (type.rgbaMasks != null ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
+ }
+ }
+
+ private void writePitchOrLinearSize(int height, int width, DDSType type) throws IOException {
if (type.isBlockCompression()) {
- imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * bitsOrBlockSize);
- } else {
- imageOutput.writeInt(width * bitsOrBlockSize);
+ imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * type.blockSize());
+ }
+ else {
+ imageOutput.writeInt(width * type.blockSize());
}
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
- throw new UnsupportedOperationException("Direct Draw Surface does not support metadata.");
+ DDSType type = param instanceof DDSImageWriteParam
+ ? ((DDSImageWriteParam) param).type()
+ : DDSImageWriteParam.DEFAULT_TYPE;
+
+ return new DDSImageMetadata(imageType, type);
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
- throw new UnsupportedOperationException("Direct Draw Surface does not support metadata.");
+ // Nothing useful to convert here...
+ return getDefaultImageMetadata(imageType, param);
}
public static void main(String[] args) throws IOException {
- if (args.length != 1) throw new IllegalArgumentException("Use 1 input file at a time.");
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Use 1 input file at a time.");
+ }
ImageIO.write(ImageIO.read(new File(args[0])), "dds", new MemoryCacheImageOutputStream(Files.newOutputStream(Paths.get("output.dds"))));
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterSpi.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterSpi.java
index 8a8931e7..4d75aeb0 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterSpi.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterSpi.java
@@ -4,6 +4,7 @@ import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
+
import java.util.Locale;
public final class DDSImageWriterSpi extends ImageWriterSpiBase {
@@ -13,7 +14,12 @@ public final class DDSImageWriterSpi extends ImageWriterSpiBase {
@Override
public boolean canEncodeImage(ImageTypeSpecifier type) {
- return true;
+ int numBands = type.getNumBands();
+ if (numBands < 3 || numBands > 4) {
+ return false;
+ }
+
+ return type.getSampleModel().getSampleSize(0) == 8;
}
@Override
@@ -23,6 +29,6 @@ public final class DDSImageWriterSpi extends ImageWriterSpiBase {
@Override
public String getDescription(Locale locale) {
- return "Direct Draw Surface (DDS) Image Writer";
+ return "DirectDraw Surface (DDS) Image Writer";
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java
index 33e8c823..0baf8593 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java
@@ -35,18 +35,16 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
final class DDSProviderInfo extends ReaderWriterProviderInfo {
DDSProviderInfo() {
super(
- DDSProviderInfo.class,
- new String[]{"DDS", "dds"},
- new String[]{"dds"},
- new String[]{"image/vnd-ms.dds"},
- "com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
- new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi"},
- "com.twelvemonkeys.imageio.plugins.dds.DDSImageWriter",
- new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi"},
- false, null, null,
- null, null, true,
- null, null, null,
- null
+ DDSProviderInfo.class,
+ new String[] { "DDS", "dds" },
+ new String[] { "dds" },
+ new String[] { "image/vnd-ms.dds" },
+ "com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
+ new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi" },
+ "com.twelvemonkeys.imageio.plugins.dds.DDSImageWriter",
+ new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi" },
+ false, null, null, null, null,
+ true, null, null, null, null
);
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java
index 03364fc9..1c480a9e 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java
@@ -74,7 +74,6 @@ final class DDSReader {
static final Order RGB_16_ORDER = new Order(11, 5, 0, -1); // no alpha | 5 red | 6 green | 5 blue
private final DDSHeader header;
- private DX10Header dxt10Header;
DDSReader(DDSHeader header) {
this.header = header;
@@ -82,18 +81,20 @@ final class DDSReader {
int[] read(ImageInputStream imageInput, int imageIndex) throws IOException {
// type
- DDSType type = getType();
- if (type == DDSType.DXT10) {
- dxt10Header = DX10Header.read(imageInput);
- type = dxt10Header.getDDSType();
- }
+ DDSType type = header.getType();
// offset buffer to index mipmap image
byte[] buffer = null;
for (int i = 0; i <= imageIndex; i++) {
- int len = getLength(type, i);
- buffer = new byte[len];
- imageInput.readFully(buffer);
+ int len = getBufferLength(type, i);
+
+ if (i == imageIndex) {
+ buffer = new byte[len];
+ imageInput.readFully(buffer);
+ }
+ else {
+ imageInput.seek(imageInput.getStreamPosition() + len);
+ }
}
int width = header.getWidth(imageIndex);
@@ -135,82 +136,17 @@ final class DDSReader {
}
}
- private DDSType getType() throws IIOException {
- int flags = header.getPixelFormatFlags();
-
- if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
- // DXT
- int type = header.getFourCC();
- return DDSType.valueOf(type);
- } else if ((flags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
- // RGB
- int bitCount = header.getBitCount();
- int redMask = header.getRedMask();
- int greenMask = header.getGreenMask();
- int blueMask = header.getBlueMask();
- int alphaMask = ((flags & 0x01) != 0) ? header.getAlphaMask() : 0; // 0x01 alpha
- if (bitCount == 16) {
- if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
- // A1R5G5B5
- return DDSType.A1R5G5B5;
- } else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
- // X1R5G5B5
- return DDSType.X1R5G5B5;
- } else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
- // A4R4G4B4
- return DDSType.A4R4G4B4;
- } else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
- // X4R4G4B4
- return DDSType.X4R4G4B4;
- } else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
- // R5G6B5
- return DDSType.R5G6B5;
- } else {
- throw new IIOException("Unsupported 16bit RGB image.");
- }
- } else if (bitCount == 24) {
- if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
- // R8G8B8
- return DDSType.R8G8B8;
- } else {
- throw new IIOException("Unsupported 24bit RGB image.");
- }
- } else if (bitCount == 32) {
- if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
- // A8B8G8R8
- return DDSType.A8B8G8R8;
- } else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
- // X8B8G8R8
- return DDSType.X8B8G8R8;
- } else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
- // A8R8G8B8
- return DDSType.A8R8G8B8;
- } else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
- // X8R8G8B8
- return DDSType.X8R8G8B8;
- } else {
- throw new IIOException("Unsupported 32bit RGB image.");
- }
- } else {
- throw new IIOException("Unsupported bit count: " + bitCount);
- }
- } else {
- throw new IIOException("Unsupported YUV or LUMINANCE image.");
- }
- }
-
- private int getLength(DDSType type, int imageIndex) throws IIOException {
+ private int getBufferLength(DDSType type, int imageIndex) throws IIOException {
int width = header.getWidth(imageIndex);
int height = header.getHeight(imageIndex);
switch (type) {
case DXT1:
- return 8 * ((width + 3) / 4) * ((height + 3) / 4);
case DXT2:
case DXT3:
case DXT4:
case DXT5:
- return 16 * ((width + 3) / 4) * ((height + 3) / 4);
+ return type.blockSize() * ((width + 3) / 4) * ((height + 3) / 4);
case A1R5G5B5:
case X1R5G5B5:
case A4R4G4B4:
@@ -221,9 +157,9 @@ final class DDSReader {
case X8B8G8R8:
case A8R8G8B8:
case X8R8G8B8:
- return (type.value() & 0xFF) * width * height;
+ return type.blockSize() * width * height;
default:
- throw new IIOException("Unknown type: " + Integer.toHexString(type.value()));
+ throw new IIOException("Unknown type: " + type);
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSType.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSType.java
index da552c85..234f2718 100644
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSType.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSType.java
@@ -30,41 +30,156 @@
package com.twelvemonkeys.imageio.plugins.dds;
+import static com.twelvemonkeys.imageio.plugins.dds.BlockCompression.*;
+import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.*;
+
+/**
+ * Compression Algorithms
+ * An extended Non-DX10 FourCC list
+ */
enum DDSType {
- DXT1(0x31545844),
- DXT2(0x32545844),
- DXT3(0x33545844),
- DXT4(0x34545844),
- DXT5(0x35545844),
- DXT10(0x30315844),
- A1R5G5B5((1 << 16) | 2),
- X1R5G5B5((2 << 16) | 2),
- A4R4G4B4((3 << 16) | 2),
- X4R4G4B4((4 << 16) | 2),
- R5G6B5((5 << 16) | 2),
- R8G8B8((1 << 16) | 3),
- A8B8G8R8((1 << 16) | 4),
- X8B8G8R8((2 << 16) | 4),
- A8R8G8B8((3 << 16) | 4),
- X8R8G8B8((4 << 16) | 4);
+ // Compressed types
+ DXT1('D' + ('X' << 8) + ('T' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC1_UNORM, BC1),
+ DXT2('D' + ('X' << 8) + ('T' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
+ DXT3('D' + ('X' << 8) + ('T' << 16) + ('3' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
+ DXT4('D' + ('X' << 8) + ('T' << 16) + ('4' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
+ DXT5('D' + ('X' << 8) + ('T' << 16) + ('5' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
- private final int value;
+ ATI1('A' + ('T' << 8) + ('I' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4), // AKA BC4U
+ BC4U('B' + ('C' << 8) + ('4' << 16) + ('U' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4),
+ BC4S('B' + ('C' << 8) + ('4' << 16) + ('S' << 24), 8, DXGI.DXGI_FORMAT_BC4_SNORM, BC4),
+ ATI2('A' + ('T' << 8) + ('I' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5), // AKA BC5U
+ BC5U('B' + ('C' << 8) + ('5' << 16) + ('U' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5),
+ BC5S('B' + ('C' << 8) + ('5' << 16) + ('S' << 24), 16, DXGI.DXGI_FORMAT_BC5_SNORM, BC5),
- DDSType(int value) {
- this.value = value;
+ // Special case, see DXT10Header.dxgiFormat for real format
+ DXT10('D' + ('X' << 8) + ('1' << 16) + ('0' << 24), -1, DXGI.DXGI_FORMAT_UNKNOWN, null),
+
+ // Custom uncompressed pixel formats
+ // TODO: Consider swapping byte order to reflect the DXGI format?
+ A1R5G5B5(2, DXGI.DXGI_FORMAT_B5G5R5A1_UNORM, A1R5G5B5_MASKS),
+ X1R5G5B5(2, DXGI.DXGI_FORMAT_UNKNOWN, X1R5G5B5_MASKS),
+ A4R4G4B4(2, DXGI.DXGI_FORMAT_B4G4R4A4_UNORM, A4R4G4B4_MASKS),
+ X4R4G4B4(2, DXGI.DXGI_FORMAT_UNKNOWN, X4R4G4B4_MASKS),
+ R5G6B5( 2, DXGI.DXGI_FORMAT_B5G6R5_UNORM, R5G6B5_MASKS),
+ R8G8B8( 3, DXGI.DXGI_FORMAT_UNKNOWN, R8G8B8_MASKS),
+ A8B8G8R8(4, DXGI.DXGI_FORMAT_R8G8B8A8_UNORM, A8B8G8R8_MASKS),
+ X8B8G8R8(4, DXGI.DXGI_FORMAT_UNKNOWN, X8B8G8R8_MASKS),
+ A8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8A8_UNORM, A8R8G8B8_MASKS),
+ X8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8X8_UNORM, X8R8G8B8_MASKS);
+
+ private final int fourCC;
+ private final int blockSize;
+ private final int dxgiFormat;
+ final BlockCompression compression;
+ final int[] rgbaMasks;
+
+ DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression) {
+ this(fourCC, blockSize, dxgiFormat, compression, null);
}
- public int value() {
- return value;
+ DDSType(int blockSize, int dxgiFormat, int[] rgbaMasks) {
+ this(0, blockSize, dxgiFormat, null, rgbaMasks);
}
- public static DDSType valueOf(int value) {
- for (DDSType type : DDSType.values()) {
- if (value == type.value()) {
- return type;
+ DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression, int[] rgbaMasks) {
+ this.fourCC = fourCC;
+ this.blockSize = blockSize;
+ this.dxgiFormat = dxgiFormat;
+ this.compression = compression;
+ this.rgbaMasks = rgbaMasks;
+ }
+
+ public int fourCC() {
+ return fourCC;
+ }
+
+ public int blockSize() {
+ return blockSize;
+ }
+
+ public boolean isFourCC() {
+ return fourCC != 0;
+ }
+
+ public boolean isBlockCompression() {
+ return compression != null;
+ }
+
+ public int dxgiFormat() {
+ return dxgiFormat;
+ }
+
+ public static DDSType fromFourCC(int fourCC) {
+ if (fourCC != 0) {
+ for (DDSType type : values()) {
+ if (fourCC == type.fourCC()) {
+ return type;
+ }
}
}
- throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", value));
+ throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", fourCC));
+ }
+
+ public static DDSType fromDXGIFormat(int dxgiFormat) {
+ switch (dxgiFormat) {
+ case DXGI.DXGI_FORMAT_R8G8B8A8_TYPELESS:
+ case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM:
+ case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ case DXGI.DXGI_FORMAT_R8G8B8A8_UINT:
+ return A8B8G8R8; // ABGR
+
+ case DXGI.DXGI_FORMAT_B8G8R8A8_TYPELESS:
+ case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM:
+ case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ return A8R8G8B8; // ARGB
+
+ case DXGI.DXGI_FORMAT_B8G8R8X8_TYPELESS:
+ case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM:
+ case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
+ return X8R8G8B8;
+
+ case DXGI.DXGI_FORMAT_B5G5R5A1_UNORM:
+ return A1R5G5B5;
+
+ case DXGI.DXGI_FORMAT_B4G4R4A4_UNORM:
+ return A4R4G4B4;
+
+ case DXGI.DXGI_FORMAT_B5G6R5_UNORM:
+ return R5G6B5;
+
+ case DXGI.DXGI_FORMAT_BC1_TYPELESS:
+ case DXGI.DXGI_FORMAT_BC1_UNORM:
+ case DXGI.DXGI_FORMAT_BC1_UNORM_SRGB:
+ return DXT1;
+
+ case DXGI.DXGI_FORMAT_BC2_TYPELESS:
+ case DXGI.DXGI_FORMAT_BC2_UNORM:
+ case DXGI.DXGI_FORMAT_BC2_UNORM_SRGB:
+ return DXT2;
+
+ case DXGI.DXGI_FORMAT_BC3_TYPELESS:
+ case DXGI.DXGI_FORMAT_BC3_UNORM:
+ case DXGI.DXGI_FORMAT_BC3_UNORM_SRGB:
+ return DXT4;
+
+ case DXGI.DXGI_FORMAT_BC4_TYPELESS:
+ case DXGI.DXGI_FORMAT_BC4_UNORM:
+ return BC4U;
+
+ case DXGI.DXGI_FORMAT_BC4_SNORM:
+ return BC4S;
+
+ case DXGI.DXGI_FORMAT_BC5_TYPELESS:
+ case DXGI.DXGI_FORMAT_BC5_UNORM:
+ return BC5U;
+
+ case DXGI.DXGI_FORMAT_BC5_SNORM:
+ return BC5S;
+
+ default:
+ throw new IllegalArgumentException("Unsupported DXGI_FORMAT: " + dxgiFormat);
+ }
}
}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSWriterParam.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSWriterParam.java
deleted file mode 100644
index a59269cc..00000000
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSWriterParam.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package com.twelvemonkeys.imageio.plugins.dds;
-
-import javax.imageio.ImageWriteParam;
-import java.util.Objects;
-
-public class DDSWriterParam extends ImageWriteParam {
- public static final DDSWriterParam DEFAULT_PARAM = DDSWriterParam.builder().formatBC5().build();
- private final int optionalBitFlags;
- private final DDSEncoderType encoderType;
- private final boolean enableDxt10;
-
- DDSWriterParam(int optionalBitFlags, DDSEncoderType encoderType, boolean isUsingDxt10) {
- super();
- this.optionalBitFlags = optionalBitFlags;
- this.encoderType = encoderType;
- this.enableDxt10 = isUsingDxt10;
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- int getOptionalBitFlags() {
- return this.optionalBitFlags;
- }
-
- DDSEncoderType getEncoderType() {
- return this.encoderType;
- }
-
- public boolean isUsingDxt10() {
- return enableDxt10;
- }
-
- int getDxgiFormat() {
- return getEncoderType().getDx10Format();
- }
-
- public static final class Builder {
- //we use Set collection to prevent duplications of bitflag setter calls
- private int optionalBitFlag;
- private DDSEncoderType encoderType;
- private boolean isUsingDxt10;
-
- public Builder() {
- this.optionalBitFlag = 0;
- encoderType = null;
- isUsingDxt10 = false;
- }
-
- /**
- * Enable saving file as Direct3D 10+ format.
- */
- public Builder enableDX10() {
- isUsingDxt10 = true;
- return this;
- }
-
- /**
- * Set the compression type to be BC1 (DXT1).
- * If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC1_UNORM.
- */
- public Builder formatBC1() {
- encoderType = DDSEncoderType.BC1;
- return setFlag(DDSFlags.DDSD_LINEARSIZE);
- }
-
- /**
- * Set the compression type to be BC2 (DXT3).
- * If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC2_UNORM.
- */
- public Builder formatBC2() {
- encoderType = DDSEncoderType.BC2;
- return setFlag(DDSFlags.DDSD_LINEARSIZE);
- }
-
- /**
- * Set the compression type to be BC3 (DXT5).
- * If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC3_UNORM.
- */
- public Builder formatBC3() {
- encoderType = DDSEncoderType.BC3;
- return setFlag(DDSFlags.DDSD_LINEARSIZE);
- }
-
- /**
- * Set the compression type to be BC4 (ATI1).
- * If DXT10 is enabled, This will set DXGI Format to DXGI_FORMAT_BC4_UNORM.
- */
- public Builder formatBC4() {
- encoderType = DDSEncoderType.BC4;
- return setFlag(DDSFlags.DDSD_LINEARSIZE);
- }
-
- /**
- * Set the compression type to be BC5 (ATI2).
- * This will set DXGI Format to DXGI_FORMAT_BC5_UNORM.
- */
- public Builder formatBC5() {
- encoderType = DDSEncoderType.BC5;
- return setFlag(DDSFlags.DDSD_LINEARSIZE);
- }
-
- public Builder setFlag(DDSFlags flag) {
- optionalBitFlag |= flag.getValue();
- return this;
- }
-
- /**
- * Set other optional flags for the DDS Header.
- */
- public Builder setFlags(DDSFlags... flags) {
- for (DDSFlags flag : flags)
- setFlag(flag);
- return this;
- }
-
- public DDSWriterParam build() {
- Objects.requireNonNull(encoderType, "no DDS format specified.");
- return new DDSWriterParam(optionalBitFlag, encoderType, isUsingDxt10);
- }
-
- public enum DDSFlags {
- DDSD_PITCH(DDS.FLAG_PITCH),// Required when pitch is provided for an uncompressed texture.
- DDSD_MIPMAPCOUNT(DDS.FLAG_MIPMAPCOUNT),// Required in a mipmapped texture.
- DDSD_LINEARSIZE(DDS.FLAG_LINEARSIZE),// Required when pitch is provided for a compressed texture.
- DDSD_DEPTH(DDS.FLAG_DEPTH);// Required in a depth texture.
-
- private final int flag;
- DDSFlags(int flag) {
- this.flag = flag;
- }
-
- public int getValue() {
- return flag;
- }
- }
- }
-}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10DXGIFormat.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10DXGIFormat.java
deleted file mode 100644
index 0a1678d7..00000000
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10DXGIFormat.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.twelvemonkeys.imageio.plugins.dds;
-
-import java.util.Arrays;
-import java.util.function.IntPredicate;
-
-/**
- * Enum that lists a certain types of DXGI Format this reader supports to read.
- *
- * DXGI Format List
- */
-public enum DX10DXGIFormat {
- BC1(DDSType.DXT1, rangeInclusive(70, 72)),
- BC2(DDSType.DXT2, rangeInclusive(73, 75)),
- BC3(DDSType.DXT5, rangeInclusive(76, 78)),
- //BC7(99),
- B8G8R8A8(DDSType.A8B8G8R8, exactly(87, 90, 91)),
- B8G8R8X8(DDSType.X8B8G8R8, exactly(88, 92, 93)),
- R8G8B8A8(DDSType.A8R8G8B8, rangeInclusive(27, 32));
- private final DDSType ddsType;
- private final IntPredicate dxgiFormat;
-
- DX10DXGIFormat(DDSType ddsType, IntPredicate dxgiFormat) {
- this.ddsType = ddsType;
- this.dxgiFormat = dxgiFormat;
- }
-
- DDSType getCorrespondingType() {
- return ddsType;
- }
-
- static DX10DXGIFormat getFormat(int value) {
- for (DX10DXGIFormat format : values()) {
- if (format.dxgiFormat.test(value)) return format;
- }
-
- throw new IllegalArgumentException("Unsupported DXGI_FORMAT : " + value);
- }
-
-
- /**
- * @param acceptedValues values in DXGI Formats List, passed values are expected to be in ascending order
- */
- private static IntPredicate exactly(int... acceptedValues) {
- return test -> Arrays.binarySearch(acceptedValues, test) >= 0;
- }
-
- private static IntPredicate rangeInclusive(int from, int to) {
- return test -> from <= test && test <= to;
- }
-}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10Header.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10Header.java
deleted file mode 100644
index d599d7f8..00000000
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DX10Header.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.twelvemonkeys.imageio.plugins.dds;
-
-import javax.imageio.stream.ImageInputStream;
-import java.io.IOException;
-
-//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
-public final class DX10Header {
- final DX10DXGIFormat dxgiFormat;
- final int resourceDimension, miscFlag, arraySize, miscFlags2;
-
- private DX10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
- this.dxgiFormat = DX10DXGIFormat.getFormat(dxgiFormat);
- this.resourceDimension = resourceDimension;
- if (this.resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D)
- throw new IllegalArgumentException("Resource dimension " + resourceDimension + " is not supported, expected 3.");
- this.miscFlag = miscFlag;
- this.arraySize = arraySize;
- this.miscFlags2 = miscFlags2;
- }
-
- static DX10Header read(ImageInputStream inputStream) throws IOException {
- int dxgiFormat = inputStream.readInt();
- int resourceDimension = inputStream.readInt();
- int miscFlag = inputStream.readInt();
- int arraySize = inputStream.readInt();
- int miscFlags2 = inputStream.readInt();
- return new DX10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
- }
-
- DDSType getDDSType() {
- return dxgiFormat.getCorrespondingType();
- }
-}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXGI.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXGI.java
new file mode 100644
index 00000000..318048df
--- /dev/null
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXGI.java
@@ -0,0 +1,129 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+/**
+ * DXGI Format List
+ */
+interface DXGI {
+ int DXGI_FORMAT_UNKNOWN = 0;
+ int DXGI_FORMAT_R32G32B32A32_TYPELESS = 1;
+ int DXGI_FORMAT_R32G32B32A32_FLOAT = 2;
+ int DXGI_FORMAT_R32G32B32A32_UINT = 3;
+ int DXGI_FORMAT_R32G32B32A32_SINT = 4;
+ int DXGI_FORMAT_R32G32B32_TYPELESS = 5;
+ int DXGI_FORMAT_R32G32B32_FLOAT = 6;
+ int DXGI_FORMAT_R32G32B32_UINT = 7;
+ int DXGI_FORMAT_R32G32B32_SINT = 8;
+ int DXGI_FORMAT_R16G16B16A16_TYPELESS = 9;
+ int DXGI_FORMAT_R16G16B16A16_FLOAT = 10;
+ int DXGI_FORMAT_R16G16B16A16_UNORM = 11;
+ int DXGI_FORMAT_R16G16B16A16_UINT = 12;
+ int DXGI_FORMAT_R16G16B16A16_SNORM = 13;
+ int DXGI_FORMAT_R16G16B16A16_SINT = 14;
+ int DXGI_FORMAT_R32G32_TYPELESS = 15;
+ int DXGI_FORMAT_R32G32_FLOAT = 16;
+ int DXGI_FORMAT_R32G32_UINT = 17;
+ int DXGI_FORMAT_R32G32_SINT = 18;
+ int DXGI_FORMAT_R32G8X24_TYPELESS = 19;
+ int DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20;
+ int DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21;
+ int DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22;
+ int DXGI_FORMAT_R10G10B10A2_TYPELESS = 23;
+ int DXGI_FORMAT_R10G10B10A2_UNORM = 24;
+ int DXGI_FORMAT_R10G10B10A2_UINT = 25;
+ int DXGI_FORMAT_R11G11B10_FLOAT = 26;
+ int DXGI_FORMAT_R8G8B8A8_TYPELESS = 27;
+ int DXGI_FORMAT_R8G8B8A8_UNORM = 28;
+ int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29;
+ int DXGI_FORMAT_R8G8B8A8_UINT = 30;
+ int DXGI_FORMAT_R8G8B8A8_SNORM = 31;
+ int DXGI_FORMAT_R8G8B8A8_SINT = 32;
+ int DXGI_FORMAT_R16G16_TYPELESS = 33;
+ int DXGI_FORMAT_R16G16_FLOAT = 34;
+ int DXGI_FORMAT_R16G16_UNORM = 35;
+ int DXGI_FORMAT_R16G16_UINT = 36;
+ int DXGI_FORMAT_R16G16_SNORM = 37;
+ int DXGI_FORMAT_R16G16_SINT = 38;
+ int DXGI_FORMAT_R32_TYPELESS = 39;
+ int DXGI_FORMAT_D32_FLOAT = 40;
+ int DXGI_FORMAT_R32_FLOAT = 41;
+ int DXGI_FORMAT_R32_UINT = 42;
+ int DXGI_FORMAT_R32_SINT = 43;
+ int DXGI_FORMAT_R24G8_TYPELESS = 44;
+ int DXGI_FORMAT_D24_UNORM_S8_UINT = 45;
+ int DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46;
+ int DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47;
+ int DXGI_FORMAT_R8G8_TYPELESS = 48;
+ int DXGI_FORMAT_R8G8_UNORM = 49;
+ int DXGI_FORMAT_R8G8_UINT = 50;
+ int DXGI_FORMAT_R8G8_SNORM = 51;
+ int DXGI_FORMAT_R8G8_SINT = 52;
+ int DXGI_FORMAT_R16_TYPELESS = 53;
+ int DXGI_FORMAT_R16_FLOAT = 54;
+ int DXGI_FORMAT_D16_UNORM = 55;
+ int DXGI_FORMAT_R16_UNORM = 56;
+ int DXGI_FORMAT_R16_UINT = 57;
+ int DXGI_FORMAT_R16_SNORM = 58;
+ int DXGI_FORMAT_R16_SINT = 59;
+ int DXGI_FORMAT_R8_TYPELESS = 60;
+ int DXGI_FORMAT_R8_UNORM = 61;
+ int DXGI_FORMAT_R8_UINT = 62;
+ int DXGI_FORMAT_R8_SNORM = 63;
+ int DXGI_FORMAT_R8_SINT = 64;
+ int DXGI_FORMAT_A8_UNORM = 65;
+ int DXGI_FORMAT_R1_UNORM = 66;
+ int DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67;
+ int DXGI_FORMAT_R8G8_B8G8_UNORM = 68;
+ int DXGI_FORMAT_G8R8_G8B8_UNORM = 69;
+ int DXGI_FORMAT_BC1_TYPELESS = 70;
+ int DXGI_FORMAT_BC1_UNORM = 71;
+ int DXGI_FORMAT_BC1_UNORM_SRGB = 72;
+ int DXGI_FORMAT_BC2_TYPELESS = 73;
+ int DXGI_FORMAT_BC2_UNORM = 74;
+ int DXGI_FORMAT_BC2_UNORM_SRGB = 75;
+ int DXGI_FORMAT_BC3_TYPELESS = 76;
+ int DXGI_FORMAT_BC3_UNORM = 77;
+ int DXGI_FORMAT_BC3_UNORM_SRGB = 78;
+ int DXGI_FORMAT_BC4_TYPELESS = 79;
+ int DXGI_FORMAT_BC4_UNORM = 80;
+ int DXGI_FORMAT_BC4_SNORM = 81;
+ int DXGI_FORMAT_BC5_TYPELESS = 82;
+ int DXGI_FORMAT_BC5_UNORM = 83;
+ int DXGI_FORMAT_BC5_SNORM = 84;
+ int DXGI_FORMAT_B5G6R5_UNORM = 85;
+ int DXGI_FORMAT_B5G5R5A1_UNORM = 86;
+ int DXGI_FORMAT_B8G8R8A8_UNORM = 87;
+ int DXGI_FORMAT_B8G8R8X8_UNORM = 88;
+ int DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89;
+ int DXGI_FORMAT_B8G8R8A8_TYPELESS = 90;
+ int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91;
+ int DXGI_FORMAT_B8G8R8X8_TYPELESS = 92;
+ int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93;
+ int DXGI_FORMAT_BC6H_TYPELESS = 94;
+ int DXGI_FORMAT_BC6H_UF16 = 95;
+ int DXGI_FORMAT_BC6H_SF16 = 96;
+ int DXGI_FORMAT_BC7_TYPELESS = 97;
+ int DXGI_FORMAT_BC7_UNORM = 98;
+ int DXGI_FORMAT_BC7_UNORM_SRGB = 99;
+ int DXGI_FORMAT_AYUV = 100;
+ int DXGI_FORMAT_Y410 = 101;
+ int DXGI_FORMAT_Y416 = 102;
+ int DXGI_FORMAT_NV12 = 103;
+ int DXGI_FORMAT_P010 = 104;
+ int DXGI_FORMAT_P016 = 105;
+ int DXGI_FORMAT_420_OPAQUE = 106;
+ int DXGI_FORMAT_YUY2 = 107;
+ int DXGI_FORMAT_Y210 = 108;
+ int DXGI_FORMAT_Y216 = 109;
+ int DXGI_FORMAT_NV11 = 110;
+ int DXGI_FORMAT_AI44 = 111;
+ int DXGI_FORMAT_IA44 = 112;
+ int DXGI_FORMAT_P8 = 113;
+ int DXGI_FORMAT_A8P8 = 114;
+ int DXGI_FORMAT_B4G4R4A4_UNORM = 115;
+ int DXGI_FORMAT_P208 = 130;
+ int DXGI_FORMAT_V208 = 131;
+ int DXGI_FORMAT_V408 = 132;
+ int DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189;
+ int DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190;
+ int DXGI_FORMAT_FORCE_UINT = 0xffffffff;
+}
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXT10Header.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXT10Header.java
new file mode 100644
index 00000000..e052df81
--- /dev/null
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DXT10Header.java
@@ -0,0 +1,45 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * @see DDS_HEADER_DXT10 structure
+ */
+final class DXT10Header {
+ final int dxgiFormat;
+ final int resourceDimension;
+ final int miscFlag;
+ final int arraySize;
+ final int miscFlags2;
+
+ private final DDSType type;
+
+ private DXT10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
+ type = DDSType.fromDXGIFormat(dxgiFormat); // Validates dxgiFormat
+ if (resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D) {
+ throw new IllegalArgumentException(String.format("Resource dimension %d is not supported, expected: %d",
+ resourceDimension, DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D));
+ }
+
+ this.dxgiFormat = dxgiFormat;
+ this.resourceDimension = resourceDimension;
+ this.miscFlag = miscFlag;
+ this.arraySize = arraySize;
+ this.miscFlags2 = miscFlags2;
+ }
+
+ static DXT10Header read(ImageInputStream inputStream) throws IOException {
+ int dxgiFormat = inputStream.readInt();
+ int resourceDimension = inputStream.readInt();
+ int miscFlag = inputStream.readInt();
+ int arraySize = inputStream.readInt();
+ int miscFlags2 = inputStream.readInt();
+
+ return new DXT10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
+ }
+
+ DDSType getType() {
+ return type;
+ }
+}
diff --git a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadataTest.java b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadataTest.java
new file mode 100644
index 00000000..41840da4
--- /dev/null
+++ b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadataTest.java
@@ -0,0 +1,101 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.image.BufferedImage;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.NodeList;
+
+class DDSImageMetadataTest {
+ @Test
+ void standardMetadataDXT1() {
+ DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.DXT1);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
+ assertEquals(1, compressionTypeNames.getLength());
+ IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
+ assertEquals("DXT1", compressionTypeName.getAttribute("value"));
+
+ NodeList losslesses = tree.getElementsByTagName("Lossless");
+ assertEquals(1, losslesses.getLength());
+ IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
+ assertEquals("FALSE", lossless.getAttribute("value"));
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("nonpremultiplied", alpha.getAttribute("value"));
+ }
+
+ @Test
+ void standardMetadataA8R8G8B8() {
+ DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.A8R8G8B8);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressions = tree.getElementsByTagName("Compression");
+ assertEquals(0, compressions.getLength());
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("nonpremultiplied", alpha.getAttribute("value"));
+ }
+
+ @Test
+ void standardMetadataX8R8G8B8() {
+ DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X8R8G8B8);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressions = tree.getElementsByTagName("Compression");
+ assertEquals(0, compressions.getLength());
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("8 8 8 0", bitsPerSample.getAttribute("value")); // Or just 8 8 8?
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("none", alpha.getAttribute("value"));
+ }
+
+ @Test
+ void standardMetadataX1R5G5B5() {
+ DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X1R5G5B5);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressions = tree.getElementsByTagName("Compression");
+ assertEquals(0, compressions.getLength());
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("5 5 5 0", bitsPerSample.getAttribute("value")); // Or just 5 5 5?
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("none", alpha.getAttribute("value"));
+ }
+
+ private static DDSImageMetadata createDDSImageMetadata(int bufferedImageType, DDSType ddsType) {
+ return new DDSImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(bufferedImageType), ddsType);
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderTest.java b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderTest.java
index 5b4c347b..c8662a12 100644
--- a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderTest.java
+++ b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderTest.java
@@ -30,14 +30,27 @@
package com.twelvemonkeys.imageio.plugins.dds;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
import java.awt.Dimension;
+import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.NodeList;
+
public class DDSImageReaderTest extends ImageReaderAbstractTest {
@Override
protected ImageReaderSpi createProvider() {
@@ -110,4 +123,67 @@ public class DDSImageReaderTest extends ImageReaderAbstractTest
protected List getMIMETypes() {
return Collections.singletonList("image/vnd-ms.dds");
}
+
+ @Test
+ void metadataDXT5() throws IOException {
+ ImageReader reader = createReader();
+
+ try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_DXT5.dds"))) {
+ reader.setInput(inputStream);
+
+ IIOMetadata metadata = reader.getImageMetadata(0);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
+ assertEquals(1, compressionTypeNames.getLength());
+ IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
+ assertEquals("DXT5", compressionTypeName.getAttribute("value"));
+
+ NodeList losslesses = tree.getElementsByTagName("Lossless");
+ assertEquals(1, losslesses.getLength());
+ IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
+ assertEquals("FALSE", lossless.getAttribute("value"));
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("nonpremultiplied", alpha.getAttribute("value"));
+ }
+ finally {
+ reader.dispose();
+ }
+ }
+
+ @Test
+ void metadataRGB565() throws IOException {
+ ImageReader reader = createReader();
+
+ try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_R5G6B5.dds"))) {
+ reader.setInput(inputStream);
+
+ IIOMetadata metadata = reader.getImageMetadata(0);
+ IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ NodeList compressions = tree.getElementsByTagName("Compression");
+ assertEquals(0, compressions.getLength());
+
+ NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
+ assertEquals(1, bitsPerSamples.getLength());
+ IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
+ assertEquals("5 6 5 0", bitsPerSample.getAttribute("value")); // or "5 6 5"
+
+ NodeList alphas = tree.getElementsByTagName("Alpha");
+ assertEquals(1, alphas.getLength());
+ IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
+ assertEquals("none", alpha.getAttribute("value"));
+ }
+ finally {
+ reader.dispose();
+ }
+ }
}
diff --git a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParamTest.java b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParamTest.java
new file mode 100644
index 00000000..beeb2a59
--- /dev/null
+++ b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriteParamTest.java
@@ -0,0 +1,55 @@
+package com.twelvemonkeys.imageio.plugins.dds;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Arrays;
+
+import javax.imageio.ImageWriteParam;
+
+import org.junit.jupiter.api.Test;
+
+class DDSImageWriteParamTest {
+ @Test
+ void defaultParam() {
+ DDSImageWriteParam param = new DDSImageWriteParam();
+ assertEquals(DDSImageWriteParam.DEFAULT_TYPE, param.type());
+ }
+
+ @Test
+ void compressionTypes() {
+ DDSImageWriteParam param = new DDSImageWriteParam();
+
+ String[] compressionTypes = param.getCompressionTypes();
+ DDSType[] values = Arrays.stream(DDSType.values())
+ .filter(DDSType::isBlockCompression)
+ .toArray(DDSType[]::new);
+
+ assertEquals(values.length + 1, compressionTypes.length);
+
+ for (int i = 0; i < values.length; i++) {
+ DDSType type = values[i];
+ assertEquals(type.name(), compressionTypes[i + 1]);
+ }
+
+ assertEquals("None", compressionTypes[0]);
+ }
+
+ @Test
+ void setCompression() {
+ DDSImageWriteParam param = new DDSImageWriteParam();
+
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+
+ String[] compressionTypes = param.getCompressionTypes();
+ for (String compressionType : compressionTypes) {
+ param.setCompressionType(compressionType);
+ assertEquals(compressionType, param.getCompressionType());
+
+ if (!"None".equals(compressionType)) {
+ DDSType type = DDSType.valueOf(compressionType);
+
+ assertEquals(type, param.type());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterTest.java b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterTest.java
index 2f38e086..4903c07f 100644
--- a/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterTest.java
+++ b/imageio/imageio-dds/src/test/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageWriterTest.java
@@ -1,13 +1,38 @@
package com.twelvemonkeys.imageio.plugins.dds;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
+import javax.imageio.IIOException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+
import java.awt.image.BufferedImage;
-import java.util.Collections;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
-public class DDSImageWriterTest extends ImageWriterAbstractTest {
+import org.junit.jupiter.api.Test;
+
+class DDSImageWriterTest extends ImageWriterAbstractTest {
@Override
protected ImageWriterSpi createProvider() {
return new DDSImageWriterSpi();
@@ -15,8 +40,147 @@ public class DDSImageWriterTest extends ImageWriterAbstractTest
@Override
protected List getTestData() {
- return Collections.singletonList(
- new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB_PRE)
+ return Arrays.asList(
+ new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB),
+ new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB),
+ new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB),
+ new BufferedImage(32, 32, BufferedImage.TYPE_4BYTE_ABGR),
+ new BufferedImage(16, 16, BufferedImage.TYPE_3BYTE_BGR)
);
}
+
+ @Test
+ void writeRasters() throws IOException {
+ ImageWriter writer = createWriter();
+
+ assertTrue(writer.canWriteRasters());
+
+ // Full tests in super class
+ }
+
+ @Test
+ void writeMipmap() throws IOException {
+ ImageWriter writer = createWriter();
+
+ try {
+ assertTrue(writer.canWriteSequence());
+
+ List testData = getTestData();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int previousSize = 0;
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+ writer.prepareWriteSequence(null);
+ ImageWriteParam param = writer.getDefaultWriteParam();
+
+ assertTrue(buffer.size() > previousSize);
+ previousSize = buffer.size();
+
+ for (BufferedImage image : testData) {
+ writer.writeToSequence(new IIOImage(drawSomething(image), null, null), param);
+ }
+
+ writer.endWriteSequence();
+ assertTrue(buffer.size() > previousSize, "No image data written");
+ }
+ catch (IOException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+
+ // Verify that we can read the file back...
+ ImageReader reader = ImageIO.getImageReader(writer);
+
+ try (ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
+ stream.seek(0);
+ reader.setInput(stream);
+
+ assertEquals(testData.size(), reader.getNumImages(false));
+
+ for (int i = 0; i < testData.size(); i++) {
+ BufferedImage image = reader.read(i, null);
+
+ assertNotNull(image);
+ assertEquals(testData.get(i).getWidth(), image.getWidth());
+ assertEquals(testData.get(i).getHeight(), image.getHeight());
+ }
+ }
+ finally {
+ reader.dispose();
+ }
+
+ }
+ finally {
+ writer.dispose();
+ }
+ }
+
+ @Test
+ void writeMipmapDifferentCompression() throws IOException {
+ ImageWriter writer = createWriter();
+
+ try {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ IIOWriteWarningListener listener = mock();
+ writer.addIIOWriteWarningListener(listener);
+ writer.setOutput(stream);
+
+ writer.prepareWriteSequence(null);
+ ImageWriteParam param = writer.getDefaultWriteParam();
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ param.setCompressionType("DXT2");
+
+ // Write first with DXT2
+ List testData = getTestData();
+ writer.writeToSequence(new IIOImage(drawSomething(testData.get(0)), null, null), param);
+
+ // Repeat with different type
+ IIOImage image = new IIOImage(drawSomething(testData.get(1)), null, null);
+ param.setCompressionType("DXT1");
+
+ writer.writeToSequence(image, param);
+
+ // Verify warning is issued
+ verify(listener).warningOccurred(eq(writer), eq(1), anyString());
+ verifyNoMoreInteractions(listener);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+ }
+ finally {
+ writer.dispose();
+ }
+ }
+
+ @Test
+ void writeMipmapUnexpectedSize() throws IOException {
+ ImageWriter writer = createWriter();
+
+ try {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ writer.prepareWriteSequence(null);
+ ImageWriteParam param = writer.getDefaultWriteParam();
+ BufferedImage testData = getTestData().get(0);
+
+ IIOImage image = new IIOImage(drawSomething(testData), null, null);
+ writer.writeToSequence(image, param);
+
+ // Repeat with same size... boom.
+ assertThrows(IIOException.class, () -> writer.writeToSequence(image, param));
+ }
+ catch (IOException e) {
+ throw new AssertionError(e.getMessage(), e);
+ }
+ }
+ finally {
+ writer.dispose();
+ }
+ }
}
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/TupleType.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/TupleType.java
index 24c11e23..b8a91302 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/TupleType.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/TupleType.java
@@ -111,6 +111,7 @@ enum TupleType {
static TupleType forPAM(Raster raster) {
SampleModel sampleModel = raster.getSampleModel();
+
switch (sampleModel.getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
@@ -145,8 +146,12 @@ enum TupleType {
return TupleType.RGB;
}
else if (bands == 4) {
+ // Ambiguous, could also be CMYK...
return TupleType.RGB_ALPHA;
}
+ else if (bands == 5) {
+ return TupleType.CMYK_ALPHA;
+ }
// ...else fall through...
}
@@ -154,7 +159,7 @@ enum TupleType {
}
static TupleType forPAM(ImageTypeSpecifier type) {
- // Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
+ // Support only 1 bit b/w, 8-16 bit gray, 8-16 bit/sample RGB and 8-16 bit/sample CMYK
switch (type.getBufferedImageType()) {
// 1 bit b/w or b/w + a
case BufferedImage.TYPE_BYTE_BINARY: