From dc59c66209f00cc9f4c5874e27074ba4fb0fb792 Mon Sep 17 00:00:00 2001
From: Harald Kuhr
Date: Wed, 11 Mar 2026 14:51:01 +0100
Subject: [PATCH] More clean-up: Removed optional flags from param, header size
validation, metadata now reports compresion as lossy
---
.../twelvemonkeys/imageio/util/IIOUtil.java | 4 +--
.../imageio/plugins/dds/DDS.java | 1 +
.../imageio/plugins/dds/DDSHeader.java | 15 ++++++++---
.../plugins/dds/DDSImageDataEncoder.java | 16 +++++-------
.../imageio/plugins/dds/DDSImageMetadata.java | 1 +
.../imageio/plugins/dds/DDSImageReader.java | 9 ++++++-
.../plugins/dds/DDSImageWriteParam.java | 26 -------------------
.../imageio/plugins/dds/DDSImageWriter.java | 17 ++++++++----
.../plugins/dds/DDSImageMetadataTest.java | 13 ++++++----
9 files changed, 50 insertions(+), 52 deletions(-)
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 912a8910..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
@@ -72,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));
}
@@ -86,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));
}
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 713fbb71..126956cf 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
@@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.plugins.dds;
interface DDS {
int MAGIC = ('D' << 24) + ('D' << 16) + ('S' << 8) + ' '; // Big-Endian
int HEADER_SIZE = 124;
+ int PIXELFORMAT_SIZE = 32;
// Header Flags
int FLAG_CAPS = 1; // Required in every .dds file.
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 dee8e985..4a315939 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
@@ -111,6 +111,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]
@@ -243,10 +246,12 @@ final class DDSHeader {
@Override
public String toString() {
return "DDSHeader{" +
- "flags=" + flags +
+ "flags=" + Integer.toBinaryString(flags) +
", mipMapCount=" + mipMapCount +
- ", dimensions=" + Arrays.toString(dimensions) +
- ", pixelFormatFlags=" + pixelFormatFlags +
+ ", dimensions=" + Arrays.toString(Arrays.stream(dimensions)
+ .map(DDSHeader::dimensionToString)
+ .toArray(String[]::new)) +
+ ", pixelFormatFlags=" + Integer.toBinaryString(pixelFormatFlags) +
", fourCC=" + fourCC +
", bitCount=" + bitCount +
", redMask=" + redMask +
@@ -255,4 +260,8 @@ final class DDSHeader {
", alphaMask=" + 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 dadd26d5..acf242f2 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
@@ -13,15 +13,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() {}
@@ -32,7 +28,7 @@ class DDSImageDataEncoder {
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, BlockCompression compression) throws IOException {
- // TODO: compression == null for custom RGB data?
+ // TODO: Support compression == null for uncompressed RGB(A/X) data?
switch (compression) {
case BC1:
diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
index 575795b5..ff77fef9 100755
--- a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
+++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageMetadata.java
@@ -39,6 +39,7 @@ 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")
);
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 b74bfc65..474b7ff5 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
@@ -101,6 +101,11 @@ public final class DDSImageReader extends ImageReaderBase {
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);
}
@@ -161,9 +166,11 @@ public final class DDSImageReader extends ImageReaderBase {
private void readHeader() throws IOException {
if (header == null) {
- imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); // TODO: Move to setInput?
header = DDSHeader.read(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
+
+ System.out.println("header = " + header);
}
imageInput.seek(imageInput.getFlushedPosition());
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
index 33eea204..18ec5a53 100644
--- 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
@@ -22,38 +22,12 @@ public final class DDSImageWriteParam extends ImageWriteParam {
return compressionTypes.toArray(new String[0]);
}
- private int optionalBitFlags;
private boolean writeDXT10;
DDSImageWriteParam() {
canWriteCompressed = true;
compressionTypes = COMPRESSION_TYPES;
compressionType = DEFAULT_TYPE.name();
- setLinearSize();
- }
-
- // TODO: Set this always for compressed images?
- public void setLinearSize() {
- optionalBitFlags |= DDS.FLAG_LINEARSIZE;
- }
-
- public void clearLinearSize() {
- optionalBitFlags &= ~DDS.FLAG_LINEARSIZE;
- }
-
- // TODO: Set this always for uncompressed images?
- public void setPitch() {
- optionalBitFlags |= DDS.FLAG_PITCH;
- }
-
- public void clearPitch() {
- optionalBitFlags &= ~DDS.FLAG_PITCH;
- }
-
- // TODO: Other flags?
-
- public int optionalBitFlags() {
- return optionalBitFlags;
}
public void setWriteDX10() {
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 87beed3b..22f50b46 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
@@ -35,6 +35,10 @@ class DDSImageWriter extends ImageWriterBase {
return new DDSImageWriteParam();
}
+ // TODO: Suppport MipMaps using sequence methods
+ // This involves seeking backwards, updating the mipmap flag and mipmapcount in the header... :-/
+ // + ensuring that each level is half the size of the previous, but still a multiple of 4...
+
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
assertOutput();
@@ -52,7 +56,7 @@ class DDSImageWriter extends ImageWriterBase {
imageOutput.writeInt(DDS.MAGIC);
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
- writeHeader(image, ddsParam.type(), ddsParam.isWriteDXT10(), ddsParam.optionalBitFlags());
+ writeHeader(image, ddsParam.type(), ddsParam.isWriteDXT10());
if (ddsParam.isWriteDXT10()) {
writeDXT10Header(ddsParam.getDxgiFormat());
}
@@ -96,9 +100,10 @@ class DDSImageWriter extends ImageWriterBase {
}
}
- private void writeHeader(IIOImage image, DDSType type, boolean writeDXT10, int optionalBitFlags) throws IOException {
+ private void writeHeader(IIOImage image, 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 | optionalBitFlags);
+ 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);
RenderedImage renderedImage = image.getRenderedImage();
int height = renderedImage.getHeight();
@@ -182,7 +187,8 @@ class DDSImageWriter extends ImageWriterBase {
private void writePixelFormatFlags(DDSType type, boolean writeDXT10) throws IOException {
if (writeDXT10 || type.isFourCC()) {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
- } else {
+ }
+ else {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (type.rgbaMasks != null ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
}
}
@@ -190,7 +196,8 @@ class DDSImageWriter extends ImageWriterBase {
private void writePitchOrLinearSize(int height, int width, DDSType type) throws IOException {
if (type.isBlockCompression()) {
imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * type.blockSize());
- } else {
+ }
+ else {
imageOutput.writeInt(width * type.blockSize());
}
}
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
index 4e2a6993..12ccff12 100644
--- 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
@@ -17,13 +17,16 @@ class DDSImageMetadataTest {
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.DXT1);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
- NodeList compressions = tree.getElementsByTagName("CompressionTypeName");
- assertEquals(1, compressions.getLength());
+ NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
+ assertEquals(1, compressionTypeNames.getLength());
+ IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
+ assertEquals("DXT1", compressionTypeName.getAttribute("value"));
- IIOMetadataNode compression = (IIOMetadataNode) compressions.item(0);
- assertEquals("DXT1", compression.getAttribute("value"));
+ NodeList losslesses = tree.getElementsByTagName("Lossless");
+ assertEquals(1, losslesses.getLength());
+ IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
+ assertEquals("FALSE", lossless.getAttribute("value"));
- // TODO: This should probably not have alpha...
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);