001/** 002 * Copyright (c) 2008-2014 Ardor Labs, Inc. 003 * 004 * This file is part of Ardor3D. 005 * 006 * Ardor3D is free software: you can redistribute it and/or modify it 007 * under the terms of its license which may be found in the accompanying 008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>. 009 */ 010 011package com.ardor3d.image.util.dds; 012 013import static com.ardor3d.image.util.dds.DdsUtils.flipDXT; 014import static com.ardor3d.image.util.dds.DdsUtils.getInt; 015import static com.ardor3d.image.util.dds.DdsUtils.isSet; 016import static com.ardor3d.image.util.dds.DdsUtils.shiftCount; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.nio.ByteBuffer; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.logging.Logger; 024 025import com.ardor3d.image.Image; 026import com.ardor3d.image.ImageDataFormat; 027import com.ardor3d.image.PixelDataType; 028import com.ardor3d.image.util.ImageLoader; 029import com.ardor3d.image.util.ImageUtils; 030import com.ardor3d.util.LittleEndianDataInput; 031import com.ardor3d.util.geom.BufferUtils; 032 033/** 034 * <p> 035 * <code>DdsLoader</code> is an image loader that reads in a DirectX DDS file. 036 * </p> 037 * Supports 2D images, volume images and cubemaps in the following formats:<br> 038 * Compressed:<br> 039 * <ul> 040 * <li>DXT1A</li> 041 * <li>DXT3</li> 042 * <li>DXT5</li> 043 * <li>LATC</li> 044 * </ul> 045 * Uncompressed:<br> 046 * <ul> 047 * <li>RGB</li> 048 * <li>RGBA</li> 049 * <li>Luminance</li> 050 * <li>LuminanceAlpha</li> 051 * <li>Alpha</li> 052 * </ul> 053 * Note that Cubemaps must have all 6 faces defined to load properly. FIXME: Needs a software inflater for compressed 054 * formats in cases where support is not present? Maybe JSquish? 055 */ 056public class DdsLoader implements ImageLoader { 057 private static final Logger logger = Logger.getLogger(DdsLoader.class.getName()); 058 059 @Override 060 public Image load(final InputStream is, final boolean flipVertically) throws IOException { 061 try (final LittleEndianDataInput in = new LittleEndianDataInput(is)) { 062 063 // Read and check magic word... 064 final int dwMagic = in.readInt(); 065 if (dwMagic != getInt("DDS ")) { 066 throw new Error("Not a dds file."); 067 } 068 logger.finest("Reading DDS file."); 069 070 // Create our data store; 071 final DdsImageInfo info = new DdsImageInfo(); 072 073 info.flipVertically = flipVertically; 074 075 // Read standard dds header 076 info.header = DdsHeader.read(in); 077 078 // if applicable, read DX10 header 079 info.headerDX10 = info.header.ddpf.dwFourCC == getInt("DX10") ? DdsHeaderDX10.read(in) : null; 080 081 // Create our new image 082 final Image image = new Image(); 083 image.setWidth(info.header.dwWidth); 084 image.setHeight(info.header.dwHeight); 085 086 // update depth based on flags / header 087 updateDepth(image, info); 088 089 // add our format and image data. 090 populateImage(image, info, in); 091 092 // return the loaded image 093 return image; 094 } 095 } 096 097 private static final void updateDepth(final Image image, final DdsImageInfo info) { 098 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP)) { 099 int depth = 0; 100 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEX)) { 101 depth++; 102 } 103 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEX)) { 104 depth++; 105 } 106 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEY)) { 107 depth++; 108 } 109 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEY)) { 110 depth++; 111 } 112 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEZ)) { 113 depth++; 114 } 115 if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEZ)) { 116 depth++; 117 } 118 119 if (depth != 6) { 120 throw new Error("Cubemaps without all faces defined are not currently supported."); 121 } 122 123 image.setDepth(depth); 124 } else { 125 // make sure we have at least depth of 1. 126 image.setDepth(info.header.dwDepth > 0 ? info.header.dwDepth : 1); 127 } 128 } 129 130 private static final void populateImage(final Image image, final DdsImageInfo info, final LittleEndianDataInput in) 131 throws IOException { 132 final int flags = info.header.ddpf.dwFlags; 133 134 final boolean compressedFormat = isSet(flags, DdsPixelFormat.DDPF_FOURCC); 135 final boolean rgb = isSet(flags, DdsPixelFormat.DDPF_RGB); 136 final boolean alphaPixels = isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS); 137 final boolean lum = isSet(flags, DdsPixelFormat.DDPF_LUMINANCE); 138 final boolean alpha = isSet(flags, DdsPixelFormat.DDPF_ALPHA); 139 140 if (compressedFormat) { 141 final int fourCC = info.header.ddpf.dwFourCC; 142 // DXT1 format 143 if (fourCC == getInt("DXT1")) { 144 info.bpp = 4; 145 // if (isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS)) { 146 // XXX: many authoring tools do not set alphapixels, so we'll error on the side of alpha 147 logger.finest("DDS format: DXT1A"); 148 image.setDataFormat(ImageDataFormat.PrecompressedDXT1A); 149 // } else { 150 // logger.finest("DDS format: DXT1"); 151 // image.setDataFormat(ImageDataFormat.PrecompressedDXT1); 152 // } 153 } 154 155 // DXT3 format 156 else if (fourCC == getInt("DXT3")) { 157 logger.finest("DDS format: DXT3"); 158 info.bpp = 8; 159 image.setDataFormat(ImageDataFormat.PrecompressedDXT3); 160 } 161 162 // DXT5 format 163 else if (fourCC == getInt("DXT5")) { 164 logger.finest("DDS format: DXT5"); 165 info.bpp = 8; 166 image.setDataFormat(ImageDataFormat.PrecompressedDXT5); 167 } 168 169 // DXT10 info present... 170 else if (fourCC == getInt("DX10")) { 171 switch (info.headerDX10.dxgiFormat) { 172 case DXGI_FORMAT_BC4_UNORM: 173 logger.finest("DXGI format: BC4_UNORM"); 174 info.bpp = 4; 175 image.setDataFormat(ImageDataFormat.PrecompressedLATC_L); 176 break; 177 case DXGI_FORMAT_BC5_UNORM: 178 logger.finest("DXGI format: BC5_UNORM"); 179 info.bpp = 8; 180 image.setDataFormat(ImageDataFormat.PrecompressedLATC_LA); 181 break; 182 default: 183 throw new Error("dxgiFormat not supported: " + info.headerDX10.dxgiFormat); 184 } 185 } 186 187 // DXT2 format - unsupported 188 else if (fourCC == getInt("DXT2")) { 189 logger.finest("DDS format: DXT2"); 190 throw new Error("DXT2 is not supported."); 191 } 192 193 // DXT4 format - unsupported 194 else if (fourCC == getInt("DXT4")) { 195 logger.finest("DDS format: DXT4"); 196 throw new Error("DXT4 is not supported."); 197 } 198 199 // Unsupported compressed type. 200 else { 201 throw new Error("unsupported compressed dds format found (" + fourCC + ")"); 202 } 203 } 204 205 // not a compressed format 206 else { 207 // TODO: more use of bit masks? 208 // TODO: Use bit size instead of hardcoded 8 bytes? (need to also implement in readUncompressed) 209 image.setDataType(PixelDataType.UnsignedByte); 210 211 info.bpp = info.header.ddpf.dwRGBBitCount; 212 213 // One of the RGB formats? 214 if (rgb) { 215 if (alphaPixels) { 216 logger.finest("DDS format: uncompressed rgba"); 217 image.setDataFormat(ImageDataFormat.RGBA); 218 } else { 219 logger.finest("DDS format: uncompressed rgb "); 220 image.setDataFormat(ImageDataFormat.RGB); 221 } 222 } 223 224 // A luminance or alpha format 225 else if (lum || alphaPixels) { 226 if (lum && alphaPixels) { 227 logger.finest("DDS format: uncompressed LumAlpha"); 228 image.setDataFormat(ImageDataFormat.LuminanceAlpha); 229 } 230 231 else if (lum) { 232 logger.finest("DDS format: uncompressed Lum"); 233 image.setDataFormat(ImageDataFormat.Luminance); 234 } 235 236 else if (alpha) { 237 logger.finest("DDS format: uncompressed Alpha"); 238 image.setDataFormat(ImageDataFormat.Alpha); 239 } 240 } // end luminance/alpha type 241 242 // Unsupported type. 243 else { 244 throw new Error("unsupported uncompressed dds format found."); 245 } 246 } 247 248 info.calcMipmapSizes(compressedFormat); 249 image.setMipMapByteSizes(info.mipmapByteSizes); 250 251 // Add up total byte size of single depth layer 252 int totalSize = 0; 253 for (final int size : info.mipmapByteSizes) { 254 totalSize += size; 255 } 256 257 // Go through and load in image data 258 final List<ByteBuffer> imageData = new ArrayList<>(); 259 for (int i = 0; i < image.getDepth(); i++) { 260 // read in compressed data 261 if (compressedFormat) { 262 imageData.add(readDXT(in, totalSize, info, image)); 263 } 264 265 // read in uncompressed data 266 else if (rgb || lum || alpha) { 267 imageData.add(readUncompressed(in, totalSize, rgb, lum, alpha, alphaPixels, info, image)); 268 } 269 } 270 271 // set on image 272 image.setData(imageData); 273 } 274 275 static final ByteBuffer readDXT(final LittleEndianDataInput in, final int totalSize, final DdsImageInfo info, 276 final Image image) throws IOException { 277 int mipWidth = info.header.dwWidth; 278 int mipHeight = info.header.dwHeight; 279 280 final ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); 281 for (int mip = 0; mip < info.header.dwMipMapCount; mip++) { 282 final byte[] data = new byte[info.mipmapByteSizes[mip]]; 283 in.readFully(data); 284 if (!info.flipVertically) { 285 buffer.put(data); 286 } else { 287 final byte[] flipped = flipDXT(data, mipWidth, mipHeight, image.getDataFormat()); 288 buffer.put(flipped); 289 290 mipWidth = Math.max(mipWidth / 2, 1); 291 mipHeight = Math.max(mipHeight / 2, 1); 292 } 293 } 294 buffer.rewind(); 295 return buffer; 296 } 297 298 private static ByteBuffer readUncompressed(final LittleEndianDataInput in, final int totalSize, 299 final boolean useRgb, final boolean useLum, final boolean useAlpha, final boolean useAlphaPixels, 300 final DdsImageInfo info, final Image image) throws IOException { 301 final int redLumShift = shiftCount(info.header.ddpf.dwRBitMask); 302 final int greenShift = shiftCount(info.header.ddpf.dwGBitMask); 303 final int blueShift = shiftCount(info.header.ddpf.dwBBitMask); 304 final int alphaShift = shiftCount(info.header.ddpf.dwABitMask); 305 306 final int sourcebytesPP = info.header.ddpf.dwRGBBitCount / 8; 307 final int targetBytesPP = ImageUtils.getPixelByteSize(image.getDataFormat(), image.getDataType()); 308 309 final ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); 310 311 int mipWidth = info.header.dwWidth; 312 int mipHeight = info.header.dwHeight; 313 int offset = 0; 314 315 for (int mip = 0; mip < info.header.dwMipMapCount; mip++) { 316 for (int y = 0; y < mipHeight; y++) { 317 for (int x = 0; x < mipWidth; x++) { 318 final byte[] b = new byte[sourcebytesPP]; 319 in.readFully(b); 320 321 final int i = getInt(b); 322 323 final byte redLum = (byte) (((i & info.header.ddpf.dwRBitMask) >> redLumShift)); 324 final byte green = (byte) (((i & info.header.ddpf.dwGBitMask) >> greenShift)); 325 final byte blue = (byte) (((i & info.header.ddpf.dwBBitMask) >> blueShift)); 326 final byte alpha = (byte) (((i & info.header.ddpf.dwABitMask) >> alphaShift)); 327 328 if (info.flipVertically) { 329 dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); 330 } 331 332 if (useAlpha) { 333 dataBuffer.put(alpha); 334 } else if (useLum) { 335 if (useAlphaPixels) { 336 dataBuffer.put(redLum).put(alpha); 337 } else { 338 dataBuffer.put(redLum); 339 } 340 } else if (useRgb) { 341 if (useAlphaPixels) { 342 dataBuffer.put(redLum).put(green).put(blue).put(alpha); 343 } else { 344 dataBuffer.put(redLum).put(green).put(blue); 345 } 346 } 347 } 348 } 349 350 offset += mipWidth * mipHeight * targetBytesPP; 351 352 mipWidth = Math.max(mipWidth / 2, 1); 353 mipHeight = Math.max(mipHeight / 2, 1); 354 } 355 356 return dataBuffer; 357 } 358 359 private final static class DdsImageInfo { 360 boolean flipVertically; 361 int bpp = 0; 362 DdsHeader header; 363 DdsHeaderDX10 headerDX10; 364 int mipmapByteSizes[]; 365 366 void calcMipmapSizes(final boolean compressed) { 367 int width = header.dwWidth; 368 int height = header.dwHeight; 369 int size = 0; 370 371 mipmapByteSizes = new int[header.dwMipMapCount]; 372 373 for (int i = 0; i < header.dwMipMapCount; i++) { 374 if (compressed) { 375 size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2; 376 } else { 377 size = width * height * bpp / 8; 378 } 379 380 mipmapByteSizes[i] = ((size + 3) / 4) * 4; 381 382 width = Math.max(width / 2, 1); 383 height = Math.max(height / 2, 1); 384 } 385 } 386 } 387}