efl/src/modules/evas/image_loaders/dds/evas_image_load_dds.c

570 lines
15 KiB
C

/* @file evas_image_load_dds.c
* @author Jean-Philippe ANDRE <jpeg@videolan.org>
*
* 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 <ddraw.h>
#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;
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,
Evas_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_dxt5;
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, Evas_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,
Evas_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_file_open_dds,
evas_image_load_file_close_dds,
evas_image_load_file_head_dds,
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