mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-01 00:00:02 -04:00
DDS cleanup (#1262)
* Refactorings and code clean-up
* Major rework/standardization:
* DDSEncoderType, DX10DXGIFormat merged with DDSType for a single way to describe a DDS format
* Added constants for DXGI formats
* DDSImageWriteParam is now mutable and supports standard way of setting compression type
* DDSImageMetadata now supports more of the format
Performance:
* DDSReader now use seek() to jump to correct mipmap instead of reading all bytes
* DDSImageWriter now uses getTile(0, 0) instead of getData() for better performance
* Fix JavaDoc 🎉
* Sonar issues + roll back accidental check-in
* More clean-up: Removed optional flags from param, header size validation, metadata now reports compresion as lossy
* More clean-up: Now keeps stream byte order consistent (LE), support for Raster, more tests
* Mipmap support using ImageIO sequence API
* Added raster write test
+ fixed a small issue for PAM
* Sonar issues
This commit is contained in:
+3
-2
@@ -23,6 +23,7 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
|||||||
* {@link ImageTypeSpecifier}.
|
* {@link ImageTypeSpecifier}.
|
||||||
* Other values or overrides may be specified using the builder.
|
* Other values or overrides may be specified using the builder.
|
||||||
*
|
*
|
||||||
|
* @see <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html">Standard (Plug-in Neutral) Metadata Format Specification</a>
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
*/
|
*/
|
||||||
public class StandardImageMetadataSupport extends AbstractMetadata {
|
public class StandardImageMetadataSupport extends AbstractMetadata {
|
||||||
@@ -79,11 +80,11 @@ public class StandardImageMetadataSupport extends AbstractMetadata {
|
|||||||
textEntries = builder.textEntries;
|
textEntries = builder.textEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(ImageTypeSpecifier type) {
|
protected static Builder builder(ImageTypeSpecifier type) {
|
||||||
return new Builder(type);
|
return new Builder(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
protected static class Builder {
|
||||||
private final ImageTypeSpecifier type;
|
private final ImageTypeSpecifier type;
|
||||||
private ColorSpaceType colorSpaceType;
|
private ColorSpaceType colorSpaceType;
|
||||||
private boolean blackIsZero = true;
|
private boolean blackIsZero = true;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import com.twelvemonkeys.lang.Validate;
|
|||||||
|
|
||||||
import javax.imageio.IIOParam;
|
import javax.imageio.IIOParam;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.spi.IIOServiceProvider;
|
import javax.imageio.spi.IIOServiceProvider;
|
||||||
import javax.imageio.spi.ServiceRegistry;
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
@@ -45,7 +47,9 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
@@ -68,7 +72,7 @@ public final class IIOUtil {
|
|||||||
*/
|
*/
|
||||||
public static InputStream createStreamAdapter(final ImageInputStream pStream) {
|
public static InputStream createStreamAdapter(final ImageInputStream pStream) {
|
||||||
// TODO: Include stream start pos?
|
// 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));
|
return new BufferedInputStream(new IIOInputStreamAdapter(pStream));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +86,7 @@ public final class IIOUtil {
|
|||||||
*/
|
*/
|
||||||
public static InputStream createStreamAdapter(final ImageInputStream pStream, final long pLength) {
|
public static InputStream createStreamAdapter(final ImageInputStream pStream, final long pLength) {
|
||||||
// TODO: Include stream start pos?
|
// 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));
|
return new BufferedInputStream(new IIOInputStreamAdapter(pStream, pLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,4 +363,115 @@ public final class IIOUtil {
|
|||||||
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
|
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies all the standard param values from source to destination.
|
||||||
|
* <p>
|
||||||
|
* Typical use (in some imaginary {@code FooImageWriter} class):
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ImageWriteParam param = ...
|
||||||
|
* FooImageWriteparam fooParam = param instanceof FooImageWriteParam
|
||||||
|
* ? (FooImageWriteParam) param
|
||||||
|
* : copyStandardParams(param, getDefaultWriteParam());
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* May also be useful for {@code ImageReader}s that delegate reading to other plugins
|
||||||
|
* (like a TIFF plugin delegating JPEG format decoding to a {@code JPEGImageReader}).
|
||||||
|
*
|
||||||
|
* @param source the source parameter, may be {@code null}
|
||||||
|
* @param destination the destination parameter
|
||||||
|
* @return destination
|
||||||
|
*
|
||||||
|
* @param <T> the plugin specific subclass of {@code IIOParam}
|
||||||
|
*
|
||||||
|
* @throws NullPointerException if destination is {@code null}
|
||||||
|
*/
|
||||||
|
public static <T extends IIOParam> T copyStandardParams(IIOParam source, T destination) {
|
||||||
|
Objects.requireNonNull(destination);
|
||||||
|
Validate.isTrue(source != destination, "source must be different from destination");
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
copyIIOParams(source, destination);
|
||||||
|
|
||||||
|
// TODO: API & usage... Is it ever useful to copy from a read to a write param or vice versa?
|
||||||
|
// If not, maybe throw an IllegalArgumentException instead
|
||||||
|
|
||||||
|
if (source instanceof ImageReadParam && destination instanceof ImageReadParam) {
|
||||||
|
copyImageReadParams((ImageReadParam) source, (ImageReadParam) destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source instanceof ImageWriteParam && destination instanceof ImageWriteParam) {
|
||||||
|
copyImageWriteParams((ImageWriteParam) source, (ImageWriteParam) destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyImageWriteParams(ImageWriteParam source, ImageWriteParam destination) {
|
||||||
|
// TODO: Usage... It's very unlikely that compression settings of one plugin is compatible with another...
|
||||||
|
// Is the the below useful?
|
||||||
|
// Also, is it okay to just silently ignore settings from one format that isn't compatible with another?
|
||||||
|
|
||||||
|
// Quirky API, we can't query for compression mode, unless source.canWriteCompressed is true...
|
||||||
|
if (source.canWriteCompressed() && destination.canWriteCompressed()) {
|
||||||
|
int compressionMode = source.getCompressionMode();
|
||||||
|
destination.setCompressionMode(compressionMode);
|
||||||
|
|
||||||
|
if (compressionMode == ImageWriteParam.MODE_EXPLICIT
|
||||||
|
&& source.getCompressionType() != null
|
||||||
|
&& Arrays.asList(destination.getCompressionTypes()).contains(source.getCompressionType())) {
|
||||||
|
destination.setCompressionType(source.getCompressionType());
|
||||||
|
destination.setCompressionQuality(source.getCompressionQuality());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.canWriteProgressive() && destination.canWriteProgressive()) {
|
||||||
|
destination.setProgressiveMode(source.getProgressiveMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.canWriteTiles() && destination.canWriteTiles()) {
|
||||||
|
int tilingMode = source.getTilingMode();
|
||||||
|
destination.setTilingMode(tilingMode);
|
||||||
|
|
||||||
|
if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
|
||||||
|
// TODO: What if source can offset (and has offsets) and dest can't? Is it ok to just ignore the setting?
|
||||||
|
boolean canWriteOffsetTiles = source.canOffsetTiles() && destination.canOffsetTiles();
|
||||||
|
|
||||||
|
destination.setTiling(
|
||||||
|
source.getTileWidth(), source.getTileHeight(),
|
||||||
|
canWriteOffsetTiles ? source.getTileGridXOffset() : 0,
|
||||||
|
canWriteOffsetTiles ? source.getTileGridYOffset() : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyImageReadParams(ImageReadParam source, ImageReadParam destination) {
|
||||||
|
destination.setDestination(source.getDestination());
|
||||||
|
destination.setDestinationBands(source.getDestinationBands());
|
||||||
|
|
||||||
|
if (destination.canSetSourceRenderSize()) {
|
||||||
|
destination.setSourceRenderSize(source.getSourceRenderSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.setSourceProgressivePasses(
|
||||||
|
source.getSourceMinProgressivePass(),
|
||||||
|
source.getSourceMaxProgressivePass()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copyIIOParams(IIOParam source, IIOParam destination) {
|
||||||
|
destination.setController(source.getController());
|
||||||
|
destination.setSourceSubsampling(
|
||||||
|
source.getSourceXSubsampling(), source.getSourceYSubsampling(),
|
||||||
|
source.getSubsamplingXOffset(), source.getSubsamplingYOffset()
|
||||||
|
);
|
||||||
|
destination.setSourceRegion(source.getSourceRegion());
|
||||||
|
destination.setSourceBands(source.getSourceBands());
|
||||||
|
destination.setDestinationOffset(source.getDestinationOffset());
|
||||||
|
destination.setDestinationType(source.getDestinationType());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
package com.twelvemonkeys.imageio.util;
|
package com.twelvemonkeys.imageio.util;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.plugins.bmp.BMPImageWriteParam;
|
||||||
|
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IIOUtilTest
|
* IIOUtilTest
|
||||||
*/
|
*/
|
||||||
@@ -204,4 +215,222 @@ public class IIOUtilTest {
|
|||||||
private int divCeil(int numerator, int denominator) {
|
private int divCeil(int numerator, int denominator) {
|
||||||
return (numerator + denominator - 1) / denominator;
|
return (numerator + denominator - 1) / denominator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsDestinationNull() {
|
||||||
|
ImageReadParam param = new ImageReadParam();
|
||||||
|
|
||||||
|
assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(null, null));
|
||||||
|
assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(param, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsSame() {
|
||||||
|
ImageReadParam param = new ImageReadParam();
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> IIOUtil.copyStandardParams(param, param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsSourceNull() {
|
||||||
|
ImageReadParam param = new ImageReadParam() {
|
||||||
|
@Override
|
||||||
|
public void setSourceRegion(Rectangle sourceRegion) {
|
||||||
|
fail("Should not be invoked");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertSame(param, IIOUtil.copyStandardParams(null, param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsImageReadParam() {
|
||||||
|
int sourceXSubsampling = 3;
|
||||||
|
int sourceYSubsampling = 4;
|
||||||
|
int subsamplingXOffset = 1;
|
||||||
|
int subsamplingYOffset = 2;
|
||||||
|
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
|
||||||
|
int[] sourceBands = { 0, 1, 2 };
|
||||||
|
|
||||||
|
Point destinationOffset = new Point(7, 9);
|
||||||
|
int[] destinationBands = { 2, 1, 0 };
|
||||||
|
|
||||||
|
ImageReadParam sourceParam = new ImageReadParam();
|
||||||
|
sourceParam.setSourceRegion(sourceRegion);
|
||||||
|
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
|
||||||
|
sourceParam.setSourceBands(sourceBands);
|
||||||
|
|
||||||
|
sourceParam.setDestinationOffset(destinationOffset);
|
||||||
|
sourceParam.setDestinationBands(destinationBands);
|
||||||
|
|
||||||
|
JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
|
||||||
|
|
||||||
|
assertEquals(sourceRegion, jpegParam.getSourceRegion());
|
||||||
|
assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
|
||||||
|
assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
|
||||||
|
assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
|
||||||
|
assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
|
||||||
|
assertArrayEquals(sourceBands, jpegParam.getSourceBands());
|
||||||
|
|
||||||
|
assertEquals(destinationOffset, jpegParam.getDestinationOffset());
|
||||||
|
assertArrayEquals(destinationBands, jpegParam.getDestinationBands());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsImageReadParamDestination() {
|
||||||
|
// Destination and destination type is mutually exclusive
|
||||||
|
BufferedImage destination = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
ImageReadParam sourceParam = new ImageReadParam();
|
||||||
|
sourceParam.setDestination(destination);
|
||||||
|
|
||||||
|
assertEquals(destination, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsImageReadParamDestinationType() {
|
||||||
|
// Destination and destination type is mutually exclusive
|
||||||
|
ImageTypeSpecifier destinationType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
|
||||||
|
ImageReadParam sourceParam = new ImageReadParam();
|
||||||
|
sourceParam.setDestinationType(destinationType);
|
||||||
|
|
||||||
|
assertEquals(destinationType, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestinationType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsReadToWrite() {
|
||||||
|
int sourceXSubsampling = 3;
|
||||||
|
int sourceYSubsampling = 4;
|
||||||
|
int subsamplingXOffset = 1;
|
||||||
|
int subsamplingYOffset = 2;
|
||||||
|
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
|
||||||
|
int[] sourceBands = { 0, 1, 2 };
|
||||||
|
|
||||||
|
Point destinationOffset = new Point(7, 9);
|
||||||
|
|
||||||
|
ImageWriteParam sourceParam = new ImageWriteParam(null);
|
||||||
|
sourceParam.setSourceRegion(sourceRegion);
|
||||||
|
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
|
||||||
|
sourceParam.setSourceBands(sourceBands);
|
||||||
|
|
||||||
|
sourceParam.setDestinationOffset(destinationOffset);
|
||||||
|
|
||||||
|
JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
|
||||||
|
|
||||||
|
assertEquals(sourceRegion, jpegParam.getSourceRegion());
|
||||||
|
assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
|
||||||
|
assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
|
||||||
|
assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
|
||||||
|
assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
|
||||||
|
assertArrayEquals(sourceBands, jpegParam.getSourceBands());
|
||||||
|
|
||||||
|
assertEquals(destinationOffset, jpegParam.getDestinationOffset());
|
||||||
|
assertNull(jpegParam.getDestinationBands()); // Only in read param
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsImageWriteParam() {
|
||||||
|
int sourceXSubsampling = 3;
|
||||||
|
int sourceYSubsampling = 4;
|
||||||
|
int subsamplingXOffset = 1;
|
||||||
|
int subsamplingYOffset = 2;
|
||||||
|
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
|
||||||
|
int[] sourceBands = { 0, 1, 2 };
|
||||||
|
|
||||||
|
Point destinationOffset = new Point(7, 9);
|
||||||
|
|
||||||
|
ImageWriteParam sourceParam = new ImageWriteParam(null);
|
||||||
|
sourceParam.setSourceRegion(sourceRegion);
|
||||||
|
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
|
||||||
|
sourceParam.setSourceBands(sourceBands);
|
||||||
|
|
||||||
|
sourceParam.setDestinationOffset(destinationOffset);
|
||||||
|
|
||||||
|
BMPImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new BMPImageWriteParam());
|
||||||
|
|
||||||
|
assertEquals(sourceRegion, fooParam.getSourceRegion());
|
||||||
|
assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
|
||||||
|
assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
|
||||||
|
assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
|
||||||
|
assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
|
||||||
|
assertArrayEquals(sourceBands, fooParam.getSourceBands());
|
||||||
|
|
||||||
|
assertEquals(destinationOffset, fooParam.getDestinationOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copyStandardParamsImageWriteParamEverything() {
|
||||||
|
int sourceXSubsampling = 3;
|
||||||
|
int sourceYSubsampling = 4;
|
||||||
|
int subsamplingXOffset = 1;
|
||||||
|
int subsamplingYOffset = 2;
|
||||||
|
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
|
||||||
|
int[] sourceBands = { 0, 1, 2 };
|
||||||
|
|
||||||
|
Point destinationOffset = new Point(7, 9);
|
||||||
|
|
||||||
|
String compressionType = "Foo";
|
||||||
|
float quality = 0.42f;
|
||||||
|
|
||||||
|
ImageWriteParam sourceParam = new ImageWriteParam() {
|
||||||
|
{
|
||||||
|
canWriteProgressive = true;
|
||||||
|
|
||||||
|
canWriteTiles = true;
|
||||||
|
canOffsetTiles = true;
|
||||||
|
|
||||||
|
canWriteCompressed = true;
|
||||||
|
compressionTypes = new String[] { "Foo", "Bar" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sourceParam.setSourceRegion(sourceRegion);
|
||||||
|
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
|
||||||
|
sourceParam.setSourceBands(sourceBands);
|
||||||
|
|
||||||
|
sourceParam.setDestinationOffset(destinationOffset);
|
||||||
|
|
||||||
|
sourceParam.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); // Default is COPY_FROM_METADATA...
|
||||||
|
sourceParam.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
sourceParam.setTiling(1, 2, 3, 4);
|
||||||
|
sourceParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
sourceParam.setCompressionType(compressionType);
|
||||||
|
sourceParam.setCompressionQuality(quality);
|
||||||
|
|
||||||
|
FooImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new FooImageWriteParam());
|
||||||
|
|
||||||
|
assertEquals(sourceRegion, fooParam.getSourceRegion());
|
||||||
|
assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
|
||||||
|
assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
|
||||||
|
assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
|
||||||
|
assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
|
||||||
|
assertArrayEquals(sourceBands, fooParam.getSourceBands());
|
||||||
|
|
||||||
|
assertEquals(destinationOffset, fooParam.getDestinationOffset());
|
||||||
|
|
||||||
|
assertEquals(ImageWriteParam.MODE_DEFAULT, fooParam.getProgressiveMode());
|
||||||
|
|
||||||
|
assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getTilingMode());
|
||||||
|
assertEquals(1, fooParam.getTileWidth());
|
||||||
|
assertEquals(2, fooParam.getTileHeight());
|
||||||
|
assertEquals(3, fooParam.getTileGridXOffset());
|
||||||
|
assertEquals(4, fooParam.getTileGridYOffset());
|
||||||
|
|
||||||
|
assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getCompressionMode());
|
||||||
|
assertEquals(compressionType, fooParam.getCompressionType());
|
||||||
|
assertEquals(quality, fooParam.getCompressionQuality());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A basic param that supports "everything"
|
||||||
|
static class FooImageWriteParam extends ImageWriteParam {
|
||||||
|
FooImageWriteParam() {
|
||||||
|
canWriteProgressive = true;
|
||||||
|
|
||||||
|
canWriteTiles = true;
|
||||||
|
canOffsetTiles = true;
|
||||||
|
|
||||||
|
canWriteCompressed = true;
|
||||||
|
compressionType = "Unset";
|
||||||
|
compressionTypes = new String[] { "Bar", "Foo" };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+46
-8
@@ -34,6 +34,7 @@ import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
|||||||
|
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
@@ -84,6 +85,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
|
|
||||||
protected static BufferedImage drawSomething(final BufferedImage image) {
|
protected static BufferedImage drawSomething(final BufferedImage image) {
|
||||||
Graphics2D g = image.createGraphics();
|
Graphics2D g = image.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int width = image.getWidth();
|
int width = image.getWidth();
|
||||||
int height = image.getHeight();
|
int height = image.getHeight();
|
||||||
@@ -131,18 +133,54 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
public void testWrite() throws IOException {
|
public void testWrite() throws IOException {
|
||||||
ImageWriter writer = createWriter();
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
for (RenderedImage testData : getTestData()) {
|
try {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
for (RenderedImage testData : getTestData()) {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
writer.setOutput(stream);
|
writer.setOutput(stream);
|
||||||
writer.write(drawSomething((BufferedImage) testData));
|
writer.write(drawSomething((BufferedImage) testData));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(buffer.size() > 0, "No image data written");
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
}
|
||||||
throw new AssertionError(e.getMessage(), e);
|
finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteRaster() throws IOException {
|
||||||
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!writer.canWriteRasters()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(buffer.size() > 0, "No image data written");
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
|
||||||
|
for (RenderedImage testData : getTestData()) {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(testData.getTile(0, 0), null, null), param);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(buffer.size() > 0, "No image data written");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
enum BlockCompression {
|
||||||
|
BC1,
|
||||||
|
BC2,
|
||||||
|
BC3,
|
||||||
|
BC4,
|
||||||
|
BC5,
|
||||||
|
// BC6H,
|
||||||
|
// BC7
|
||||||
|
}
|
||||||
@@ -33,8 +33,10 @@ package com.twelvemonkeys.imageio.plugins.dds;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
interface DDS {
|
interface DDS {
|
||||||
int MAGIC = ('D' << 24) + ('D' << 16) + ('S' << 8) + ' '; //Big-Endian
|
int MAGIC = 'D' + ('D' << 8) + ('S' << 16) + (' ' << 24); // Little-Endian
|
||||||
|
|
||||||
int HEADER_SIZE = 124;
|
int HEADER_SIZE = 124;
|
||||||
|
int PIXELFORMAT_SIZE = 32;
|
||||||
|
|
||||||
// Header Flags
|
// Header Flags
|
||||||
int FLAG_CAPS = 1; // Required in every .dds file.
|
int FLAG_CAPS = 1; // Required in every .dds file.
|
||||||
@@ -47,7 +49,6 @@ interface DDS {
|
|||||||
int FLAG_DEPTH = 1 << 23; // Required in a depth texture.
|
int FLAG_DEPTH = 1 << 23; // Required in a depth texture.
|
||||||
|
|
||||||
// Pixel Format Flags
|
// Pixel Format Flags
|
||||||
int DDSPF_SIZE = 32;
|
|
||||||
int PIXEL_FORMAT_FLAG_ALPHAPIXELS = 0x1;
|
int PIXEL_FORMAT_FLAG_ALPHAPIXELS = 0x1;
|
||||||
int PIXEL_FORMAT_FLAG_ALPHA = 0x2;
|
int PIXEL_FORMAT_FLAG_ALPHA = 0x2;
|
||||||
int PIXEL_FORMAT_FLAG_FOURCC = 0x04;
|
int PIXEL_FORMAT_FLAG_FOURCC = 0x04;
|
||||||
@@ -56,16 +57,6 @@ interface DDS {
|
|||||||
//DX10 Resource Dimensions
|
//DX10 Resource Dimensions
|
||||||
int D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3;
|
int D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3;
|
||||||
|
|
||||||
//DXGI Formats (DX10)
|
|
||||||
int DXGI_FORMAT_BC1_UNORM = 71;
|
|
||||||
int DXGI_FORMAT_BC2_UNORM = 72;
|
|
||||||
int DXGI_FORMAT_BC3_UNORM = 77;
|
|
||||||
int DXGI_FORMAT_BC4_UNORM = 80;
|
|
||||||
int DXGI_FORMAT_BC5_UNORM = 83;
|
|
||||||
int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91;
|
|
||||||
int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93;
|
|
||||||
int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29;
|
|
||||||
|
|
||||||
//dwCaps
|
//dwCaps
|
||||||
int DDSCAPS_COMPLEX = 0x8;
|
int DDSCAPS_COMPLEX = 0x8;
|
||||||
int DDSCAPS_MIPMAP = 0x400000;
|
int DDSCAPS_MIPMAP = 0x400000;
|
||||||
|
|||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists a number of supported encoders for block compressors and uncompressed types.
|
|
||||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI Format List</a>
|
|
||||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#compression-algorithms">Compression Algorithms</a>
|
|
||||||
* <a href="https://github.com/microsoft/DirectXTK12/wiki/DDSTextureLoader#remarks">An extended Non-DX10 FourCC list</a>
|
|
||||||
*/
|
|
||||||
public enum DDSEncoderType {
|
|
||||||
BC1(DDSType.DXT1.value(), DDS.DXGI_FORMAT_BC1_UNORM, 8),
|
|
||||||
BC2(DDSType.DXT2.value(), DDS.DXGI_FORMAT_BC2_UNORM, 16),
|
|
||||||
BC3(DDSType.DXT5.value(), DDS.DXGI_FORMAT_BC3_UNORM, 16),
|
|
||||||
BC4(0x31495441, DDS.DXGI_FORMAT_BC4_UNORM, 8),
|
|
||||||
BC5(0x32495441, DDS.DXGI_FORMAT_BC5_UNORM, 16);
|
|
||||||
|
|
||||||
private final int fourCC;
|
|
||||||
private final int dx10DxgiFormat;
|
|
||||||
private final int bitCountOrBlockSize;
|
|
||||||
private final int[] rgbaMask;
|
|
||||||
|
|
||||||
//fourCC constructor
|
|
||||||
DDSEncoderType(int fourCC, int dx10DxgiFormat, int blockSize) {
|
|
||||||
this.fourCC = fourCC;
|
|
||||||
this.dx10DxgiFormat = dx10DxgiFormat;
|
|
||||||
bitCountOrBlockSize = blockSize;
|
|
||||||
rgbaMask = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//non-fourCC constructor (e.g. A8R8G8B8)
|
|
||||||
DDSEncoderType(int dx10DxgiFormat, int bitCount, int[] masks) {
|
|
||||||
fourCC = 0;
|
|
||||||
this.dx10DxgiFormat = dx10DxgiFormat;
|
|
||||||
bitCountOrBlockSize = bitCount;
|
|
||||||
rgbaMask = masks;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isFourCC() {
|
|
||||||
return fourCC != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getFourCC() {
|
|
||||||
return fourCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isAlphaMaskSupported() {
|
|
||||||
return !isFourCC() && rgbaMask[3] > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isBlockCompression() {
|
|
||||||
return this.isFourCC();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getBitsOrBlockSize() {
|
|
||||||
return bitCountOrBlockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getRGBAMask() {
|
|
||||||
return rgbaMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDx10Format() {
|
|
||||||
return dx10DxgiFormat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+111
-23
@@ -30,11 +30,22 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A1R5G5B5_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A4R4G4B4_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8B8G8R8_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8R8G8B8_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R5G6B5_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R8G8B8_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X1R5G5B5_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X4R4G4B4_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8B8G8R8_MASKS;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8R8G8B8_MASKS;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.util.Arrays;
|
||||||
|
|
||||||
final class DDSHeader {
|
final class DDSHeader {
|
||||||
|
|
||||||
@@ -52,17 +63,17 @@ final class DDSHeader {
|
|||||||
private int blueMask;
|
private int blueMask;
|
||||||
private int alphaMask;
|
private int alphaMask;
|
||||||
|
|
||||||
|
DXT10Header dxt10Header;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
|
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
|
||||||
DDSHeader header = new DDSHeader();
|
DDSHeader header = new DDSHeader();
|
||||||
|
|
||||||
// Read MAGIC bytes [0,3]
|
// Read MAGIC bytes [0,3]
|
||||||
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
|
||||||
int magic = imageInput.readInt();
|
int magic = imageInput.readInt();
|
||||||
if (magic != DDS.MAGIC) {
|
if (magic != DDS.MAGIC) {
|
||||||
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
|
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
|
||||||
}
|
}
|
||||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
|
|
||||||
// DDS_HEADER structure
|
// DDS_HEADER structure
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
|
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
|
||||||
@@ -97,6 +108,9 @@ final class DDSHeader {
|
|||||||
|
|
||||||
// DDS_PIXELFORMAT structure
|
// DDS_PIXELFORMAT structure
|
||||||
int px_dwSize = imageInput.readInt(); // [76,79]
|
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.pixelFormatFlags = imageInput.readInt(); // [80,83]
|
||||||
header.fourCC = imageInput.readInt(); // [84,87]
|
header.fourCC = imageInput.readInt(); // [84,87]
|
||||||
@@ -113,6 +127,10 @@ final class DDSHeader {
|
|||||||
|
|
||||||
int dwReserved2 = imageInput.readInt(); // [124,127]
|
int dwReserved2 = imageInput.readInt(); // [124,127]
|
||||||
|
|
||||||
|
if (header.fourCC == DDSType.DXT10.fourCC()) {
|
||||||
|
header.dxt10Header = DXT10Header.read(imageInput);
|
||||||
|
}
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,31 +164,101 @@ final class DDSHeader {
|
|||||||
return mipMapCount;
|
return mipMapCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getBitCount() {
|
DDSType getType() throws IIOException {
|
||||||
return bitCount;
|
if (dxt10Header != null) {
|
||||||
|
return dxt10Header.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRawType();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFourCC() {
|
DDSType getRawType() throws IIOException {
|
||||||
return fourCC;
|
if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
|
||||||
|
// DXT
|
||||||
|
return DDSType.fromFourCC(fourCC);
|
||||||
|
}
|
||||||
|
else if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
|
||||||
|
// RGB
|
||||||
|
int alphaMask = ((pixelFormatFlags & 0x01) != 0) ? this.alphaMask : 0; // 0x01 alpha
|
||||||
|
|
||||||
|
if (bitCount == 16) {
|
||||||
|
if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
|
||||||
|
// A1R5G5B5
|
||||||
|
return DDSType.A1R5G5B5;
|
||||||
|
}
|
||||||
|
else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
|
||||||
|
// X1R5G5B5
|
||||||
|
return DDSType.X1R5G5B5;
|
||||||
|
}
|
||||||
|
else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
|
||||||
|
// A4R4G4B4
|
||||||
|
return DDSType.A4R4G4B4;
|
||||||
|
}
|
||||||
|
else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
|
||||||
|
// X4R4G4B4
|
||||||
|
return DDSType.X4R4G4B4;
|
||||||
|
}
|
||||||
|
else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
|
||||||
|
// R5G6B5
|
||||||
|
return DDSType.R5G6B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported 16bit RGB image.");
|
||||||
|
}
|
||||||
|
else if (bitCount == 24) {
|
||||||
|
if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
|
||||||
|
// R8G8B8
|
||||||
|
return DDSType.R8G8B8;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported 24bit RGB image.");
|
||||||
|
}
|
||||||
|
else if (bitCount == 32) {
|
||||||
|
if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
|
||||||
|
// A8B8G8R8
|
||||||
|
return DDSType.A8B8G8R8;
|
||||||
|
}
|
||||||
|
else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
|
||||||
|
// X8B8G8R8
|
||||||
|
return DDSType.X8B8G8R8;
|
||||||
|
}
|
||||||
|
else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
|
||||||
|
// A8R8G8B8
|
||||||
|
return DDSType.A8R8G8B8;
|
||||||
|
}
|
||||||
|
else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
|
||||||
|
// X8R8G8B8
|
||||||
|
return DDSType.X8R8G8B8;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported 32bit RGB image.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported bit count: " + bitCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported YUV or LUMINANCE image.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPixelFormatFlags() {
|
@Override
|
||||||
return pixelFormatFlags;
|
public String toString() {
|
||||||
|
return "DDSHeader{" +
|
||||||
|
"flags=" + Integer.toBinaryString(flags) +
|
||||||
|
", mipMapCount=" + mipMapCount +
|
||||||
|
", dimensions=" + Arrays.toString(Arrays.stream(dimensions)
|
||||||
|
.map(DDSHeader::dimensionToString)
|
||||||
|
.toArray(String[]::new)) +
|
||||||
|
", pixelFormatFlags=" + Integer.toBinaryString(pixelFormatFlags) +
|
||||||
|
", fourCC=" + fourCC +
|
||||||
|
", bitCount=" + bitCount +
|
||||||
|
", redMask=" + redMask +
|
||||||
|
", greenMask=" + greenMask +
|
||||||
|
", blueMask=" + blueMask +
|
||||||
|
", alphaMask=" + alphaMask +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
int getRedMask() {
|
private static String dimensionToString(Dimension dimension) {
|
||||||
return redMask;
|
return String.format("%dx%d", dimension.width, dimension.height);
|
||||||
}
|
|
||||||
|
|
||||||
int getGreenMask() {
|
|
||||||
return greenMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getBlueMask() {
|
|
||||||
return blueMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getAlphaMask() {
|
|
||||||
return alphaMask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-25
@@ -3,7 +3,6 @@ package com.twelvemonkeys.imageio.plugins.dds;
|
|||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.image.Raster;
|
import java.awt.image.Raster;
|
||||||
import java.awt.image.RenderedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.ARGB_ORDER;
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.ARGB_ORDER;
|
||||||
@@ -13,15 +12,11 @@ import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.RGB_16_ORDER;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A designated class to encode image data to binary.
|
* A designated class to encode image data to binary.
|
||||||
* <p>
|
*
|
||||||
* References:
|
* @see <a href="https://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/">GPU DXT Decompression</a>.
|
||||||
* <p>
|
* @see <a href="https://sv-journal.org/2014-1/06/en/index.php">TEXTURE COMPRESSION TECHNIQUES</a>.
|
||||||
* [1] <a href="https://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/">GPU DXT Decompression</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>
|
||||||
* [2] <a href="https://sv-journal.org/2014-1/06/en/index.php">TEXTURE COMPRESSION TECHNIQUES</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>
|
||||||
* [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>
|
|
||||||
*/
|
*/
|
||||||
class DDSImageDataEncoder {
|
class DDSImageDataEncoder {
|
||||||
private DDSImageDataEncoder() {}
|
private DDSImageDataEncoder() {}
|
||||||
@@ -31,25 +26,27 @@ class DDSImageDataEncoder {
|
|||||||
private static final int BC4_CHANNEL_ALPHA = 3; //BC3 reuses algorithm from BC4 but uses alpha channelIndex for sampling.
|
private static final int BC4_CHANNEL_ALPHA = 3; //BC3 reuses algorithm from BC4 but uses alpha channelIndex for sampling.
|
||||||
private static final int BC4_CHANNEL_GREEN = 1; //same re-usage as BC3 but for green channel BC5 uses
|
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, DDSEncoderType type) throws IOException {
|
static void writeImageData(ImageOutputStream imageOutput, Raster raster, BlockCompression compression) throws IOException {
|
||||||
switch (type) {
|
// TODO: Support compression == null for uncompressed RGB(A/X) data?
|
||||||
|
|
||||||
|
switch (compression) {
|
||||||
case BC1:
|
case BC1:
|
||||||
new BlockCompressor1(false).encode(imageOutput, renderedImage);
|
new BlockCompressor1(false).encode(imageOutput, raster);
|
||||||
break;
|
break;
|
||||||
case BC2:
|
case BC2:
|
||||||
new BlockCompressor2().encode(imageOutput, renderedImage);
|
new BlockCompressor2().encode(imageOutput, raster);
|
||||||
break;
|
break;
|
||||||
case BC3:
|
case BC3:
|
||||||
new BlockCompressor3().encode(imageOutput, renderedImage);
|
new BlockCompressor3().encode(imageOutput, raster);
|
||||||
break;
|
break;
|
||||||
case BC4:
|
case BC4:
|
||||||
new BlockCompressor4(BC4_CHANNEL_RED).encode(imageOutput, renderedImage);
|
new BlockCompressor4(BC4_CHANNEL_RED).encode(imageOutput, raster);
|
||||||
break;
|
break;
|
||||||
case BC5:
|
case BC5:
|
||||||
new BlockCompressor5().encode(imageOutput, renderedImage);
|
new BlockCompressor5().encode(imageOutput, raster);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("DDS Type is not supported for encoder yet : " + type);
|
throw new IllegalArgumentException("DDS block compression is not supported yet: " + compression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +170,14 @@ class DDSImageDataEncoder {
|
|||||||
boolean getBlockEndpoints(int[] sampledColors, int[] paletteBuffer) {
|
boolean getBlockEndpoints(int[] sampledColors, int[] paletteBuffer) {
|
||||||
if (sampledColors.length != 64)
|
if (sampledColors.length != 64)
|
||||||
throw new IllegalStateException("Unintended behaviour, expecting sampled colors of block to be 64, got " + sampledColors.length);
|
throw new IllegalStateException("Unintended behaviour, expecting sampled colors of block to be 64, got " + sampledColors.length);
|
||||||
int minR = 0xff; int minG = 0xff; int minB = 0xff;
|
int minR = 0xff;
|
||||||
int maxR = 0; int maxG = 0; int maxB = 0;
|
int minG = 0xff;
|
||||||
|
int minB = 0xff;
|
||||||
|
|
||||||
|
int maxR = 0;
|
||||||
|
int maxG = 0;
|
||||||
|
int maxB = 0;
|
||||||
|
|
||||||
boolean alphaMode = false;
|
boolean alphaMode = false;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < 64) {
|
while (i < 64) {
|
||||||
@@ -209,7 +212,6 @@ class DDSImageDataEncoder {
|
|||||||
return alphaMode;
|
return alphaMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Reference [3] Page 7
|
//Reference [3] Page 7
|
||||||
boolean getBlockEndpoints2(int[] sampled, int[] paletteBuffer) {
|
boolean getBlockEndpoints2(int[] sampled, int[] paletteBuffer) {
|
||||||
int maxDistance = -1;
|
int maxDistance = -1;
|
||||||
@@ -415,10 +417,10 @@ class DDSImageDataEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode(ImageOutputStream imageOutput, RenderedImage image) throws IOException {
|
void encode(ImageOutputStream imageOutput, Raster raster) throws IOException {
|
||||||
int blocksXCount = (image.getWidth() + 3) / 4;
|
int blocksXCount = (raster.getWidth() + 3) / 4;
|
||||||
int blocksYCount = (image.getHeight() + 3) / 4;
|
int blocksYCount = (raster.getHeight() + 3) / 4;
|
||||||
Raster raster = image.getData();
|
|
||||||
for (int blockY = 0; blockY < blocksYCount; blockY++) {
|
for (int blockY = 0; blockY < blocksYCount; blockY++) {
|
||||||
for (int blockX = 0; blockX < blocksXCount; blockX++) {
|
for (int blockX = 0; blockX < blocksXCount; blockX++) {
|
||||||
raster.getPixels(blockX * 4, blockY * 4, 4, 4, samples);
|
raster.getPixels(blockX * 4, blockY * 4, 4, 4, samples);
|
||||||
|
|||||||
+36
-13
@@ -34,25 +34,48 @@ import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
|||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
|
||||||
final class DDSMetadata extends StandardImageMetadataSupport {
|
final class DDSImageMetadata extends StandardImageMetadataSupport {
|
||||||
DDSMetadata(ImageTypeSpecifier type, DDSHeader header) {
|
|
||||||
super(builder(type)
|
DDSImageMetadata(ImageTypeSpecifier specifier, DDSType type) {
|
||||||
.withCompressionTypeName(compressionName(header))
|
super(builder(specifier)
|
||||||
.withFormatVersion("1.0")
|
.withCompressionTypeName(compressionName(type))
|
||||||
|
.withCompressionLossless(!type.isBlockCompression())
|
||||||
|
.withBitsPerSample(bitsPerSample(type))
|
||||||
|
.withFormatVersion("1.0")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String compressionName(DDSHeader header) {
|
private static String compressionName(DDSType type) {
|
||||||
// If the fourCC is valid, compression is one of the DXTn versions, otherwise None
|
if (type != null && type.isFourCC()) {
|
||||||
int flags = header.getPixelFormatFlags();
|
|
||||||
|
|
||||||
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
|
|
||||||
// DXTn
|
|
||||||
DDSType type = DDSType.valueOf(header.getFourCC());
|
|
||||||
|
|
||||||
return type.name();
|
return type.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "None";
|
return "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int[] bitsPerSample(DDSType type) {
|
||||||
|
if (type.isBlockCompression()) {
|
||||||
|
return null; // Use defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] bitsPerSample = new int[4];
|
||||||
|
|
||||||
|
for (int i = 0; i < bitsPerSample.length; i++) {
|
||||||
|
bitsPerSample[i] = countMaskBits(type.rgbaMasks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countMaskBits(int mask) {
|
||||||
|
// See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
|
||||||
|
int count;
|
||||||
|
|
||||||
|
for (count = 0; mask != 0; count++) {
|
||||||
|
mask &= mask - 1; // clear the least significant bit set
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+17
-3
@@ -48,6 +48,12 @@ import java.util.Iterator;
|
|||||||
|
|
||||||
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
|
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageReader implementation for Microsoft DirectDraw Surface (DDS) format.
|
||||||
|
*
|
||||||
|
* @author Paul Allen
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
*/
|
||||||
public final class DDSImageReader extends ImageReaderBase {
|
public final class DDSImageReader extends ImageReaderBase {
|
||||||
|
|
||||||
private DDSHeader header;
|
private DDSHeader header;
|
||||||
@@ -90,7 +96,16 @@ public final class DDSImageReader extends ImageReaderBase {
|
|||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
readHeader();
|
readHeader();
|
||||||
|
|
||||||
// TODO: Implement for the specific formats...
|
DDSType type = header.getType();
|
||||||
|
if (!type.isBlockCompression() && type.rgbaMasks[3] == 0) {
|
||||||
|
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);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,14 +161,13 @@ public final class DDSImageReader extends ImageReaderBase {
|
|||||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier imageType = getRawImageType(imageIndex);
|
ImageTypeSpecifier imageType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
return new DDSMetadata(imageType, header);
|
return new DDSImageMetadata(imageType, header.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readHeader() throws IOException {
|
private void readHeader() throws IOException {
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
header = DDSHeader.read(imageInput);
|
header = DDSHeader.read(imageInput);
|
||||||
|
|
||||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
|||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public final class DDSImageReaderSpi extends ImageReaderSpiBase {
|
public final class DDSImageReaderSpi extends ImageReaderSpiBase {
|
||||||
@@ -52,10 +53,15 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
ImageInputStream stream = (ImageInputStream) source;
|
ImageInputStream stream = (ImageInputStream) source;
|
||||||
|
|
||||||
stream.mark();
|
stream.mark();
|
||||||
|
ByteOrder byteOrder = stream.getByteOrder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
return stream.readInt() == DDS.MAGIC;
|
return stream.readInt() == DDS.MAGIC;
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
|
stream.setByteOrder(byteOrder);
|
||||||
stream.reset();
|
stream.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +73,6 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription(Locale locale) {
|
public String getDescription(Locale locale) {
|
||||||
return "Direct DrawSurface (DDS) Image Reader";
|
return "DirectDraw Surface (DDS) Image Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public final class DDSImageWriteParam extends ImageWriteParam {
|
||||||
|
|
||||||
|
static final DDSType DEFAULT_TYPE = DDSType.DXT5;
|
||||||
|
private static final String[] COMPRESSION_TYPES = compressionTypes();
|
||||||
|
|
||||||
|
private static String[] compressionTypes() {
|
||||||
|
// TODO: Maybe hardcode subset of values that we actually support writing?
|
||||||
|
List<String> compressionTypes = Arrays.stream(DDSType.values())
|
||||||
|
.filter(DDSType::isBlockCompression)
|
||||||
|
.map(Enum::name)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
compressionTypes.add(0, "None");
|
||||||
|
|
||||||
|
return compressionTypes.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean writeDXT10;
|
||||||
|
|
||||||
|
DDSImageWriteParam() {
|
||||||
|
canWriteCompressed = true;
|
||||||
|
compressionTypes = COMPRESSION_TYPES;
|
||||||
|
compressionType = DEFAULT_TYPE.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWriteDX10() {
|
||||||
|
writeDXT10 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearWriteDX10() {
|
||||||
|
writeDXT10 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWriteDXT10() {
|
||||||
|
return writeDXT10;
|
||||||
|
}
|
||||||
|
|
||||||
|
DDSType type() {
|
||||||
|
if (compressionType == null || compressionType.equals("None")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DDSType.valueOf(compressionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getDxgiFormat() {
|
||||||
|
DDSType type = type();
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
return type.dxgiFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DXGI.DXGI_FORMAT_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
+230
-85
@@ -1,7 +1,9 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||||
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.IIOImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
@@ -9,6 +11,8 @@ import javax.imageio.ImageWriteParam;
|
|||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.awt.image.Raster;
|
import java.awt.image.Raster;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -18,79 +22,191 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A designated class to begin writing DDS file with headers, class {@link DDSImageDataEncoder} will handle image data encoding process
|
* ImageWriter implementation for Microsoft DirectDraw Surface (DDS) format.
|
||||||
|
*
|
||||||
|
* @author KhanTypo
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
*/
|
*/
|
||||||
class DDSImageWriter extends ImageWriterBase {
|
class DDSImageWriter extends ImageWriterBase {
|
||||||
|
|
||||||
|
private long startPos;
|
||||||
|
// TODO: Create a SequenceSupport class that handles sequence prepare/write/end
|
||||||
|
private int mipmapIndex = -1;
|
||||||
|
private DDSType mipmapType;
|
||||||
|
private Dimension mipmapDimension;
|
||||||
|
|
||||||
protected DDSImageWriter(ImageWriterSpi provider) {
|
protected DDSImageWriter(ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DDSWriterParam getDefaultWriteParam() {
|
public DDSImageWriteParam getDefaultWriteParam() {
|
||||||
return DDSWriterParam.DEFAULT_PARAM;
|
return new DDSImageWriteParam();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetMembers() {
|
||||||
|
mipmapIndex = -1;
|
||||||
|
mipmapType = null;
|
||||||
|
mipmapDimension = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canWriteRasters() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canWriteSequence() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
|
||||||
|
assertOutput();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endWriteSequence() throws IOException {
|
||||||
|
assertOutput();
|
||||||
|
|
||||||
|
if (mipmapIndex < 0) {
|
||||||
|
throw new IllegalStateException("prepareWriteSequence not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go back and update hader
|
||||||
|
updateHeader(mipmapIndex);
|
||||||
|
|
||||||
|
mipmapIndex = -1;
|
||||||
|
mipmapType = null;
|
||||||
|
mipmapDimension = null;
|
||||||
|
|
||||||
|
imageOutput.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
||||||
assertOutput();
|
prepareWriteSequence(streamMetadata);
|
||||||
RenderedImage renderedImage = image.getRenderedImage();
|
writeToSequence(image, param);
|
||||||
ensureTextureSize(renderedImage);
|
endWriteSequence();
|
||||||
ensureImageChannels(renderedImage);
|
}
|
||||||
|
|
||||||
DDSWriterParam ddsParam = param instanceof DDSWriterParam ? ((DDSWriterParam) param) : this.getDefaultWriteParam();
|
@Override
|
||||||
|
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
|
||||||
|
if (mipmapIndex < 0) {
|
||||||
|
throw new IllegalStateException("prepareWriteSequence not called");
|
||||||
|
}
|
||||||
|
|
||||||
processImageStarted(0);
|
Raster raster = getRaster(image);
|
||||||
imageOutput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
ensureImageChannels(raster);
|
||||||
imageOutput.writeInt(DDS.MAGIC);
|
ensureTextureDimension(raster);
|
||||||
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
|
|
||||||
writeHeader(image, ddsParam);
|
DDSImageWriteParam ddsParam = param instanceof DDSImageWriteParam
|
||||||
writeDXT10Header(ddsParam);
|
? ((DDSImageWriteParam) param)
|
||||||
|
: IIOUtil.copyStandardParams(param, getDefaultWriteParam());
|
||||||
|
|
||||||
//image data encoding
|
DDSType type = ddsParam.type();
|
||||||
|
if (mipmapType == null) {
|
||||||
|
mipmapType = type;
|
||||||
|
}
|
||||||
|
else if (type != mipmapType) {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mipmapIndex == 0) {
|
||||||
|
writeHeader(raster.getWidth(), raster.getHeight(), mipmapType, ddsParam.isWriteDXT10());
|
||||||
|
if (ddsParam.isWriteDXT10()) {
|
||||||
|
writeDXT10Header(ddsParam.getDxgiFormat());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageStarted(mipmapIndex);
|
||||||
processImageProgress(0f);
|
processImageProgress(0f);
|
||||||
DDSImageDataEncoder.writeImageData(imageOutput, renderedImage, ddsParam.getEncoderType());
|
|
||||||
processImageProgress(100f);
|
|
||||||
|
|
||||||
imageOutput.flush();
|
DDSImageDataEncoder.writeImageData(imageOutput, raster, mipmapType.compression);
|
||||||
|
|
||||||
|
processImageProgress(100f);
|
||||||
processImageComplete();
|
processImageComplete();
|
||||||
|
|
||||||
|
mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight());
|
||||||
|
mipmapIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Raster getRaster(IIOImage image) throws IIOException {
|
||||||
|
if (image.hasRaster()) {
|
||||||
|
return image.getRaster();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RenderedImage renderedImage = image.getRenderedImage();
|
||||||
|
|
||||||
|
if (renderedImage.getNumXTiles() != 1 || renderedImage.getNumYTiles() != 1) {
|
||||||
|
throw new IIOException("Only single tile images supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedImage.getTile(0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checking if the image has 3 channels (RGB) or 4 channels (RGBA) and if image has 8 bits/channel.
|
* Checking if the image has 3 channels (RGB) or 4 channels (RGBA) and if image has 8 bits/channel.
|
||||||
|
*
|
||||||
|
* @see DDSImageWriterSpi#canEncodeImage(ImageTypeSpecifier)
|
||||||
*/
|
*/
|
||||||
|
private void ensureImageChannels(Raster data) throws IIOException {
|
||||||
private void ensureImageChannels(RenderedImage renderedImage) {
|
|
||||||
Raster data = renderedImage.getData();
|
|
||||||
int numBands = data.getNumBands();
|
int numBands = data.getNumBands();
|
||||||
if (numBands < 3)
|
if (numBands < 3 || numBands > 4) {
|
||||||
throw new IllegalStateException("Only image with 3 channels (RGB) or 4 channels (RGBA) is supported, got " + numBands + " channels");
|
throw new IIOException(
|
||||||
|
"Only image with 3 channels (RGB) or 4 channels (RGBA) is supported, got " + numBands + " channels");
|
||||||
|
}
|
||||||
|
|
||||||
int sampleSize = data.getSampleModel().getSampleSize(0);
|
int sampleSize = data.getSampleModel().getSampleSize(0);
|
||||||
if (sampleSize != 8)
|
if (sampleSize != 8) {
|
||||||
throw new IllegalStateException("Only image with 8 bits/channel is supported, got " + sampleSize);
|
throw new IIOException("Only image with 8 bits/channel is supported, got " + sampleSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checking if an image can be evenly divided into blocks of 4x4, ideally a power of 2.
|
* Checking if an image can be evenly divided into blocks of 4x4, ideally a power of 2.
|
||||||
* e.g. 16x16, 32x32, 512x128, 512x512, 1024x512, 1024x1024, 2048x1024, ...
|
* e.g. 16x16, 32x32, 512x128, 512x512, 1024x512, 1024x1024, 2048x1024...
|
||||||
*/
|
*/
|
||||||
private void ensureTextureSize(RenderedImage renderedImage) {
|
private void ensureTextureDimension(Raster raster) throws IIOException {
|
||||||
int w = renderedImage.getWidth();
|
int width = raster.getWidth();
|
||||||
int h = renderedImage.getHeight();
|
int height = raster.getHeight();
|
||||||
if (w % 4 != 0 || h % 4 != 0)
|
|
||||||
throw new IllegalStateException(String.format("Image size must be dividable by 4, ideally a power of 2; got (%d x %d)", w, h));
|
// Should also allow mipmaps 2x2 and 1x1?
|
||||||
|
if (width % 4 != 0 || height % 4 != 0) {
|
||||||
|
throw new IIOException(String.format("Image dimensions must be dividable by 4, ideally a power of 2; got %dx%d", width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mipmapDimension != null && (mipmapDimension.width != width * 2|| mipmapDimension.height != height * 2)) {
|
||||||
|
throw new IIOException(
|
||||||
|
String.format("For mipmap, image dimensions must be exactly half of previous (%dx%d); got %dx%d",
|
||||||
|
mipmapDimension.width, mipmapDimension.height, width, height)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeHeader(int width, int height, DDSType type, boolean writeDXT10) throws IOException {
|
||||||
private void writeHeader(IIOImage image, DDSWriterParam param) throws IOException {
|
|
||||||
imageOutput.writeInt(DDS.HEADER_SIZE);
|
imageOutput.writeInt(DDS.HEADER_SIZE);
|
||||||
imageOutput.writeInt(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT | param.getOptionalBitFlags());
|
int linearSizeOrPitch = type.isBlockCompression() ? DDS.FLAG_LINEARSIZE : DDS.FLAG_PITCH;
|
||||||
RenderedImage renderedImage = image.getRenderedImage();
|
imageOutput.writeInt(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT | linearSizeOrPitch);
|
||||||
int height = renderedImage.getHeight();
|
|
||||||
imageOutput.writeInt(height);
|
imageOutput.writeInt(height);
|
||||||
int width = renderedImage.getWidth();
|
|
||||||
imageOutput.writeInt(width);
|
imageOutput.writeInt(width);
|
||||||
writePitchOrLinearSize(height, width, param);
|
writePitchOrLinearSize(height, width, type);
|
||||||
//dwDepth
|
//dwDepth
|
||||||
imageOutput.writeInt(0);
|
imageOutput.writeInt(0);
|
||||||
//dwMipmapCount
|
//dwMipmapCount
|
||||||
@@ -98,97 +214,126 @@ class DDSImageWriter extends ImageWriterBase {
|
|||||||
//reserved
|
//reserved
|
||||||
imageOutput.write(new byte[44]);
|
imageOutput.write(new byte[44]);
|
||||||
//pixFmt
|
//pixFmt
|
||||||
writePixelFormat(param);
|
writePixelFormat(type, writeDXT10);
|
||||||
//dwCaps, right now we keep it simple by only using DDSCAP_TEXTURE as it is required.
|
//dwCaps, right now we keep it simple by only using DDSCAP_TEXTURE as it is required.
|
||||||
imageOutput.writeInt(DDS.DDSCAPS_TEXTURE);
|
imageOutput.writeInt(DDS.DDSCAPS_TEXTURE);
|
||||||
//dwCaps2, unused for now as we are not working with cube maps
|
//dwCaps2, unused for now as we are not working with cube maps
|
||||||
imageOutput.writeInt(0);
|
imageOutput.writeInt(0);
|
||||||
//dwCaps3, dwCaps4, dwReserved2 : 3 unused integers
|
//dwCaps3, dwCaps4, dwReserved2 : 3 unused integers
|
||||||
imageOutput.write(new byte[12]);
|
imageOutput.write(new byte[12]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHeader(int mipmapCount) throws IOException {
|
||||||
|
if (mipmapCount == 1) {
|
||||||
|
// Fast case, nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long streamPosition = imageOutput.getStreamPosition();
|
||||||
|
imageOutput.seek(startPos + 8); // Seek back to start + 4 magic + 4 header size
|
||||||
|
|
||||||
|
int flags = imageOutput.readInt();
|
||||||
|
imageOutput.seek(imageOutput.getStreamPosition() - 4);
|
||||||
|
imageOutput.writeInt(flags | DDS.FLAG_MIPMAPCOUNT);
|
||||||
|
|
||||||
|
imageOutput.seek(imageOutput.getStreamPosition() + 16);
|
||||||
|
imageOutput.writeInt(mipmapCount);
|
||||||
|
|
||||||
|
imageOutput.seek(streamPosition); // Restore pos
|
||||||
}
|
}
|
||||||
|
|
||||||
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat
|
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat
|
||||||
private void writePixelFormat(DDSWriterParam param) throws IOException {
|
private void writePixelFormat(DDSType type, boolean writeDXT10) throws IOException {
|
||||||
imageOutput.writeInt(DDS.DDSPF_SIZE);
|
imageOutput.writeInt(DDS.PIXELFORMAT_SIZE);
|
||||||
writePixelFormatFlags(param);
|
writePixelFormatFlags(type, writeDXT10);
|
||||||
writeFourCC(param);
|
writeFourCC(type, writeDXT10);
|
||||||
writeRGBAData(param);
|
writeRGBAData(type, writeDXT10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeDXT10Header(DDSWriterParam param) throws IOException {
|
private void writeDXT10Header(int dxgiFormat) throws IOException {
|
||||||
if (param.isUsingDxt10()) {
|
//dxgiFormat
|
||||||
//dxgiFormat
|
imageOutput.writeInt(dxgiFormat);
|
||||||
imageOutput.writeInt(param.getDxgiFormat());
|
//resourceDimension
|
||||||
//resourceDimension
|
imageOutput.writeInt(DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D);
|
||||||
imageOutput.writeInt(DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D);
|
//miscFlag
|
||||||
//miscFlag
|
imageOutput.writeInt(0);
|
||||||
imageOutput.writeInt(0);
|
//arraySize
|
||||||
//arraySize
|
imageOutput.writeInt(1);
|
||||||
imageOutput.writeInt(1);
|
//miscFlag2
|
||||||
//miscFlag2
|
imageOutput.writeInt(0);
|
||||||
imageOutput.writeInt(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRGBAData(DDSWriterParam param) throws IOException {
|
private void writeRGBAData(DDSType type, boolean writeDXT10) throws IOException {
|
||||||
if (!param.isUsingDxt10() && !param.getEncoderType().isFourCC()) {
|
if (!writeDXT10 && !type.isFourCC()) {
|
||||||
//dwRGBBitCount
|
//dwRGBBitCount
|
||||||
imageOutput.writeInt(param.getEncoderType().getBitsOrBlockSize());
|
imageOutput.writeInt(type.blockSize() * 8); // TODO: Is bitcount always a multiple of 8?
|
||||||
|
|
||||||
int[] mask = param.getEncoderType().getRGBAMask();
|
int[] mask = type.rgbaMasks;
|
||||||
//dwRBitMask
|
//dwRBitMask
|
||||||
imageOutput.writeInt(mask[0]);
|
imageOutput.writeInt(mask[0]);
|
||||||
//dwGBitMask
|
//dwGBitMask
|
||||||
imageOutput.writeInt(mask[1]);
|
imageOutput.writeInt(mask[1]);
|
||||||
//dwBitMask
|
//dwBBitMask
|
||||||
imageOutput.writeInt(mask[2]);
|
imageOutput.writeInt(mask[2]);
|
||||||
//dwABitMask
|
//dwABitMask
|
||||||
imageOutput.writeInt(mask[3]);
|
imageOutput.writeInt(mask[3]);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
//write 5 zero integers as fourCC is used
|
//write 5 zero integers as fourCC is used
|
||||||
imageOutput.write(new byte[20]);
|
imageOutput.write(new byte[20]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeFourCC(DDSWriterParam param) throws IOException {
|
private void writeFourCC(DDSType type, boolean writeDXT10) throws IOException {
|
||||||
if (param.isUsingDxt10()) {
|
if (writeDXT10) {
|
||||||
imageOutput.writeInt(DDSType.DXT10.value());
|
imageOutput.writeInt(DDSType.DXT10.fourCC());
|
||||||
} else if (param.getEncoderType().isFourCC())
|
}
|
||||||
imageOutput.writeInt(param.getEncoderType().getFourCC());
|
else if (type.isFourCC()) {
|
||||||
|
imageOutput.writeInt(type.fourCC());
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
private void writePixelFormatFlags(DDSWriterParam param) throws IOException {
|
// No fourCC, custom format...
|
||||||
if (param.isUsingDxt10() || param.getEncoderType().isFourCC()) {
|
imageOutput.writeInt(0);
|
||||||
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
|
|
||||||
} else {
|
|
||||||
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (param.getEncoderType().isAlphaMaskSupported() ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writePitchOrLinearSize(int height, int width, DDSWriterParam param) throws IOException {
|
private void writePixelFormatFlags(DDSType type, boolean writeDXT10) throws IOException {
|
||||||
DDSEncoderType type = param.getEncoderType();
|
if (writeDXT10 || type.isFourCC()) {
|
||||||
int bitsOrBlockSize = type.getBitsOrBlockSize();
|
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (type.rgbaMasks != null ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePitchOrLinearSize(int height, int width, DDSType type) throws IOException {
|
||||||
if (type.isBlockCompression()) {
|
if (type.isBlockCompression()) {
|
||||||
imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * bitsOrBlockSize);
|
imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * type.blockSize());
|
||||||
} else {
|
}
|
||||||
imageOutput.writeInt(width * bitsOrBlockSize);
|
else {
|
||||||
|
imageOutput.writeInt(width * type.blockSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||||
throw new UnsupportedOperationException("Direct Draw Surface does not support metadata.");
|
DDSType type = param instanceof DDSImageWriteParam
|
||||||
|
? ((DDSImageWriteParam) param).type()
|
||||||
|
: DDSImageWriteParam.DEFAULT_TYPE;
|
||||||
|
|
||||||
|
return new DDSImageMetadata(imageType, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||||
throw new UnsupportedOperationException("Direct Draw Surface does not support metadata.");
|
// Nothing useful to convert here...
|
||||||
|
return getDefaultImageMetadata(imageType, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
if (args.length != 1) throw new IllegalArgumentException("Use 1 input file at a time.");
|
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"))));
|
ImageIO.write(ImageIO.read(new File(args[0])), "dds", new MemoryCacheImageOutputStream(Files.newOutputStream(Paths.get("output.dds"))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -4,6 +4,7 @@ import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
|||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public final class DDSImageWriterSpi extends ImageWriterSpiBase {
|
public final class DDSImageWriterSpi extends ImageWriterSpiBase {
|
||||||
@@ -13,7 +14,12 @@ public final class DDSImageWriterSpi extends ImageWriterSpiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canEncodeImage(ImageTypeSpecifier type) {
|
public boolean canEncodeImage(ImageTypeSpecifier type) {
|
||||||
return true;
|
int numBands = type.getNumBands();
|
||||||
|
if (numBands < 3 || numBands > 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.getSampleModel().getSampleSize(0) == 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -23,6 +29,6 @@ public final class DDSImageWriterSpi extends ImageWriterSpiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription(Locale locale) {
|
public String getDescription(Locale locale) {
|
||||||
return "Direct Draw Surface (DDS) Image Writer";
|
return "DirectDraw Surface (DDS) Image Writer";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-12
@@ -35,18 +35,16 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
|||||||
final class DDSProviderInfo extends ReaderWriterProviderInfo {
|
final class DDSProviderInfo extends ReaderWriterProviderInfo {
|
||||||
DDSProviderInfo() {
|
DDSProviderInfo() {
|
||||||
super(
|
super(
|
||||||
DDSProviderInfo.class,
|
DDSProviderInfo.class,
|
||||||
new String[]{"DDS", "dds"},
|
new String[] { "DDS", "dds" },
|
||||||
new String[]{"dds"},
|
new String[] { "dds" },
|
||||||
new String[]{"image/vnd-ms.dds"},
|
new String[] { "image/vnd-ms.dds" },
|
||||||
"com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
|
"com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
|
||||||
new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi"},
|
new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi" },
|
||||||
"com.twelvemonkeys.imageio.plugins.dds.DDSImageWriter",
|
"com.twelvemonkeys.imageio.plugins.dds.DDSImageWriter",
|
||||||
new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi"},
|
new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi" },
|
||||||
false, null, null,
|
false, null, null, null, null,
|
||||||
null, null, true,
|
true, null, null, null, null
|
||||||
null, null, null,
|
|
||||||
null
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-78
@@ -74,7 +74,6 @@ final class DDSReader {
|
|||||||
static final Order RGB_16_ORDER = new Order(11, 5, 0, -1); // no alpha | 5 red | 6 green | 5 blue
|
static final Order RGB_16_ORDER = new Order(11, 5, 0, -1); // no alpha | 5 red | 6 green | 5 blue
|
||||||
|
|
||||||
private final DDSHeader header;
|
private final DDSHeader header;
|
||||||
private DX10Header dxt10Header;
|
|
||||||
|
|
||||||
DDSReader(DDSHeader header) {
|
DDSReader(DDSHeader header) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
@@ -82,18 +81,20 @@ final class DDSReader {
|
|||||||
|
|
||||||
int[] read(ImageInputStream imageInput, int imageIndex) throws IOException {
|
int[] read(ImageInputStream imageInput, int imageIndex) throws IOException {
|
||||||
// type
|
// type
|
||||||
DDSType type = getType();
|
DDSType type = header.getType();
|
||||||
if (type == DDSType.DXT10) {
|
|
||||||
dxt10Header = DX10Header.read(imageInput);
|
|
||||||
type = dxt10Header.getDDSType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// offset buffer to index mipmap image
|
// offset buffer to index mipmap image
|
||||||
byte[] buffer = null;
|
byte[] buffer = null;
|
||||||
for (int i = 0; i <= imageIndex; i++) {
|
for (int i = 0; i <= imageIndex; i++) {
|
||||||
int len = getLength(type, i);
|
int len = getBufferLength(type, i);
|
||||||
buffer = new byte[len];
|
|
||||||
imageInput.readFully(buffer);
|
if (i == imageIndex) {
|
||||||
|
buffer = new byte[len];
|
||||||
|
imageInput.readFully(buffer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
imageInput.seek(imageInput.getStreamPosition() + len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int width = header.getWidth(imageIndex);
|
int width = header.getWidth(imageIndex);
|
||||||
@@ -135,82 +136,17 @@ final class DDSReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DDSType getType() throws IIOException {
|
private int getBufferLength(DDSType type, int imageIndex) throws IIOException {
|
||||||
int flags = header.getPixelFormatFlags();
|
|
||||||
|
|
||||||
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
|
|
||||||
// DXT
|
|
||||||
int type = header.getFourCC();
|
|
||||||
return DDSType.valueOf(type);
|
|
||||||
} else if ((flags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
|
|
||||||
// RGB
|
|
||||||
int bitCount = header.getBitCount();
|
|
||||||
int redMask = header.getRedMask();
|
|
||||||
int greenMask = header.getGreenMask();
|
|
||||||
int blueMask = header.getBlueMask();
|
|
||||||
int alphaMask = ((flags & 0x01) != 0) ? header.getAlphaMask() : 0; // 0x01 alpha
|
|
||||||
if (bitCount == 16) {
|
|
||||||
if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
|
|
||||||
// A1R5G5B5
|
|
||||||
return DDSType.A1R5G5B5;
|
|
||||||
} else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
|
|
||||||
// X1R5G5B5
|
|
||||||
return DDSType.X1R5G5B5;
|
|
||||||
} else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
|
|
||||||
// A4R4G4B4
|
|
||||||
return DDSType.A4R4G4B4;
|
|
||||||
} else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
|
|
||||||
// X4R4G4B4
|
|
||||||
return DDSType.X4R4G4B4;
|
|
||||||
} else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
|
|
||||||
// R5G6B5
|
|
||||||
return DDSType.R5G6B5;
|
|
||||||
} else {
|
|
||||||
throw new IIOException("Unsupported 16bit RGB image.");
|
|
||||||
}
|
|
||||||
} else if (bitCount == 24) {
|
|
||||||
if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
|
|
||||||
// R8G8B8
|
|
||||||
return DDSType.R8G8B8;
|
|
||||||
} else {
|
|
||||||
throw new IIOException("Unsupported 24bit RGB image.");
|
|
||||||
}
|
|
||||||
} else if (bitCount == 32) {
|
|
||||||
if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
|
|
||||||
// A8B8G8R8
|
|
||||||
return DDSType.A8B8G8R8;
|
|
||||||
} else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
|
|
||||||
// X8B8G8R8
|
|
||||||
return DDSType.X8B8G8R8;
|
|
||||||
} else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
|
|
||||||
// A8R8G8B8
|
|
||||||
return DDSType.A8R8G8B8;
|
|
||||||
} else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
|
|
||||||
// X8R8G8B8
|
|
||||||
return DDSType.X8R8G8B8;
|
|
||||||
} else {
|
|
||||||
throw new IIOException("Unsupported 32bit RGB image.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IIOException("Unsupported bit count: " + bitCount);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IIOException("Unsupported YUV or LUMINANCE image.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getLength(DDSType type, int imageIndex) throws IIOException {
|
|
||||||
int width = header.getWidth(imageIndex);
|
int width = header.getWidth(imageIndex);
|
||||||
int height = header.getHeight(imageIndex);
|
int height = header.getHeight(imageIndex);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DXT1:
|
case DXT1:
|
||||||
return 8 * ((width + 3) / 4) * ((height + 3) / 4);
|
|
||||||
case DXT2:
|
case DXT2:
|
||||||
case DXT3:
|
case DXT3:
|
||||||
case DXT4:
|
case DXT4:
|
||||||
case DXT5:
|
case DXT5:
|
||||||
return 16 * ((width + 3) / 4) * ((height + 3) / 4);
|
return type.blockSize() * ((width + 3) / 4) * ((height + 3) / 4);
|
||||||
case A1R5G5B5:
|
case A1R5G5B5:
|
||||||
case X1R5G5B5:
|
case X1R5G5B5:
|
||||||
case A4R4G4B4:
|
case A4R4G4B4:
|
||||||
@@ -221,9 +157,9 @@ final class DDSReader {
|
|||||||
case X8B8G8R8:
|
case X8B8G8R8:
|
||||||
case A8R8G8B8:
|
case A8R8G8B8:
|
||||||
case X8R8G8B8:
|
case X8R8G8B8:
|
||||||
return (type.value() & 0xFF) * width * height;
|
return type.blockSize() * width * height;
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown type: " + Integer.toHexString(type.value()));
|
throw new IIOException("Unknown type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+141
-26
@@ -30,41 +30,156 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.BlockCompression.*;
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#compression-algorithms">Compression Algorithms</a>
|
||||||
|
* <a href="https://github.com/microsoft/DirectXTK12/wiki/DDSTextureLoader#remarks">An extended Non-DX10 FourCC list</a>
|
||||||
|
*/
|
||||||
enum DDSType {
|
enum DDSType {
|
||||||
DXT1(0x31545844),
|
// Compressed types
|
||||||
DXT2(0x32545844),
|
DXT1('D' + ('X' << 8) + ('T' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC1_UNORM, BC1),
|
||||||
DXT3(0x33545844),
|
DXT2('D' + ('X' << 8) + ('T' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
|
||||||
DXT4(0x34545844),
|
DXT3('D' + ('X' << 8) + ('T' << 16) + ('3' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
|
||||||
DXT5(0x35545844),
|
DXT4('D' + ('X' << 8) + ('T' << 16) + ('4' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
|
||||||
DXT10(0x30315844),
|
DXT5('D' + ('X' << 8) + ('T' << 16) + ('5' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
|
||||||
A1R5G5B5((1 << 16) | 2),
|
|
||||||
X1R5G5B5((2 << 16) | 2),
|
|
||||||
A4R4G4B4((3 << 16) | 2),
|
|
||||||
X4R4G4B4((4 << 16) | 2),
|
|
||||||
R5G6B5((5 << 16) | 2),
|
|
||||||
R8G8B8((1 << 16) | 3),
|
|
||||||
A8B8G8R8((1 << 16) | 4),
|
|
||||||
X8B8G8R8((2 << 16) | 4),
|
|
||||||
A8R8G8B8((3 << 16) | 4),
|
|
||||||
X8R8G8B8((4 << 16) | 4);
|
|
||||||
|
|
||||||
private final int value;
|
ATI1('A' + ('T' << 8) + ('I' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4), // AKA BC4U
|
||||||
|
BC4U('B' + ('C' << 8) + ('4' << 16) + ('U' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4),
|
||||||
|
BC4S('B' + ('C' << 8) + ('4' << 16) + ('S' << 24), 8, DXGI.DXGI_FORMAT_BC4_SNORM, BC4),
|
||||||
|
ATI2('A' + ('T' << 8) + ('I' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5), // AKA BC5U
|
||||||
|
BC5U('B' + ('C' << 8) + ('5' << 16) + ('U' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5),
|
||||||
|
BC5S('B' + ('C' << 8) + ('5' << 16) + ('S' << 24), 16, DXGI.DXGI_FORMAT_BC5_SNORM, BC5),
|
||||||
|
|
||||||
DDSType(int value) {
|
// Special case, see DXT10Header.dxgiFormat for real format
|
||||||
this.value = value;
|
DXT10('D' + ('X' << 8) + ('1' << 16) + ('0' << 24), -1, DXGI.DXGI_FORMAT_UNKNOWN, null),
|
||||||
|
|
||||||
|
// Custom uncompressed pixel formats
|
||||||
|
// TODO: Consider swapping byte order to reflect the DXGI format?
|
||||||
|
A1R5G5B5(2, DXGI.DXGI_FORMAT_B5G5R5A1_UNORM, A1R5G5B5_MASKS),
|
||||||
|
X1R5G5B5(2, DXGI.DXGI_FORMAT_UNKNOWN, X1R5G5B5_MASKS),
|
||||||
|
A4R4G4B4(2, DXGI.DXGI_FORMAT_B4G4R4A4_UNORM, A4R4G4B4_MASKS),
|
||||||
|
X4R4G4B4(2, DXGI.DXGI_FORMAT_UNKNOWN, X4R4G4B4_MASKS),
|
||||||
|
R5G6B5( 2, DXGI.DXGI_FORMAT_B5G6R5_UNORM, R5G6B5_MASKS),
|
||||||
|
R8G8B8( 3, DXGI.DXGI_FORMAT_UNKNOWN, R8G8B8_MASKS),
|
||||||
|
A8B8G8R8(4, DXGI.DXGI_FORMAT_R8G8B8A8_UNORM, A8B8G8R8_MASKS),
|
||||||
|
X8B8G8R8(4, DXGI.DXGI_FORMAT_UNKNOWN, X8B8G8R8_MASKS),
|
||||||
|
A8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8A8_UNORM, A8R8G8B8_MASKS),
|
||||||
|
X8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8X8_UNORM, X8R8G8B8_MASKS);
|
||||||
|
|
||||||
|
private final int fourCC;
|
||||||
|
private final int blockSize;
|
||||||
|
private final int dxgiFormat;
|
||||||
|
final BlockCompression compression;
|
||||||
|
final int[] rgbaMasks;
|
||||||
|
|
||||||
|
DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression) {
|
||||||
|
this(fourCC, blockSize, dxgiFormat, compression, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int value() {
|
DDSType(int blockSize, int dxgiFormat, int[] rgbaMasks) {
|
||||||
return value;
|
this(0, blockSize, dxgiFormat, null, rgbaMasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DDSType valueOf(int value) {
|
DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression, int[] rgbaMasks) {
|
||||||
for (DDSType type : DDSType.values()) {
|
this.fourCC = fourCC;
|
||||||
if (value == type.value()) {
|
this.blockSize = blockSize;
|
||||||
return type;
|
this.dxgiFormat = dxgiFormat;
|
||||||
|
this.compression = compression;
|
||||||
|
this.rgbaMasks = rgbaMasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int fourCC() {
|
||||||
|
return fourCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int blockSize() {
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFourCC() {
|
||||||
|
return fourCC != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockCompression() {
|
||||||
|
return compression != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int dxgiFormat() {
|
||||||
|
return dxgiFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DDSType fromFourCC(int fourCC) {
|
||||||
|
if (fourCC != 0) {
|
||||||
|
for (DDSType type : values()) {
|
||||||
|
if (fourCC == type.fourCC()) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", value));
|
throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", fourCC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DDSType fromDXGIFormat(int dxgiFormat) {
|
||||||
|
switch (dxgiFormat) {
|
||||||
|
case DXGI.DXGI_FORMAT_R8G8B8A8_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||||
|
case DXGI.DXGI_FORMAT_R8G8B8A8_UINT:
|
||||||
|
return A8B8G8R8; // ABGR
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8A8_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||||
|
return A8R8G8B8; // ARGB
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8X8_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
||||||
|
return X8R8G8B8;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_B5G5R5A1_UNORM:
|
||||||
|
return A1R5G5B5;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_B4G4R4A4_UNORM:
|
||||||
|
return A4R4G4B4;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_B5G6R5_UNORM:
|
||||||
|
return R5G6B5;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC1_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_BC1_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||||
|
return DXT1;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC2_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_BC2_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||||
|
return DXT2;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC3_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_BC3_UNORM:
|
||||||
|
case DXGI.DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||||
|
return DXT4;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC4_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_BC4_UNORM:
|
||||||
|
return BC4U;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC4_SNORM:
|
||||||
|
return BC4S;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC5_TYPELESS:
|
||||||
|
case DXGI.DXGI_FORMAT_BC5_UNORM:
|
||||||
|
return BC5U;
|
||||||
|
|
||||||
|
case DXGI.DXGI_FORMAT_BC5_SNORM:
|
||||||
|
return BC5S;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported DXGI_FORMAT: " + dxgiFormat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-139
@@ -1,139 +0,0 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
|
||||||
|
|
||||||
import javax.imageio.ImageWriteParam;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class DDSWriterParam extends ImageWriteParam {
|
|
||||||
public static final DDSWriterParam DEFAULT_PARAM = DDSWriterParam.builder().formatBC5().build();
|
|
||||||
private final int optionalBitFlags;
|
|
||||||
private final DDSEncoderType encoderType;
|
|
||||||
private final boolean enableDxt10;
|
|
||||||
|
|
||||||
DDSWriterParam(int optionalBitFlags, DDSEncoderType encoderType, boolean isUsingDxt10) {
|
|
||||||
super();
|
|
||||||
this.optionalBitFlags = optionalBitFlags;
|
|
||||||
this.encoderType = encoderType;
|
|
||||||
this.enableDxt10 = isUsingDxt10;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getOptionalBitFlags() {
|
|
||||||
return this.optionalBitFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
DDSEncoderType getEncoderType() {
|
|
||||||
return this.encoderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUsingDxt10() {
|
|
||||||
return enableDxt10;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getDxgiFormat() {
|
|
||||||
return getEncoderType().getDx10Format();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
//we use Set collection to prevent duplications of bitflag setter calls
|
|
||||||
private int optionalBitFlag;
|
|
||||||
private DDSEncoderType encoderType;
|
|
||||||
private boolean isUsingDxt10;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
this.optionalBitFlag = 0;
|
|
||||||
encoderType = null;
|
|
||||||
isUsingDxt10 = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable saving file as Direct3D 10+ format.
|
|
||||||
*/
|
|
||||||
public Builder enableDX10() {
|
|
||||||
isUsingDxt10 = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compression type to be BC1 (DXT1).
|
|
||||||
* If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC1_UNORM.
|
|
||||||
*/
|
|
||||||
public Builder formatBC1() {
|
|
||||||
encoderType = DDSEncoderType.BC1;
|
|
||||||
return setFlag(DDSFlags.DDSD_LINEARSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compression type to be BC2 (DXT3).
|
|
||||||
* If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC2_UNORM.
|
|
||||||
*/
|
|
||||||
public Builder formatBC2() {
|
|
||||||
encoderType = DDSEncoderType.BC2;
|
|
||||||
return setFlag(DDSFlags.DDSD_LINEARSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compression type to be BC3 (DXT5).
|
|
||||||
* If DXT10 is enabled, this will set DXGI Format to DXGI_FORMAT_BC3_UNORM.
|
|
||||||
*/
|
|
||||||
public Builder formatBC3() {
|
|
||||||
encoderType = DDSEncoderType.BC3;
|
|
||||||
return setFlag(DDSFlags.DDSD_LINEARSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compression type to be BC4 (ATI1).
|
|
||||||
* If DXT10 is enabled, This will set DXGI Format to DXGI_FORMAT_BC4_UNORM.
|
|
||||||
*/
|
|
||||||
public Builder formatBC4() {
|
|
||||||
encoderType = DDSEncoderType.BC4;
|
|
||||||
return setFlag(DDSFlags.DDSD_LINEARSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compression type to be BC5 (ATI2).
|
|
||||||
* This will set DXGI Format to DXGI_FORMAT_BC5_UNORM.
|
|
||||||
*/
|
|
||||||
public Builder formatBC5() {
|
|
||||||
encoderType = DDSEncoderType.BC5;
|
|
||||||
return setFlag(DDSFlags.DDSD_LINEARSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setFlag(DDSFlags flag) {
|
|
||||||
optionalBitFlag |= flag.getValue();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set other optional flags for the DDS Header.
|
|
||||||
*/
|
|
||||||
public Builder setFlags(DDSFlags... flags) {
|
|
||||||
for (DDSFlags flag : flags)
|
|
||||||
setFlag(flag);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DDSWriterParam build() {
|
|
||||||
Objects.requireNonNull(encoderType, "no DDS format specified.");
|
|
||||||
return new DDSWriterParam(optionalBitFlag, encoderType, isUsingDxt10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DDSFlags {
|
|
||||||
DDSD_PITCH(DDS.FLAG_PITCH),// Required when pitch is provided for an uncompressed texture.
|
|
||||||
DDSD_MIPMAPCOUNT(DDS.FLAG_MIPMAPCOUNT),// Required in a mipmapped texture.
|
|
||||||
DDSD_LINEARSIZE(DDS.FLAG_LINEARSIZE),// Required when pitch is provided for a compressed texture.
|
|
||||||
DDSD_DEPTH(DDS.FLAG_DEPTH);// Required in a depth texture.
|
|
||||||
|
|
||||||
private final int flag;
|
|
||||||
DDSFlags(int flag) {
|
|
||||||
this.flag = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue() {
|
|
||||||
return flag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.IntPredicate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum that lists a certain types of DXGI Format this reader supports to read.
|
|
||||||
*
|
|
||||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI Format List</a>
|
|
||||||
*/
|
|
||||||
public enum DX10DXGIFormat {
|
|
||||||
BC1(DDSType.DXT1, rangeInclusive(70, 72)),
|
|
||||||
BC2(DDSType.DXT2, rangeInclusive(73, 75)),
|
|
||||||
BC3(DDSType.DXT5, rangeInclusive(76, 78)),
|
|
||||||
//BC7(99),
|
|
||||||
B8G8R8A8(DDSType.A8B8G8R8, exactly(87, 90, 91)),
|
|
||||||
B8G8R8X8(DDSType.X8B8G8R8, exactly(88, 92, 93)),
|
|
||||||
R8G8B8A8(DDSType.A8R8G8B8, rangeInclusive(27, 32));
|
|
||||||
private final DDSType ddsType;
|
|
||||||
private final IntPredicate dxgiFormat;
|
|
||||||
|
|
||||||
DX10DXGIFormat(DDSType ddsType, IntPredicate dxgiFormat) {
|
|
||||||
this.ddsType = ddsType;
|
|
||||||
this.dxgiFormat = dxgiFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
DDSType getCorrespondingType() {
|
|
||||||
return ddsType;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DX10DXGIFormat getFormat(int value) {
|
|
||||||
for (DX10DXGIFormat format : values()) {
|
|
||||||
if (format.dxgiFormat.test(value)) return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported DXGI_FORMAT : " + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param acceptedValues values in DXGI Formats List, passed values are expected to be in ascending order
|
|
||||||
*/
|
|
||||||
private static IntPredicate exactly(int... acceptedValues) {
|
|
||||||
return test -> Arrays.binarySearch(acceptedValues, test) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IntPredicate rangeInclusive(int from, int to) {
|
|
||||||
return test -> from <= test && test <= to;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
|
||||||
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
|
|
||||||
public final class DX10Header {
|
|
||||||
final DX10DXGIFormat dxgiFormat;
|
|
||||||
final int resourceDimension, miscFlag, arraySize, miscFlags2;
|
|
||||||
|
|
||||||
private DX10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
|
|
||||||
this.dxgiFormat = DX10DXGIFormat.getFormat(dxgiFormat);
|
|
||||||
this.resourceDimension = resourceDimension;
|
|
||||||
if (this.resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D)
|
|
||||||
throw new IllegalArgumentException("Resource dimension " + resourceDimension + " is not supported, expected 3.");
|
|
||||||
this.miscFlag = miscFlag;
|
|
||||||
this.arraySize = arraySize;
|
|
||||||
this.miscFlags2 = miscFlags2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DX10Header read(ImageInputStream inputStream) throws IOException {
|
|
||||||
int dxgiFormat = inputStream.readInt();
|
|
||||||
int resourceDimension = inputStream.readInt();
|
|
||||||
int miscFlag = inputStream.readInt();
|
|
||||||
int arraySize = inputStream.readInt();
|
|
||||||
int miscFlags2 = inputStream.readInt();
|
|
||||||
return new DX10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
|
|
||||||
}
|
|
||||||
|
|
||||||
DDSType getDDSType() {
|
|
||||||
return dxgiFormat.getCorrespondingType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI Format List</a>
|
||||||
|
*/
|
||||||
|
interface DXGI {
|
||||||
|
int DXGI_FORMAT_UNKNOWN = 0;
|
||||||
|
int DXGI_FORMAT_R32G32B32A32_TYPELESS = 1;
|
||||||
|
int DXGI_FORMAT_R32G32B32A32_FLOAT = 2;
|
||||||
|
int DXGI_FORMAT_R32G32B32A32_UINT = 3;
|
||||||
|
int DXGI_FORMAT_R32G32B32A32_SINT = 4;
|
||||||
|
int DXGI_FORMAT_R32G32B32_TYPELESS = 5;
|
||||||
|
int DXGI_FORMAT_R32G32B32_FLOAT = 6;
|
||||||
|
int DXGI_FORMAT_R32G32B32_UINT = 7;
|
||||||
|
int DXGI_FORMAT_R32G32B32_SINT = 8;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_TYPELESS = 9;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_FLOAT = 10;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_UNORM = 11;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_UINT = 12;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_SNORM = 13;
|
||||||
|
int DXGI_FORMAT_R16G16B16A16_SINT = 14;
|
||||||
|
int DXGI_FORMAT_R32G32_TYPELESS = 15;
|
||||||
|
int DXGI_FORMAT_R32G32_FLOAT = 16;
|
||||||
|
int DXGI_FORMAT_R32G32_UINT = 17;
|
||||||
|
int DXGI_FORMAT_R32G32_SINT = 18;
|
||||||
|
int DXGI_FORMAT_R32G8X24_TYPELESS = 19;
|
||||||
|
int DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20;
|
||||||
|
int DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21;
|
||||||
|
int DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22;
|
||||||
|
int DXGI_FORMAT_R10G10B10A2_TYPELESS = 23;
|
||||||
|
int DXGI_FORMAT_R10G10B10A2_UNORM = 24;
|
||||||
|
int DXGI_FORMAT_R10G10B10A2_UINT = 25;
|
||||||
|
int DXGI_FORMAT_R11G11B10_FLOAT = 26;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_TYPELESS = 27;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_UNORM = 28;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_UINT = 30;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_SNORM = 31;
|
||||||
|
int DXGI_FORMAT_R8G8B8A8_SINT = 32;
|
||||||
|
int DXGI_FORMAT_R16G16_TYPELESS = 33;
|
||||||
|
int DXGI_FORMAT_R16G16_FLOAT = 34;
|
||||||
|
int DXGI_FORMAT_R16G16_UNORM = 35;
|
||||||
|
int DXGI_FORMAT_R16G16_UINT = 36;
|
||||||
|
int DXGI_FORMAT_R16G16_SNORM = 37;
|
||||||
|
int DXGI_FORMAT_R16G16_SINT = 38;
|
||||||
|
int DXGI_FORMAT_R32_TYPELESS = 39;
|
||||||
|
int DXGI_FORMAT_D32_FLOAT = 40;
|
||||||
|
int DXGI_FORMAT_R32_FLOAT = 41;
|
||||||
|
int DXGI_FORMAT_R32_UINT = 42;
|
||||||
|
int DXGI_FORMAT_R32_SINT = 43;
|
||||||
|
int DXGI_FORMAT_R24G8_TYPELESS = 44;
|
||||||
|
int DXGI_FORMAT_D24_UNORM_S8_UINT = 45;
|
||||||
|
int DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46;
|
||||||
|
int DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47;
|
||||||
|
int DXGI_FORMAT_R8G8_TYPELESS = 48;
|
||||||
|
int DXGI_FORMAT_R8G8_UNORM = 49;
|
||||||
|
int DXGI_FORMAT_R8G8_UINT = 50;
|
||||||
|
int DXGI_FORMAT_R8G8_SNORM = 51;
|
||||||
|
int DXGI_FORMAT_R8G8_SINT = 52;
|
||||||
|
int DXGI_FORMAT_R16_TYPELESS = 53;
|
||||||
|
int DXGI_FORMAT_R16_FLOAT = 54;
|
||||||
|
int DXGI_FORMAT_D16_UNORM = 55;
|
||||||
|
int DXGI_FORMAT_R16_UNORM = 56;
|
||||||
|
int DXGI_FORMAT_R16_UINT = 57;
|
||||||
|
int DXGI_FORMAT_R16_SNORM = 58;
|
||||||
|
int DXGI_FORMAT_R16_SINT = 59;
|
||||||
|
int DXGI_FORMAT_R8_TYPELESS = 60;
|
||||||
|
int DXGI_FORMAT_R8_UNORM = 61;
|
||||||
|
int DXGI_FORMAT_R8_UINT = 62;
|
||||||
|
int DXGI_FORMAT_R8_SNORM = 63;
|
||||||
|
int DXGI_FORMAT_R8_SINT = 64;
|
||||||
|
int DXGI_FORMAT_A8_UNORM = 65;
|
||||||
|
int DXGI_FORMAT_R1_UNORM = 66;
|
||||||
|
int DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67;
|
||||||
|
int DXGI_FORMAT_R8G8_B8G8_UNORM = 68;
|
||||||
|
int DXGI_FORMAT_G8R8_G8B8_UNORM = 69;
|
||||||
|
int DXGI_FORMAT_BC1_TYPELESS = 70;
|
||||||
|
int DXGI_FORMAT_BC1_UNORM = 71;
|
||||||
|
int DXGI_FORMAT_BC1_UNORM_SRGB = 72;
|
||||||
|
int DXGI_FORMAT_BC2_TYPELESS = 73;
|
||||||
|
int DXGI_FORMAT_BC2_UNORM = 74;
|
||||||
|
int DXGI_FORMAT_BC2_UNORM_SRGB = 75;
|
||||||
|
int DXGI_FORMAT_BC3_TYPELESS = 76;
|
||||||
|
int DXGI_FORMAT_BC3_UNORM = 77;
|
||||||
|
int DXGI_FORMAT_BC3_UNORM_SRGB = 78;
|
||||||
|
int DXGI_FORMAT_BC4_TYPELESS = 79;
|
||||||
|
int DXGI_FORMAT_BC4_UNORM = 80;
|
||||||
|
int DXGI_FORMAT_BC4_SNORM = 81;
|
||||||
|
int DXGI_FORMAT_BC5_TYPELESS = 82;
|
||||||
|
int DXGI_FORMAT_BC5_UNORM = 83;
|
||||||
|
int DXGI_FORMAT_BC5_SNORM = 84;
|
||||||
|
int DXGI_FORMAT_B5G6R5_UNORM = 85;
|
||||||
|
int DXGI_FORMAT_B5G5R5A1_UNORM = 86;
|
||||||
|
int DXGI_FORMAT_B8G8R8A8_UNORM = 87;
|
||||||
|
int DXGI_FORMAT_B8G8R8X8_UNORM = 88;
|
||||||
|
int DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89;
|
||||||
|
int DXGI_FORMAT_B8G8R8A8_TYPELESS = 90;
|
||||||
|
int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91;
|
||||||
|
int DXGI_FORMAT_B8G8R8X8_TYPELESS = 92;
|
||||||
|
int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93;
|
||||||
|
int DXGI_FORMAT_BC6H_TYPELESS = 94;
|
||||||
|
int DXGI_FORMAT_BC6H_UF16 = 95;
|
||||||
|
int DXGI_FORMAT_BC6H_SF16 = 96;
|
||||||
|
int DXGI_FORMAT_BC7_TYPELESS = 97;
|
||||||
|
int DXGI_FORMAT_BC7_UNORM = 98;
|
||||||
|
int DXGI_FORMAT_BC7_UNORM_SRGB = 99;
|
||||||
|
int DXGI_FORMAT_AYUV = 100;
|
||||||
|
int DXGI_FORMAT_Y410 = 101;
|
||||||
|
int DXGI_FORMAT_Y416 = 102;
|
||||||
|
int DXGI_FORMAT_NV12 = 103;
|
||||||
|
int DXGI_FORMAT_P010 = 104;
|
||||||
|
int DXGI_FORMAT_P016 = 105;
|
||||||
|
int DXGI_FORMAT_420_OPAQUE = 106;
|
||||||
|
int DXGI_FORMAT_YUY2 = 107;
|
||||||
|
int DXGI_FORMAT_Y210 = 108;
|
||||||
|
int DXGI_FORMAT_Y216 = 109;
|
||||||
|
int DXGI_FORMAT_NV11 = 110;
|
||||||
|
int DXGI_FORMAT_AI44 = 111;
|
||||||
|
int DXGI_FORMAT_IA44 = 112;
|
||||||
|
int DXGI_FORMAT_P8 = 113;
|
||||||
|
int DXGI_FORMAT_A8P8 = 114;
|
||||||
|
int DXGI_FORMAT_B4G4R4A4_UNORM = 115;
|
||||||
|
int DXGI_FORMAT_P208 = 130;
|
||||||
|
int DXGI_FORMAT_V208 = 131;
|
||||||
|
int DXGI_FORMAT_V408 = 132;
|
||||||
|
int DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189;
|
||||||
|
int DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190;
|
||||||
|
int DXGI_FORMAT_FORCE_UINT = 0xffffffff;
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10">DDS_HEADER_DXT10 structure</a>
|
||||||
|
*/
|
||||||
|
final class DXT10Header {
|
||||||
|
final int dxgiFormat;
|
||||||
|
final int resourceDimension;
|
||||||
|
final int miscFlag;
|
||||||
|
final int arraySize;
|
||||||
|
final int miscFlags2;
|
||||||
|
|
||||||
|
private final DDSType type;
|
||||||
|
|
||||||
|
private DXT10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
|
||||||
|
type = DDSType.fromDXGIFormat(dxgiFormat); // Validates dxgiFormat
|
||||||
|
if (resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D) {
|
||||||
|
throw new IllegalArgumentException(String.format("Resource dimension %d is not supported, expected: %d",
|
||||||
|
resourceDimension, DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dxgiFormat = dxgiFormat;
|
||||||
|
this.resourceDimension = resourceDimension;
|
||||||
|
this.miscFlag = miscFlag;
|
||||||
|
this.arraySize = arraySize;
|
||||||
|
this.miscFlags2 = miscFlags2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DXT10Header read(ImageInputStream inputStream) throws IOException {
|
||||||
|
int dxgiFormat = inputStream.readInt();
|
||||||
|
int resourceDimension = inputStream.readInt();
|
||||||
|
int miscFlag = inputStream.readInt();
|
||||||
|
int arraySize = inputStream.readInt();
|
||||||
|
int miscFlags2 = inputStream.readInt();
|
||||||
|
|
||||||
|
return new DXT10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DDSType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
class DDSImageMetadataTest {
|
||||||
|
@Test
|
||||||
|
void standardMetadataDXT1() {
|
||||||
|
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.DXT1);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
|
||||||
|
assertEquals(1, compressionTypeNames.getLength());
|
||||||
|
IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
|
||||||
|
assertEquals("DXT1", compressionTypeName.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList losslesses = tree.getElementsByTagName("Lossless");
|
||||||
|
assertEquals(1, losslesses.getLength());
|
||||||
|
IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
|
||||||
|
assertEquals("FALSE", lossless.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void standardMetadataA8R8G8B8() {
|
||||||
|
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.A8R8G8B8);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressions = tree.getElementsByTagName("Compression");
|
||||||
|
assertEquals(0, compressions.getLength());
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void standardMetadataX8R8G8B8() {
|
||||||
|
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X8R8G8B8);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressions = tree.getElementsByTagName("Compression");
|
||||||
|
assertEquals(0, compressions.getLength());
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("8 8 8 0", bitsPerSample.getAttribute("value")); // Or just 8 8 8?
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("none", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void standardMetadataX1R5G5B5() {
|
||||||
|
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X1R5G5B5);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressions = tree.getElementsByTagName("Compression");
|
||||||
|
assertEquals(0, compressions.getLength());
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("5 5 5 0", bitsPerSample.getAttribute("value")); // Or just 5 5 5?
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("none", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DDSImageMetadata createDDSImageMetadata(int bufferedImageType, DDSType ddsType) {
|
||||||
|
return new DDSImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(bufferedImageType), ddsType);
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
@@ -30,14 +30,27 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader> {
|
public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader> {
|
||||||
@Override
|
@Override
|
||||||
protected ImageReaderSpi createProvider() {
|
protected ImageReaderSpi createProvider() {
|
||||||
@@ -110,4 +123,67 @@ public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader>
|
|||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Collections.singletonList("image/vnd-ms.dds");
|
return Collections.singletonList("image/vnd-ms.dds");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void metadataDXT5() throws IOException {
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
|
||||||
|
try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_DXT5.dds"))) {
|
||||||
|
reader.setInput(inputStream);
|
||||||
|
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
|
||||||
|
assertEquals(1, compressionTypeNames.getLength());
|
||||||
|
IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
|
||||||
|
assertEquals("DXT5", compressionTypeName.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList losslesses = tree.getElementsByTagName("Lossless");
|
||||||
|
assertEquals(1, losslesses.getLength());
|
||||||
|
IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
|
||||||
|
assertEquals("FALSE", lossless.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void metadataRGB565() throws IOException {
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
|
||||||
|
try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_R5G6B5.dds"))) {
|
||||||
|
reader.setInput(inputStream);
|
||||||
|
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
NodeList compressions = tree.getElementsByTagName("Compression");
|
||||||
|
assertEquals(0, compressions.getLength());
|
||||||
|
|
||||||
|
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
|
||||||
|
assertEquals(1, bitsPerSamples.getLength());
|
||||||
|
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
|
||||||
|
assertEquals("5 6 5 0", bitsPerSample.getAttribute("value")); // or "5 6 5"
|
||||||
|
|
||||||
|
NodeList alphas = tree.getElementsByTagName("Alpha");
|
||||||
|
assertEquals(1, alphas.getLength());
|
||||||
|
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
|
||||||
|
assertEquals("none", alpha.getAttribute("value"));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class DDSImageWriteParamTest {
|
||||||
|
@Test
|
||||||
|
void defaultParam() {
|
||||||
|
DDSImageWriteParam param = new DDSImageWriteParam();
|
||||||
|
assertEquals(DDSImageWriteParam.DEFAULT_TYPE, param.type());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void compressionTypes() {
|
||||||
|
DDSImageWriteParam param = new DDSImageWriteParam();
|
||||||
|
|
||||||
|
String[] compressionTypes = param.getCompressionTypes();
|
||||||
|
DDSType[] values = Arrays.stream(DDSType.values())
|
||||||
|
.filter(DDSType::isBlockCompression)
|
||||||
|
.toArray(DDSType[]::new);
|
||||||
|
|
||||||
|
assertEquals(values.length + 1, compressionTypes.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
DDSType type = values[i];
|
||||||
|
assertEquals(type.name(), compressionTypes[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("None", compressionTypes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setCompression() {
|
||||||
|
DDSImageWriteParam param = new DDSImageWriteParam();
|
||||||
|
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
|
||||||
|
String[] compressionTypes = param.getCompressionTypes();
|
||||||
|
for (String compressionType : compressionTypes) {
|
||||||
|
param.setCompressionType(compressionType);
|
||||||
|
assertEquals(compressionType, param.getCompressionType());
|
||||||
|
|
||||||
|
if (!"None".equals(compressionType)) {
|
||||||
|
DDSType type = DDSType.valueOf(compressionType);
|
||||||
|
|
||||||
|
assertEquals(type, param.type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+168
-4
@@ -1,13 +1,38 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.dds;
|
package com.twelvemonkeys.imageio.plugins.dds;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.event.IIOWriteWarningListener;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.Collections;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DDSImageWriterTest extends ImageWriterAbstractTest<DDSImageWriter> {
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class DDSImageWriterTest extends ImageWriterAbstractTest<DDSImageWriter> {
|
||||||
@Override
|
@Override
|
||||||
protected ImageWriterSpi createProvider() {
|
protected ImageWriterSpi createProvider() {
|
||||||
return new DDSImageWriterSpi();
|
return new DDSImageWriterSpi();
|
||||||
@@ -15,8 +40,147 @@ public class DDSImageWriterTest extends ImageWriterAbstractTest<DDSImageWriter>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<BufferedImage> getTestData() {
|
protected List<BufferedImage> getTestData() {
|
||||||
return Collections.singletonList(
|
return Arrays.asList(
|
||||||
new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB_PRE)
|
new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB),
|
||||||
|
new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB),
|
||||||
|
new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB),
|
||||||
|
new BufferedImage(32, 32, BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
|
new BufferedImage(16, 16, BufferedImage.TYPE_3BYTE_BGR)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeRasters() throws IOException {
|
||||||
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
|
assertTrue(writer.canWriteRasters());
|
||||||
|
|
||||||
|
// Full tests in super class
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeMipmap() throws IOException {
|
||||||
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertTrue(writer.canWriteSequence());
|
||||||
|
|
||||||
|
List<BufferedImage> testData = getTestData();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
int previousSize = 0;
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
writer.prepareWriteSequence(null);
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
|
||||||
|
assertTrue(buffer.size() > previousSize);
|
||||||
|
previousSize = buffer.size();
|
||||||
|
|
||||||
|
for (BufferedImage image : testData) {
|
||||||
|
writer.writeToSequence(new IIOImage(drawSomething(image), null, null), param);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.endWriteSequence();
|
||||||
|
assertTrue(buffer.size() > previousSize, "No image data written");
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we can read the file back...
|
||||||
|
ImageReader reader = ImageIO.getImageReader(writer);
|
||||||
|
|
||||||
|
try (ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
|
||||||
|
stream.seek(0);
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
assertEquals(testData.size(), reader.getNumImages(false));
|
||||||
|
|
||||||
|
for (int i = 0; i < testData.size(); i++) {
|
||||||
|
BufferedImage image = reader.read(i, null);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(testData.get(i).getWidth(), image.getWidth());
|
||||||
|
assertEquals(testData.get(i).getHeight(), image.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeMipmapDifferentCompression() throws IOException {
|
||||||
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
IIOWriteWarningListener listener = mock();
|
||||||
|
writer.addIIOWriteWarningListener(listener);
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
writer.prepareWriteSequence(null);
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
param.setCompressionType("DXT2");
|
||||||
|
|
||||||
|
// Write first with DXT2
|
||||||
|
List<BufferedImage> testData = getTestData();
|
||||||
|
writer.writeToSequence(new IIOImage(drawSomething(testData.get(0)), null, null), param);
|
||||||
|
|
||||||
|
// Repeat with different type
|
||||||
|
IIOImage image = new IIOImage(drawSomething(testData.get(1)), null, null);
|
||||||
|
param.setCompressionType("DXT1");
|
||||||
|
|
||||||
|
writer.writeToSequence(image, param);
|
||||||
|
|
||||||
|
// Verify warning is issued
|
||||||
|
verify(listener).warningOccurred(eq(writer), eq(1), anyString());
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeMipmapUnexpectedSize() throws IOException {
|
||||||
|
ImageWriter writer = createWriter();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
writer.prepareWriteSequence(null);
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
BufferedImage testData = getTestData().get(0);
|
||||||
|
|
||||||
|
IIOImage image = new IIOImage(drawSomething(testData), null, null);
|
||||||
|
writer.writeToSequence(image, param);
|
||||||
|
|
||||||
|
// Repeat with same size... boom.
|
||||||
|
assertThrows(IIOException.class, () -> writer.writeToSequence(image, param));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -111,6 +111,7 @@ enum TupleType {
|
|||||||
|
|
||||||
static TupleType forPAM(Raster raster) {
|
static TupleType forPAM(Raster raster) {
|
||||||
SampleModel sampleModel = raster.getSampleModel();
|
SampleModel sampleModel = raster.getSampleModel();
|
||||||
|
|
||||||
switch (sampleModel.getTransferType()) {
|
switch (sampleModel.getTransferType()) {
|
||||||
case DataBuffer.TYPE_BYTE:
|
case DataBuffer.TYPE_BYTE:
|
||||||
case DataBuffer.TYPE_USHORT:
|
case DataBuffer.TYPE_USHORT:
|
||||||
@@ -145,8 +146,12 @@ enum TupleType {
|
|||||||
return TupleType.RGB;
|
return TupleType.RGB;
|
||||||
}
|
}
|
||||||
else if (bands == 4) {
|
else if (bands == 4) {
|
||||||
|
// Ambiguous, could also be CMYK...
|
||||||
return TupleType.RGB_ALPHA;
|
return TupleType.RGB_ALPHA;
|
||||||
}
|
}
|
||||||
|
else if (bands == 5) {
|
||||||
|
return TupleType.CMYK_ALPHA;
|
||||||
|
}
|
||||||
// ...else fall through...
|
// ...else fall through...
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +159,7 @@ enum TupleType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static TupleType forPAM(ImageTypeSpecifier type) {
|
static TupleType forPAM(ImageTypeSpecifier type) {
|
||||||
// Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
|
// Support only 1 bit b/w, 8-16 bit gray, 8-16 bit/sample RGB and 8-16 bit/sample CMYK
|
||||||
switch (type.getBufferedImageType()) {
|
switch (type.getBufferedImageType()) {
|
||||||
// 1 bit b/w or b/w + a
|
// 1 bit b/w or b/w + a
|
||||||
case BufferedImage.TYPE_BYTE_BINARY:
|
case BufferedImage.TYPE_BYTE_BINARY:
|
||||||
|
|||||||
Reference in New Issue
Block a user