More clean-up: Removed optional flags from param, header size validation, metadata now reports compresion as lossy

This commit is contained in:
Harald Kuhr
2026-03-11 14:51:01 +01:00
parent 2a0b15f33f
commit dc59c66209
9 changed files with 50 additions and 52 deletions

View File

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

View File

@@ -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.

View File

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

View File

@@ -13,15 +13,11 @@ import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.RGB_16_ORDER;
/**
* A designated class to encode image data to binary.
* <p>
* References:
* <p>
* [1] <a href="https://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/">GPU DXT Decompression</a>.
* [2] <a href="https://sv-journal.org/2014-1/06/en/index.php">TEXTURE COMPRESSION TECHNIQUES</a>.
* [3] <a href="https://mrelusive.com/publications/papers/Real-Time-Dxt-Compression.pdf">Real-Time DXT Compression by J.M.P. van Waveren</a>
* [4] <a href="https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.pdf">Khronos Data Format Specification v1.4 by Andrew Garrard</a>
* </p>
* </p>
*
* @see <a href="https://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/">GPU DXT Decompression</a>.
* @see <a href="https://sv-journal.org/2014-1/06/en/index.php">TEXTURE COMPRESSION TECHNIQUES</a>.
* @see <a href="https://mrelusive.com/publications/papers/Real-Time-Dxt-Compression.pdf">Real-Time DXT Compression by J.M.P. van Waveren</a>
* @see <a href="https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.pdf">Khronos Data Format Specification v1.4 by Andrew Garrard</a>
*/
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:

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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