1131 lines
30 KiB
C
1131 lines
30 KiB
C
/*
|
|
SDL_image: An example image loading library for use with SDL
|
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
/* This is a XCF image file loading framework */
|
|
|
|
#include <SDL3/SDL_endian.h>
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
#ifdef LOAD_XCF
|
|
|
|
#ifdef DEBUG
|
|
#include <SDL3/SDL_stdinc.h>
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#ifndef SDL_SIZE_MAX
|
|
#define SDL_SIZE_MAX ((size_t)-1)
|
|
#endif
|
|
#define MAX_XCF_SIZE 20000 /* arbitrary limit to avoid integer overflow. */
|
|
|
|
#ifdef DEBUG
|
|
static char prop_names [][30] = {
|
|
"end",
|
|
"colormap",
|
|
"active_layer",
|
|
"active_channel",
|
|
"selection",
|
|
"floating_selection",
|
|
"opacity",
|
|
"mode",
|
|
"visible",
|
|
"linked",
|
|
"preserve_transparency",
|
|
"apply_mask",
|
|
"edit_mask",
|
|
"show_mask",
|
|
"show_masked",
|
|
"offsets",
|
|
"color",
|
|
"compression",
|
|
"guides",
|
|
"resolution",
|
|
"tattoo",
|
|
"parasites",
|
|
"unit",
|
|
"paths",
|
|
"user_unit"
|
|
};
|
|
#endif
|
|
|
|
|
|
typedef enum
|
|
{
|
|
PROP_END = 0,
|
|
PROP_COLORMAP = 1,
|
|
PROP_ACTIVE_LAYER = 2,
|
|
PROP_ACTIVE_CHANNEL = 3,
|
|
PROP_SELECTION = 4,
|
|
PROP_FLOATING_SELECTION = 5,
|
|
PROP_OPACITY = 6,
|
|
PROP_MODE = 7,
|
|
PROP_VISIBLE = 8,
|
|
PROP_LINKED = 9,
|
|
PROP_PRESERVE_TRANSPARENCY = 10,
|
|
PROP_APPLY_MASK = 11,
|
|
PROP_EDIT_MASK = 12,
|
|
PROP_SHOW_MASK = 13,
|
|
PROP_SHOW_MASKED = 14,
|
|
PROP_OFFSETS = 15,
|
|
PROP_COLOR = 16,
|
|
PROP_COMPRESSION = 17,
|
|
PROP_GUIDES = 18,
|
|
PROP_RESOLUTION = 19,
|
|
PROP_TATTOO = 20,
|
|
PROP_PARASITES = 21,
|
|
PROP_UNIT = 22,
|
|
PROP_PATHS = 23,
|
|
PROP_USER_UNIT = 24
|
|
} xcf_prop_type;
|
|
|
|
typedef enum {
|
|
COMPR_NONE = 0,
|
|
COMPR_RLE = 1,
|
|
COMPR_ZLIB = 2,
|
|
COMPR_FRACTAL = 3
|
|
} xcf_compr_type;
|
|
|
|
typedef enum {
|
|
IMAGE_RGB = 0,
|
|
IMAGE_GREYSCALE = 1,
|
|
IMAGE_INDEXED = 2
|
|
} xcf_image_type;
|
|
|
|
typedef struct {
|
|
Uint32 id;
|
|
Uint32 length;
|
|
union {
|
|
struct {
|
|
Uint32 num;
|
|
char *cmap;
|
|
} colormap; /* 1 */
|
|
struct {
|
|
Uint32 drawable_offset;
|
|
} floating_selection; /* 5 */
|
|
Uint32 opacity;
|
|
Uint32 mode;
|
|
Uint32 visible;
|
|
int linked;
|
|
int preserve_transparency;
|
|
int apply_mask;
|
|
int show_mask;
|
|
struct {
|
|
Sint32 x;
|
|
Sint32 y;
|
|
} offset;
|
|
unsigned char color[3];
|
|
Uint8 compression;
|
|
struct {
|
|
Sint32 x;
|
|
Sint32 y;
|
|
} resolution;
|
|
struct {
|
|
char *name;
|
|
Uint32 flags;
|
|
Uint32 size;
|
|
char *data;
|
|
} parasite;
|
|
} data;
|
|
} xcf_prop;
|
|
|
|
typedef struct {
|
|
char sign[14];
|
|
Uint32 file_version;
|
|
Uint32 width;
|
|
Uint32 height;
|
|
Uint32 image_type;
|
|
Uint32 precision;
|
|
xcf_prop *properties;
|
|
|
|
Uint64 *layer_file_offsets;
|
|
Uint64 *channel_file_offsets;
|
|
|
|
xcf_compr_type compr;
|
|
Uint32 cm_num;
|
|
unsigned char *cm_map;
|
|
} xcf_header;
|
|
|
|
typedef struct {
|
|
Uint32 width;
|
|
Uint32 height;
|
|
Uint32 layer_type;
|
|
char *name;
|
|
xcf_prop *properties;
|
|
|
|
Uint64 hierarchy_file_offset;
|
|
Uint64 layer_mask_offset;
|
|
|
|
Uint32 offset_x;
|
|
Uint32 offset_y;
|
|
int visible;
|
|
} xcf_layer;
|
|
|
|
typedef struct {
|
|
Uint32 width;
|
|
Uint32 height;
|
|
char *name;
|
|
xcf_prop *properties;
|
|
|
|
Uint64 hierarchy_file_offset;
|
|
|
|
Uint32 color;
|
|
Uint32 opacity;
|
|
int selection;
|
|
int visible;
|
|
} xcf_channel;
|
|
|
|
typedef struct {
|
|
Uint32 width;
|
|
Uint32 height;
|
|
Uint32 bpp;
|
|
|
|
Uint64 *level_file_offsets;
|
|
} xcf_hierarchy;
|
|
|
|
typedef struct {
|
|
Uint32 width;
|
|
Uint32 height;
|
|
|
|
Uint64 *tile_file_offsets;
|
|
} xcf_level;
|
|
|
|
typedef unsigned char *xcf_tile;
|
|
|
|
typedef unsigned char *(*load_tile_type)(SDL_IOStream *, size_t, int, int, int);
|
|
|
|
|
|
/* See if an image is contained in a data source */
|
|
bool IMG_isXCF(SDL_IOStream *src)
|
|
{
|
|
Sint64 start;
|
|
bool is_XCF = false;
|
|
char magic[14];
|
|
|
|
if (src) {
|
|
start = SDL_TellIO(src);
|
|
if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) {
|
|
if (SDL_strncmp(magic, "gimp xcf ", 9) == 0) {
|
|
is_XCF = true;
|
|
}
|
|
}
|
|
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
|
|
}
|
|
return is_XCF;
|
|
}
|
|
|
|
static char *read_string(SDL_IOStream *src)
|
|
{
|
|
Sint64 remaining;
|
|
Uint32 tmp;
|
|
char *data = NULL;
|
|
|
|
if (SDL_ReadU32BE(src, &tmp)) {
|
|
if (tmp == 0) {
|
|
data = (char *) SDL_malloc(1);
|
|
if (data) {
|
|
data[0] = 0;
|
|
}
|
|
return data;
|
|
}
|
|
remaining = SDL_GetIOSize(src) - SDL_TellIO(src);
|
|
if ((Sint32)tmp > 0 && tmp <= remaining) {
|
|
data = (char *)SDL_malloc(tmp);
|
|
if (data) {
|
|
if (SDL_ReadIO(src, data, tmp) == tmp) {
|
|
data[tmp - 1] = '\0';
|
|
} else {
|
|
SDL_free(data);
|
|
data = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static Uint64 read_offset(SDL_IOStream *src, const xcf_header *h)
|
|
{
|
|
Uint64 offset = 0; /* starting with version 11, offsets are 64 bits */
|
|
Uint32 offset32;
|
|
|
|
if (h->file_version >= 11) {
|
|
if (SDL_ReadU32BE(src, &offset32)) {
|
|
offset |= offset32;
|
|
offset <<= 32;
|
|
}
|
|
}
|
|
if (SDL_ReadU32BE(src, &offset32)) {
|
|
offset |= offset32;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static int xcf_read_property(SDL_IOStream *src, xcf_prop *prop)
|
|
{
|
|
Uint32 len;
|
|
|
|
if (!SDL_ReadU32BE(src, &prop->id) ||
|
|
!SDL_ReadU32BE(src, &prop->length)) {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
SDL_Log("%.8" SDL_PRIu64 ": %s(%u): %u\n", SDL_TellIO (src), prop->id < 25 ? prop_names [prop->id] : "unknown", prop->id, prop->length);
|
|
#endif
|
|
|
|
switch (prop->id) {
|
|
case PROP_COLORMAP:
|
|
if (!SDL_ReadU32BE(src, &prop->data.colormap.num)) {
|
|
return 0;
|
|
}
|
|
prop->data.colormap.cmap = (char *)SDL_malloc(sizeof(char) * prop->data.colormap.num * 3);
|
|
if (!prop->data.colormap.cmap) {
|
|
return 0;
|
|
}
|
|
if (SDL_ReadIO(src, prop->data.colormap.cmap, prop->data.colormap.num * 3) != prop->data.colormap.num * 3) {
|
|
SDL_free(prop->data.colormap.cmap);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case PROP_OFFSETS:
|
|
if (!SDL_ReadS32BE(src, &prop->data.offset.x) ||
|
|
!SDL_ReadS32BE(src, &prop->data.offset.y)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case PROP_OPACITY:
|
|
if (!SDL_ReadU32BE(src, &prop->data.opacity)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case PROP_COMPRESSION:
|
|
case PROP_COLOR:
|
|
if (prop->length > sizeof(prop->data)) {
|
|
len = sizeof(prop->data);
|
|
} else {
|
|
len = prop->length;
|
|
}
|
|
if (SDL_ReadIO(src, &prop->data, len) != len) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case PROP_VISIBLE:
|
|
if (!SDL_ReadU32BE(src, &prop->data.visible)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
if (SDL_SeekIO(src, prop->length, SDL_IO_SEEK_CUR) < 0)
|
|
return 0; /* ERROR */
|
|
}
|
|
return 1; /* OK */
|
|
}
|
|
|
|
static void free_xcf_header(xcf_header *h)
|
|
{
|
|
if (h) {
|
|
SDL_free(h->cm_map);
|
|
SDL_free(h->layer_file_offsets);
|
|
SDL_free(h);
|
|
}
|
|
}
|
|
|
|
static xcf_header *read_xcf_header(SDL_IOStream *src)
|
|
{
|
|
xcf_header *h;
|
|
xcf_prop prop;
|
|
|
|
h = (xcf_header *)SDL_calloc(1, sizeof(xcf_header));
|
|
if (!h) {
|
|
return NULL;
|
|
}
|
|
|
|
if (SDL_ReadIO(src, h->sign, 14) != 14) {
|
|
SDL_free(h);
|
|
return NULL;
|
|
}
|
|
if (!SDL_ReadU32BE(src, &h->width) ||
|
|
!SDL_ReadU32BE(src, &h->height) ||
|
|
!SDL_ReadU32BE(src, &h->image_type)) {
|
|
free_xcf_header(h);
|
|
return NULL;
|
|
}
|
|
|
|
if ((h->width > MAX_XCF_SIZE) || (h->height > MAX_XCF_SIZE)) {
|
|
SDL_SetError("Gimp image too large (%ux%u)", (unsigned int)h->width, (unsigned int)h->height);
|
|
free_xcf_header(h);
|
|
return NULL;
|
|
}
|
|
|
|
if (h->sign[9] == 'v' && h->sign[10] >= '0' && h->sign[10] <= '9' && h->sign[11] >= '0' && h->sign[11] <= '9' && h->sign[12] >= '0' && h->sign[12] <= '9')
|
|
h->file_version = (h->sign[10] - '0') * 100 + (h->sign[11] - '0') * 10 + (h->sign[12] - '0');
|
|
else
|
|
h->file_version = 0;
|
|
#ifdef DEBUG
|
|
SDL_Log("XCF signature : %.14s (version %" SDL_PRIu32 ")\n", h->sign, h->file_version);
|
|
SDL_Log(" (%" SDL_PRIu32 ",%" SDL_PRIu32 ") type=%" SDL_PRIu32 "\n", h->width, h->height, h->image_type);
|
|
#endif
|
|
if (h->file_version >= 4) {
|
|
if (!SDL_ReadU32BE(src, &h->precision)) {
|
|
free_xcf_header(h);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
h->precision = 150;
|
|
}
|
|
|
|
h->properties = NULL;
|
|
h->layer_file_offsets = NULL;
|
|
h->compr = COMPR_NONE;
|
|
h->cm_num = 0;
|
|
h->cm_map = NULL;
|
|
|
|
/* Just read, don't save */
|
|
do {
|
|
if (!xcf_read_property(src, &prop)) {
|
|
free_xcf_header(h);
|
|
return NULL;
|
|
}
|
|
if (prop.id == PROP_COMPRESSION) {
|
|
h->compr = (xcf_compr_type)prop.data.compression;
|
|
} else if (prop.id == PROP_COLORMAP) {
|
|
Uint32 cm_num;
|
|
unsigned char *cm_map;
|
|
|
|
cm_num = prop.data.colormap.num;
|
|
cm_map = (unsigned char *)SDL_realloc(h->cm_map, sizeof(unsigned char) * 3 * cm_num);
|
|
if (cm_map) {
|
|
h->cm_num = cm_num;
|
|
h->cm_map = cm_map;
|
|
SDL_memcpy(h->cm_map, prop.data.colormap.cmap, sizeof(unsigned char) * 3 * h->cm_num);
|
|
}
|
|
SDL_free(prop.data.colormap.cmap);
|
|
|
|
if (!cm_map) {
|
|
free_xcf_header(h);
|
|
return NULL;
|
|
}
|
|
}
|
|
} while (prop.id != PROP_END);
|
|
|
|
return h;
|
|
}
|
|
|
|
static void free_xcf_layer(xcf_layer *l)
|
|
{
|
|
if (l) {
|
|
SDL_free(l->name);
|
|
SDL_free(l);
|
|
}
|
|
}
|
|
|
|
static xcf_layer *read_xcf_layer(SDL_IOStream *src, const xcf_header *h)
|
|
{
|
|
xcf_layer *l;
|
|
xcf_prop prop;
|
|
|
|
l = (xcf_layer *)SDL_calloc(1, sizeof(xcf_layer));
|
|
if (!l) {
|
|
return NULL;
|
|
}
|
|
if (!SDL_ReadU32BE(src, &l->width) ||
|
|
!SDL_ReadU32BE(src, &l->height) ||
|
|
!SDL_ReadU32BE(src, &l->layer_type)) {
|
|
free_xcf_layer(l);
|
|
return NULL;
|
|
}
|
|
|
|
if ((l->width > MAX_XCF_SIZE) || (l->height > MAX_XCF_SIZE)) {
|
|
SDL_SetError("Gimp layer too large (%ux%u)", (unsigned int)l->width, (unsigned int)l->height);
|
|
free_xcf_layer(l);
|
|
return NULL;
|
|
}
|
|
|
|
l->name = read_string(src);
|
|
#ifdef DEBUG
|
|
SDL_Log("layer (%d,%d) type=%u '%s'\n", l->width, l->height, l->layer_type, l->name);
|
|
#endif
|
|
|
|
do {
|
|
if (!xcf_read_property(src, &prop)) {
|
|
free_xcf_layer(l);
|
|
return NULL;
|
|
}
|
|
if (prop.id == PROP_OFFSETS) {
|
|
l->offset_x = prop.data.offset.x;
|
|
l->offset_y = prop.data.offset.y;
|
|
} else if (prop.id == PROP_VISIBLE) {
|
|
l->visible = prop.data.visible ? 1 : 0;
|
|
} else if (prop.id == PROP_COLORMAP) {
|
|
SDL_free(prop.data.colormap.cmap);
|
|
}
|
|
} while (prop.id != PROP_END);
|
|
|
|
l->hierarchy_file_offset = read_offset(src, h);
|
|
l->layer_mask_offset = read_offset(src, h);
|
|
|
|
return l;
|
|
}
|
|
|
|
static void free_xcf_channel(xcf_channel *c)
|
|
{
|
|
if (c) {
|
|
SDL_free(c->name);
|
|
SDL_free(c);
|
|
}
|
|
}
|
|
|
|
static xcf_channel *read_xcf_channel(SDL_IOStream *src, const xcf_header *h)
|
|
{
|
|
xcf_channel *l;
|
|
xcf_prop prop;
|
|
|
|
l = (xcf_channel *)SDL_calloc(1, sizeof(xcf_channel));
|
|
if (!l) {
|
|
return NULL;
|
|
}
|
|
if (!SDL_ReadU32BE(src, &l->width) ||
|
|
!SDL_ReadU32BE(src, &l->height)) {
|
|
free_xcf_channel(l);
|
|
return NULL;
|
|
}
|
|
|
|
if ((l->width > MAX_XCF_SIZE) || (l->height > MAX_XCF_SIZE)) {
|
|
SDL_SetError("Gimp channel too large (%ux%u)", (unsigned int)l->width, (unsigned int)l->height);
|
|
free_xcf_channel(l);
|
|
return NULL;
|
|
}
|
|
|
|
l->name = read_string(src);
|
|
#ifdef DEBUG
|
|
SDL_Log("channel (%u,%u) '%s'\n", l->width, l->height, l->name);
|
|
#endif
|
|
|
|
l->selection = 0;
|
|
do {
|
|
if (!xcf_read_property (src, &prop)) {
|
|
free_xcf_channel(l);
|
|
return NULL;
|
|
}
|
|
switch (prop.id) {
|
|
case PROP_OPACITY:
|
|
l->opacity = prop.data.opacity << 24;
|
|
break;
|
|
case PROP_COLOR:
|
|
l->color = ((Uint32) prop.data.color[0] << 16)
|
|
| ((Uint32) prop.data.color[1] << 8)
|
|
| ((Uint32) prop.data.color[2]);
|
|
break;
|
|
case PROP_SELECTION:
|
|
l->selection = 1;
|
|
break;
|
|
case PROP_VISIBLE:
|
|
l->visible = prop.data.visible ? 1 : 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} while (prop.id != PROP_END);
|
|
|
|
l->hierarchy_file_offset = read_offset(src, h);
|
|
|
|
return l;
|
|
}
|
|
|
|
static void free_xcf_hierarchy(xcf_hierarchy *h)
|
|
{
|
|
if (h) {
|
|
SDL_free(h->level_file_offsets);
|
|
SDL_free(h);
|
|
}
|
|
}
|
|
|
|
static xcf_hierarchy *read_xcf_hierarchy(SDL_IOStream *src, const xcf_header *head)
|
|
{
|
|
xcf_hierarchy *h;
|
|
int i;
|
|
|
|
h = (xcf_hierarchy *)SDL_calloc(1, sizeof(xcf_hierarchy));
|
|
if (!h) {
|
|
return NULL;
|
|
}
|
|
if (!SDL_ReadU32BE(src, &h->width) ||
|
|
!SDL_ReadU32BE(src, &h->height) ||
|
|
!SDL_ReadU32BE(src, &h->bpp)) {
|
|
free_xcf_hierarchy(h);
|
|
return NULL;
|
|
}
|
|
|
|
if ((h->width > MAX_XCF_SIZE) || (h->height > MAX_XCF_SIZE)) {
|
|
SDL_SetError("Gimp image too large (%ux%u)", (unsigned int)h->width, (unsigned int)h->height);
|
|
free_xcf_hierarchy(h);
|
|
return NULL;
|
|
}
|
|
|
|
i = 0;
|
|
do {
|
|
h->level_file_offsets = (Uint64 *)SDL_realloc(h->level_file_offsets, sizeof(*h->level_file_offsets) * (i+1));
|
|
h->level_file_offsets[i] = read_offset(src, head);
|
|
} while (h->level_file_offsets[i++]);
|
|
|
|
return h;
|
|
}
|
|
|
|
static void free_xcf_level(xcf_level *l)
|
|
{
|
|
if (l) {
|
|
SDL_free(l->tile_file_offsets);
|
|
SDL_free(l);
|
|
}
|
|
}
|
|
|
|
static xcf_level *read_xcf_level(SDL_IOStream *src, const xcf_header *h)
|
|
{
|
|
xcf_level *l;
|
|
int i;
|
|
|
|
l = (xcf_level *)SDL_calloc(1, sizeof(xcf_level));
|
|
if (!l) {
|
|
return NULL;
|
|
}
|
|
if (!SDL_ReadU32BE(src, &l->width) ||
|
|
!SDL_ReadU32BE(src, &l->height)) {
|
|
free_xcf_level(l);
|
|
return NULL;
|
|
}
|
|
|
|
if ((l->width > MAX_XCF_SIZE) || (l->height > MAX_XCF_SIZE)) {
|
|
SDL_SetError("Gimp level too large (%ux%u)", (unsigned int)l->width, (unsigned int)l->height);
|
|
free_xcf_level(l);
|
|
return NULL;
|
|
}
|
|
|
|
i = 0;
|
|
do {
|
|
Uint64 *tile_file_offsets = (Uint64 *)SDL_realloc(l->tile_file_offsets, sizeof(*l->tile_file_offsets) * (i+1));
|
|
if (!tile_file_offsets) {
|
|
free_xcf_level(l);
|
|
return NULL;
|
|
}
|
|
l->tile_file_offsets = tile_file_offsets;
|
|
l->tile_file_offsets[i] = read_offset(src, h);
|
|
} while (l->tile_file_offsets[i++]);
|
|
|
|
return l;
|
|
}
|
|
|
|
static void free_xcf_tile(unsigned char *t)
|
|
{
|
|
SDL_free(t);
|
|
}
|
|
|
|
static unsigned char *load_xcf_tile_none (SDL_IOStream *src, size_t len, int bpp, int x, int y)
|
|
{
|
|
unsigned char *load = NULL;
|
|
|
|
if (len < (size_t)(x * y * bpp)) {
|
|
SDL_SetError("Gimp image invalid tile offsets");
|
|
return NULL;
|
|
}
|
|
|
|
load = (unsigned char *)SDL_malloc(len);
|
|
if (load != NULL) {
|
|
if (SDL_ReadIO(src, load, len) != len) {
|
|
SDL_free(load);
|
|
load = NULL;
|
|
}
|
|
}
|
|
return load;
|
|
}
|
|
|
|
static unsigned char *load_xcf_tile_rle(SDL_IOStream *src, size_t len, int bpp, int x, int y)
|
|
{
|
|
unsigned char *load, *t, *data, *d;
|
|
int i, size, j, length;
|
|
unsigned char val;
|
|
size_t amount_read;
|
|
|
|
if (len == 0) { /* probably bogus data. */
|
|
return NULL;
|
|
}
|
|
|
|
t = load = (unsigned char *)SDL_calloc(1, len);
|
|
if (load == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
amount_read = SDL_ReadIO(src, load, len);
|
|
if (amount_read == 0) {
|
|
SDL_free(load);
|
|
return NULL;
|
|
}
|
|
|
|
data = (unsigned char *)SDL_calloc(1, x*y*bpp);
|
|
if (!data) {
|
|
SDL_free(load);
|
|
return NULL;
|
|
}
|
|
unsigned char *data_end = data + x*y*bpp;
|
|
for (i = 0; i < bpp; i++) {
|
|
d = data + i;
|
|
size = x*y;
|
|
|
|
while (size > 0) {
|
|
val = *t++;
|
|
|
|
length = val;
|
|
if (length >= 128) {
|
|
length = 255 - (length - 1);
|
|
if (length == 128) {
|
|
length = (*t << 8) + t[1];
|
|
t += 2;
|
|
}
|
|
|
|
if (((size_t)(t - load) + length) >= amount_read) {
|
|
break; /* bogus data */
|
|
} else if (length > size) {
|
|
break; /* bogus data */
|
|
}
|
|
|
|
size -= length;
|
|
|
|
while (length-- > 0) {
|
|
if (d >= data_end) {
|
|
break;
|
|
}
|
|
*d = *t++;
|
|
d += bpp;
|
|
}
|
|
} else {
|
|
length += 1;
|
|
if (length == 128) {
|
|
length = (*t << 8) + t[1];
|
|
t += 2;
|
|
}
|
|
|
|
if (((size_t)(t - load)) >= amount_read) {
|
|
break; /* bogus data */
|
|
} else if (length > size) {
|
|
break; /* bogus data */
|
|
}
|
|
|
|
size -= length;
|
|
|
|
val = *t++;
|
|
|
|
for (j = 0; j < length; j++) {
|
|
if (d >= data_end) {
|
|
break;
|
|
}
|
|
*d = val;
|
|
d += bpp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (size > 0) {
|
|
break; /* just drop out, untouched data initialized to zero. */
|
|
}
|
|
|
|
}
|
|
SDL_free(load);
|
|
|
|
return data;
|
|
}
|
|
|
|
static Uint32 rgb2grey(Uint32 a)
|
|
{
|
|
Uint8 l = (Uint8)(0.2990 * ((a & 0x00FF0000) >> 16)
|
|
+ 0.5870 * ((a & 0x0000FF00) >> 8)
|
|
+ 0.1140 * ((a & 0x000000FF)));
|
|
return (l << 16) | (l << 8) | l;
|
|
}
|
|
|
|
static void create_channel_surface(SDL_Surface *surf, xcf_image_type itype, Uint32 color, Uint32 opacity)
|
|
{
|
|
Uint32 c = 0;
|
|
|
|
switch (itype) {
|
|
case IMAGE_RGB:
|
|
case IMAGE_INDEXED:
|
|
c = opacity | color;
|
|
break;
|
|
case IMAGE_GREYSCALE:
|
|
c = opacity | rgb2grey (color);
|
|
break;
|
|
}
|
|
SDL_FillSurfaceRect(surf, NULL, c);
|
|
}
|
|
|
|
static int
|
|
do_layer_surface(SDL_Surface *surface, SDL_IOStream *src, xcf_header *head, xcf_layer *layer, load_tile_type load_tile)
|
|
{
|
|
xcf_hierarchy *hierarchy;
|
|
xcf_level *level;
|
|
unsigned char *tile;
|
|
Uint8 *p8;
|
|
Uint32 *p;
|
|
int i, j;
|
|
Uint32 x, y, tx, ty, ox, oy;
|
|
Uint32 *row;
|
|
Uint64 length;
|
|
|
|
if (SDL_SeekIO(src, layer->hierarchy_file_offset, SDL_IO_SEEK_SET) < 0) {
|
|
return 1;
|
|
}
|
|
hierarchy = read_xcf_hierarchy(src, head);
|
|
if (!hierarchy) {
|
|
SDL_SetError("Failed to read XCF image hierarchy");
|
|
return 1;
|
|
}
|
|
|
|
if (hierarchy->bpp > 4) { /* unsupported. */
|
|
SDL_SetError("Unknown Gimp image bpp (%u)", (unsigned int) hierarchy->bpp);
|
|
free_xcf_hierarchy(hierarchy);
|
|
return 1;
|
|
}
|
|
|
|
level = NULL;
|
|
for (i = 0; hierarchy->level_file_offsets[i]; i++) {
|
|
if (SDL_SeekIO(src, hierarchy->level_file_offsets[i], SDL_IO_SEEK_SET) < 0)
|
|
break;
|
|
|
|
if (i > 0) /* skip level except the 1st one, just like GIMP does */
|
|
continue;
|
|
|
|
level = read_xcf_level(src, head);
|
|
if (!level) {
|
|
free_xcf_hierarchy(hierarchy);
|
|
return 1;
|
|
}
|
|
|
|
ty = tx = 0;
|
|
for (j = 0; level->tile_file_offsets[j]; j++) {
|
|
SDL_SeekIO(src, level->tile_file_offsets[j], SDL_IO_SEEK_SET);
|
|
ox = tx + 64 > level->width ? level->width % 64 : 64;
|
|
oy = ty + 64 > level->height ? level->height % 64 : 64;
|
|
length = ox*oy*6;
|
|
|
|
if (level->tile_file_offsets[j + 1] > level->tile_file_offsets[j]) {
|
|
length = level->tile_file_offsets[j + 1] - level->tile_file_offsets[j];
|
|
}
|
|
if (length <= SDL_SIZE_MAX) {
|
|
tile = load_tile(src, (size_t)length, hierarchy->bpp, ox, oy);
|
|
} else {
|
|
SDL_SetError("Gimp image invalid tile offsets");
|
|
tile = NULL;
|
|
}
|
|
if (!tile) {
|
|
if (hierarchy) {
|
|
free_xcf_hierarchy(hierarchy);
|
|
}
|
|
if (level) {
|
|
free_xcf_level(level);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
p8 = tile;
|
|
p = (Uint32 *) p8;
|
|
|
|
/* Bounds check: reject layer if tile data exceeds buffer */
|
|
if ((Uint64)ox * oy * hierarchy->bpp > (Uint64)(hierarchy->width * hierarchy->height * hierarchy->bpp)) {
|
|
SDL_SetError("Gimp image invalid tile");
|
|
free_xcf_tile(tile);
|
|
free_xcf_level(level);
|
|
free_xcf_hierarchy(hierarchy);
|
|
return 1;
|
|
}
|
|
|
|
for (y = ty; y < ty + oy; y++) {
|
|
if ((y >= (Uint32)surface->h) || ((tx+ox) > (Uint32)surface->w)) {
|
|
break;
|
|
}
|
|
row = (Uint32 *) ((Uint8 *) surface->pixels + y * surface->pitch + tx * 4);
|
|
switch (hierarchy->bpp) {
|
|
case 4:
|
|
for (x = tx; x < tx + ox; x++)
|
|
*row++ = SDL_Swap32(*p++);
|
|
break;
|
|
case 3:
|
|
for (x = tx; x < tx + ox; x++) {
|
|
*row = 0xFF000000;
|
|
*row |= ((Uint32)*p8++ << 16);
|
|
*row |= ((Uint32)*p8++ << 8);
|
|
*row |= ((Uint32)*p8++ << 0);
|
|
row++;
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Indexed / Greyscale + Alpha */
|
|
switch (head->image_type) {
|
|
case IMAGE_INDEXED:
|
|
for (x = tx; x < tx + ox; x++) {
|
|
Uint8 c = *p8++;
|
|
Uint8 a = *p8++;
|
|
if (c < head->cm_num) {
|
|
*row++ = ((Uint32)(head->cm_map[c * 3]) << 16) |
|
|
((Uint32)(head->cm_map[c * 3 + 1]) << 8) |
|
|
((Uint32)(head->cm_map[c * 3 + 2]) << 0) |
|
|
((Uint32)a << 24);
|
|
} else {
|
|
*row++ = 0;
|
|
}
|
|
}
|
|
break;
|
|
case IMAGE_GREYSCALE:
|
|
for (x = tx; x < tx + ox; x++) {
|
|
Uint8 c = *p8++;
|
|
Uint8 a = *p8++;
|
|
*row++ = ((Uint32)c << 16) |
|
|
((Uint32)c << 8) |
|
|
((Uint32)c << 0) |
|
|
((Uint32)a << 24);
|
|
}
|
|
break;
|
|
default:
|
|
SDL_SetError("Unknown Gimp image type (%" SDL_PRIu32 ")", head->image_type);
|
|
if (hierarchy) {
|
|
free_xcf_hierarchy(hierarchy);
|
|
}
|
|
if (level)
|
|
free_xcf_level(level);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1:
|
|
/* Indexed / Greyscale */
|
|
switch (head->image_type) {
|
|
case IMAGE_INDEXED:
|
|
for (x = tx; x < tx + ox; x++) {
|
|
Uint8 c = *p8++;
|
|
if (c < head->cm_num) {
|
|
*row++ = 0xFF000000 |
|
|
((Uint32)(head->cm_map[c * 3]) << 16) |
|
|
((Uint32)(head->cm_map[c * 3 + 1]) << 8) |
|
|
((Uint32)(head->cm_map[c * 3 + 2]) << 0);
|
|
} else {
|
|
*row++ = 0;
|
|
}
|
|
}
|
|
break;
|
|
case IMAGE_GREYSCALE:
|
|
for (x = tx; x < tx + ox; x++) {
|
|
Uint8 c = *p8++;
|
|
*row++ = 0xFF000000 |
|
|
(((Uint32)c) << 16) |
|
|
(((Uint32)c) << 8) |
|
|
(((Uint32)c) << 0);
|
|
}
|
|
break;
|
|
default:
|
|
SDL_SetError("Unknown Gimp image type (%" SDL_PRIu32 ")\n", head->image_type);
|
|
if (tile)
|
|
free_xcf_tile(tile);
|
|
if (level)
|
|
free_xcf_level(level);
|
|
if (hierarchy)
|
|
free_xcf_hierarchy(hierarchy);
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
free_xcf_tile(tile);
|
|
|
|
tx += 64;
|
|
if (tx >= level->width) {
|
|
tx = 0;
|
|
ty += 64;
|
|
}
|
|
if (ty >= level->height) {
|
|
break;
|
|
}
|
|
}
|
|
free_xcf_level(level);
|
|
}
|
|
|
|
free_xcf_hierarchy(hierarchy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SDL_Surface *IMG_LoadXCF_IO(SDL_IOStream *src)
|
|
{
|
|
Sint64 start;
|
|
const char *error = NULL;
|
|
SDL_Surface *surface, *lays;
|
|
xcf_header *head;
|
|
xcf_layer *layer;
|
|
xcf_channel **channel = NULL;
|
|
int chnls = 0;
|
|
int i, offsets;
|
|
Sint64 offset, fp;
|
|
load_tile_type load_tile;
|
|
|
|
if (!src) {
|
|
/* The error message has been set in SDL_IOFromFile */
|
|
return NULL;
|
|
}
|
|
start = SDL_TellIO(src);
|
|
|
|
/* Initialize the data we will clean up when we're done */
|
|
surface = NULL;
|
|
|
|
head = read_xcf_header(src);
|
|
if (!head) {
|
|
error = "Couldn't read header";
|
|
goto done;
|
|
}
|
|
|
|
switch (head->compr) {
|
|
case COMPR_NONE:
|
|
load_tile = load_xcf_tile_none;
|
|
break;
|
|
case COMPR_RLE:
|
|
load_tile = load_xcf_tile_rle;
|
|
break;
|
|
default:
|
|
error = "Unsupported compression";
|
|
goto done;
|
|
}
|
|
|
|
/* Create the surface of the appropriate type */
|
|
surface = SDL_CreateSurface(head->width, head->height, SDL_PIXELFORMAT_ARGB8888);
|
|
if (surface == NULL) {
|
|
error = "Out of memory";
|
|
goto done;
|
|
}
|
|
|
|
offsets = 0;
|
|
|
|
while ((offset = read_offset(src, head)) != 0) {
|
|
head->layer_file_offsets = (Uint64 *)SDL_realloc(head->layer_file_offsets, sizeof(Uint64) * (offsets + 1));
|
|
head->layer_file_offsets[offsets] = offset;
|
|
offsets++;
|
|
}
|
|
fp = SDL_TellIO (src);
|
|
|
|
lays = SDL_CreateSurface(head->width, head->height, SDL_PIXELFORMAT_ARGB8888);
|
|
if (lays == NULL) {
|
|
error = "Out of memory";
|
|
goto done;
|
|
}
|
|
|
|
/* Blit layers backwards, because Gimp saves them highest first */
|
|
for (i = offsets; i > 0; i--) {
|
|
SDL_Rect rs, rd;
|
|
SDL_SeekIO(src, head->layer_file_offsets[i-1], SDL_IO_SEEK_SET);
|
|
|
|
layer = read_xcf_layer(src, head);
|
|
if (layer != NULL) {
|
|
if (layer->visible) {
|
|
if (do_layer_surface(lays, src, head, layer, load_tile) != 0) {
|
|
error = SDL_GetError();
|
|
goto done;
|
|
}
|
|
rs.x = 0;
|
|
rs.y = 0;
|
|
rs.w = layer->width;
|
|
rs.h = layer->height;
|
|
rd.x = layer->offset_x;
|
|
rd.y = layer->offset_y;
|
|
rd.w = layer->width;
|
|
rd.h = layer->height;
|
|
|
|
SDL_BlitSurface(lays, &rs, surface, &rd);
|
|
}
|
|
free_xcf_layer(layer);
|
|
}
|
|
}
|
|
SDL_DestroySurface(lays);
|
|
|
|
SDL_SeekIO(src, fp, SDL_IO_SEEK_SET);
|
|
|
|
/* read channels */
|
|
while ((offset = read_offset (src, head)) != 0) {
|
|
channel = (xcf_channel **)SDL_realloc(channel, sizeof(xcf_channel *) * (chnls + 1));
|
|
fp = SDL_TellIO(src);
|
|
if (SDL_SeekIO(src, offset, SDL_IO_SEEK_SET) < 0) {
|
|
error = "invalid channel offset";
|
|
goto done;
|
|
}
|
|
channel[chnls] = read_xcf_channel(src, head);
|
|
if (channel[chnls] != NULL) {
|
|
chnls++;
|
|
}
|
|
SDL_SeekIO(src, fp, SDL_IO_SEEK_SET);
|
|
}
|
|
|
|
if (chnls) {
|
|
SDL_Surface *chs;
|
|
|
|
chs = SDL_CreateSurface(head->width, head->height, SDL_PIXELFORMAT_ARGB8888);
|
|
if (chs == NULL) {
|
|
error = "Out of memory";
|
|
goto done;
|
|
}
|
|
for (i = 0; i < chnls; i++) {
|
|
/* SDL_Log ("CNLBLT %i\n", i); */
|
|
if (!channel[i]->selection && channel[i]->visible) {
|
|
create_channel_surface(chs, (xcf_image_type)head->image_type, channel [i]->color, channel [i]->opacity);
|
|
SDL_BlitSurface (chs, NULL, surface, NULL);
|
|
}
|
|
}
|
|
SDL_DestroySurface(chs);
|
|
}
|
|
|
|
done:
|
|
for (i = 0; i < chnls; i++) {
|
|
free_xcf_channel(channel[i]);
|
|
}
|
|
SDL_free(channel);
|
|
|
|
free_xcf_header(head);
|
|
|
|
if (error) {
|
|
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
|
|
if (surface) {
|
|
SDL_DestroySurface(surface);
|
|
surface = NULL;
|
|
}
|
|
SDL_SetError("%s", error);
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
#else
|
|
|
|
/* See if an image is contained in a data source */
|
|
bool IMG_isXCF(SDL_IOStream *src)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* Load a XCF type image from an SDL datasource */
|
|
SDL_Surface *IMG_LoadXCF_IO(SDL_IOStream *src)
|
|
{
|
|
SDL_SetError("SDL_image built without XCF support");
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* LOAD_XCF */
|