Updated to worldwind release 20070817
[worldwind-tracker.git] / gov / nasa / worldwind / DDSConverter.java
blob6ebb4063615334cfa90fedfc6927bf0cf41ab9a2
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;
9 import java.awt.image.*;
10 import java.nio.*;
11 import java.io.*;
13 /**
14 * @author Tom Gaskins
15 * @version $Id: DDSConverter.java 2027 2007-06-14 02:41:44Z tgaskins $
17 public class DDSConverter
19 static final int DDSD_CAPS = 0x0001;
20 static final int DDSD_HEIGHT = 0x0002;
21 static final int DDSD_WIDTH = 0x0004;
22 static final int DDSD_PIXELFORMAT = 0x1000;
23 static final int DDSD_MIPMAPCOUNT = 0x20000;
24 static final int DDSD_LINEARSIZE = 0x80000;
25 static final int DDPF_FOURCC = 0x0004;
26 static final int DDSCAPS_TEXTURE = 0x1000;
28 protected static class Color
30 private int r, g, b;
32 public Color()
34 this.r = this.g = this.b = 0;
37 public Color(int r, int g, int b)
39 this.r = r;
40 this.g = g;
41 this.b = b;
44 public boolean equals(Object o)
46 if (this == o)
47 return true;
48 if (o == null || getClass() != o.getClass())
49 return false;
51 final gov.nasa.worldwind.DDSConverter.Color color = (gov.nasa.worldwind.DDSConverter.Color) o;
53 if (b != color.b)
54 return false;
55 if (g != color.g)
56 return false;
57 //noinspection RedundantIfStatement
58 if (r != color.r)
59 return false;
61 return true;
64 public int hashCode()
66 int result;
67 result = r;
68 result = 29 * result + g;
69 result = 29 * result + b;
70 return result;
74 public static ByteBuffer convertToDDS(ByteBuffer image, String mimeType) throws IOException
76 if (image == null)
78 String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
79 WorldWind.logger().log(java.util.logging.Level.FINE, message);
80 throw new IllegalArgumentException(message);
83 if (mimeType == null)
85 String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
86 gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
87 throw new IllegalArgumentException(message);
90 String suffix = WWIO.getSuffixForMimeType(mimeType);
91 if (suffix == null)
93 String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
94 WorldWind.logger().log(java.util.logging.Level.FINE, message);
95 throw new IllegalArgumentException(message);
98 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
100 return convertToDDS(tempFile);
103 public static ByteBuffer convertToDDS(File file) throws IOException
105 if (file == null)
107 String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
108 WorldWind.logger().log(java.util.logging.Level.FINE, message);
109 throw new IllegalArgumentException(message);
112 if (!file.exists() || !file.canRead())
114 String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
115 WorldWind.logger().log(java.util.logging.Level.FINE, message);
116 throw new IllegalArgumentException(message);
119 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
120 if (image == null)
122 return null;
125 // Don't waste the space for transparency if
126 if (image.getColorModel().hasAlpha())
127 return convertToDxt3(image);
128 else
129 return convertToDxt1NoTransparency(image);
132 public static ByteBuffer convertToDxt1NoTransparency(ByteBuffer image, String mimeType)
133 throws IOException
135 if (image == null)
137 String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
138 WorldWind.logger().log(java.util.logging.Level.FINE, message);
139 throw new IllegalArgumentException(message);
142 if (mimeType == null)
144 String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
145 gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
146 throw new IllegalArgumentException(message);
149 String suffix = WWIO.getSuffixForMimeType(mimeType);
150 if (suffix == null)
152 String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
153 WorldWind.logger().log(java.util.logging.Level.FINE, message);
154 throw new IllegalArgumentException(message);
157 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
159 return convertToDxt1NoTransparency(tempFile);
162 public static ByteBuffer convertToDxt1NoTransparency(File file) throws IOException
164 if (file == null)
165 { //
166 String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
167 WorldWind.logger().log(java.util.logging.Level.FINE, message);
168 throw new IllegalArgumentException(message);
171 if (!file.exists() || !file.canRead())
173 String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
174 WorldWind.logger().log(java.util.logging.Level.FINE, message);
175 throw new IllegalArgumentException(message);
178 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
180 if (image == null)
182 return null; // TODO: log
185 return convertToDxt1NoTransparency(image);
188 public static ByteBuffer convertToDxt1NoTransparency(BufferedImage image)
190 if (image == null)
192 return null;
195 int[] pixels = new int[16];
196 int bufferSize = 128 + image.getWidth() * image.getHeight() / 2;
197 ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
198 buffer.order(ByteOrder.LITTLE_ENDIAN);
199 buildHeaderDxt1(buffer, image.getWidth(), image.getHeight());
201 int numTilesWide = image.getWidth() / 4;
202 int numTilesHigh = image.getHeight() / 4;
203 for (int i = 0; i < numTilesHigh; i++)
205 for (int j = 0; j < numTilesWide; j++)
207 java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
208 originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
209 Color[] colors = getColors888(pixels);
211 for (int k = 0; k < pixels.length; k++)
213 pixels[k] = getPixel565(colors[k]);
214 colors[k] = getColor565(pixels[k]);
217 int[] extremaIndices = determineExtremeColors(colors);
218 if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
220 int t = extremaIndices[0];
221 extremaIndices[0] = extremaIndices[1];
222 extremaIndices[1] = t;
225 buffer.putShort((short) pixels[extremaIndices[0]]);
226 buffer.putShort((short) pixels[extremaIndices[1]]);
228 long bitmask = computeBitMask(colors, extremaIndices);
229 buffer.putInt((int) bitmask);
233 return buffer;
236 public static ByteBuffer convertToDxt3(ByteBuffer image, String mimeType)
237 throws IOException
239 if (image == null)
241 String message = WorldWind.retrieveErrMsg("nullValue.ByteBufferIsNull");
242 WorldWind.logger().log(java.util.logging.Level.FINE, message);
243 throw new IllegalArgumentException(message);
246 if (mimeType == null)
248 String message = WorldWind.retrieveErrMsg("nullValue.MimeTypeIsNull");
249 gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
250 throw new IllegalArgumentException(message);
253 String suffix = WWIO.getSuffixForMimeType(mimeType);
254 if (suffix == null)
256 String message = WorldWind.retrieveErrMsg("DDSConverter.UnsupportedMimeType") + mimeType;
257 WorldWind.logger().log(java.util.logging.Level.FINE, message);
258 throw new IllegalArgumentException(message);
261 File tempFile = WWIO.saveBufferToTempFile(image, suffix);
263 return convertToDxt3(tempFile);
266 public static ByteBuffer convertToDxt3(File file) throws IOException
268 if (file == null)
270 String message = WorldWind.retrieveErrMsg("nullValue.FileIsNull");
271 WorldWind.logger().log(java.util.logging.Level.FINE, message);
272 throw new IllegalArgumentException(message);
275 if (!file.exists() || !file.canRead())
277 String message = WorldWind.retrieveErrMsg("DDSConverter.NoFileOrNoPermission");
278 WorldWind.logger().log(java.util.logging.Level.FINE, message);
279 throw new IllegalArgumentException(message);
282 java.awt.image.BufferedImage image = javax.imageio.ImageIO.read(file);
283 if (image == null)
285 return null;
288 return convertToDxt3(image);
291 public static ByteBuffer convertToDxt3(BufferedImage image) throws IOException
293 if (image == null)
294 return null; // TODO: arg check
296 // Don't waste the space for transparency if
297 if (!image.getColorModel().hasAlpha())
298 return convertToDxt1NoTransparency(image);
300 int[] pixels = new int[16];
301 int bufferSize = 128 + image.getWidth() * image.getHeight();
302 ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
303 buffer.order(ByteOrder.LITTLE_ENDIAN);
304 buildHeaderDxt3(buffer, image.getWidth(), image.getHeight());
306 int numTilesWide = image.getWidth() / 4;
307 int numTilesHigh = image.getHeight() / 4;
308 for (int i = 0; i < numTilesHigh; i++)
310 for (int j = 0; j < numTilesWide; j++)
312 java.awt.image.BufferedImage originalTile = image.getSubimage(j * 4, i * 4, 4, 4);
313 originalTile.getRGB(0, 0, 4, 4, pixels, 0, 4);
314 Color[] colors = getColors888(pixels);
316 // Store the alhpa table.
317 for (int k = 0; k < pixels.length; k += 2)
319 buffer.put((byte) ((pixels[k] >>> 24) | (pixels[k + 1] >>> 28)));
322 for (int k = 0; k < pixels.length; k++)
324 pixels[k] = getPixel565(colors[k]);
325 colors[k] = getColor565(pixels[k]);
328 int[] extremaIndices = determineExtremeColors(colors);
329 if (pixels[extremaIndices[0]] < pixels[extremaIndices[1]])
331 int t = extremaIndices[0];
332 extremaIndices[0] = extremaIndices[1];
333 extremaIndices[1] = t;
336 buffer.putShort((short) pixels[extremaIndices[0]]);
337 buffer.putShort((short) pixels[extremaIndices[1]]);
339 long bitmask = computeBitMask(colors, extremaIndices);
340 buffer.putInt((int) bitmask);
344 return buffer;
347 protected static void buildHeaderDxt1(ByteBuffer buffer, int width, int height)
349 buffer.rewind();
350 buffer.put((byte) 'D');
351 buffer.put((byte) 'D');
352 buffer.put((byte) 'S');
353 buffer.put((byte) ' ');
354 buffer.putInt(124);
355 int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
356 buffer.putInt(flag);
357 buffer.putInt(height);
358 buffer.putInt(width);
359 buffer.putInt(width * height / 2);
360 buffer.putInt(0); // depth
361 buffer.putInt(0); // mipmap count
362 buffer.position(buffer.position() + 44); // 11 unused double-words
363 buffer.putInt(32); // pixel format size
364 buffer.putInt(DDPF_FOURCC);
365 buffer.put((byte) 'D');
366 buffer.put((byte) 'X');
367 buffer.put((byte) 'T');
368 buffer.put((byte) '1');
369 buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
370 buffer.putInt(0); // rgb bit masks for RGB formats
371 buffer.putInt(0); // rgb bit masks for RGB formats
372 buffer.putInt(0); // rgb bit masks for RGB formats
373 buffer.putInt(0); // alpha mask for RGB formats
374 buffer.putInt(DDSCAPS_TEXTURE);
375 buffer.putInt(0); // ddsCaps2
376 buffer.position(buffer.position() + 12); // 3 unused double-words
379 protected static void buildHeaderDxt3(ByteBuffer buffer, int width, int height)
381 buffer.rewind();
382 buffer.put((byte) 'D');
383 buffer.put((byte) 'D');
384 buffer.put((byte) 'S');
385 buffer.put((byte) ' ');
386 buffer.putInt(124);
387 int flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
388 buffer.putInt(flag);
389 buffer.putInt(height);
390 buffer.putInt(width);
391 buffer.putInt(width * height);
392 buffer.putInt(0); // depth
393 buffer.putInt(0); // mipmap count
394 buffer.position(buffer.position() + 44); // 11 unused double-words
395 buffer.putInt(32); // pixel format size
396 buffer.putInt(DDPF_FOURCC);
397 buffer.put((byte) 'D');
398 buffer.put((byte) 'X');
399 buffer.put((byte) 'T');
400 buffer.put((byte) '3');
401 buffer.putInt(0); // bits per pixel for RGB (non-compressed) formats
402 buffer.putInt(0); // rgb bit masks for RGB formats
403 buffer.putInt(0); // rgb bit masks for RGB formats
404 buffer.putInt(0); // rgb bit masks for RGB formats
405 buffer.putInt(0); // alpha mask for RGB formats
406 buffer.putInt(DDSCAPS_TEXTURE);
407 buffer.putInt(0); // ddsCaps2
408 buffer.position(buffer.position() + 12); // 3 unused double-words
411 protected static int[] determineExtremeColors(Color[] colors)
413 int farthest = Integer.MIN_VALUE;
414 int[] ex = new int[2];
416 for (int i = 0; i < colors.length - 1; i++)
418 for (int j = i + 1; j < colors.length; j++)
420 int d = distance(colors[i], colors[j]);
421 if (d > farthest)
423 farthest = d;
424 ex[0] = i;
425 ex[1] = j;
430 return ex;
433 protected static long computeBitMask(Color[] colors, int[] extremaIndices)
435 Color[] colorPoints = new Color[] {null, null, new Color(), new Color()};
436 colorPoints[0] = colors[extremaIndices[0]];
437 colorPoints[1] = colors[extremaIndices[1]];
438 if (colorPoints[0].equals(colorPoints[1]))
439 return 0;
441 // colorPoints[0].r = (colorPoints[0].r & 0xF8) | (colorPoints[0].r >> 5 );
442 // colorPoints[0].g = (colorPoints[0].g & 0xFC) | (colorPoints[0].g >> 6 );
443 // colorPoints[0].b = (colorPoints[0].b & 0xF8) | (colorPoints[0].b >> 5 );
445 // colorPoints[1].r = (colorPoints[1].r & 0xF8) | (colorPoints[1].r >> 5 );
446 // colorPoints[1].g = (colorPoints[1].g & 0xFC) | (colorPoints[1].g >> 6 );
447 // colorPoints[1].b = (colorPoints[1].b & 0xF8) | (colorPoints[1].b >> 5 );
449 colorPoints[2].r = (2 * colorPoints[0].r + colorPoints[1].r + 1) / 3;
450 colorPoints[2].g = (2 * colorPoints[0].g + colorPoints[1].g + 1) / 3;
451 colorPoints[2].b = (2 * colorPoints[0].b + colorPoints[1].b + 1) / 3;
452 colorPoints[3].r = (colorPoints[0].r + 2 * colorPoints[1].r + 1) / 3;
453 colorPoints[3].g = (colorPoints[0].g + 2 * colorPoints[1].g + 1) / 3;
454 colorPoints[3].b = (colorPoints[0].b + 2 * colorPoints[1].b + 1) / 3;
456 long bitmask = 0;
457 for (int i = 0; i < colors.length; i++)
459 int closest = Integer.MAX_VALUE;
460 int mask = 0;
461 for (int j = 0; j < colorPoints.length; j++)
463 int d = distance(colors[i], colorPoints[j]);
464 if (d < closest)
466 closest = d;
467 mask = j;
470 bitmask |= mask << i * 2;
473 return bitmask;
476 protected static int getPixel565(Color color)
478 int r = color.r >> 3;
479 int g = color.g >> 2;
480 int b = color.b >> 3;
481 return r << 11 | g << 5 | b;
484 protected static Color getColor565(int pixel)
486 Color color = new Color();
488 color.r = (int) (((long) pixel) & 0xf800) >> 11;
489 color.g = (int) (((long) pixel) & 0x07e0) >> 5;
490 color.b = (int) (((long) pixel) & 0x001f);
492 return color;
495 protected static Color getColor888(int r8g8b8)
497 return new Color(
498 (int) (((long) r8g8b8) & 0xff0000) >> 16,
499 (int) (((long) r8g8b8) & 0x00ff00) >> 8,
500 (int) (((long) r8g8b8) & 0x0000ff)
504 protected static Color[] getColors888(int[] pixels)
506 Color[] colors = new Color[pixels.length];
508 for (int i = 0; i < pixels.length; i++)
510 colors[i] = new Color();
511 colors[i].r = (int) (((long) pixels[i]) & 0xff0000) >> 16;
512 colors[i].g = (int) (((long) pixels[i]) & 0x00ff00) >> 8;
513 colors[i].b = (int) (((long) pixels[i]) & 0x0000ff);
516 return colors;
519 protected static int distance(Color ca, Color cb)
521 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);
524 protected static void equalTransparentCase(TransparentColor[] colors, int[] extremaIndices, short value)
526 // we want extremaIndices[0] to be greater than extremaIndices[1]
528 if (value == 0)
530 // transparent
531 colors[extremaIndices[0]] = TransparentColor.OFF_TRANSPARENT;
533 /*else
535 // not transparent anywhere - it's all one colour, so we don't need to bother making changes
540 protected static int distance(TransparentColor ca, TransparentColor cb)
542 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)
543 + (cb.a - ca.a) * (cb.a - ca.a);
546 // public static void main(String[] args)
547 // {
548 // try
549 // {
550 // String fileName = "testdata/0000_0001";
551 // ByteBuffer buffer = convertToDxt1NoTransparency(new File(fileName + ".jpg"));
552 // buffer.rewind();
553 // FileOutputStream fos = new FileOutputStream(fileName + ".dds");
554 // channels.FileChannel channel = fos.getChannel();
555 // channel.write(buffer);
556 // }
557 // catch (IOException e)
558 // {
559 // e.printStackTrace();
560 // }
562 // return;
563 // }
565 protected static long computeBitMask(TransparentColor[] colors, int[] extremaIndices)
567 TransparentColor[] colorPoints = {null, null, new TransparentColor(), new TransparentColor()};
569 colorPoints[0] = colors[extremaIndices[0]];
570 colorPoints[1] = colors[extremaIndices[1]];
572 colorPoints[2].r = (colorPoints[0].r + colorPoints[1].r) / 2;
573 colorPoints[2].g = (colorPoints[0].g + colorPoints[1].g) / 2;
574 colorPoints[2].b = (colorPoints[0].b + colorPoints[1].b) / 2;
575 colorPoints[2].a = 1;
577 colorPoints[3].r = 0;
578 colorPoints[3].g = 0;
579 colorPoints[3].b = 0;
580 colorPoints[3].a = 0;
582 long bitmask = 0;
583 for (int i = 0; i < colors.length; i++)
585 int closest = Integer.MAX_VALUE;
586 int mask = 0;
587 if (colors[i].a == 0)
589 mask = 3;
591 else
593 for (int j = 0; j < colorPoints.length; j++)
595 int d = distance(colors[i], colorPoints[j]);
596 if (d < closest)
598 closest = d;
599 mask = j;
603 bitmask |= mask << i * 2;
606 return bitmask;
609 protected static short getShort5551(TransparentColor color)
611 short s = 0;
612 s |= ((color.r & 0x0f8) << 8) | ((color.g & 0x0f8) << 4) | ((color.b & 0x0f8) >> 3) | ((color.a & 0x0f8) >> 7);
613 // System.out.println(Integer.toBinaryString(s));
614 return s;
617 protected static int[] determineExtremeColors(TransparentColor[] colors)
619 int farthest = Integer.MIN_VALUE;
620 int[] ex = {0, 0};
622 for (int i = 0; i < colors.length - 1; i++)
624 for (int j = i + 1; j < colors.length; j++)
626 int d = distance(colors[i], colors[j]);
627 if (d > farthest)
629 farthest = d;
630 ex[0] = i;
631 ex[1] = j;
636 return ex;
639 protected static TransparentColor[] getColors5551(int[] pixels)
641 TransparentColor colors[] = new TransparentColor[pixels.length];
643 for (int i = 0; i < pixels.length; i++)
645 colors[i] = generateColor5551(pixels[i]);
647 return colors;
650 protected static TransparentColor generateColor5551(int pixel)
652 short alpha = (short) (pixel >> 24);
653 if ((alpha & 0xf0) == 0)
655 return TransparentColor.TRANSPARENT;
658 // ok, it's not transparent - that's already been ruled out.
660 TransparentColor tc = new TransparentColor();
661 tc.a = 0x000000ff;
662 tc.r = (pixel & 0x00ff0000) >> 16;
663 tc.g = (pixel & 0x0000ff00) >> 8;
664 tc.b = (pixel & 0x000000ff);
666 return tc;
669 protected static class TransparentColor
671 private static final TransparentColor TRANSPARENT = new TransparentColor(0, 0, 0, 0);
672 private static final TransparentColor OFF_TRANSPARENT = new TransparentColor(0, 0, 1, 0);
673 private int r, g, b, a;
675 private TransparentColor()
679 private TransparentColor(int r, int g, int b, int a)
681 this.r = r;
682 this.g = g;
683 this.b = b;
684 this.a = a;
687 public boolean equals(Object o)
689 if (this == o)
690 return true;
691 if (o == null || getClass() != o.getClass())
692 return false;
694 final gov.nasa.worldwind.DDSConverter.TransparentColor that =
695 (gov.nasa.worldwind.DDSConverter.TransparentColor) o;
697 if (a != that.a)
698 return false;
699 if (b != that.b)
700 return false;
701 if (g != that.g)
702 return false;
703 //noinspection RedundantIfStatement
704 if (r != that.r)
705 return false;
707 return true;
710 public int hashCode()
712 int result;
713 result = r;
714 result = 29 * result + g;
715 result = 29 * result + b;
716 result = 29 * result + a;
717 return result;
720 public String toString()
722 return "TransColor argb: " + this.a + ", " + this.r + ", " + this.g + ", " + this.b;