Updated to worldwind release 20070817
[worldwind-tracker.git] / gov / nasa / worldwind / formats / dds / DDSConverter.java
blobdf80eb6da4ae18d271013f30e11950b4811fd0af
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind.formats.dds;
9 import gov.nasa.worldwind.util.*;
11 import java.awt.image.*;
12 import java.io.*;
13 import java.nio.*;
15 /**
16 * @author Tom Gaskins
17 * @version $Id: DDSConverter.java 2471 2007-07-31 21:50:57Z tgaskins $
19 public class DDSConverter
21 private static final int DDSD_CAPS = 0x0001;
22 private static final int DDSD_HEIGHT = 0x0002;
23 private static final int DDSD_WIDTH = 0x0004;
24 private static final int DDSD_PIXELFORMAT = 0x1000;
25 private static final int DDSD_MIPMAPCOUNT = 0x20000;
26 private static final int DDSD_LINEARSIZE = 0x80000;
27 private static final int DDPF_FOURCC = 0x0004;
28 private static final int DDSCAPS_TEXTURE = 0x1000;
30 protected static class Color
32 private int r, g, b;
34 public Color()
36 this.r = this.g = this.b = 0;
39 public Color(int r, int g, int b)
41 this.r = r;
42 this.g = g;
43 this.b = b;
46 public boolean equals(Object o)
48 if (this == o)
49 return true;
50 if (o == null || getClass() != o.getClass())
51 return false;
53 final Color color = (Color) o;
55 if (b != color.b)
56 return false;
57 if (g != color.g)
58 return false;
59 //noinspection RedundantIfStatement
60 if (r != color.r)
61 return false;
63 return true;
66 public int hashCode()
68 int result;
69 result = r;
70 result = 29 * result + g;
71 result = 29 * result + b;
72 return result;
76 public static ByteBuffer convertToDDS(ByteBuffer image, String mimeType) throws IOException
78 if (image == null)
80 String message = Logging.getMessage("nullValue.ByteBufferIsNull");
81 Logging.logger().severe(message);
82 throw new IllegalArgumentException(message);
85 if (mimeType == null)
87 String message = Logging.getMessage("nullValue.MimeTypeIsNull");
88 Logging.logger().severe(message);
89 throw new IllegalArgumentException(message);
92 String suffix = WWIO.getSuffixForMimeType(mimeType);
93 if (suffix == null)
95 String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType);
96 Logging.logger().severe(message);
97 throw new IllegalArgumentException(message);
100 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
102 return convertToDDS(tempFile);
105 public static ByteBuffer convertToDDS(File file) throws IOException
107 if (file == null)
109 String message = Logging.getMessage("nullValue.FileIsNull");
110 Logging.logger().severe(message);
111 throw new IllegalArgumentException(message);
114 if (!file.exists() || !file.canRead())
116 String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission");
117 Logging.logger().severe(message);
118 throw new IllegalArgumentException(message);
121 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
122 if (image == null)
124 return null;
127 // Don't waste the space for transparency if
128 if (image.getColorModel().hasAlpha())
129 return convertToDxt3(image);
130 else
131 return convertToDxt1NoTransparency(image);
134 public static ByteBuffer convertToDxt1NoTransparency(ByteBuffer image, String mimeType)
135 throws IOException
137 if (image == null)
139 String message = Logging.getMessage("nullValue.ByteBufferIsNull");
140 Logging.logger().severe(message);
141 throw new IllegalArgumentException(message);
144 if (mimeType == null)
146 String message = Logging.getMessage("nullValue.MimeTypeIsNull");
147 Logging.logger().severe(message);
148 throw new IllegalArgumentException(message);
151 String suffix = WWIO.getSuffixForMimeType(mimeType);
152 if (suffix == null)
154 String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType);
155 Logging.logger().severe(message);
156 throw new IllegalArgumentException(message);
159 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
161 return convertToDxt1NoTransparency(tempFile);
164 public static ByteBuffer convertToDxt1NoTransparency(File file) throws IOException
166 if (file == null)
167 { //
168 String message = Logging.getMessage("nullValue.FileIsNull");
169 Logging.logger().severe(message);
170 throw new IllegalArgumentException(message);
173 if (!file.exists() || !file.canRead())
175 String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission");
176 Logging.logger().severe(message);
177 throw new IllegalArgumentException(message);
180 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
182 if (image == null)
184 return null; // TODO: logger
187 return convertToDxt1NoTransparency(image);
190 public static ByteBuffer convertToDxt1NoTransparency(BufferedImage image)
192 if (image == null)
194 return null;
197 int[] pixels = new int[16];
198 int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
199 ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
200 buffer.order(ByteOrder.LITTLE_ENDIAN);
201 buildHeaderDxt1(buffer, image.getWidth(), image.getHeight());
203 int numTilesWide = image.getWidth() / 4;
204 int numTilesHigh = image.getHeight() / 4;
205 for (int i = 0; i < numTilesHigh; i++)
207 for (int j = 0; j < numTilesWide; j++)
209 java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
210 originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
211 Color[] colors = getColors888(pixels);
213 for (int k = 0; k < pixels.length; k++)
215 pixels[k] = getPixel565(colors[k]);
216 colors[k] = getColor565(pixels[k]);
219 int[] extremaIndices = determineExtremeColors(colors);
220 if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
222 int t = extremaIndices[0];
223 extremaIndices[0] = extremaIndices[1];
224 extremaIndices[1] = t;
227 buffer.putShort((short) pixels[extremaIndices[0]]);
228 buffer.putShort((short) pixels[extremaIndices[1]]);
230 long bitmask = computeBitMask(colors, extremaIndices);
231 buffer.putInt((int) bitmask);
235 return buffer;
238 public static ByteBuffer convertToDxt3(ByteBuffer image, String mimeType)
239 throws IOException
241 if (image == null)
243 String message = Logging.getMessage("nullValue.ByteBufferIsNull");
244 Logging.logger().severe(message);
245 throw new IllegalArgumentException(message);
248 if (mimeType == null)
250 String message = Logging.getMessage("nullValue.MimeTypeIsNull");
251 Logging.logger().severe(message);
252 throw new IllegalArgumentException(message);
255 String suffix = WWIO.getSuffixForMimeType(mimeType);
256 if (suffix == null)
258 String message = Logging.getMessage("DDSConverter.UnsupportedMimeType", mimeType);
259 Logging.logger().severe(message);
260 throw new IllegalArgumentException(message);
263 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
265 return convertToDxt3(tempFile);
268 public static ByteBuffer convertToDxt3(File file) throws IOException
270 if (file == null)
272 String message = Logging.getMessage("nullValue.FileIsNull");
273 Logging.logger().severe(message);
274 throw new IllegalArgumentException(message);
277 if (!file.exists() || !file.canRead())
279 String message = Logging.getMessage("DDSConverter.NoFileOrNoPermission");
280 Logging.logger().severe(message);
281 throw new IllegalArgumentException(message);
284 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
285 if (image == null)
287 return null;
290 return convertToDxt3(image);
293 public static ByteBuffer convertToDxt3(BufferedImage image)
295 if (image == null)
296 return null; // TODO: arg check
298 // Don't waste the space for transparency if
299 if (!image.getColorModel().hasAlpha())
300 return convertToDxt1NoTransparency(image);
302 int[] pixels = new int[16];
303 int bufferSize = 128 + image.getWidth() * image.getHeight();
304 ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
305 buffer.order(ByteOrder.LITTLE_ENDIAN);
306 buildHeaderDxt3(buffer, image.getWidth(), image.getHeight());
308 int numTilesWide = image.getWidth() / 4;
309 int numTilesHigh = image.getHeight() / 4;
310 for (int i = 0; i < numTilesHigh; i++)
312 for (int j = 0; j < numTilesWide; j++)
314 java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
315 originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
316 Color[] colors = getColors888(pixels);
318 // Store the alhpa table.
319 for (int k = 0; k < pixels.length; k += 2)
321 buffer.put((byte) ((pixels[k] >>> 24) | (pixels[k + 1] >>> 28)));
324 for (int k = 0; k < pixels.length; k++)
326 pixels[k] = getPixel565(colors[k]);
327 colors[k] = getColor565(pixels[k]);
330 int[] extremaIndices = determineExtremeColors(colors);
331 if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
333 int t = extremaIndices[0];
334 extremaIndices[0] = extremaIndices[1];
335 extremaIndices[1] = t;
338 buffer.putShort((short) pixels[extremaIndices[0]]);
339 buffer.putShort((short) pixels[extremaIndices[1]]);
341 long bitmask = computeBitMask(colors, extremaIndices);
342 buffer.putInt((int) bitmask);
346 return buffer;
349 protected static void buildHeaderDxt1(ByteBuffer buffer, int width, int height)
351 buffer.rewind();
352 buffer.put((byte) 'D');
353 buffer.put((byte) 'D');
354 buffer.put((byte) 'S');
355 buffer.put((byte) ' ');
356 buffer.putInt(124);
357 int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
358 buffer.putInt(flag);
359 buffer.putInt(height);
360 buffer.putInt(width);
361 buffer.putInt(width * height / 2);
362 buffer.putInt(0); // depth
363 buffer.putInt(0); // mipmap count
364 buffer.position(buffer.position() + 44); // 11 unused double-words
365 buffer.putInt(32); // pixel format size
366 buffer.putInt(DDPF_FOURCC);
367 buffer.put((byte) 'D');
368 buffer.put((byte) 'X');
369 buffer.put((byte) 'T');
370 buffer.put((byte) '1');
371 buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
372 buffer.putInt(0); // rgb bit masks for RGB formats
373 buffer.putInt(0); // rgb bit masks for RGB formats
374 buffer.putInt(0); // rgb bit masks for RGB formats
375 buffer.putInt(0); // alpha mask for RGB formats
376 buffer.putInt(DDSCAPS_TEXTURE);
377 buffer.putInt(0); // ddsCaps2
378 buffer.position(buffer.position() + 12); // 3 unused double-words
381 protected static void buildHeaderDxt3(ByteBuffer buffer, int width, int height)
383 buffer.rewind();
384 buffer.put((byte) 'D');
385 buffer.put((byte) 'D');
386 buffer.put((byte) 'S');
387 buffer.put((byte) ' ');
388 buffer.putInt(124);
389 int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
390 buffer.putInt(flag);
391 buffer.putInt(height);
392 buffer.putInt(width);
393 buffer.putInt(width * height);
394 buffer.putInt(0); // depth
395 buffer.putInt(0); // mipmap count
396 buffer.position(buffer.position() + 44); // 11 unused double-words
397 buffer.putInt(32); // pixel format size
398 buffer.putInt(DDPF_FOURCC);
399 buffer.put((byte) 'D');
400 buffer.put((byte) 'X');
401 buffer.put((byte) 'T');
402 buffer.put((byte) '3');
403 buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
404 buffer.putInt(0); // rgb bit masks for RGB formats
405 buffer.putInt(0); // rgb bit masks for RGB formats
406 buffer.putInt(0); // rgb bit masks for RGB formats
407 buffer.putInt(0); // alpha mask for RGB formats
408 buffer.putInt(DDSCAPS_TEXTURE);
409 buffer.putInt(0); // ddsCaps2
410 buffer.position(buffer.position() + 12); // 3 unused double-words
413 protected static int[] determineExtremeColors(Color[] colors)
415 int farthest = Integer.MIN_VALUE;
416 int[] ex = new int[2];
418 for (int i = 0; i < colors.length - 1; i++)
420 for (int j = i + 1; j < colors.length; j++)
422 int d = distance(colors[i], colors[j]);
423 if (d > farthest)
425 farthest = d;
426 ex[0] = i;
427 ex[1] = j;
432 return ex;
435 protected static long computeBitMask(Color[] colors, int[] extremaIndices)
437 Color[] colorPoints = new Color[] {null, null, new Color(), new Color()};
438 colorPoints[0] = colors[extremaIndices[0]];
439 colorPoints[1] = colors[extremaIndices[1]];
440 if (colorPoints[0].equals(colorPoints[1]))
441 return 0;
443 // colorPoints[0].r = (colorPoints[0].r & 0xF8) | (colorPoints[0].r >> 5 );
444 // colorPoints[0].g = (colorPoints[0].g & 0xFC) | (colorPoints[0].g >> 6 );
445 // colorPoints[0].b = (colorPoints[0].b & 0xF8) | (colorPoints[0].b >> 5 );
447 // colorPoints[1].r = (colorPoints[1].r & 0xF8) | (colorPoints[1].r >> 5 );
448 // colorPoints[1].g = (colorPoints[1].g & 0xFC) | (colorPoints[1].g >> 6 );
449 // colorPoints[1].b = (colorPoints[1].b & 0xF8) | (colorPoints[1].b >> 5 );
451 colorPoints[2].r = (2 * colorPoints[0].r + colorPoints[1].r + 1) / 3;
452 colorPoints[2].g = (2 * colorPoints[0].g + colorPoints[1].g + 1) / 3;
453 colorPoints[2].b = (2 * colorPoints[0].b + colorPoints[1].b + 1) / 3;
454 colorPoints[3].r = (colorPoints[0].r + 2 * colorPoints[1].r + 1) / 3;
455 colorPoints[3].g = (colorPoints[0].g + 2 * colorPoints[1].g + 1) / 3;
456 colorPoints[3].b = (colorPoints[0].b + 2 * colorPoints[1].b + 1) / 3;
458 long bitmask = 0;
459 for (int i = 0; i < colors.length; i++)
461 int closest = Integer.MAX_VALUE;
462 int mask = 0;
463 for (int j = 0; j < colorPoints.length; j++)
465 int d = distance(colors[i], colorPoints[j]);
466 if (d < closest)
468 closest = d;
469 mask = j;
472 bitmask |= mask << i * 2;
475 return bitmask;
478 protected static int getPixel565(Color color)
480 int r = color.r >> 3;
481 int g = color.g >> 2;
482 int b = color.b >> 3;
483 return r << 11 | g << 5 | b;
486 protected static Color getColor565(int pixel)
488 Color color = new Color();
490 color.r = (int) (((long) pixel) & 0xf800) >> 11;
491 color.g = (int) (((long) pixel) & 0x07e0) >> 5;
492 color.b = (int) (((long) pixel) & 0x001f);
494 return color;
497 protected static Color getColor888(int r8g8b8)
499 return new Color(
500 (int) (((long) r8g8b8) & 0xff0000) >> 16,
501 (int) (((long) r8g8b8) & 0x00ff00) >> 8,
502 (int) (((long) r8g8b8) & 0x0000ff)
506 protected static Color[] getColors888(int[] pixels)
508 Color[] colors = new Color[pixels.length];
510 for (int i = 0; i < pixels.length; i++)
512 colors[i] = new Color();
513 colors[i].r = (int) (((long) pixels[i]) & 0xff0000) >> 16;
514 colors[i].g = (int) (((long) pixels[i]) & 0x00ff00) >> 8;
515 colors[i].b = (int) (((long) pixels[i]) & 0x0000ff);
518 return colors;
521 protected static int distance(Color ca, Color cb)
523 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);
526 protected static void equalTransparentCase(TransparentColor[] colors, int[] extremaIndices, short value)
528 // we want extremaIndices[0] to be greater than extremaIndices[1]
530 if (value == 0)
532 // transparent
533 colors[extremaIndices[0]] = TransparentColor.OFF_TRANSPARENT;
535 /*else
537 // not transparent anywhere - it's all one colour, so we don't need to bother making changes
542 protected static int distance(TransparentColor ca, TransparentColor cb)
544 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)
545 + (cb.a - ca.a) * (cb.a - ca.a);
548 // public static void main(String[] args)
549 // {
550 // try
551 // {
552 // String fileName = "testdata/0000_0001";
553 // ByteBuffer buffer = convertToDxt1NoTransparency(new File(fileName + ".jpg"));
554 // buffer.rewind();
555 // FileOutputStream fos = new FileOutputStream(fileName + ".dds");
556 // channels.FileChannel channel = fos.getChannel();
557 // channel.write(buffer);
558 // }
559 // catch (IOException e)
560 // {
561 // e.printStackTrace();
562 // }
564 // return;
565 // }
567 protected static long computeBitMask(TransparentColor[] colors, int[] extremaIndices)
569 TransparentColor[] colorPoints = {null, null, new TransparentColor(), new TransparentColor()};
571 colorPoints[0] = colors[extremaIndices[0]];
572 colorPoints[1] = colors[extremaIndices[1]];
574 colorPoints[2].r = (colorPoints[0].r + colorPoints[1].r) / 2;
575 colorPoints[2].g = (colorPoints[0].g + colorPoints[1].g) / 2;
576 colorPoints[2].b = (colorPoints[0].b + colorPoints[1].b) / 2;
577 colorPoints[2].a = 1;
579 colorPoints[3].r = 0;
580 colorPoints[3].g = 0;
581 colorPoints[3].b = 0;
582 colorPoints[3].a = 0;
584 long bitmask = 0;
585 for (int i = 0; i < colors.length; i++)
587 int closest = Integer.MAX_VALUE;
588 int mask = 0;
589 if (colors[i].a == 0)
591 mask = 3;
593 else
595 for (int j = 0; j < colorPoints.length; j++)
597 int d = distance(colors[i], colorPoints[j]);
598 if (d < closest)
600 closest = d;
601 mask = j;
605 bitmask |= mask << i * 2;
608 return bitmask;
611 protected static short getShort5551(TransparentColor color)
613 short s = 0;
614 s |= ((color.r & 0x0f8) << 8) | ((color.g & 0x0f8) << 4) | ((color.b & 0x0f8) >> 3) | ((color.a & 0x0f8) >> 7);
615 // System.out.println(Integer.toBinaryString(s));
616 return s;
619 protected static int[] determineExtremeColors(TransparentColor[] colors)
621 int farthest = Integer.MIN_VALUE;
622 int[] ex = {0, 0};
624 for (int i = 0; i < colors.length - 1; i++)
626 for (int j = i + 1; j < colors.length; j++)
628 int d = distance(colors[i], colors[j]);
629 if (d > farthest)
631 farthest = d;
632 ex[0] = i;
633 ex[1] = j;
638 return ex;
641 protected static TransparentColor[] getColors5551(int[] pixels)
643 TransparentColor colors[] = new TransparentColor[pixels.length];
645 for (int i = 0; i < pixels.length; i++)
647 colors[i] = generateColor5551(pixels[i]);
649 return colors;
652 protected static TransparentColor generateColor5551(int pixel)
654 short alpha = (short) (pixel >> 24);
655 if ((alpha & 0xf0) == 0)
657 return TransparentColor.TRANSPARENT;
660 // ok, it's not transparent - that's already been ruled out.
662 TransparentColor tc = new TransparentColor();
663 tc.a = 0x000000ff;
664 tc.r = (pixel & 0x00ff0000) >> 16;
665 tc.g = (pixel & 0x0000ff00) >> 8;
666 tc.b = (pixel & 0x000000ff);
668 return tc;
671 protected static class TransparentColor
673 private static final TransparentColor TRANSPARENT = new TransparentColor(0, 0, 0, 0);
674 private static final TransparentColor OFF_TRANSPARENT = new TransparentColor(0, 0, 1, 0);
675 private int r, g, b, a;
677 private TransparentColor()
681 private TransparentColor(int r, int g, int b, int a)
683 this.r = r;
684 this.g = g;
685 this.b = b;
686 this.a = a;
689 public boolean equals(Object o)
691 if (this == o)
692 return true;
693 if (o == null || getClass() != o.getClass())
694 return false;
696 final TransparentColor that =
697 (TransparentColor) o;
699 if (a != that.a)
700 return false;
701 if (b != that.b)
702 return false;
703 if (g != that.g)
704 return false;
705 //noinspection RedundantIfStatement
706 if (r != that.r)
707 return false;
709 return true;
712 public int hashCode()
714 int result;
715 result = r;
716 result = 29 * result + g;
717 result = 29 * result + b;
718 result = 29 * result + a;
719 return result;
722 public String toString()
724 return "TransColor argb: " + this.a + ", " + this.r + ", " + this.g + ", " + this.b;