/* @file evas_image_load_dds.c * @author Jean-Philippe ANDRE * * Load Microsoft DirectDraw Surface files. * Decode S3TC image format. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "Evas_Loader.h" #include "s3tc.h" #ifdef _WIN32 # include #endif #define DDS_HEADER_SIZE 128 typedef struct _Evas_Loader_Internal Evas_Loader_Internal; struct _Evas_Loader_Internal { Eina_File *f; Evas_Colorspace format; unsigned int stride, block_size, data_size; struct { unsigned int flags; unsigned int fourcc; unsigned int rgb_bitcount; unsigned int r_mask; unsigned int g_mask; unsigned int b_mask; unsigned int a_mask; // TODO: check mipmaps to load faster a small image :) } pf; // pixel format }; #undef FOURCC #ifndef WORDS_BIGENDIAN # define FOURCC(a,b,c,d) ((d << 24) | (c << 16) | (b << 8) | a) #else # define FOURCC(a,b,c,d) ((a << 24) | (b << 16) | (c << 8) | d) #endif #ifndef DIRECTDRAW_VERSION // DIRECTDRAW_VERSION is defined in ddraw.h // These definitions are from the MSDN reference. enum DDSFlags { DDSD_CAPS = 0x1, DDSD_HEIGHT = 0x2, DDSD_WIDTH = 0x4, DDSD_PITCH = 0x8, DDSD_PIXELFORMAT = 0x1000, DDSD_MIPMAPCOUNT = 0x20000, DDSD_LINEARSIZE = 0x80000, DDSD_DEPTH = 0x800000 }; enum DDSPixelFormatFlags { DDPF_ALPHAPIXELS = 0x1, DDPF_ALPHA = 0x2, DDPF_FOURCC = 0x4, DDPF_RGB = 0x40, DDPF_YUV = 0x200, DDPF_LUMINANCE = 0x20000 }; enum DDSCaps { DDSCAPS_COMPLEX = 0x8, DDSCAPS_MIPMAP = 0x400000, DDSCAPS_TEXTURE = 0x1000 }; #endif static const Evas_Colorspace cspaces_s3tc_dxt1_rgb[] = { EVAS_COLORSPACE_RGB_S3TC_DXT1, EVAS_COLORSPACE_ARGB8888 }; static const Evas_Colorspace cspaces_s3tc_dxt1_rgba[] = { //EVAS_COLORSPACE_RGBA_S3TC_DXT1, EVAS_COLORSPACE_ARGB8888 }; static const Evas_Colorspace cspaces_s3tc_dxt2[] = { EVAS_COLORSPACE_RGBA_S3TC_DXT2, EVAS_COLORSPACE_ARGB8888 }; static const Evas_Colorspace cspaces_s3tc_dxt3[] = { //EVAS_COLORSPACE_RGBA_S3TC_DXT3, EVAS_COLORSPACE_ARGB8888 }; static const Evas_Colorspace cspaces_s3tc_dxt4[] = { EVAS_COLORSPACE_RGBA_S3TC_DXT4, EVAS_COLORSPACE_ARGB8888 }; static const Evas_Colorspace cspaces_s3tc_dxt5[] = { //EVAS_COLORSPACE_RGBA_S3TC_DXT5, EVAS_COLORSPACE_ARGB8888 }; static void * evas_image_load_file_open_dds(Eina_File *f, Eina_Stringshare *key EINA_UNUSED, Evas_Image_Load_Opts *opts EINA_UNUSED, Evas_Image_Animated *animated EINA_UNUSED, int *error) { Evas_Loader_Internal *loader; if (eina_file_size_get(f) <= DDS_HEADER_SIZE) { *error = EVAS_LOAD_ERROR_CORRUPT_FILE; return NULL; } loader = calloc(1, sizeof (Evas_Loader_Internal)); if (!loader) { *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; return NULL; } loader->f = eina_file_dup(f); if (!loader->f) { free(loader); *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; return NULL; } return loader; } static void evas_image_load_file_close_dds(void *loader_data) { Evas_Loader_Internal *loader = loader_data; if (loader->f) eina_file_close(loader->f); free(loader); } static inline unsigned int _dword_read(const char **m) { unsigned int val = *((unsigned int *) *m); *m += 4; return val; } #define FAIL() do { /*fprintf(stderr, "DDS: ERROR at %s:%d\n", __FUNCTION__, __LINE__);*/ goto on_error; } while (0) static Eina_Bool evas_image_load_file_head_dds(void *loader_data, Emile_Image_Property *prop, int *error) { static const unsigned int base_flags = /* 0x1007 */ DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; Evas_Loader_Internal *loader = loader_data; unsigned int flags, height, width, pitchOrLinearSize, caps, caps2; Eina_Bool has_linearsize, has_mipmapcount; const char *m; char *map; map = eina_file_map_all(loader->f, EINA_FILE_SEQUENTIAL); if (!map) { *error = EVAS_LOAD_ERROR_CORRUPT_FILE; return EINA_FALSE; } m = map; *error = EVAS_LOAD_ERROR_CORRUPT_FILE; if (strncmp(m, "DDS ", 4) != 0) // TODO: Add support for DX10 goto on_error; m += 4; // Read DDS_HEADER if (_dword_read(&m) != 124) FAIL(); flags = _dword_read(&m); if ((flags & base_flags) != (base_flags)) FAIL(); if ((flags & ~(DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE)) != base_flags) { // TODO: A lot of modes are not supported. *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; FAIL(); } has_linearsize = !!(flags & DDSD_LINEARSIZE); if (!has_linearsize) FAIL(); has_mipmapcount = !!(flags & DDSD_MIPMAPCOUNT); (void) has_mipmapcount; // We don't really care about it. height = _dword_read(&m); width = _dword_read(&m); pitchOrLinearSize = _dword_read(&m); if (!width || !height) FAIL(); // Skip depth & mipmap count + reserved[11] m += 13 * sizeof(unsigned int); // Entering DDS_PIXELFORMAT ddspf if (_dword_read(&m) != 32) FAIL(); loader->pf.flags = _dword_read(&m); if (!(loader->pf.flags & DDPF_FOURCC)) FAIL(); // Unsupported (uncompressed formats may not have a FOURCC) loader->pf.fourcc = _dword_read(&m); loader->block_size = 16; switch (loader->pf.fourcc) { case FOURCC('D', 'X', 'T', '1'): loader->block_size = 8; if ((loader->pf.flags & DDPF_ALPHAPIXELS) == 0) { prop->alpha = EINA_FALSE; prop->cspaces = cspaces_s3tc_dxt1_rgb; loader->format = EVAS_COLORSPACE_RGB_S3TC_DXT1; } else { prop->alpha = EINA_TRUE; prop->cspaces = cspaces_s3tc_dxt1_rgba; loader->format = EVAS_COLORSPACE_RGBA_S3TC_DXT1; } break; case FOURCC('D', 'X', 'T', '2'): loader->format = EVAS_COLORSPACE_RGBA_S3TC_DXT2; prop->alpha = EINA_TRUE; prop->cspaces = cspaces_s3tc_dxt2; break; case FOURCC('D', 'X', 'T', '3'): loader->format = EVAS_COLORSPACE_RGBA_S3TC_DXT3; prop->alpha = EINA_TRUE; prop->cspaces = cspaces_s3tc_dxt3; break; case FOURCC('D', 'X', 'T', '4'): loader->format = EVAS_COLORSPACE_RGBA_S3TC_DXT4; prop->alpha = EINA_TRUE; prop->cspaces = cspaces_s3tc_dxt4; break; case FOURCC('D', 'X', 'T', '5'): loader->format = EVAS_COLORSPACE_RGBA_S3TC_DXT5; prop->alpha = EINA_TRUE; prop->cspaces = cspaces_s3tc_dxt5; break; case FOURCC('D', 'X', '1', '0'): *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; FAIL(); default: // TODO: Implement decoding support for uncompressed formats FAIL(); } loader->pf.rgb_bitcount = _dword_read(&m); loader->pf.r_mask = _dword_read(&m); loader->pf.g_mask = _dword_read(&m); loader->pf.b_mask = _dword_read(&m); loader->pf.a_mask = _dword_read(&m); caps = _dword_read(&m); if ((caps & DDSCAPS_TEXTURE) == 0) { *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; FAIL(); } caps2 = _dword_read(&m); if (caps2 != 0) { // Cube maps not supported *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; FAIL(); } // Since the rest is unused, just ignore it. loader->stride = ((width + 3) >> 2) * loader->block_size; loader->data_size = loader->stride * ((height + 3) >> 2); if (loader->data_size != pitchOrLinearSize) FAIL(); // Invalid size! // Check file size if (eina_file_size_get(loader->f) < (DDS_HEADER_SIZE + loader->data_size)) FAIL(); prop->h = height; prop->w = width; prop->borders.l = 4; prop->borders.t = 4; prop->borders.r = 4 - (prop->w & 0x3); prop->borders.b = 4 - (prop->h & 0x3); *error = EVAS_LOAD_ERROR_NONE; on_error: eina_file_map_free(loader->f, map); return (*error == EVAS_LOAD_ERROR_NONE); } static Eina_Bool _dds_data_load(Evas_Loader_Internal *loader, Emile_Image_Property *prop, unsigned char *map, void *pixels, int *error) { const unsigned char *src; int bsize = 16, srcstride, dststride, w, h; unsigned char *dst; void (* flip) (unsigned char *, const unsigned char *, int) = NULL; *error = EVAS_LOAD_ERROR_GENERIC; if (loader->format != prop->cspace) FAIL(); switch (loader->format) { case EVAS_COLORSPACE_RGB_S3TC_DXT1: case EVAS_COLORSPACE_RGBA_S3TC_DXT1: flip = s3tc_encode_dxt1_flip; bsize = 8; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT2: flip = s3tc_encode_dxt2_rgba_flip; bsize = 16; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT3: flip = s3tc_encode_dxt3_rgba_flip; bsize = 16; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT4: flip = s3tc_encode_dxt4_rgba_flip; bsize = 16; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT5: flip = s3tc_encode_dxt5_rgba_flip; bsize = 16; break; default: FAIL(); } src = map + DDS_HEADER_SIZE; w = prop->w; h = prop->h; srcstride = ((prop->w + 3) / 4) * bsize; dststride = ((prop->w + prop->borders.l + prop->borders.r) / 4) * bsize; // asserts EINA_SAFETY_ON_FALSE_GOTO(prop->borders.l == 4, on_error); EINA_SAFETY_ON_FALSE_GOTO(prop->borders.t == 4, on_error); EINA_SAFETY_ON_FALSE_GOTO(prop->borders.r == (4 - (w & 0x3)), on_error); EINA_SAFETY_ON_FALSE_GOTO(prop->borders.b == (4 - (h & 0x3)), on_error); if (eina_file_size_get(loader->f) < (size_t) (DDS_HEADER_SIZE + srcstride * h / 4)) { *error = EVAS_LOAD_ERROR_CORRUPT_FILE; goto on_error; } // First, copy the real data for (int y = 0; y < h; y += 4) { dst = ((unsigned char *) pixels) + ((y / 4) + 1) * dststride + bsize; memcpy(dst, src, srcstride); src += srcstride; } // Top for (int x = 0; x < w; x += 4) { src = map + DDS_HEADER_SIZE + (x / 4) * bsize; dst = ((unsigned char *) pixels) + ((x / 4) + 1) * bsize; flip(dst, src, EINA_TRUE); } // Left for (int y = 0; y < h; y += 4) { src = map + DDS_HEADER_SIZE + (y / 4) * srcstride; dst = ((unsigned char *) pixels) + ((y / 4) + 1) * dststride; flip(dst, src, EINA_FALSE); } // Top-left dst = pixels; src = dst + bsize; flip(dst, src, EINA_FALSE); // Right if ((prop->w & 0x3) == 0) { for (int y = 0; y < h; y += 4) { src = map + DDS_HEADER_SIZE + ((y / 4) + 1) * srcstride - bsize; dst = ((unsigned char *) pixels) + ((y / 4) + 2) * dststride - bsize; flip(dst, src, EINA_FALSE); } // Top-right dst = ((unsigned char *) pixels) + dststride - bsize; src = dst - bsize; flip(dst, src, EINA_FALSE); } // Bottom if ((prop->h & 0x3) == 0) { for (int x = 0; x < w; x += 4) { src = map + DDS_HEADER_SIZE + ((h / 4) - 1) * srcstride + (x / 4) * bsize; dst = ((unsigned char *) pixels) + ((h / 4) + 1) * dststride + ((x / 4) + 1) * bsize; flip(dst, src, EINA_TRUE); } // Bottom-left dst = ((unsigned char *) pixels) + ((h / 4) + 1) * dststride; src = dst + bsize; flip(dst, src, EINA_FALSE); if ((prop->w & 0x3) == 0) { // Bottom-right dst = ((unsigned char *) pixels) + ((h / 4) + 2) * dststride - bsize; src = dst - bsize; flip(dst, src, EINA_FALSE); } } *error = EVAS_LOAD_ERROR_NONE; on_error: eina_file_map_free(loader->f, (void *) map); return (*error == EVAS_LOAD_ERROR_NONE); } Eina_Bool evas_image_load_file_data_dds(void *loader_data, Emile_Image_Property *prop, void *pixels, int *error) { void (*func) (unsigned int *bgra, const unsigned char *s3tc) = NULL; Evas_Loader_Internal *loader = loader_data; unsigned int *pix = pixels; unsigned char *map = NULL; const unsigned char *src; *error = EVAS_LOAD_ERROR_CORRUPT_FILE; map = eina_file_map_all(loader->f, EINA_FILE_WILLNEED); if (!map) return EINA_FALSE; src = map + DDS_HEADER_SIZE; if (eina_file_size_get(loader->f) < (DDS_HEADER_SIZE + loader->data_size)) FAIL(); if (prop->cspace != EVAS_COLORSPACE_ARGB8888) return _dds_data_load(loader, prop, map, pixels, error); // Decode to BGRA switch (loader->format) { case EVAS_COLORSPACE_RGB_S3TC_DXT1: func = s3tc_decode_dxt1_rgb; prop->premul = EINA_FALSE; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT1: func = s3tc_decode_dxt1_rgba; prop->premul = EINA_FALSE; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT2: func = s3tc_decode_dxt2_rgba; prop->premul = EINA_FALSE; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT3: func = s3tc_decode_dxt3_rgba; prop->premul = EINA_TRUE; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT4: func = s3tc_decode_dxt4_rgba; prop->premul = EINA_FALSE; break; case EVAS_COLORSPACE_RGBA_S3TC_DXT5: func = s3tc_decode_dxt5_rgba; prop->premul = EINA_TRUE; break; default: FAIL(); } if (!func) FAIL(); for (unsigned int y = 0; y < prop->h; y += 4) { int blockh = prop->h - y; if (blockh > 4) blockh = 4; for (unsigned int x = 0; x < prop->w; x += 4) { unsigned int bgra[16]; int k, j; func(bgra, src); src += loader->block_size; j = prop->w - x; if (j > 4) j = 4; for (k = 0; k < blockh; k++) { memcpy(pix + (((y + k) * prop->w) + x), bgra + (k * 4), j * sizeof (unsigned int)); }; } } *error = EVAS_LOAD_ERROR_NONE; on_error: eina_file_map_free(loader->f, (void *) map); return (*error == EVAS_LOAD_ERROR_NONE); } Evas_Image_Load_Func evas_image_load_dds_func = { EVAS_IMAGE_LOAD_VERSION, evas_image_load_file_open_dds, evas_image_load_file_close_dds, (void*) evas_image_load_file_head_dds, NULL, (void*) evas_image_load_file_data_dds, NULL, EINA_TRUE, EINA_FALSE }; static int module_open(Evas_Module *em) { if (!em) return 0; em->functions = (void *)(&evas_image_load_dds_func); return 1; } static void module_close(Evas_Module *em EINA_UNUSED) { } static Evas_Module_Api evas_modapi = { EVAS_MODULE_API_VERSION, "dds", "none", { module_open, module_close } }; EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, dds); #ifndef EVAS_STATIC_BUILD_DDS EVAS_EINA_MODULE_DEFINE(image_loader, dds); #endif