/// DDS file support, does decoding, _not_ direct uploading /// (use SOIL for that ;-) /// A bunch of DirectDraw Surface structures and flags typedef struct { unsigned int dwMagic; unsigned int dwSize; unsigned int dwFlags; unsigned int dwHeight; unsigned int dwWidth; unsigned int dwPitchOrLinearSize; unsigned int dwDepth; unsigned int dwMipMapCount; unsigned int dwReserved1[ 11 ]; // DDPIXELFORMAT struct { unsigned int dwSize; unsigned int dwFlags; unsigned int dwFourCC; unsigned int dwRGBBitCount; unsigned int dwRBitMask; unsigned int dwGBitMask; unsigned int dwBBitMask; unsigned int dwAlphaBitMask; } sPixelFormat; // DDCAPS2 struct { unsigned int dwCaps1; unsigned int dwCaps2; unsigned int dwDDSX; unsigned int dwReserved; } sCaps; unsigned int dwReserved2; } DDS_header ; // the following constants were copied directly off the MSDN website // The dwFlags member of the original DDSURFACEDESC2 structure // can be set to one or more of the following values. #define DDSD_CAPS 0x00000001 #define DDSD_HEIGHT 0x00000002 #define DDSD_WIDTH 0x00000004 #define DDSD_PITCH 0x00000008 #define DDSD_PIXELFORMAT 0x00001000 #define DDSD_MIPMAPCOUNT 0x00020000 #define DDSD_LINEARSIZE 0x00080000 #define DDSD_DEPTH 0x00800000 // DirectDraw Pixel Format #define DDPF_ALPHAPIXELS 0x00000001 #define DDPF_FOURCC 0x00000004 #define DDPF_RGB 0x00000040 // The dwCaps1 member of the DDSCAPS2 structure can be // set to one or more of the following values. #define DDSCAPS_COMPLEX 0x00000008 #define DDSCAPS_TEXTURE 0x00001000 #define DDSCAPS_MIPMAP 0x00400000 // The dwCaps2 member of the DDSCAPS2 structure can be // set to one or more of the following values. #define DDSCAPS2_CUBEMAP 0x00000200 #define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 #define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 #define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 #define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 #define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 #define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 #define DDSCAPS2_VOLUME 0x00200000 static int dds_test(stbi *s) { // check the magic number if (get8(s) != 'D') return 0; if (get8(s) != 'D') return 0; if (get8(s) != 'S') return 0; if (get8(s) != ' ') return 0; // check header size if (get32le(s) != 124) return 0; return 1; } #ifndef STBI_NO_STDIO int stbi_dds_test_file (FILE *f) { stbi s; int r,n = ftell(f); start_file(&s,f); r = dds_test(&s); fseek(f,n,SEEK_SET); return r; } #endif int stbi_dds_test_memory (stbi_uc const *buffer, int len) { stbi s; start_mem(&s,buffer, len); return dds_test(&s); } // helper functions int stbi_convert_bit_range( int c, int from_bits, int to_bits ) { int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); return (b + (b >> from_bits)) >> from_bits; } void stbi_rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) { *r = stbi_convert_bit_range( (c >> 11) & 31, 5, 8 ); *g = stbi_convert_bit_range( (c >> 05) & 63, 6, 8 ); *b = stbi_convert_bit_range( (c >> 00) & 31, 5, 8 ); } void stbi_decode_DXT1_block( unsigned char uncompressed[16*4], unsigned char compressed[8] ) { int next_bit = 4*8; int i, r, g, b; int c0, c1; unsigned char decode_colors[4*4]; // find the 2 primary colors c0 = compressed[0] + (compressed[1] << 8); c1 = compressed[2] + (compressed[3] << 8); stbi_rgb_888_from_565( c0, &r, &g, &b ); decode_colors[0] = r; decode_colors[1] = g; decode_colors[2] = b; decode_colors[3] = 255; stbi_rgb_888_from_565( c1, &r, &g, &b ); decode_colors[4] = r; decode_colors[5] = g; decode_colors[6] = b; decode_colors[7] = 255; if( c0 > c1 ) { // no alpha, 2 interpolated colors decode_colors[8] = (2*decode_colors[0] + decode_colors[4]) / 3; decode_colors[9] = (2*decode_colors[1] + decode_colors[5]) / 3; decode_colors[10] = (2*decode_colors[2] + decode_colors[6]) / 3; decode_colors[11] = 255; decode_colors[12] = (decode_colors[0] + 2*decode_colors[4]) / 3; decode_colors[13] = (decode_colors[1] + 2*decode_colors[5]) / 3; decode_colors[14] = (decode_colors[2] + 2*decode_colors[6]) / 3; decode_colors[15] = 255; } else { // 1 interpolated color, alpha decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; decode_colors[11] = 255; decode_colors[12] = 0; decode_colors[13] = 0; decode_colors[14] = 0; decode_colors[15] = 0; } // decode the block for( i = 0; i < 16*4; i += 4 ) { int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 4; next_bit += 2; uncompressed[i+0] = decode_colors[idx+0]; uncompressed[i+1] = decode_colors[idx+1]; uncompressed[i+2] = decode_colors[idx+2]; uncompressed[i+3] = decode_colors[idx+3]; } // done } void stbi_decode_DXT23_alpha_block( unsigned char uncompressed[16*4], unsigned char compressed[8] ) { int i, next_bit = 0; // each alpha value gets 4 bits for( i = 3; i < 16*4; i += 4 ) { uncompressed[i] = stbi_convert_bit_range( (compressed[next_bit>>3] >> (next_bit&7)) & 15, 4, 8 ); next_bit += 4; } } void stbi_decode_DXT45_alpha_block( unsigned char uncompressed[16*4], unsigned char compressed[8] ) { int i, next_bit = 8*2; unsigned char decode_alpha[8]; // each alpha value gets 3 bits, and the 1st 2 bytes are the range decode_alpha[0] = compressed[0]; decode_alpha[1] = compressed[1]; if( decode_alpha[0] > decode_alpha[1] ) { // 6 step intermediate decode_alpha[2] = (6*decode_alpha[0] + 1*decode_alpha[1]) / 7; decode_alpha[3] = (5*decode_alpha[0] + 2*decode_alpha[1]) / 7; decode_alpha[4] = (4*decode_alpha[0] + 3*decode_alpha[1]) / 7; decode_alpha[5] = (3*decode_alpha[0] + 4*decode_alpha[1]) / 7; decode_alpha[6] = (2*decode_alpha[0] + 5*decode_alpha[1]) / 7; decode_alpha[7] = (1*decode_alpha[0] + 6*decode_alpha[1]) / 7; } else { // 4 step intermediate, pluss full and none decode_alpha[2] = (4*decode_alpha[0] + 1*decode_alpha[1]) / 5; decode_alpha[3] = (3*decode_alpha[0] + 2*decode_alpha[1]) / 5; decode_alpha[4] = (2*decode_alpha[0] + 3*decode_alpha[1]) / 5; decode_alpha[5] = (1*decode_alpha[0] + 4*decode_alpha[1]) / 5; decode_alpha[6] = 0; decode_alpha[7] = 255; } for( i = 3; i < 16*4; i += 4 ) { int idx = 0, bit; bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; idx += bit << 0; ++next_bit; bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; idx += bit << 1; ++next_bit; bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; idx += bit << 2; ++next_bit; uncompressed[i] = decode_alpha[idx & 7]; } // done } void stbi_decode_DXT_color_block( unsigned char uncompressed[16*4], unsigned char compressed[8] ) { int next_bit = 4*8; int i, r, g, b; int c0, c1; unsigned char decode_colors[4*3]; // find the 2 primary colors c0 = compressed[0] + (compressed[1] << 8); c1 = compressed[2] + (compressed[3] << 8); stbi_rgb_888_from_565( c0, &r, &g, &b ); decode_colors[0] = r; decode_colors[1] = g; decode_colors[2] = b; stbi_rgb_888_from_565( c1, &r, &g, &b ); decode_colors[3] = r; decode_colors[4] = g; decode_colors[5] = b; // Like DXT1, but no choicees: // no alpha, 2 interpolated colors decode_colors[6] = (2*decode_colors[0] + decode_colors[3]) / 3; decode_colors[7] = (2*decode_colors[1] + decode_colors[4]) / 3; decode_colors[8] = (2*decode_colors[2] + decode_colors[5]) / 3; decode_colors[9] = (decode_colors[0] + 2*decode_colors[3]) / 3; decode_colors[10] = (decode_colors[1] + 2*decode_colors[4]) / 3; decode_colors[11] = (decode_colors[2] + 2*decode_colors[5]) / 3; // decode the block for( i = 0; i < 16*4; i += 4 ) { int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 3; next_bit += 2; uncompressed[i+0] = decode_colors[idx+0]; uncompressed[i+1] = decode_colors[idx+1]; uncompressed[i+2] = decode_colors[idx+2]; } // done } static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) { // all variables go up front stbi_uc *dds_data = NULL; stbi_uc block[16*4]; stbi_uc compressed[8]; int flags, DXT_family; int has_alpha, has_mipmap; int is_compressed, cubemap_faces; int block_pitch, num_blocks; DDS_header header; int i, sz, cf; // load the header if( sizeof( DDS_header ) != 128 ) { return NULL; } getn( s, (stbi_uc*)(&header), 128 ); // and do some checking if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) return NULL; if( header.dwSize != 124 ) return NULL; flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; if( (header.dwFlags & flags) != flags ) return NULL; /* According to the MSDN spec, the dwFlags should contain DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if uncompressed. Some DDS writers do not conform to the spec, so I need to make my reader more tolerant */ if( header.sPixelFormat.dwSize != 32 ) return NULL; flags = DDPF_FOURCC | DDPF_RGB; if( (header.sPixelFormat.dwFlags & flags) == 0 ) return NULL; if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) return NULL; // get the image data s->img_x = header.dwWidth; s->img_y = header.dwHeight; s->img_n = 4; is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; /* I need cubemaps to have square faces */ cubemap_faces &= (s->img_x == s->img_y); cubemap_faces *= 5; cubemap_faces += 1; block_pitch = (s->img_x+3) >> 2; num_blocks = block_pitch * ((s->img_y+3) >> 2); /* let the user know what's going on */ *x = s->img_x; *y = s->img_y; *comp = s->img_n; /* is this uncompressed? */ if( is_compressed ) { /* compressed */ // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; if( (DXT_family < 1) || (DXT_family > 5) ) return NULL; /* check the expected size...oops, nevermind... those non-compliant writers leave dwPitchOrLinearSize == 0 */ // passed all the tests, get the RAM for decoding sz = (s->img_x)*(s->img_y)*4*cubemap_faces; dds_data = (unsigned char*)malloc( sz ); /* do this once for each face */ for( cf = 0; cf < cubemap_faces; ++ cf ) { // now read and decode all the blocks for( i = 0; i < num_blocks; ++i ) { // where are we? int bx, by, bw=4, bh=4; int ref_x = 4 * (i % block_pitch); int ref_y = 4 * (i / block_pitch); // get the next block's worth of compressed data, and decompress it if( DXT_family == 1 ) { // DXT1 getn( s, compressed, 8 ); stbi_decode_DXT1_block( block, compressed ); } else if( DXT_family < 4 ) { // DXT2/3 getn( s, compressed, 8 ); stbi_decode_DXT23_alpha_block ( block, compressed ); getn( s, compressed, 8 ); stbi_decode_DXT_color_block ( block, compressed ); } else { // DXT4/5 getn( s, compressed, 8 ); stbi_decode_DXT45_alpha_block ( block, compressed ); getn( s, compressed, 8 ); stbi_decode_DXT_color_block ( block, compressed ); } // is this a partial block? if( ref_x + 4 > s->img_x ) { bw = s->img_x - ref_x; } if( ref_y + 4 > s->img_y ) { bh = s->img_y - ref_y; } // now drop our decompressed data into the buffer for( by = 0; by < bh; ++by ) { int idx = 4*((ref_y+by+cf*s->img_x)*s->img_x + ref_x); for( bx = 0; bx < bw*4; ++bx ) { dds_data[idx+bx] = block[by*16+bx]; } } } /* done reading and decoding the main image... skip MIPmaps if present */ if( has_mipmap ) { int block_size = 16; if( DXT_family == 1 ) { block_size = 8; } for( i = 1; i < header.dwMipMapCount; ++i ) { int mx = s->img_x >> (i + 2); int my = s->img_y >> (i + 2); if( mx < 1 ) { mx = 1; } if( my < 1 ) { my = 1; } skip( s, mx*my*block_size ); } } }/* per cubemap face */ } else { /* uncompressed */ DXT_family = 0; s->img_n = 3; if( has_alpha ) { s->img_n = 4; } *comp = s->img_n; sz = s->img_x*s->img_y*s->img_n*cubemap_faces; dds_data = (unsigned char*)malloc( sz ); /* do this once for each face */ for( cf = 0; cf < cubemap_faces; ++ cf ) { /* read the main image for this face */ getn( s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n ); /* done reading and decoding the main image... skip MIPmaps if present */ if( has_mipmap ) { for( i = 1; i < header.dwMipMapCount; ++i ) { int mx = s->img_x >> i; int my = s->img_y >> i; if( mx < 1 ) { mx = 1; } if( my < 1 ) { my = 1; } skip( s, mx*my*s->img_n ); } } } /* data was BGR, I need it RGB */ for( i = 0; i < sz; i += s->img_n ) { unsigned char temp = dds_data[i]; dds_data[i] = dds_data[i+2]; dds_data[i+2] = temp; } } /* finished decompressing into RGBA, adjust the y size if we have a cubemap note: sz is already up to date */ s->img_y *= cubemap_faces; *y = s->img_y; // did the user want something else, or // see if all the alpha values are 255 (i.e. no transparency) has_alpha = 0; if( s->img_n == 4) { for( i = 3; (i < sz) && (has_alpha == 0); i += 4 ) { has_alpha |= (dds_data[i] < 255); } } if( (req_comp <= 4) && (req_comp >= 1) ) { // user has some requirements, meet them if( req_comp != s->img_n ) { dds_data = convert_format( dds_data, s->img_n, req_comp, s->img_x, s->img_y ); *comp = s->img_n; } } else { // user had no requirements, only drop to RGB is no alpha if( (has_alpha == 0) && (s->img_n == 4) ) { dds_data = convert_format( dds_data, 4, 3, s->img_x, s->img_y ); *comp = 3; } } // OK, done return dds_data; } #ifndef STBI_NO_STDIO stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) { stbi s; start_file(&s,f); return dds_load(&s,x,y,comp,req_comp); } stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp) { stbi_uc *data; FILE *f = fopen(filename, "rb"); if (!f) return NULL; data = stbi_dds_load_from_file(f,x,y,comp,req_comp); fclose(f); return data; } #endif stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi s; start_mem(&s,buffer, len); return dds_load(&s,x,y,comp,req_comp); }