From 456749ded8081d451ed8faf6f2f2c3939e1fa8bf Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 13 Mar 2026 11:40:06 +0100 Subject: [PATCH] Using new sequence support in DDSImageWriter + some minor bonus clean-up --- .../imageio/plugins/dds/DDSHeader.java | 30 ++++------ .../imageio/plugins/dds/DDSImageMetadata.java | 1 - .../imageio/plugins/dds/DDSImageReader.java | 7 +++ .../imageio/plugins/dds/DDSImageWriter.java | 57 ++++++++----------- 4 files changed, 43 insertions(+), 52 deletions(-) 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 a9a7898a..684d5033 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 @@ -47,9 +47,12 @@ import java.awt.Dimension; import java.io.IOException; import java.util.Arrays; +/** + * @see DDS_HEADER structure + * @see Programming Guide for DDS + */ final class DDSHeader { - // https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide private int flags; private int mipMapCount; @@ -69,26 +72,16 @@ final class DDSHeader { static DDSHeader read(final ImageInputStream imageInput) throws IOException { DDSHeader header = new DDSHeader(); - // Read MAGIC bytes [0,3] - 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)); - } - - // DDS_HEADER structure - // https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header int dwSize = imageInput.readInt(); // [4,7] if (dwSize != DDS.HEADER_SIZE) { throw new IIOException(String.format("Invalid DDS header size (expected %d): %d", DDS.HEADER_SIZE, dwSize)); } - // Verify setFlags + // Verify flags header.flags = imageInput.readInt(); // [8,11] - if (!header.getFlag(DDS.FLAG_CAPS - | DDS.FLAG_HEIGHT - | DDS.FLAG_WIDTH - | DDS.FLAG_PIXELFORMAT)) { - throw new IIOException("Required DDS Flag missing in header: " + Integer.toBinaryString(header.flags)); + if (!header.hasFlag(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT)) { + // NOTE: The Microsoft DDS documentation mention that readers should not rely on these flags... + throw new IIOException("Required DDS flag missing in header: " + Integer.toBinaryString(header.flags)); } // Read Height & Width @@ -109,7 +102,7 @@ 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)); + throw new IIOException(String.format("Invalid DDS pixel format structure size (expected %d): %d", DDS.PIXELFORMAT_SIZE, dwSize)); } header.pixelFormatFlags = imageInput.readInt(); // [80,83] @@ -128,6 +121,7 @@ final class DDSHeader { int dwReserved2 = imageInput.readInt(); // [124,127] if (header.fourCC == DDSType.DXT10.fourCC()) { + // If DXT10, the DXT10 header will follow immediately header.dxt10Header = DXT10Header.read(imageInput); } @@ -146,8 +140,8 @@ final class DDSHeader { } } - private boolean getFlag(int mask) { - return (flags & mask) != 0; + private boolean hasFlag(int mask) { + return (flags & mask) == mask; } int getWidth(int imageIndex) { 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 ff77fef9..1e37cc83 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 @@ -77,5 +77,4 @@ final class DDSImageMetadata extends StandardImageMetadataSupport { 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 bef6f42e..1a7389ed 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 @@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.plugins.dds; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import javax.imageio.IIOException; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; @@ -167,6 +168,12 @@ public final class DDSImageReader extends ImageReaderBase { private void readHeader() throws IOException { if (header == null) { imageInput.setByteOrder(ByteOrder.LITTLE_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)); + } + header = DDSHeader.read(imageInput); imageInput.flushBefore(imageInput.getStreamPosition()); } 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 690fa371..31fe832d 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 @@ -2,6 +2,7 @@ package com.twelvemonkeys.imageio.plugins.dds; import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.util.SequenceSupport; import javax.imageio.IIOException; import javax.imageio.IIOImage; @@ -29,9 +30,9 @@ import java.nio.file.Paths; */ class DDSImageWriter extends ImageWriterBase { - private long startPos; - // TODO: Create a SequenceSupport class that handles sequence prepare/write/end - private int mipmapIndex = -1; + private final SequenceSupport mipmapSequence = new SequenceSupport(); + + private long headerStartPos; private DDSType mipmapType; private Dimension mipmapDimension; @@ -46,7 +47,8 @@ class DDSImageWriter extends ImageWriterBase { @Override protected void resetMembers() { - mipmapIndex = -1; + headerStartPos = 0; + mipmapSequence.reset(); mipmapType = null; mipmapDimension = null; } @@ -64,30 +66,22 @@ class DDSImageWriter extends ImageWriterBase { @Override public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException { assertOutput(); + mipmapSequence.start(); - 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(); + + headerStartPos = imageOutput.getStreamPosition(); } @Override public void endWriteSequence() throws IOException { - assertOutput(); + int mipmapCount = mipmapSequence.end(); - if (mipmapIndex < 0) { - throw new IllegalStateException("prepareWriteSequence not called"); - } + // Go back and update header + updateHeader(mipmapCount); - // Go back and update hader - updateHeader(mipmapIndex); - - mipmapIndex = -1; mipmapType = null; mipmapDimension = null; @@ -103,13 +97,12 @@ class DDSImageWriter extends ImageWriterBase { @Override public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException { - if (mipmapIndex < 0) { - throw new IllegalStateException("prepareWriteSequence not called"); - } + int mipmapIndex = mipmapSequence.advance(); Raster raster = getRaster(image); ensureImageChannels(raster); ensureTextureDimension(raster); + mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight()); DDSImageWriteParam ddsParam = param instanceof DDSImageWriteParam ? ((DDSImageWriteParam) param) @@ -120,7 +113,7 @@ class DDSImageWriter extends ImageWriterBase { mipmapType = type; } else if (type != mipmapType) { - processWarningOccurred(mipmapIndex, "All images in DDS MipMap must use same pixel format and compression"); + 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"); @@ -140,9 +133,6 @@ class DDSImageWriter extends ImageWriterBase { processImageProgress(100f); processImageComplete(); - - mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight()); - mipmapIndex++; } private static Raster getRaster(IIOImage image) throws IIOException { @@ -210,7 +200,7 @@ class DDSImageWriter extends ImageWriterBase { //dwDepth imageOutput.writeInt(0); //dwMipmapCount - imageOutput.writeInt(1); + imageOutput.writeInt(1); // Should probably write 0 here for non-mipmap? //reserved imageOutput.write(new byte[44]); //pixFmt @@ -230,7 +220,7 @@ class DDSImageWriter extends ImageWriterBase { } long streamPosition = imageOutput.getStreamPosition(); - imageOutput.seek(startPos + 8); // Seek back to start + 4 magic + 4 header size + imageOutput.seek(headerStartPos + 4); // Seek back to header start, skip 4 byte header size int flags = imageOutput.readInt(); imageOutput.seek(imageOutput.getStreamPosition() - 4); @@ -268,15 +258,14 @@ class DDSImageWriter extends ImageWriterBase { //dwRGBBitCount imageOutput.writeInt(type.blockSize() * 8); // TODO: Is bitcount always a multiple of 8? - int[] mask = type.rgbaMasks; //dwRBitMask - imageOutput.writeInt(mask[0]); + imageOutput.writeInt(type.rgbaMasks[0]); //dwGBitMask - imageOutput.writeInt(mask[1]); + imageOutput.writeInt(type.rgbaMasks[1]); //dwBBitMask - imageOutput.writeInt(mask[2]); + imageOutput.writeInt(type.rgbaMasks[2]); //dwABitMask - imageOutput.writeInt(mask[3]); + imageOutput.writeInt(type.rgbaMasks[3]); } else { //write 5 zero integers as fourCC is used @@ -302,7 +291,8 @@ class DDSImageWriter extends ImageWriterBase { imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC); } else { - imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (type.rgbaMasks != null ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0)); + imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB + | (type.rgbaMasks != null && type.rgbaMasks[3] != 0 ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0)); } } @@ -334,6 +324,7 @@ class DDSImageWriter extends ImageWriterBase { 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")))); } }