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
.formats
.dds
;
9 import gov
.nasa
.worldwind
.util
.*;
11 import java
.awt
.image
.*;
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
36 this.r
= this.g
= this.b
= 0;
39 public Color(int r
, int g
, int b
)
46 public boolean equals(Object o
)
50 if (o
== null || getClass() != o
.getClass())
53 final Color color
= (Color
) o
;
59 //noinspection RedundantIfStatement
70 result
= 29 * result
+ g
;
71 result
= 29 * result
+ b
;
76 public static ByteBuffer
convertToDDS(ByteBuffer image
, String mimeType
) throws IOException
80 String message
= Logging
.getMessage("nullValue.ByteBufferIsNull");
81 Logging
.logger().severe(message
);
82 throw new IllegalArgumentException(message
);
87 String message
= Logging
.getMessage("nullValue.MimeTypeIsNull");
88 Logging
.logger().severe(message
);
89 throw new IllegalArgumentException(message
);
92 String suffix
= WWIO
.getSuffixForMimeType(mimeType
);
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
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
);
127 // Don't waste the space for transparency if
128 if (image
.getColorModel().hasAlpha())
129 return convertToDxt3(image
);
131 return convertToDxt1NoTransparency(image
);
134 public static ByteBuffer
convertToDxt1NoTransparency(ByteBuffer image
, String mimeType
)
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
);
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
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
);
184 return null; // TODO: logger
187 return convertToDxt1NoTransparency(image
);
190 public static ByteBuffer
convertToDxt1NoTransparency(BufferedImage image
)
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
);
238 public static ByteBuffer
convertToDxt3(ByteBuffer image
, String mimeType
)
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
);
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
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
);
290 return convertToDxt3(image
);
293 public static ByteBuffer
convertToDxt3(BufferedImage image
)
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
);
349 protected static void buildHeaderDxt1(ByteBuffer buffer
, int width
, int height
)
352 buffer
.put((byte) 'D');
353 buffer
.put((byte) 'D');
354 buffer
.put((byte) 'S');
355 buffer
.put((byte) ' ');
357 int flag
= DDSD_CAPS
| DDSD_HEIGHT
| DDSD_WIDTH
| DDSD_PIXELFORMAT
| DDSD_MIPMAPCOUNT
| DDSD_LINEARSIZE
;
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
)
384 buffer
.put((byte) 'D');
385 buffer
.put((byte) 'D');
386 buffer
.put((byte) 'S');
387 buffer
.put((byte) ' ');
389 int flag
= DDSD_CAPS
| DDSD_HEIGHT
| DDSD_WIDTH
| DDSD_PIXELFORMAT
| DDSD_MIPMAPCOUNT
| DDSD_LINEARSIZE
;
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
]);
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]))
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;
459 for (int i
= 0; i
< colors
.length
; i
++)
461 int closest
= Integer
.MAX_VALUE
;
463 for (int j
= 0; j
< colorPoints
.length
; j
++)
465 int d
= distance(colors
[i
], colorPoints
[j
]);
472 bitmask
|= mask
<< i
* 2;
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);
497 protected static Color
getColor888(int r8g8b8
)
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);
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]
533 colors
[extremaIndices
[0]] = TransparentColor
.OFF_TRANSPARENT
;
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)
552 // String fileName = "testdata/0000_0001";
553 // ByteBuffer buffer = convertToDxt1NoTransparency(new File(fileName + ".jpg"));
555 // FileOutputStream fos = new FileOutputStream(fileName + ".dds");
556 // channels.FileChannel channel = fos.getChannel();
557 // channel.write(buffer);
559 // catch (IOException e)
561 // e.printStackTrace();
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;
585 for (int i
= 0; i
< colors
.length
; i
++)
587 int closest
= Integer
.MAX_VALUE
;
589 if (colors
[i
].a
== 0)
595 for (int j
= 0; j
< colorPoints
.length
; j
++)
597 int d
= distance(colors
[i
], colorPoints
[j
]);
605 bitmask
|= mask
<< i
* 2;
611 protected static short getShort5551(TransparentColor color
)
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));
619 protected static int[] determineExtremeColors(TransparentColor
[] colors
)
621 int farthest
= Integer
.MIN_VALUE
;
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
]);
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
]);
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();
664 tc
.r
= (pixel
& 0x00ff0000) >> 16;
665 tc
.g
= (pixel
& 0x0000ff00) >> 8;
666 tc
.b
= (pixel
& 0x000000ff);
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
)
689 public boolean equals(Object o
)
693 if (o
== null || getClass() != o
.getClass())
696 final TransparentColor that
=
697 (TransparentColor
) o
;
705 //noinspection RedundantIfStatement
712 public int hashCode()
716 result
= 29 * result
+ g
;
717 result
= 29 * result
+ b
;
718 result
= 29 * result
+ a
;
722 public String
toString()
724 return "TransColor argb: " + this.a
+ ", " + this.r
+ ", " + this.g
+ ", " + this.b
;