/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.formats.dds; import gov.nasa.worldwind.util.*; import java.awt.image.*; import java.io.*; import java.nio.*; /** * @author Tom Gaskins * @version $Id: DDSConverter.java 5053 2008-04-14 04:55:33Z tgaskins $ */ public class DDSConverter { private static final int DDSD_CAPS = 0x0001; private static final int DDSD_HEIGHT = 0x0002; private static final int DDSD_WIDTH = 0x0004; private static final int DDSD_PIXELFORMAT = 0x1000; private static final int DDSD_MIPMAPCOUNT = 0x20000; private static final int DDSD_LINEARSIZE = 0x80000; private static final int DDPF_FOURCC = 0x0004; private static final int DDSCAPS_TEXTURE = 0x1000; protected static class Color { private int r, g, b; public Color() { this.r = this.g = this.b = 0; } public Color(int r, int g, int b) { this.r = r; this.g = g; this.b = b; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final Color color = (Color) o; if (b != color.b) return false; if (g != color.g) return false; //noinspection RedundantIfStatement if (r != color.r) return false; return true; } public int hashCode() { int result; result = r; result = 29 * result + g; result = 29 * result + b; return result; } } public static ByteBuffer convertToDDS(ByteBuffer image, String mimeType) throws IOException { if (image == null) { String message = Logging.getMessage("nullValue.ByteBufferIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (mimeType == null) { String message = Logging.getMessage("nullValue.MimeTypeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } String suffix = WWIO.makeSuffixForMimeType(mimeType); if (suffix == null) { String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType); Logging.logger().severe(message); throw new IllegalArgumentException(message); } File tempFile = WWIO.saveBufferToTempFile(image, suffix); return convertToDDS(tempFile); } public static ByteBuffer convertToDDS(File file) throws IOException { if (file == null) { String message = Logging.getMessage("nullValue.FileIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!file.exists() || !file.canRead()) { String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); if (image == null) { return null; } // Don't waste the space for transparency if if (image.getColorModel().hasAlpha()) return convertToDxt3(image); else return convertToDxt1NoTransparency(image); } public static ByteBuffer convertToDxt1NoTransparency(ByteBuffer image, String mimeType) throws IOException { if (image == null) { String message = Logging.getMessage("nullValue.ByteBufferIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (mimeType == null) { String message = Logging.getMessage("nullValue.MimeTypeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } String suffix = WWIO.makeSuffixForMimeType(mimeType); if (suffix == null) { String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType); Logging.logger().severe(message); throw new IllegalArgumentException(message); } File tempFile = WWIO.saveBufferToTempFile(image, suffix); return convertToDxt1NoTransparency(tempFile); } public static ByteBuffer convertToDxt1NoTransparency(File file) throws IOException { if (file == null) { // String message = Logging.getMessage("nullValue.FileIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!file.exists() || !file.canRead()) { String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); if (image == null) { return null; // TODO: logger } return convertToDxt1NoTransparency(image); } public static ByteBuffer convertToDxt1NoTransparency(BufferedImage image) { if (image == null) { return null; } int[] pixels = new int[16]; int bufferSize = 128 + image.getWidth() * image.getHeight() / 2; ByteBuffer buffer = ByteBuffer.allocate(bufferSize); buffer.order(ByteOrder.LITTLE_ENDIAN); buildHeaderDxt1(buffer, image.getWidth(), image.getHeight()); int numTilesWide = image.getWidth() / 4; int numTilesHigh = image.getHeight() / 4; for (int i = 0; i < numTilesHigh; i++) { for (int j = 0; j < numTilesWide; j++) { java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4); originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); Color[] colors = getColors888(pixels); for (int k = 0; k < pixels.length; k++) { pixels[k] = getPixel565(colors[k]); colors[k] = getColor565(pixels[k]); } int[] extremaIndices = determineExtremeColors(colors); if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]]) { int t = extremaIndices[0]; extremaIndices[0] = extremaIndices[1]; extremaIndices[1] = t; } buffer.putShort((short) pixels[extremaIndices[0]]); buffer.putShort((short) pixels[extremaIndices[1]]); long bitmask = computeBitMask(colors, extremaIndices); buffer.putInt((int) bitmask); } } return buffer; } public static ByteBuffer convertToDxt3(ByteBuffer image, String mimeType) throws IOException { if (image == null) { String message = Logging.getMessage("nullValue.ByteBufferIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (mimeType == null) { String message = Logging.getMessage("nullValue.MimeTypeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } String suffix = WWIO.makeSuffixForMimeType(mimeType); if (suffix == null) { String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType); Logging.logger().severe(message); throw new IllegalArgumentException(message); } File tempFile = WWIO.saveBufferToTempFile(image, suffix); return convertToDxt3(tempFile); } public static ByteBuffer convertToDxt3(File file) throws IOException { if (file == null) { String message = Logging.getMessage("nullValue.FileIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!file.exists() || !file.canRead()) { String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file); if (image == null) { return null; } return convertToDxt3(image); } public static ByteBuffer convertToDxt3(BufferedImage image) { if (image == null) return null; // TODO: arg check // Don't waste the space for transparency if if (!image.getColorModel().hasAlpha()) return convertToDxt1NoTransparency(image); int[] pixels = new int[16]; int bufferSize = 128 + image.getWidth() * image.getHeight(); ByteBuffer buffer = ByteBuffer.allocate(bufferSize); buffer.order(ByteOrder.LITTLE_ENDIAN); buildHeaderDxt3(buffer, image.getWidth(), image.getHeight()); int numTilesWide = image.getWidth() / 4; int numTilesHigh = image.getHeight() / 4; for (int i = 0; i < numTilesHigh; i++) { for (int j = 0; j < numTilesWide; j++) { java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4); originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4); Color[] colors = getColors888(pixels); // Store the alhpa table. for (int k = 0; k < pixels.length; k += 2) { buffer.put((byte) ((pixels[k] >>> 28) | (pixels[k + 1] >>> 24))); } for (int k = 0; k < pixels.length; k++) { pixels[k] = getPixel565(colors[k]); colors[k] = getColor565(pixels[k]); } int[] extremaIndices = determineExtremeColors(colors); if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]]) { int t = extremaIndices[0]; extremaIndices[0] = extremaIndices[1]; extremaIndices[1] = t; } buffer.putShort((short) pixels[extremaIndices[0]]); buffer.putShort((short) pixels[extremaIndices[1]]); long bitmask = computeBitMask(colors, extremaIndices); buffer.putInt((int) bitmask); } } return buffer; } protected static void buildHeaderDxt1(ByteBuffer buffer, int width, int height) { buffer.rewind(); buffer.put((byte) 'D'); buffer.put((byte) 'D'); buffer.put((byte) 'S'); buffer.put((byte) ' '); buffer.putInt(124); int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE; buffer.putInt(flag); buffer.putInt(height); buffer.putInt(width); buffer.putInt(width * height / 2); buffer.putInt(0); // depth buffer.putInt(0); // mipmap count buffer.position(buffer.position() + 44); // 11 unused double-words buffer.putInt(32); // pixel format size buffer.putInt(DDPF_FOURCC); buffer.put((byte) 'D'); buffer.put((byte) 'X'); buffer.put((byte) 'T'); buffer.put((byte) '1'); buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // alpha mask for RGB formats buffer.putInt(DDSCAPS_TEXTURE); buffer.putInt(0); // ddsCaps2 buffer.position(buffer.position() + 12); // 3 unused double-words } protected static void buildHeaderDxt3(ByteBuffer buffer, int width, int height) { buffer.rewind(); buffer.put((byte) 'D'); buffer.put((byte) 'D'); buffer.put((byte) 'S'); buffer.put((byte) ' '); buffer.putInt(124); int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE; buffer.putInt(flag); buffer.putInt(height); buffer.putInt(width); buffer.putInt(width * height); buffer.putInt(0); // depth buffer.putInt(0); // mipmap count buffer.position(buffer.position() + 44); // 11 unused double-words buffer.putInt(32); // pixel format size buffer.putInt(DDPF_FOURCC); buffer.put((byte) 'D'); buffer.put((byte) 'X'); buffer.put((byte) 'T'); buffer.put((byte) '3'); buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // rgb bit masks for RGB formats buffer.putInt(0); // alpha mask for RGB formats buffer.putInt(DDSCAPS_TEXTURE); buffer.putInt(0); // ddsCaps2 buffer.position(buffer.position() + 12); // 3 unused double-words } protected static int[] determineExtremeColors(Color[] colors) { int farthest = Integer.MIN_VALUE; int[] ex = new int[2]; for (int i = 0; i < colors.length - 1; i++) { for (int j = i + 1; j < colors.length; j++) { int d = distance(colors[i], colors[j]); if (d > farthest) { farthest = d; ex[0] = i; ex[1] = j; } } } return ex; } protected static long computeBitMask(Color[] colors, int[] extremaIndices) { Color[] colorPoints = new Color[] {null, null, new Color(), new Color()}; colorPoints[0] = colors[extremaIndices[0]]; colorPoints[1] = colors[extremaIndices[1]]; if (colorPoints[0].equals(colorPoints[1])) return 0; // colorPoints[0].r = (colorPoints[0].r & 0xF8) | (colorPoints[0].r >> 5 ); // colorPoints[0].g = (colorPoints[0].g & 0xFC) | (colorPoints[0].g >> 6 ); // colorPoints[0].b = (colorPoints[0].b & 0xF8) | (colorPoints[0].b >> 5 ); // // colorPoints[1].r = (colorPoints[1].r & 0xF8) | (colorPoints[1].r >> 5 ); // colorPoints[1].g = (colorPoints[1].g & 0xFC) | (colorPoints[1].g >> 6 ); // colorPoints[1].b = (colorPoints[1].b & 0xF8) | (colorPoints[1].b >> 5 ); colorPoints[2].r = (2 * colorPoints[0].r + colorPoints[1].r + 1) / 3; colorPoints[2].g = (2 * colorPoints[0].g + colorPoints[1].g + 1) / 3; colorPoints[2].b = (2 * colorPoints[0].b + colorPoints[1].b + 1) / 3; colorPoints[3].r = (colorPoints[0].r + 2 * colorPoints[1].r + 1) / 3; colorPoints[3].g = (colorPoints[0].g + 2 * colorPoints[1].g + 1) / 3; colorPoints[3].b = (colorPoints[0].b + 2 * colorPoints[1].b + 1) / 3; long bitmask = 0; for (int i = 0; i < colors.length; i++) { int closest = Integer.MAX_VALUE; int mask = 0; for (int j = 0; j < colorPoints.length; j++) { int d = distance(colors[i], colorPoints[j]); if (d < closest) { closest = d; mask = j; } } bitmask |= mask << i * 2; } return bitmask; } protected static int getPixel565(Color color) { int r = color.r >> 3; int g = color.g >> 2; int b = color.b >> 3; return r << 11 | g << 5 | b; } protected static Color getColor565(int pixel) { Color color = new Color(); color.r = (int) (((long) pixel) & 0xf800) >> 11; color.g = (int) (((long) pixel) & 0x07e0) >> 5; color.b = (int) (((long) pixel) & 0x001f); return color; } protected static Color getColor888(int r8g8b8) { return new Color( (int) (((long) r8g8b8) & 0xff0000) >> 16, (int) (((long) r8g8b8) & 0x00ff00) >> 8, (int) (((long) r8g8b8) & 0x0000ff) ); } protected static Color[] getColors888(int[] pixels) { Color[] colors = new Color[pixels.length]; for (int i = 0; i < pixels.length; i++) { colors[i] = new Color(); colors[i].r = (int) (((long) pixels[i]) & 0xff0000) >> 16; colors[i].g = (int) (((long) pixels[i]) & 0x00ff00) >> 8; colors[i].b = (int) (((long) pixels[i]) & 0x0000ff); } return colors; } protected static int distance(Color ca, Color cb) { return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b); } protected static void equalTransparentCase(TransparentColor[] colors, int[] extremaIndices, short value) { // we want extremaIndices[0] to be greater than extremaIndices[1] if (value == 0) { // transparent colors[extremaIndices[0]] = TransparentColor.OFF_TRANSPARENT; } /*else { // not transparent anywhere - it's all one colour, so we don't need to bother making changes }*/ } protected static int distance(TransparentColor ca, TransparentColor cb) { return (cb.r - ca.r) * (cb.r - ca.r) + (cb.g - ca.g) * (cb.g - ca.g) + (cb.b - ca.b) * (cb.b - ca.b) + (cb.a - ca.a) * (cb.a - ca.a); } // public static void main(String[] args) // { // try // { // String fileName = "testdata/0000_0001"; // ByteBuffer buffer = convertToDxt1NoTransparency(new File(fileName + ".jpg")); // buffer.rewind(); // FileOutputStream fos = new FileOutputStream(fileName + ".dds"); // channels.FileChannel channel = fos.getChannel(); // channel.write(buffer); // } // catch (IOException e) // { // e.printStackTrace(); // } // // return; // } protected static long computeBitMask(TransparentColor[] colors, int[] extremaIndices) { TransparentColor[] colorPoints = {null, null, new TransparentColor(), new TransparentColor()}; colorPoints[0] = colors[extremaIndices[0]]; colorPoints[1] = colors[extremaIndices[1]]; colorPoints[2].r = (colorPoints[0].r + colorPoints[1].r) / 2; colorPoints[2].g = (colorPoints[0].g + colorPoints[1].g) / 2; colorPoints[2].b = (colorPoints[0].b + colorPoints[1].b) / 2; colorPoints[2].a = 1; colorPoints[3].r = 0; colorPoints[3].g = 0; colorPoints[3].b = 0; colorPoints[3].a = 0; long bitmask = 0; for (int i = 0; i < colors.length; i++) { int closest = Integer.MAX_VALUE; int mask = 0; if (colors[i].a == 0) { mask = 3; } else { for (int j = 0; j < colorPoints.length; j++) { int d = distance(colors[i], colorPoints[j]); if (d < closest) { closest = d; mask = j; } } } bitmask |= mask << i * 2; } return bitmask; } protected static short getShort5551(TransparentColor color) { short s = 0; s |= ((color.r & 0x0f8) << 8) | ((color.g & 0x0f8) << 4) | ((color.b & 0x0f8) >> 3) | ((color.a & 0x0f8) >> 7); // System.out.println(Integer.toBinaryString(s)); return s; } protected static int[] determineExtremeColors(TransparentColor[] colors) { int farthest = Integer.MIN_VALUE; int[] ex = {0, 0}; for (int i = 0; i < colors.length - 1; i++) { for (int j = i + 1; j < colors.length; j++) { int d = distance(colors[i], colors[j]); if (d > farthest) { farthest = d; ex[0] = i; ex[1] = j; } } } return ex; } protected static TransparentColor[] getColors5551(int[] pixels) { TransparentColor colors[] = new TransparentColor[pixels.length]; for (int i = 0; i < pixels.length; i++) { colors[i] = generateColor5551(pixels[i]); } return colors; } protected static TransparentColor generateColor5551(int pixel) { short alpha = (short) (pixel >> 24); if ((alpha & 0xf0) == 0) { return TransparentColor.TRANSPARENT; } // ok, it's not transparent - that's already been ruled out. TransparentColor tc = new TransparentColor(); tc.a = 0x000000ff; tc.r = (pixel & 0x00ff0000) >> 16; tc.g = (pixel & 0x0000ff00) >> 8; tc.b = (pixel & 0x000000ff); return tc; } protected static class TransparentColor { private static final TransparentColor TRANSPARENT = new TransparentColor(0, 0, 0, 0); private static final TransparentColor OFF_TRANSPARENT = new TransparentColor(0, 0, 1, 0); private int r, g, b, a; private TransparentColor() { } private TransparentColor(int r, int g, int b, int a) { this.r = r; this.g = g; this.b = b; this.a = a; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final TransparentColor that = (TransparentColor) o; if (a != that.a) return false; if (b != that.b) return false; if (g != that.g) return false; //noinspection RedundantIfStatement if (r != that.r) return false; return true; } public int hashCode() { int result; result = r; result = 29 * result + g; result = 29 * result + b; result = 29 * result + a; return result; } public String toString() { return "TransColor argb: " + this.a + ", " + this.r + ", " + this.g + ", " + this.b; } } }