2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
7 package gov
.nasa
.worldwind
;
9 import java
.awt
.image
.*;
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
34 this.r
= this.g
= this.b
= 0;
37 public Color(int r
, int g
, int b
)
44 public boolean equals(Object o
)
48 if (o
== null || getClass() != o
.getClass())
51 final gov
.nasa
.worldwind
.DDSConverter
.Color color
= (gov
.nasa
.worldwind
.DDSConverter
.Color
) o
;
57 //noinspection RedundantIfStatement
68 result
= 29 * result
+ g
;
69 result
= 29 * result
+ b
;
74 public static ByteBuffer
convertToDDS(ByteBuffer image
, String mimeType
) throws IOException
78 String message
= WorldWind
.retrieveErrMsg("nullValue.ByteBufferIsNull");
79 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
80 throw new IllegalArgumentException(message
);
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
);
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
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
);
125 // Don't waste the space for transparency if
126 if (image
.getColorModel().hasAlpha())
127 return convertToDxt3(image
);
129 return convertToDxt1NoTransparency(image
);
132 public static ByteBuffer
convertToDxt1NoTransparency(ByteBuffer image
, String mimeType
)
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
);
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
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
);
182 return null; // TODO: log
185 return convertToDxt1NoTransparency(image
);
188 public static ByteBuffer
convertToDxt1NoTransparency(BufferedImage image
)
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
);
236 public static ByteBuffer
convertToDxt3(ByteBuffer image
, String mimeType
)
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
);
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
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
);
288 return convertToDxt3(image
);
291 public static ByteBuffer
convertToDxt3(BufferedImage image
) throws IOException
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
);
347 protected static void buildHeaderDxt1(ByteBuffer buffer
, int width
, int height
)
350 buffer
.put((byte) 'D');
351 buffer
.put((byte) 'D');
352 buffer
.put((byte) 'S');
353 buffer
.put((byte) ' ');
355 int flag
= DDSD_CAPS
| DDSD_HEIGHT
| DDSD_WIDTH
| DDSD_PIXELFORMAT
| DDSD_MIPMAPCOUNT
| DDSD_LINEARSIZE
;
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
)
382 buffer
.put((byte) 'D');
383 buffer
.put((byte) 'D');
384 buffer
.put((byte) 'S');
385 buffer
.put((byte) ' ');
387 int flag
= DDSD_CAPS
| DDSD_HEIGHT
| DDSD_WIDTH
| DDSD_PIXELFORMAT
| DDSD_MIPMAPCOUNT
| DDSD_LINEARSIZE
;
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
]);
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]))
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;
457 for (int i
= 0; i
< colors
.length
; i
++)
459 int closest
= Integer
.MAX_VALUE
;
461 for (int j
= 0; j
< colorPoints
.length
; j
++)
463 int d
= distance(colors
[i
], colorPoints
[j
]);
470 bitmask
|= mask
<< i
* 2;
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);
495 protected static Color
getColor888(int r8g8b8
)
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);
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]
531 colors
[extremaIndices
[0]] = TransparentColor
.OFF_TRANSPARENT
;
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)
550 // String fileName = "testdata/0000_0001";
551 // ByteBuffer buffer = convertToDxt1NoTransparency(new File(fileName + ".jpg"));
553 // FileOutputStream fos = new FileOutputStream(fileName + ".dds");
554 // channels.FileChannel channel = fos.getChannel();
555 // channel.write(buffer);
557 // catch (IOException e)
559 // e.printStackTrace();
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;
583 for (int i
= 0; i
< colors
.length
; i
++)
585 int closest
= Integer
.MAX_VALUE
;
587 if (colors
[i
].a
== 0)
593 for (int j
= 0; j
< colorPoints
.length
; j
++)
595 int d
= distance(colors
[i
], colorPoints
[j
]);
603 bitmask
|= mask
<< i
* 2;
609 protected static short getShort5551(TransparentColor color
)
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));
617 protected static int[] determineExtremeColors(TransparentColor
[] colors
)
619 int farthest
= Integer
.MIN_VALUE
;
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
]);
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
]);
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();
662 tc
.r
= (pixel
& 0x00ff0000) >> 16;
663 tc
.g
= (pixel
& 0x0000ff00) >> 8;
664 tc
.b
= (pixel
& 0x000000ff);
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
)
687 public boolean equals(Object o
)
691 if (o
== null || getClass() != o
.getClass())
694 final gov
.nasa
.worldwind
.DDSConverter
.TransparentColor that
=
695 (gov
.nasa
.worldwind
.DDSConverter
.TransparentColor
) o
;
703 //noinspection RedundantIfStatement
710 public int hashCode()
714 result
= 29 * result
+ g
;
715 result
= 29 * result
+ b
;
716 result
= 29 * result
+ a
;
720 public String
toString()
722 return "TransColor argb: " + this.a
+ ", " + this.r
+ ", " + this.g
+ ", " + this.b
;