// ========================================================== // DDS Loader // // Design and implementation by // - Volker Gärtner (volkerg@gmx.at) // - Sherman Wilcox // // This file is part of FreeImage 3 // // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER // THIS DISCLAIMER. // // Use at your own risk! // ========================================================== #include "FreeImage.h" #include "Utilities.h" // ---------------------------------------------------------- // Definitions for the DDS format // ---------------------------------------------------------- #ifdef _WIN32 #pragma pack(push, 1) #else #pragma pack(1) #endif typedef struct tagDDPIXELFORMAT { DWORD dwSize; // size of this structure (must be 32) DWORD dwFlags; // see DDPF_* DWORD dwFourCC; DWORD dwRGBBitCount; // Total number of bits for RGB formats DWORD dwRBitMask; DWORD dwGBitMask; DWORD dwBBitMask; DWORD dwRGBAlphaBitMask; } DDPIXELFORMAT; // DIRECTDRAW PIXELFORMAT FLAGS enum { DDPF_ALPHAPIXELS = 0x00000001l, // surface has alpha channel DDPF_ALPHA = 0x00000002l, // alpha only DDPF_FOURCC = 0x00000004l, // FOURCC available DDPF_RGB = 0x00000040l // RGB(A) bitmap }; typedef struct tagDDCAPS2 { DWORD dwCaps1; // Zero or more of the DDSCAPS_* members DWORD dwCaps2; // Zero or more of the DDSCAPS2_* members DWORD dwReserved[2]; } DDCAPS2; // DIRECTDRAWSURFACE CAPABILITY FLAGS enum { DDSCAPS_ALPHA = 0x00000002l, // alpha only surface DDSCAPS_COMPLEX = 0x00000008l, // complex surface structure DDSCAPS_TEXTURE = 0x00001000l, // used as texture (should always be set) DDSCAPS_MIPMAP = 0x00400000l // Mipmap present }; enum { DDSCAPS2_CUBEMAP = 0x00000200L, DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400L, DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800L, DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000L, DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000L, DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000L, DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000L, DDSCAPS2_VOLUME = 0x00200000L }; typedef struct tagDDSURFACEDESC2 { DWORD dwSize; // size of this structure (must be 124) DWORD dwFlags; // combination of the DDSS_* flags DWORD dwHeight; DWORD dwWidth; DWORD dwPitchOrLinearSize; DWORD dwDepth; // Depth of a volume texture DWORD dwMipMapCount; DWORD dwReserved1[11]; DDPIXELFORMAT ddpfPixelFormat; DDCAPS2 ddsCaps; DWORD dwReserved2; } DDSURFACEDESC2; enum { DDSD_CAPS = 0x00000001l, DDSD_HEIGHT = 0x00000002l, DDSD_WITH = 0x00000004l, DDSD_PITCH = 0x00000008l, DDSD_ALPHABITDEPTH = 0x00000080l, DDSD_PIXELFORMAT = 0x00001000l, DDSD_MIPMAPCOUNT = 0x00020000l, DDSD_LINEARSIZE = 0x00080000l, DDSD_DEPTH = 0x00800000l }; typedef struct tagDDSHEADER { DWORD dwMagic; // FOURCC: "DDS " DDSURFACEDESC2 surfaceDesc; } DDSHEADER; #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \ ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 )) #define FOURCC_DXT1 MAKEFOURCC('D','X','T','1') #define FOURCC_DXT2 MAKEFOURCC('D','X','T','2') #define FOURCC_DXT3 MAKEFOURCC('D','X','T','3') #define FOURCC_DXT4 MAKEFOURCC('D','X','T','4') #define FOURCC_DXT5 MAKEFOURCC('D','X','T','5') // ---------------------------------------------------------- // Structures used by DXT textures // ---------------------------------------------------------- typedef struct tagColor8888 { BYTE b; BYTE g; BYTE r; BYTE a; } Color8888; typedef struct tagColor565 { WORD b : 5; WORD g : 6; WORD r : 5; } Color565; typedef struct tagDXTColBlock { Color565 colors[2]; BYTE row[4]; } DXTColBlock; typedef struct tagDXTAlphaBlockExplicit { WORD row[4]; } DXTAlphaBlockExplicit; typedef struct tagDXTAlphaBlock3BitLinear { BYTE alpha[2]; BYTE data[6]; } DXTAlphaBlock3BitLinear; typedef struct tagDXT1Block { DXTColBlock color; } DXT1Block; typedef struct tagDXT3Block { // also used by dxt2 DXTAlphaBlockExplicit alpha; DXTColBlock color; } DXT3Block; typedef struct tagDXT5Block { // also used by dxt4 DXTAlphaBlock3BitLinear alpha; DXTColBlock color; } DXT5Block; #ifdef _WIN32 # pragma pack(pop) #else # pragma pack() #endif // ---------------------------------------------------------- // Internal functions // ---------------------------------------------------------- #ifdef FREEIMAGE_BIGENDIAN static void SwapHeader(DDSHEADER *header) { SwapLong(&header->dwMagic); SwapLong(&header->surfaceDesc.dwSize); SwapLong(&header->surfaceDesc.dwFlags); SwapLong(&header->surfaceDesc.dwHeight); SwapLong(&header->surfaceDesc.dwWidth); SwapLong(&header->surfaceDesc.dwPitchOrLinearSize); SwapLong(&header->surfaceDesc.dwDepth); SwapLong(&header->surfaceDesc.dwMipMapCount); for(int i=0; i<11; i++) { SwapLong(&header->surfaceDesc.dwReserved1[i]); } SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwSize); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwFlags); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwFourCC); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwRGBBitCount); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwRBitMask); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwGBitMask); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwBBitMask); SwapLong(&header->surfaceDesc.ddpfPixelFormat.dwRGBAlphaBitMask); SwapLong(&header->surfaceDesc.ddsCaps.dwCaps1); SwapLong(&header->surfaceDesc.ddsCaps.dwCaps2); SwapLong(&header->surfaceDesc.ddsCaps.dwReserved[0]); SwapLong(&header->surfaceDesc.ddsCaps.dwReserved[1]); SwapLong(&header->surfaceDesc.dwReserved2); } #endif // ========================================================== // Get the 4 possible colors for a block // static void GetBlockColors (const DXTColBlock &block, Color8888 colors[4], bool isDXT1) { int i; // expand from 565 to 888 for (i = 0; i < 2; i++) { colors[i].a = 0xff; /* colors[i].r = (BYTE)(block.colors[i].r * 0xff / 0x1f); colors[i].g = (BYTE)(block.colors[i].g * 0xff / 0x3f); colors[i].b = (BYTE)(block.colors[i].b * 0xff / 0x1f); */ colors[i].r = (BYTE)((block.colors[i].r << 3U) | (block.colors[i].r >> 2U)); colors[i].g = (BYTE)((block.colors[i].g << 2U) | (block.colors[i].g >> 4U)); colors[i].b = (BYTE)((block.colors[i].b << 3U) | (block.colors[i].b >> 2U)); } WORD *wCol = (WORD *)block.colors; if (wCol[0] > wCol[1] || !isDXT1) { // 4 color block for (i = 0; i < 2; i++) { colors[i + 2].a = 0xff; colors[i + 2].r = (BYTE)((WORD (colors[0].r) * (2 - i) + WORD (colors[1].r) * (1 + i)) / 3); colors[i + 2].g = (BYTE)((WORD (colors[0].g) * (2 - i) + WORD (colors[1].g) * (1 + i)) / 3); colors[i + 2].b = (BYTE)((WORD (colors[0].b) * (2 - i) + WORD (colors[1].b) * (1 + i)) / 3); } } else { // 3 color block, number 4 is transparent colors[2].a = 0xff; colors[2].r = (BYTE)((WORD (colors[0].r) + WORD (colors[1].r)) / 2); colors[2].g = (BYTE)((WORD (colors[0].g) + WORD (colors[1].g)) / 2); colors[2].b = (BYTE)((WORD (colors[0].b) + WORD (colors[1].b)) / 2); colors[3].a = 0x00; colors[3].g = 0x00; colors[3].b = 0x00; colors[3].r = 0x00; } } struct DXT_INFO_1 { typedef DXT1Block Block; enum { isDXT1 = 1, bytesPerBlock = 8 }; }; struct DXT_INFO_3 { typedef DXT3Block Block; enum { isDXT1 = 1, bytesPerBlock = 16 }; }; struct DXT_INFO_5 { typedef DXT5Block Block; enum { isDXT1 = 1, bytesPerBlock = 16 }; }; template class DXT_BLOCKDECODER_BASE { protected: Color8888 m_colors[4]; const typename INFO::Block *m_pBlock; unsigned m_colorRow; public: void Setup (const BYTE *pBlock) { m_pBlock = (const typename INFO::Block *)pBlock; GetBlockColors (m_pBlock->color, m_colors, INFO::isDXT1); } void SetY (int y) { m_colorRow = m_pBlock->color.row[y]; } void GetColor (int x, int y, Color8888 &color) { unsigned bits = (m_colorRow >> (x * 2)) & 3; color = m_colors[bits]; } }; class DXT_BLOCKDECODER_1 : public DXT_BLOCKDECODER_BASE { public: typedef DXT_INFO_1 INFO; }; class DXT_BLOCKDECODER_3 : public DXT_BLOCKDECODER_BASE { public: typedef DXT_BLOCKDECODER_BASE base; typedef DXT_INFO_3 INFO; protected: unsigned m_alphaRow; public: void SetY (int y) { base::SetY (y); m_alphaRow = m_pBlock->alpha.row[y]; } void GetColor (int x, int y, Color8888 &color) { base::GetColor (x, y, color); const unsigned bits = (m_alphaRow >> (x * 4)) & 0xF; color.a = (BYTE)((bits * 0xFF) / 0xF); } }; class DXT_BLOCKDECODER_5 : public DXT_BLOCKDECODER_BASE { public: typedef DXT_BLOCKDECODER_BASE base; typedef DXT_INFO_5 INFO; protected: unsigned m_alphas[8]; unsigned m_alphaBits; int m_offset; public: void Setup (const BYTE *pBlock) { base::Setup (pBlock); const DXTAlphaBlock3BitLinear &block = m_pBlock->alpha; m_alphas[0] = block.alpha[0]; m_alphas[1] = block.alpha[1]; if (m_alphas[0] > m_alphas[1]) { // 8 alpha block for (int i = 0; i < 6; i++) { m_alphas[i + 2] = ((6 - i) * m_alphas[0] + (1 + i) * m_alphas[1] + 3) / 7; } } else { // 6 alpha block for (int i = 0; i < 4; i++) { m_alphas[i + 2] = ((4 - i) * m_alphas[0] + (1 + i) * m_alphas[1] + 2) / 5; } m_alphas[6] = 0; m_alphas[7] = 0xFF; } } void SetY (int y) { base::SetY (y); int i = y / 2; const DXTAlphaBlock3BitLinear &block = m_pBlock->alpha; m_alphaBits = unsigned(block.data[0 + i * 3]) | (unsigned(block.data[1 + i * 3]) << 8) | (unsigned(block.data[2 + i * 3]) << 16); m_offset = (y & 1) * 12; } void GetColor (int x, int y, Color8888 &color) { base::GetColor (x, y, color); unsigned bits = (m_alphaBits >> (x * 3 + m_offset)) & 7; color.a = (BYTE)m_alphas[bits]; } }; template void DecodeDXTBlock (BYTE *dstData, const BYTE *srcBlock, long dstPitch, int bw, int bh) { DECODER decoder; decoder.Setup (srcBlock); for (int y = 0; y < bh; y++) { BYTE *dst = dstData - y * dstPitch; decoder.SetY (y); for (int x = 0; x < bw; x++) { decoder.GetColor (x, y, (Color8888 &)*dst); dst += 4; } } } // ========================================================== // Plugin Interface // ========================================================== static int s_format_id; // ========================================================== // Internal functions // ========================================================== static FIBITMAP * LoadRGB (DDSURFACEDESC2 &desc, FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { int width = (int)desc.dwWidth & ~3; int height = (int)desc.dwHeight & ~3; int bpp = (int)desc.ddpfPixelFormat.dwRGBBitCount; // allocate a new dib FIBITMAP *dib = FreeImage_Allocate (width, height, bpp, desc.ddpfPixelFormat.dwRBitMask, desc.ddpfPixelFormat.dwGBitMask, desc.ddpfPixelFormat.dwBBitMask); if (dib == NULL) return NULL; #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB // Calculate the number of bytes per pixel (3 for 24-bit or 4 for 32-bit) int bytespp = FreeImage_GetLine(dib) / FreeImage_GetWidth(dib); #endif // read the file int line = CalculateLine(width, bpp); int filePitch = (desc.dwFlags & DDSD_PITCH) ? (int)desc.dwPitchOrLinearSize : line; long delta = (long)filePitch - (long)line; for (int i = 0; i < height; i++) { BYTE *pixels = FreeImage_GetScanLine(dib, height - i - 1); io->read_proc (pixels, 1, line, handle); io->seek_proc (handle, delta, SEEK_CUR); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB for(int x = 0; x < width; x++) { INPLACESWAP(pixels[FI_RGBA_RED],pixels[FI_RGBA_BLUE]); pixels += bytespp; } #endif } // enable transparency FreeImage_SetTransparent (dib, (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) ? TRUE : FALSE); if (!(desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) && bpp == 32) { // no transparency: convert to 24-bit FIBITMAP *old = dib; dib = FreeImage_ConvertTo24Bits (old); FreeImage_Unload (old); } return dib; } template static void LoadDXT_Helper (FreeImageIO *io, fi_handle handle, int page, int flags, void *data, FIBITMAP *dib, int width, int height, int line) { typedef typename DECODER::INFO INFO; typedef typename INFO::Block Block; Block *input_buffer = new(std::nothrow) Block[(width + 3) / 4]; if (!input_buffer) return; int widthRest = (int) width & 3; int heightRest = (int) height & 3; int inputLine = (width + 3) / 4; int y = 0; if (height >= 4) { for (; y < height; y += 4) { io->read_proc (input_buffer, sizeof (typename INFO::Block), inputLine, handle); // TODO: probably need some endian work here BYTE *pbSrc = (BYTE *)input_buffer; BYTE *pbDst = FreeImage_GetScanLine (dib, height - y - 1); if (width >= 4) { for (int x = 0; x < width; x += 4) { DecodeDXTBlock (pbDst, pbSrc, line, 4, 4); pbSrc += INFO::bytesPerBlock; pbDst += 4 * 4; } } if (widthRest) { DecodeDXTBlock (pbDst, pbSrc, line, widthRest, 4); } } } if (heightRest) { io->read_proc (input_buffer, sizeof (typename INFO::Block), inputLine, handle); // TODO: probably need some endian work here BYTE *pbSrc = (BYTE *)input_buffer; BYTE *pbDst = FreeImage_GetScanLine (dib, height - y - 1); if (width >= 4) { for (int x = 0; x < width; x += 4) { DecodeDXTBlock (pbDst, pbSrc, line, 4, heightRest); pbSrc += INFO::bytesPerBlock; pbDst += 4 * 4; } } if (widthRest) { DecodeDXTBlock (pbDst, pbSrc, line, widthRest, heightRest); } } delete [] input_buffer; } static FIBITMAP * LoadDXT (int type, DDSURFACEDESC2 &desc, FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { int width = (int)desc.dwWidth & ~3; int height = (int)desc.dwHeight & ~3; // allocate a 32-bit dib FIBITMAP *dib = FreeImage_Allocate (width, height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK); if (dib == NULL) return NULL; int bpp = FreeImage_GetBPP (dib); int line = CalculateLine (width, bpp); BYTE *bits = FreeImage_GetBits (dib); // select the right decoder switch (type) { case 1: LoadDXT_Helper (io, handle, page, flags, data, dib, width, height, line); break; case 3: LoadDXT_Helper (io, handle, page, flags, data, dib, width, height, line); break; case 5: LoadDXT_Helper (io, handle, page, flags, data, dib, width, height, line); break; } return dib; } // ========================================================== // Plugin Implementation // ========================================================== static const char * DLL_CALLCONV Format() { return "DDS"; } static const char * DLL_CALLCONV Description() { return "DirectX Surface"; } static const char * DLL_CALLCONV Extension() { return "dds"; } static const char * DLL_CALLCONV RegExpr() { return NULL; } static const char * DLL_CALLCONV MimeType() { return "image/x-dds"; } static BOOL DLL_CALLCONV Validate(FreeImageIO *io, fi_handle handle) { DDSHEADER header; memset(&header, 0, sizeof(header)); io->read_proc(&header, 1, sizeof(header), handle); #ifdef FREEIMAGE_BIGENDIAN SwapHeader(&header); #endif if (header.dwMagic != MAKEFOURCC ('D','D','S',' ')) return FALSE; if (header.surfaceDesc.dwSize != sizeof (header.surfaceDesc) || header.surfaceDesc.ddpfPixelFormat.dwSize != sizeof (header.surfaceDesc.ddpfPixelFormat)) return FALSE; return TRUE; } static BOOL DLL_CALLCONV SupportsExportDepth(int depth) { return FALSE; } static BOOL DLL_CALLCONV SupportsExportType(FREE_IMAGE_TYPE type) { return FALSE; } // ---------------------------------------------------------- static void * DLL_CALLCONV Open(FreeImageIO *io, fi_handle handle, BOOL read) { return NULL; } static void DLL_CALLCONV Close(FreeImageIO *io, fi_handle handle, void *data) { } // ---------------------------------------------------------- static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { DDSHEADER header; FIBITMAP *dib = NULL; memset(&header, 0, sizeof(header)); io->read_proc(&header, 1, sizeof(header), handle); #ifdef FREEIMAGE_BIGENDIAN SwapHeader(&header); #endif if (header.surfaceDesc.ddpfPixelFormat.dwFlags & DDPF_RGB) { dib = LoadRGB (header.surfaceDesc, io, handle, page, flags, data); } else if (header.surfaceDesc.ddpfPixelFormat.dwFlags & DDPF_FOURCC) { switch (header.surfaceDesc.ddpfPixelFormat.dwFourCC) { case FOURCC_DXT1: dib = LoadDXT (1, header.surfaceDesc, io, handle, page, flags, data); break; case FOURCC_DXT3: dib = LoadDXT (3, header.surfaceDesc, io, handle, page, flags, data); break; case FOURCC_DXT5: dib = LoadDXT (5, header.surfaceDesc, io, handle, page, flags, data); break; } } return dib; } /* static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { return FALSE; } */ // ========================================================== // Init // ========================================================== void DLL_CALLCONV InitDDS(Plugin *plugin, int format_id) { s_format_id = format_id; plugin->format_proc = Format; plugin->description_proc = Description; plugin->extension_proc = Extension; plugin->regexpr_proc = RegExpr; plugin->open_proc = Open; plugin->close_proc = Close; plugin->pagecount_proc = NULL; plugin->pagecapability_proc = NULL; plugin->load_proc = Load; plugin->save_proc = NULL; //Save; // not implemented (yet?) plugin->validate_proc = Validate; plugin->mime_proc = MimeType; plugin->supports_export_bpp_proc = SupportsExportDepth; plugin->supports_export_type_proc = SupportsExportType; plugin->supports_icc_profiles_proc = NULL; }