diff --git a/bom/pom.xml b/bom/pom.xml index 7df77887..d96ce6e8 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -63,6 +63,11 @@ imageio-hdr ${project.version} + + com.twelvemonkeys.imageio + imageio-dds + ${project.version} + com.twelvemonkeys.imageio imageio-icns diff --git a/imageio/imageio-dds/pom.xml b/imageio/imageio-dds/pom.xml new file mode 100644 index 00000000..c2617d20 --- /dev/null +++ b/imageio/imageio-dds/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.twelvemonkeys.imageio + imageio + 3.11.1-SNAPSHOT + + imageio-dds + TwelveMonkeys :: ImageIO :: DDS plugin + + ImageIO plugin for Microsoft Direct DrawSurface (DDS). + + + + com.twelvemonkeys.imageio.dds + + + + + com.twelvemonkeys.imageio + imageio-core + + + com.twelvemonkeys.imageio + imageio-core + test-jar + test + + + com.twelvemonkeys.imageio + imageio-metadata + + + + + + + org.apache.felix + maven-bundle-plugin + + + + osgi.serviceloader; + osgi.serviceloader=javax.imageio.spi.ImageReaderSpi + + + + + + + diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java new file mode 100644 index 00000000..b6958569 --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDS.java @@ -0,0 +1,5 @@ +package com.twelvemonkeys.imageio.plugins.dds; + +interface DDS { + byte[] MAGIC = new byte[] {'D', 'D', 'S', ' '}; +} diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java new file mode 100644 index 00000000..91dc0aa2 --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSHeader.java @@ -0,0 +1,61 @@ +package com.twelvemonkeys.imageio.plugins.dds; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; + +final class DDSHeader { + + // https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide + private int width; + private int height; + + public static DDSHeader read(final ImageInputStream imageInput) throws IOException { + DDSHeader header = new DDSHeader(); + + imageInput.mark(); + imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + + byte[] magic = new byte[4]; + imageInput.readFully(magic); + + // DDS_HEADER structure + int dwSize = imageInput.readInt(); + int dwFlags = imageInput.readInt(); + header.height = imageInput.readInt(); + header.width = imageInput.readInt(); + int dwPitchOrLinearSize = imageInput.readInt(); + int dwDepth = imageInput.readInt(); + int dwMipMapCount = imageInput.readInt(); + + byte[] dwReserved1 = new byte[11]; + imageInput.readFully(dwReserved1); + + // DDS_PIXELFORMAT structure + int px_dwSize = imageInput.readInt(); + int px_dwFlags = imageInput.readInt(); + int px_dwFourCC = imageInput.readInt(); + int px_dwRGBBitCount = imageInput.readInt(); + int px_dwRBitMask = imageInput.readInt(); + int px_dwGBitMask = imageInput.readInt(); + int px_dwBBitMask = imageInput.readInt(); + int px_dwABitMask = imageInput.readInt(); + + int dwCaps = imageInput.readInt(); + int dwCaps2 = imageInput.readInt(); + int dwCaps3 = imageInput.readInt(); + int dwCaps4 = imageInput.readInt(); + int dwReserved2 = imageInput.readInt(); + + imageInput.reset(); + return header; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } +} diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java new file mode 100644 index 00000000..5979a5c2 --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReader.java @@ -0,0 +1,97 @@ +package com.twelvemonkeys.imageio.plugins.dds; + +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.spi.ImageReaderSpi; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; + +public final class DDSImageReader extends ImageReaderBase { + + private DDSHeader header; + + public DDSImageReader(final ImageReaderSpi provider) { + super(provider); + } + + @Override + protected void resetMembers() { + header = null; + } + + @Override + public int getWidth(final int imageIndex) throws IOException { + checkBounds(imageIndex); + readHeader(); + + return header.getWidth(); + } + + @Override + public int getHeight(int imageIndex) throws IOException { + checkBounds(imageIndex); + readHeader(); + + return header.getHeight(); + } + + @Override + public Iterator getImageTypes(int imageIndex) throws IOException { + checkBounds(imageIndex); + readHeader(); + + // TODO changes based on format + ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); + return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[]{0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator(); + } + + @Override + public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { + checkBounds(imageIndex); + readHeader(); + + int width = getWidth(imageIndex); + int height = getHeight(imageIndex); + + BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height); + + // TODO + byte[] buffer = new byte[width * height * 4]; + imageInput.read(buffer); + + int[] pixels = DDSReader.read(buffer, DDSReader.ARGB, 0); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + image.setRGB(0, 0, width, height, pixels, 0, width); + + processImageComplete(); + + return image; + } + + private void readHeader() throws IOException { + if (header == null) { + header = DDSHeader.read(imageInput); + + imageInput.flushBefore(imageInput.getStreamPosition()); + } + + imageInput.seek(imageInput.getFlushedPosition()); + } + + public static void main(final String[] args) throws IOException { + File file = new File("imageio/imageio-dds/src/test/resources/dds/dxt5.dds"); + + BufferedImage image = ImageIO.read(file); + + showIt(image, file.getName()); + } +} diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java new file mode 100644 index 00000000..599168da --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSImageReaderSpi.java @@ -0,0 +1,46 @@ +package com.twelvemonkeys.imageio.plugins.dds; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; + +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Locale; + +public final class DDSImageReaderSpi extends ImageReaderSpiBase { + + public DDSImageReaderSpi() { + super(new DDSProviderInfo()); + } + + @Override + public boolean canDecodeInput(final Object source) throws IOException { + if (!(source instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream) source; + + stream.mark(); + + try { + byte[] magic = new byte[DDS.MAGIC.length]; + stream.readFully(magic); + + return Arrays.equals(DDS.MAGIC, magic); + } finally { + stream.reset(); + } + } + + @Override + public ImageReader createReaderInstance(Object extension) throws IOException { + return new DDSImageReader(this); + } + + @Override + public String getDescription(Locale locale) { + return "Direct DrawSurface (DDS) Image Reader"; + } +} diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java new file mode 100644 index 00000000..969c98da --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSProviderInfo.java @@ -0,0 +1,20 @@ +package com.twelvemonkeys.imageio.plugins.dds; + +import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; + +final class DDSProviderInfo extends ReaderWriterProviderInfo { + DDSProviderInfo() { + super( + DDSProviderInfo.class, + new String[] {"DDS", "dds"}, + new String[] {"dds"}, + new String[] {"image/vnd-ms.dds"}, + "com.twelvemonkeys.imageio.plugins.dds.DDSImageReader", + new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi"}, + null, + null, + false, null, null, null, null, + true, null, null, null, null + ); + } +} diff --git a/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java new file mode 100644 index 00000000..c12f08e7 --- /dev/null +++ b/imageio/imageio-dds/src/main/java/com/twelvemonkeys/imageio/plugins/dds/DDSReader.java @@ -0,0 +1,629 @@ +/** + * DDSReader.java + * + * Copyright (c) 2015 Kenji Sasaki + * Released under the MIT license. + * https://github.com/npedotnet/DDSReader/blob/master/LICENSE + * + * English document + * https://github.com/npedotnet/DDSReader/blob/master/README.md + * + * Japanese document + * http://3dtech.jp/wiki/index.php?DDSReader + * + */ + +package com.twelvemonkeys.imageio.plugins.dds; + +public final class DDSReader { + + public static final Order ARGB = new Order(16, 8, 0, 24); + public static final Order ABGR = new Order(0, 8, 16, 24); + + public static int getHeight(byte[] buffer) { + return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8 | (buffer[14] & 0xFF) << 16 | (buffer[15] & 0xFF) << 24; + } + + public static int getWidth(byte[] buffer) { + return (buffer[16] & 0xFF) | (buffer[17] & 0xFF) << 8 | (buffer[18] & 0xFF) << 16 | (buffer[19] & 0xFF) << 24; + } + + public static int getMipmap(byte[] buffer) { + return (buffer[28] & 0xFF) | (buffer[29] & 0xFF) << 8 | (buffer[30] & 0xFF) << 16 | (buffer[31] & 0xFF) << 24; + } + + public static int getPixelFormatFlags(byte[] buffer) { + return (buffer[80] & 0xFF) | (buffer[81] & 0xFF) << 8 | (buffer[82] & 0xFF) << 16 | (buffer[83] & 0xFF) << 24; + } + + public static int getFourCC(byte[] buffer) { + return (buffer[84] & 0xFF) << 24 | (buffer[85] & 0xFF) << 16 | (buffer[86] & 0xFF) << 8 | (buffer[87] & 0xFF); + } + + public static int getBitCount(byte[] buffer) { + return (buffer[88] & 0xFF) | (buffer[89] & 0xFF) << 8 | (buffer[90] & 0xFF) << 16 | (buffer[91] & 0xFF) << 24; + } + + public static int getRedMask(byte[] buffer) { + return (buffer[92] & 0xFF) | (buffer[93] & 0xFF) << 8 | (buffer[94] & 0xFF) << 16 | (buffer[95] & 0xFF) << 24; + } + + public static int getGreenMask(byte[] buffer) { + return (buffer[96] & 0xFF) | (buffer[97] & 0xFF) << 8 | (buffer[98] & 0xFF) << 16 | (buffer[99] & 0xFF) << 24; + } + + public static int getBlueMask(byte[] buffer) { + return (buffer[100] & 0xFF) | (buffer[101] & 0xFF) << 8 | (buffer[102] & 0xFF) << 16 | (buffer[103] & 0xFF) << 24; + } + + public static int getAlphaMask(byte[] buffer) { + return (buffer[104] & 0xFF) | (buffer[105] & 0xFF) << 8 | (buffer[106] & 0xFF) << 16 | (buffer[107] & 0xFF) << 24; + } + + public static int[] read(byte[] buffer, Order order, int mipmapLevel) { + + // header + int width = getWidth(buffer); + int height = getHeight(buffer); + int mipmap = getMipmap(buffer); + + // type + int type = getType(buffer); + if (type == 0) return null; + + // offset + int offset = 128; // header size + if (mipmapLevel > 0 && mipmapLevel < mipmap) { + for (int i = 0; i < mipmapLevel; i++) { + switch (type) { + case DXT1: + offset += 8 * ((width + 3) / 4) * ((height + 3) / 4); + break; + case DXT2: + case DXT3: + case DXT4: + case DXT5: + offset += 16 * ((width + 3) / 4) * ((height + 3) / 4); + break; + case A1R5G5B5: + case X1R5G5B5: + case A4R4G4B4: + case X4R4G4B4: + case R5G6B5: + case R8G8B8: + case A8B8G8R8: + case X8B8G8R8: + case A8R8G8B8: + case X8R8G8B8: + offset += (type & 0xFF) * width * height; + break; + } + width /= 2; + height /= 2; + } + if (width <= 0) width = 1; + if (height <= 0) height = 1; + } + + int[] pixels = null; + switch (type) { + case DXT1: + pixels = decodeDXT1(width, height, offset, buffer, order); + break; + case DXT2: + pixels = decodeDXT2(width, height, offset, buffer, order); + break; + case DXT3: + pixels = decodeDXT3(width, height, offset, buffer, order); + break; + case DXT4: + pixels = decodeDXT4(width, height, offset, buffer, order); + break; + case DXT5: + pixels = decodeDXT5(width, height, offset, buffer, order); + break; + case A1R5G5B5: + pixels = readA1R5G5B5(width, height, offset, buffer, order); + break; + case X1R5G5B5: + pixels = readX1R5G5B5(width, height, offset, buffer, order); + break; + case A4R4G4B4: + pixels = readA4R4G4B4(width, height, offset, buffer, order); + break; + case X4R4G4B4: + pixels = readX4R4G4B4(width, height, offset, buffer, order); + break; + case R5G6B5: + pixels = readR5G6B5(width, height, offset, buffer, order); + break; + case R8G8B8: + pixels = readR8G8B8(width, height, offset, buffer, order); + break; + case A8B8G8R8: + pixels = readA8B8G8R8(width, height, offset, buffer, order); + break; + case X8B8G8R8: + pixels = readX8B8G8R8(width, height, offset, buffer, order); + break; + case A8R8G8B8: + pixels = readA8R8G8B8(width, height, offset, buffer, order); + break; + case X8R8G8B8: + pixels = readX8R8G8B8(width, height, offset, buffer, order); + break; + } + + return pixels; + } + + private static int getType(byte[] buffer) { + + int type = 0; + + int flags = getPixelFormatFlags(buffer); + + if ((flags & 0x04) != 0) { + // DXT + type = getFourCC(buffer); + } else if ((flags & 0x40) != 0) { + // RGB + int bitCount = getBitCount(buffer); + int redMask = getRedMask(buffer); + int greenMask = getGreenMask(buffer); + int blueMask = getBlueMask(buffer); + int alphaMask = ((flags & 0x01) != 0) ? getAlphaMask(buffer) : 0; // 0x01 alpha + if (bitCount == 16) { + if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) { + // A1R5G5B5 + type = A1R5G5B5; + } else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) { + // X1R5G5B5 + type = X1R5G5B5; + } else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) { + // A4R4G4B4 + type = A4R4G4B4; + } else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) { + // X4R4G4B4 + type = X4R4G4B4; + } else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) { + // R5G6B5 + type = R5G6B5; + } else { + // 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 + type = R8G8B8; + } else { + // 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 + type = A8B8G8R8; + } else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) { + // X8B8G8R8 + type = X8B8G8R8; + } else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) { + // A8R8G8B8 + type = A8R8G8B8; + } else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) { + // X8R8G8B8 + type = X8R8G8B8; + } else { + // Unsupported 32bit RGB image + } + } + } else { + // YUV or LUMINANCE image + } + + return type; + + } + + private static int[] decodeDXT1(int width, int height, int offset, byte[] buffer, Order order) { + int[] pixels = new int[width * height]; + int index = offset; + int w = (width + 3) / 4; + int h = (height + 3) / 4; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + for (int k = 0; k < 4; k++) { + if (4 * i + k >= height) break; + int t0 = (buffer[index] & 0x03); + int t1 = (buffer[index] & 0x0C) >> 2; + int t2 = (buffer[index] & 0x30) >> 4; + int t3 = (buffer[index++] & 0xC0) >> 6; + pixels[4 * width * i + 4 * j + width * k + 0] = getDXTColor(c0, c1, 0xFF, t0, order); + if (4 * j + 1 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, 0xFF, t1, order); + if (4 * j + 2 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, 0xFF, t2, order); + if (4 * j + 3 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, 0xFF, t3, order); + } + } + } + return pixels; + } + + private static int[] decodeDXT2(int width, int height, int offset, byte[] buffer, Order order) { + return decodeDXT3(width, height, offset, buffer, order); + } + + private static int[] decodeDXT3(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int w = (width + 3) / 4; + int h = (height + 3) / 4; + int[] pixels = new int[width * height]; + int[] alphaTable = new int[16]; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + // create alpha table(4bit to 8bit) + for (int k = 0; k < 4; k++) { + int a0 = (buffer[index++] & 0xFF); + int a1 = (buffer[index++] & 0xFF); + // 4bit alpha to 8bit alpha + alphaTable[4 * k + 0] = 17 * ((a0 & 0xF0) >> 4); + alphaTable[4 * k + 1] = 17 * (a0 & 0x0F); + alphaTable[4 * k + 2] = 17 * ((a1 & 0xF0) >> 4); + alphaTable[4 * k + 3] = 17 * (a1 & 0x0F); + } + int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + for (int k = 0; k < 4; k++) { + if (4 * i + k >= height) break; + int t0 = (buffer[index] & 0x03); + int t1 = (buffer[index] & 0x0C) >> 2; + int t2 = (buffer[index] & 0x30) >> 4; + int t3 = (buffer[index++] & 0xC0) >> 6; + pixels[4 * width * i + 4 * j + width * k + 0] = getDXTColor(c0, c1, alphaTable[4 * k + 0], t0, order); + if (4 * j + 1 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, alphaTable[4 * k + 1], t1, order); + if (4 * j + 2 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, alphaTable[4 * k + 2], t2, order); + if (4 * j + 3 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, alphaTable[4 * k + 3], t3, order); + } + } + } + return pixels; + } + + private static int[] decodeDXT4(int width, int height, int offset, byte[] buffer, Order order) { + return decodeDXT5(width, height, offset, buffer, order); + } + + private static int[] decodeDXT5(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int w = (width + 3) / 4; + int h = (height + 3) / 4; + int[] pixels = new int[width * height]; + int[] alphaTable = new int[16]; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + // create alpha table + int a0 = (buffer[index++] & 0xFF); + int a1 = (buffer[index++] & 0xFF); + int b0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16; + index += 3; + int b1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16; + index += 3; + alphaTable[0] = b0 & 0x07; + alphaTable[1] = (b0 >> 3) & 0x07; + alphaTable[2] = (b0 >> 6) & 0x07; + alphaTable[3] = (b0 >> 9) & 0x07; + alphaTable[4] = (b0 >> 12) & 0x07; + alphaTable[5] = (b0 >> 15) & 0x07; + alphaTable[6] = (b0 >> 18) & 0x07; + alphaTable[7] = (b0 >> 21) & 0x07; + alphaTable[8] = b1 & 0x07; + alphaTable[9] = (b1 >> 3) & 0x07; + alphaTable[10] = (b1 >> 6) & 0x07; + alphaTable[11] = (b1 >> 9) & 0x07; + alphaTable[12] = (b1 >> 12) & 0x07; + alphaTable[13] = (b1 >> 15) & 0x07; + alphaTable[14] = (b1 >> 18) & 0x07; + alphaTable[15] = (b1 >> 21) & 0x07; + int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + for (int k = 0; k < 4; k++) { + if (4 * i + k >= height) break; + int t0 = (buffer[index] & 0x03); + int t1 = (buffer[index] & 0x0C) >> 2; + int t2 = (buffer[index] & 0x30) >> 4; + int t3 = (buffer[index++] & 0xC0) >> 6; + pixels[4 * width * i + 4 * j + width * k + 0] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 0]), t0, order); + if (4 * j + 1 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 1]), t1, order); + if (4 * j + 2 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 2]), t2, order); + if (4 * j + 3 >= width) continue; + pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 3]), t3, order); + } + } + } + return pixels; + } + + private static int[] readA1R5G5B5(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int r = BIT5[(rgba & A1R5G5B5_MASKS[0]) >> 10]; + int g = BIT5[(rgba & A1R5G5B5_MASKS[1]) >> 5]; + int b = BIT5[(rgba & A1R5G5B5_MASKS[2])]; + int a = 255 * ((rgba & A1R5G5B5_MASKS[3]) >> 15); + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readX1R5G5B5(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int r = BIT5[(rgba & X1R5G5B5_MASKS[0]) >> 10]; + int g = BIT5[(rgba & X1R5G5B5_MASKS[1]) >> 5]; + int b = BIT5[(rgba & X1R5G5B5_MASKS[2])]; + int a = 255; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readA4R4G4B4(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8); + int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4); + int b = 17 * ((rgba & A4R4G4B4_MASKS[2])); + int a = 17 * ((rgba & A4R4G4B4_MASKS[3]) >> 12); + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readX4R4G4B4(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8); + int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4); + int b = 17 * ((rgba & A4R4G4B4_MASKS[2])); + int a = 255; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readR5G6B5(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; + index += 2; + int r = BIT5[((rgba & R5G6B5_MASKS[0]) >> 11)]; + int g = BIT6[((rgba & R5G6B5_MASKS[1]) >> 5)]; + int b = BIT5[((rgba & R5G6B5_MASKS[2]))]; + int a = 255; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readR8G8B8(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int b = buffer[index++] & 0xFF; + int g = buffer[index++] & 0xFF; + int r = buffer[index++] & 0xFF; + int a = 255; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readA8B8G8R8(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int r = buffer[index++] & 0xFF; + int g = buffer[index++] & 0xFF; + int b = buffer[index++] & 0xFF; + int a = buffer[index++] & 0xFF; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readX8B8G8R8(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int r = buffer[index++] & 0xFF; + int g = buffer[index++] & 0xFF; + int b = buffer[index++] & 0xFF; + int a = 255; + index++; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readA8R8G8B8(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int b = buffer[index++] & 0xFF; + int g = buffer[index++] & 0xFF; + int r = buffer[index++] & 0xFF; + int a = buffer[index++] & 0xFF; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int[] readX8R8G8B8(int width, int height, int offset, byte[] buffer, Order order) { + int index = offset; + int[] pixels = new int[width * height]; + for (int i = 0; i < height * width; i++) { + int b = buffer[index++] & 0xFF; + int g = buffer[index++] & 0xFF; + int r = buffer[index++] & 0xFF; + int a = 255; + index++; + pixels[i] = (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + return pixels; + } + + private static int getDXTColor(int c0, int c1, int a, int t, Order order) { + switch (t) { + case 0: + return getDXTColor1(c0, a, order); + case 1: + return getDXTColor1(c1, a, order); + case 2: + return (c0 > c1) ? getDXTColor2_1(c0, c1, a, order) : getDXTColor1_1(c0, c1, a, order); + case 3: + return (c0 > c1) ? getDXTColor2_1(c1, c0, a, order) : 0; + } + return 0; + } + + private static int getDXTColor2_1(int c0, int c1, int a, Order order) { + // 2*c0/3 + c1/3 + int r = (2 * BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 3; + int g = (2 * BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 3; + int b = (2 * BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 3; + return (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + + private static int getDXTColor1_1(int c0, int c1, int a, Order order) { + // (c0+c1) / 2 + int r = (BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 2; + int g = (BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 2; + int b = (BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 2; + return (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + + private static int getDXTColor1(int c, int a, Order order) { + int r = BIT5[(c & 0xFC00) >> 11]; + int g = BIT6[(c & 0x07E0) >> 5]; + int b = BIT5[(c & 0x001F)]; + return (a << order.alphaShift) | (r << order.redShift) | (g << order.greenShift) | (b << order.blueShift); + } + + private static int getDXT5Alpha(int a0, int a1, int t) { + if (a0 > a1) switch (t) { + case 0: + return a0; + case 1: + return a1; + case 2: + return (6 * a0 + a1) / 7; + case 3: + return (5 * a0 + 2 * a1) / 7; + case 4: + return (4 * a0 + 3 * a1) / 7; + case 5: + return (3 * a0 + 4 * a1) / 7; + case 6: + return (2 * a0 + 5 * a1) / 7; + case 7: + return (a0 + 6 * a1) / 7; + } + else switch (t) { + case 0: + return a0; + case 1: + return a1; + case 2: + return (4 * a0 + a1) / 5; + case 3: + return (3 * a0 + 2 * a1) / 5; + case 4: + return (2 * a0 + 3 * a1) / 5; + case 5: + return (a0 + 4 * a1) / 5; + case 6: + return 0; + case 7: + return 255; + } + return 0; + } + + // Image Type + private static final int DXT1 = (0x44585431); + private static final int DXT2 = (0x44585432); + private static final int DXT3 = (0x44585433); + private static final int DXT4 = (0x44585434); + private static final int DXT5 = (0x44585435); + private static final int A1R5G5B5 = ((1 << 16) | 2); + private static final int X1R5G5B5 = ((2 << 16) | 2); + private static final int A4R4G4B4 = ((3 << 16) | 2); + private static final int X4R4G4B4 = ((4 << 16) | 2); + private static final int R5G6B5 = ((5 << 16) | 2); + private static final int R8G8B8 = ((1 << 16) | 3); + private static final int A8B8G8R8 = ((1 << 16) | 4); + private static final int X8B8G8R8 = ((2 << 16) | 4); + private static final int A8R8G8B8 = ((3 << 16) | 4); + private static final int X8R8G8B8 = ((4 << 16) | 4); + + // RGBA Masks + private static final int[] A1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x8000}; + private static final int[] X1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x0000}; + private static final int[] A4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0xF000}; + private static final int[] X4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0x0000}; + private static final int[] R5G6B5_MASKS = {0xF800, 0x07E0, 0x001F, 0x0000}; + private static final int[] R8G8B8_MASKS = {0xFF0000, 0x00FF00, 0x0000FF, 0x000000}; + private static final int[] A8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000}; + private static final int[] X8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000}; + private static final int[] A8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000}; + private static final int[] X8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000}; + + // BIT4 = 17 * index; + private static final int[] BIT5 = {0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255}; + private static final int[] BIT6 = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255}; + + private DDSReader() { + } + + private static final class Order { + Order(int redShift, int greenShift, int blueShift, int alphaShift) { + this.redShift = redShift; + this.greenShift = greenShift; + this.blueShift = blueShift; + this.alphaShift = alphaShift; + } + + public int redShift; + public int greenShift; + public int blueShift; + public int alphaShift; + } + +} diff --git a/imageio/imageio-dds/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-dds/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi new file mode 100755 index 00000000..8be73657 --- /dev/null +++ b/imageio/imageio-dds/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -0,0 +1 @@ +com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi diff --git a/imageio/imageio-dds/src/test/java/DDSImageTeaderTest.java b/imageio/imageio-dds/src/test/java/DDSImageTeaderTest.java new file mode 100644 index 00000000..3576e69e --- /dev/null +++ b/imageio/imageio-dds/src/test/java/DDSImageTeaderTest.java @@ -0,0 +1,38 @@ +import com.twelvemonkeys.imageio.plugins.dds.DDSImageReader; +import com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi; +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + +import javax.imageio.spi.ImageReaderSpi; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class DDSImageTeaderTest extends ImageReaderAbstractTest { + @Override + protected ImageReaderSpi createProvider() { + return new DDSImageReaderSpi(); + } + + @Override + protected List getTestData() { + return Collections.singletonList( + new TestData(getClassLoaderResource("/dds/dxt5.dds"), new Dimension(512, 512)) + ); + } + + @Override + protected List getFormatNames() { + return Arrays.asList("DDS", "dds"); + } + + @Override + protected List getSuffixes() { + return Arrays.asList("dds"); + } + + @Override + protected List getMIMETypes() { + return Collections.singletonList("image/vnd-ms.dds"); + } +} diff --git a/imageio/imageio-dds/src/test/resources/dds/dxt5.dds b/imageio/imageio-dds/src/test/resources/dds/dxt5.dds new file mode 100644 index 00000000..309f2c7d Binary files /dev/null and b/imageio/imageio-dds/src/test/resources/dds/dxt5.dds differ diff --git a/imageio/pom.xml b/imageio/pom.xml index e3fadcca..5c6fd041 100644 --- a/imageio/pom.xml +++ b/imageio/pom.xml @@ -33,6 +33,7 @@ imageio-bmp imageio-hdr + imageio-dds imageio-icns imageio-iff imageio-jpeg