Using new sequence support in DDSImageWriter

+ some minor bonus clean-up
This commit is contained in:
Harald Kuhr
2026-03-13 11:40:06 +01:00
committed by Harald Kuhr
parent 47a26651b4
commit 456749ded8
4 changed files with 43 additions and 52 deletions

View File

@@ -47,9 +47,12 @@ import java.awt.Dimension;
import java.io.IOException;
import java.util.Arrays;
/**
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header">DDS_HEADER structure</a>
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide">Programming Guide for DDS</a>
*/
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) {

View File

@@ -77,5 +77,4 @@ final class DDSImageMetadata extends StandardImageMetadataSupport {
return count;
}
}

View File

@@ -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());
}

View File

@@ -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"))));
}
}