-
-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Microsoft Direct DrawSurface (DDS) support. (#1021)
* Basic wrapper around DDSReader. Nasty hack to get initial plugin working... Mark and Reset the imageinput stream when reading the header. Then read the whole buffer into DDSReader and generate a fixed BufferedImage ignoring the getDestination() BufferedImage. * Read header (DDSHeader) and pass into DDSReader Remove header methods using buffer offset addressing. Switch endian for DX1-DX5 types * Fix pixel order to ARGB * Push ImageInputStream into DDSReader Unable to determine buffer length (so as a hack I over allocate buffer and read) ``` byte[] buffer = new byte[width * height * 4]; int len = imageInput.read(buffer); ``` Added test files for all supported formats. * Added processImageStarted and stubbed out processImageProgress and processReadAborted Added all DDS format test cases to getTestData * Remove offset and use imageInput.readFully Reads next image calculating/updating width/height from mipmapLevel. Probably will never get used as we only want the largest image returned. * Code cleanup and added exception handling. * Use Enum DDSType instead of int values. Pass imageIndex into mipmap. * Update imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java Improve IIOException "Invalid DDS header size" Co-authored-by: Harald Kuhr <[email protected]> * Update imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java Improve Magic IIOException Co-authored-by: Harald Kuhr <[email protected]> * Delete .run directory * Format tabs -> 4 spaces * Convert 4byte array into %08x string format. * Don't need to expose classes outside the package DDSHeader, DDSReader, DDSType * (fix) wrong public... * Move setByteOrder to DDSImageReader.readHeader * Use imageIndex to calculate height/width and buffer offset. * Delete .gitignore * Revert "Delete .gitignore" This reverts commit 71a4e73. * Undelete/Restore .gitignore * Simplify String.format into one. * Override failing tests and ignore. * Revert formatting on PNGImageReaderTest.java --------- Co-authored-by: Harald Kuhr <[email protected]>
- Loading branch information
Showing
47 changed files
with
1,177 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?xml version="1.0"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>com.twelvemonkeys.imageio</groupId> | ||
<artifactId>imageio</artifactId> | ||
<version>3.11.1-SNAPSHOT</version> | ||
</parent> | ||
<artifactId>imageio-dds</artifactId> | ||
<name>TwelveMonkeys :: ImageIO :: DDS plugin</name> | ||
<description> | ||
ImageIO plugin for Microsoft Direct DrawSurface (DDS). | ||
</description> | ||
|
||
<properties> | ||
<project.jpms.module.name>com.twelvemonkeys.imageio.dds</project.jpms.module.name> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.twelvemonkeys.imageio</groupId> | ||
<artifactId>imageio-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.twelvemonkeys.imageio</groupId> | ||
<artifactId>imageio-core</artifactId> | ||
<type>test-jar</type> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.twelvemonkeys.imageio</groupId> | ||
<artifactId>imageio-metadata</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.felix</groupId> | ||
<artifactId>maven-bundle-plugin</artifactId> | ||
<configuration> | ||
<instructions> | ||
<Provide-Capability> | ||
osgi.serviceloader; | ||
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi | ||
</Provide-Capability> | ||
</instructions> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
15 changes: 15 additions & 0 deletions
15
imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.twelvemonkeys.imageio.plugins.dds; | ||
|
||
interface DDS { | ||
byte[] MAGIC = new byte[]{'D', 'D', 'S', ' '}; | ||
int HEADER_SIZE = 124; | ||
|
||
int FLAG_CAPS = 0x1; // Required in every .dds file. | ||
int FLAG_HEIGHT = 0x2; // Required in every .dds file. | ||
int FLAG_WIDTH = 0x4; // Required in every .dds file. | ||
int FLAG_PITCH = 0x8; // Required when pitch is provided for an uncompressed texture. | ||
int FLAG_PIXELFORMAT = 0x1000; // Required in every .dds file. | ||
int FLAG_MIPMAPCOUNT = 0x20000; // Required in a mipmapped texture. | ||
int FLAG_LINEARSIZE = 0x80000; // Required when pitch is provided for a compressed texture. | ||
int FLAG_DEPTH = 0x800000; // Required in a depth texture. | ||
} |
149 changes: 149 additions & 0 deletions
149
imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package com.twelvemonkeys.imageio.plugins.dds; | ||
|
||
import javax.imageio.IIOException; | ||
import javax.imageio.stream.ImageInputStream; | ||
import java.awt.Dimension; | ||
import java.io.IOException; | ||
import java.math.BigInteger; | ||
import java.util.Arrays; | ||
|
||
final class DDSHeader { | ||
|
||
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide | ||
private int flags; | ||
|
||
private int mipMapCount; | ||
private Dimension[] dimensions; | ||
|
||
private int pixelFormatFlags; | ||
private int fourCC; | ||
private int bitCount; | ||
private int redMask; | ||
private int greenMask; | ||
private int blueMask; | ||
private int alphaMask; | ||
|
||
public static DDSHeader read(final ImageInputStream imageInput) throws IOException { | ||
DDSHeader header = new DDSHeader(); | ||
|
||
// Read MAGIC bytes [0,3] | ||
byte[] magic = new byte[DDS.MAGIC.length]; | ||
imageInput.readFully(magic); | ||
if (!Arrays.equals(DDS.MAGIC, magic)) { | ||
throw new IIOException(String.format("Not a DDS file. Expected DDS magic %08x, read %08x", new BigInteger(1, DDS.MAGIC), new BigInteger(1, 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 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.toHexString(header.flags)); | ||
} | ||
|
||
// Read Height & Width | ||
int dwHeight = imageInput.readInt(); // [12,15] | ||
int dwWidth = imageInput.readInt(); // [16,19] | ||
|
||
int dwPitchOrLinearSize = imageInput.readInt(); // [20,23] | ||
int dwDepth = imageInput.readInt(); // [24,27] | ||
header.mipMapCount = imageInput.readInt(); // [28,31] | ||
|
||
// build dimensions list | ||
header.addDimensions(dwWidth, dwHeight); | ||
|
||
byte[] dwReserved1 = new byte[11 * 4]; // [32,75] | ||
imageInput.readFully(dwReserved1); | ||
|
||
// DDS_PIXELFORMAT structure | ||
int px_dwSize = imageInput.readInt(); // [76,79] | ||
|
||
header.pixelFormatFlags = imageInput.readInt(); // [80,83] | ||
header.fourCC = imageInput.readInt(); // [84,87] | ||
header.bitCount = imageInput.readInt(); // [88,91] | ||
header.redMask = imageInput.readInt(); // [92,95] | ||
header.greenMask = imageInput.readInt(); // [96,99] | ||
header.blueMask = imageInput.readInt(); // [100,103] | ||
header.alphaMask = imageInput.readInt(); // [104,107] | ||
|
||
int dwCaps = imageInput.readInt(); // [108,111] | ||
int dwCaps2 = imageInput.readInt(); // [112,115] | ||
int dwCaps3 = imageInput.readInt(); // [116,119] | ||
int dwCaps4 = imageInput.readInt(); // [120,123] | ||
|
||
int dwReserved2 = imageInput.readInt(); // [124,127] | ||
|
||
return header; | ||
} | ||
|
||
private void addDimensions(int width, int height) { | ||
dimensions = new Dimension[getMipMapCount()]; | ||
|
||
int w = width; | ||
int h = height; | ||
for (int i = 0; i < getMipMapCount(); i++) { | ||
dimensions[i] = new Dimension(w, h); | ||
w /= 2; | ||
h /= 2; | ||
} | ||
} | ||
|
||
private boolean getFlag(int mask) { | ||
return (flags & mask) != 0; | ||
} | ||
|
||
public int getWidth(int imageIndex) { | ||
int lim = dimensions[imageIndex].width; | ||
return (lim <= 0) ? 1 : lim; | ||
} | ||
|
||
public int getHeight(int imageIndex) { | ||
int lim = dimensions[imageIndex].height; | ||
return (lim <= 0) ? 1 : lim; | ||
} | ||
|
||
public int getMipMapCount() { | ||
// 0 = (unused) or 1 = (1 level), but still only one 'base' image | ||
return (mipMapCount == 0) ? 1 : mipMapCount; | ||
} | ||
|
||
public int getAlphaMask() { | ||
return alphaMask; | ||
} | ||
|
||
public int getBitCount() { | ||
return bitCount; | ||
} | ||
|
||
public int getBlueMask() { | ||
return blueMask; | ||
} | ||
|
||
public int getFlags() { | ||
return flags; | ||
} | ||
|
||
public int getFourCC() { | ||
return fourCC; | ||
} | ||
|
||
public int getGreenMask() { | ||
return greenMask; | ||
} | ||
|
||
public int getPixelFormatFlags() { | ||
return pixelFormatFlags; | ||
} | ||
|
||
public int getRedMask() { | ||
return redMask; | ||
} | ||
} |
148 changes: 148 additions & 0 deletions
148
imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package com.twelvemonkeys.imageio.plugins.dds; | ||
|
||
import com.twelvemonkeys.imageio.ImageReaderBase; | ||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; | ||
|
||
import javax.imageio.ImageIO; | ||
import javax.imageio.ImageReadParam; | ||
import javax.imageio.ImageTypeSpecifier; | ||
import javax.imageio.spi.ImageReaderSpi; | ||
import java.awt.image.BufferedImage; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.ByteOrder; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
public final class DDSImageReader extends ImageReaderBase { | ||
|
||
private DDSHeader header; | ||
|
||
public DDSImageReader(final ImageReaderSpi provider) { | ||
super(provider); | ||
} | ||
|
||
@Override | ||
protected void resetMembers() { | ||
header = null; | ||
} | ||
|
||
@Override | ||
public int getWidth(final int imageIndex) throws IOException { | ||
checkBounds(imageIndex); | ||
readHeader(); | ||
|
||
return header.getWidth(imageIndex); | ||
} | ||
|
||
@Override | ||
public int getHeight(int imageIndex) throws IOException { | ||
checkBounds(imageIndex); | ||
readHeader(); | ||
|
||
return header.getHeight(imageIndex); | ||
} | ||
|
||
@Override | ||
public int getNumImages(final boolean allowSearch) throws IOException { | ||
readHeader(); | ||
|
||
return header.getMipMapCount(); | ||
} | ||
|
||
@Override | ||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException { | ||
checkBounds(imageIndex); | ||
readHeader(); | ||
|
||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB); | ||
} | ||
|
||
@Override | ||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException { | ||
return Collections.singletonList(getRawImageType(imageIndex)).iterator(); | ||
} | ||
|
||
@Override | ||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { | ||
checkBounds(imageIndex); | ||
readHeader(); | ||
|
||
processImageStarted(imageIndex); | ||
|
||
DDSReader dds = new DDSReader(header); | ||
int[] pixels = dds.read(imageInput, imageIndex); | ||
|
||
int width = getWidth(imageIndex); | ||
int height = getHeight(imageIndex); | ||
|
||
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height); | ||
destination.setRGB(0, 0, width, height, pixels, 0, width); | ||
|
||
// TODO: break read into raster line and add progress and abort checks | ||
processImageProgress(100f); | ||
if (abortRequested()) { | ||
processReadAborted(); | ||
} | ||
|
||
processImageComplete(); | ||
|
||
return destination; | ||
} | ||
|
||
private void readHeader() throws IOException { | ||
if (header == null) { | ||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); | ||
header = DDSHeader.read(imageInput); | ||
|
||
imageInput.flushBefore(imageInput.getStreamPosition()); | ||
} | ||
|
||
imageInput.seek(imageInput.getFlushedPosition()); | ||
} | ||
|
||
public static void main(final String[] args) throws IOException { | ||
|
||
String parentDir = "imageio/imageio-dds/src/test/resources/dds"; | ||
|
||
List<File> testFiles = new ArrayList<>(); | ||
testFiles.add(new File(parentDir, "dds_A1R5G5B5.dds")); | ||
testFiles.add(new File(parentDir, "dds_A1R5G5B5_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_A4R4G4B4.dds")); | ||
testFiles.add(new File(parentDir, "dds_A4R4G4B4_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_A8B8G8R8.dds")); | ||
testFiles.add(new File(parentDir, "dds_A8B8G8R8_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_A8R8G8B8.dds")); | ||
testFiles.add(new File(parentDir, "dds_A8R8G8B8_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT1.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT1_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT2.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT2_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT3.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT3_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT4.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT4_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT5.dds")); | ||
testFiles.add(new File(parentDir, "dds_DXT5_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_R5G6B5.dds")); | ||
testFiles.add(new File(parentDir, "dds_R5G6B5_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_R8G8B8.dds")); | ||
testFiles.add(new File(parentDir, "dds_R8G8B8_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_X1R5G5B5.dds")); | ||
testFiles.add(new File(parentDir, "dds_X1R5G5B5_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_X4R4G4B4.dds")); | ||
testFiles.add(new File(parentDir, "dds_X4R4G4B4_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_X8B8G8R8.dds")); | ||
testFiles.add(new File(parentDir, "dds_X8B8G8R8_mipmap.dds")); | ||
testFiles.add(new File(parentDir, "dds_X8R8G8B8.dds")); | ||
testFiles.add(new File(parentDir, "dds_X8R8G8B8_mipmap.dds")); | ||
|
||
for (File file : testFiles) { | ||
BufferedImage image = ImageIO.read(file); | ||
showIt(image, file.getName()); | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.