gtk2/gdk-pixbuf/io-tga.c

1001 lines
24 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "linux" -*- */
/*
* GdkPixbuf library - TGA image loader
* Copyright (C) 1999 Nicola Girardi <nikke@swlibero.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
/*
* Some NOTES about the TGA loader (2001/06/07, nikke@swlibero.org)
*
* - The module doesn't currently provide support for TGA images where the
* order of the pixels isn't left-to-right and top-to-bottom. I plan to
* add support for those files as soon as I get one of them. I haven't
* run into one yet. (And I don't seem to be able to create it with GIMP.)
*
* - The TGAFooter isn't present in all TGA files. In fact, there's an older
* format specification, still in use, which doesn't cover the TGAFooter.
* Actually, most TGA files I have are of the older type. Anyway I put the
* struct declaration here for completeness.
*
* - Error handling was designed to be very paranoid.
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include "gdk-pixbuf-private.h"
#include "gdk-pixbuf-io.h"
#undef DEBUG_TGA
#define TGA_INTERLEAVE_MASK 0xc0
#define TGA_INTERLEAVE_NONE 0x00
#define TGA_INTERLEAVE_2WAY 0x40
#define TGA_INTERLEAVE_4WAY 0x80
#define TGA_ORIGIN_MASK 0x30
#define TGA_ORIGIN_RIGHT 0x10
#define TGA_ORIGIN_UPPER 0x20
enum {
TGA_TYPE_NODATA = 0,
TGA_TYPE_PSEUDOCOLOR = 1,
TGA_TYPE_TRUECOLOR = 2,
TGA_TYPE_GRAYSCALE = 3,
TGA_TYPE_RLE_PSEUDOCOLOR = 9,
TGA_TYPE_RLE_TRUECOLOR = 10,
TGA_TYPE_RLE_GRAYSCALE = 11
};
#define LE16(p) ((p)[0] + ((p)[1] << 8))
typedef struct _IOBuffer IOBuffer;
typedef struct _TGAHeader TGAHeader;
typedef struct _TGAFooter TGAFooter;
typedef struct _TGAColormap TGAColormap;
typedef struct _TGAColor TGAColor;
typedef struct _TGAContext TGAContext;
struct _TGAHeader {
guint8 infolen;
guint8 has_cmap;
guint8 type;
guint8 cmap_start[2];
guint8 cmap_n_colors[2];
guint8 cmap_bpp;
guint8 x_origin[2];
guint8 y_origin[2];
guint8 width[2];
guint8 height[2];
guint8 bpp;
guint8 flags;
};
struct _TGAFooter {
guint32 extension_area_offset;
guint32 developer_directory_offset;
/* Standard TGA signature, "TRUEVISION-XFILE.\0". */
union {
gchar sig_full[18];
struct {
gchar sig_chunk[16];
gchar dot, null;
} sig_struct;
} sig;
};
struct _TGAColormap {
gint size;
TGAColor *cols;
};
struct _TGAColor {
guchar r, g, b, a;
};
struct _TGAContext {
TGAHeader *hdr;
guint rowstride;
guint completed_lines;
gboolean run_length_encoded;
TGAColormap *cmap;
guint cmap_size;
GdkPixbuf *pbuf;
guint pbuf_bytes;
guint pbuf_bytes_done;
guchar *pptr;
IOBuffer *in;
gboolean skipped_info;
gboolean prepared;
gboolean done;
GdkPixbufModuleSizeFunc sfunc;
GdkPixbufModulePreparedFunc pfunc;
GdkPixbufModuleUpdatedFunc ufunc;
gpointer udata;
};
struct _IOBuffer {
guchar *data;
guint size;
};
static IOBuffer *io_buffer_new(GError **err)
{
IOBuffer *buffer;
buffer = g_try_malloc(sizeof(IOBuffer));
if (!buffer) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate memory for IOBuffer struct"));
return NULL;
}
buffer->data = NULL;
buffer->size = 0;
return buffer;
}
static IOBuffer *io_buffer_append(IOBuffer *buffer,
const guchar *data, guint len,
GError **err)
{
if (!buffer)
return NULL;
if (!buffer->data) {
buffer->data = g_try_malloc(len);
if (!buffer->data) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate memory for IOBuffer data"));
g_free(buffer);
return NULL;
}
g_memmove(buffer->data, data, len);
buffer->size = len;
} else {
guchar *tmp = g_try_realloc (buffer->data, buffer->size + len);
if (!tmp) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot realloc IOBuffer data"));
g_free(buffer);
return NULL;
}
buffer->data = tmp;
g_memmove(&buffer->data[buffer->size], data, len);
buffer->size += len;
}
return buffer;
}
static IOBuffer *io_buffer_free_segment(IOBuffer *buffer,
guint count,
GError **err)
{
g_return_val_if_fail(buffer != NULL, NULL);
g_return_val_if_fail(buffer->data != NULL, NULL);
if (count == buffer->size) {
g_free(buffer->data);
buffer->data = NULL;
buffer->size = 0;
} else {
guchar *new_buf;
guint new_size;
new_size = buffer->size - count;
new_buf = g_try_malloc(new_size);
if (!new_buf) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate temporary IOBuffer data"));
g_free(buffer->data);
g_free(buffer);
return NULL;
}
g_memmove(new_buf, &buffer->data[count], new_size);
g_free(buffer->data);
buffer->data = new_buf;
buffer->size = new_size;
}
return buffer;
}
static void io_buffer_free(IOBuffer *buffer)
{
g_return_if_fail(buffer != NULL);
if (buffer->data)
g_free(buffer->data);
g_free(buffer);
}
static void free_buffer(guchar *pixels, gpointer data)
{
g_free(pixels);
}
static GdkPixbuf *get_contiguous_pixbuf (guint width,
guint height,
gboolean has_alpha)
{
guchar *pixels;
guint channels, rowstride, bytes;
if (has_alpha)
channels = 4;
else
channels = 3;
rowstride = width * channels;
if (rowstride / channels != width)
return NULL;
bytes = height * rowstride;
if (bytes / rowstride != height)
return NULL;
pixels = g_try_malloc (bytes);
if (!pixels)
return NULL;
return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, has_alpha, 8,
width, height, rowstride, free_buffer, NULL);
}
static void pixbuf_flip_row (GdkPixbuf *pixbuf, guchar *ph)
{
guchar *p, *s;
guchar tmp;
gint count;
p = ph;
s = p + pixbuf->n_channels * (pixbuf->width - 1);
while (p < s) {
for (count = pixbuf->n_channels; count > 0; count--, p++, s++) {
tmp = *p;
*p = *s;
*s = tmp;
}
s -= 2 * pixbuf->n_channels;
}
}
static void pixbuf_flip_vertically (GdkPixbuf *pixbuf)
{
guchar *ph, *sh, *p, *s;
guchar tmp;
gint count;
ph = pixbuf->pixels;
sh = pixbuf->pixels + pixbuf->height*pixbuf->rowstride;
while (ph < sh - pixbuf->rowstride) {
p = ph;
s = sh - pixbuf->rowstride;
for (count = pixbuf->n_channels * pixbuf->width; count > 0; count--, p++, s++) {
tmp = *p;
*p = *s;
*s = tmp;
}
sh -= pixbuf->rowstride;
ph += pixbuf->rowstride;
}
}
static gboolean fill_in_context(TGAContext *ctx, GError **err)
{
gboolean alpha;
guint w, h;
g_return_val_if_fail(ctx != NULL, FALSE);
ctx->run_length_encoded =
((ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR)
|| (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR)
|| (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE));
if (ctx->hdr->has_cmap)
ctx->cmap_size = ((ctx->hdr->cmap_bpp + 7) >> 3) *
LE16(ctx->hdr->cmap_n_colors);
alpha = ((ctx->hdr->bpp == 16) ||
(ctx->hdr->bpp == 32) ||
(ctx->hdr->has_cmap && (ctx->hdr->cmap_bpp == 32)));
w = LE16(ctx->hdr->width);
h = LE16(ctx->hdr->height);
if (ctx->sfunc) {
gint wi = w;
gint hi = h;
(*ctx->sfunc) (&wi, &hi, ctx->udata);
if (wi == 0 || hi == 0)
return FALSE;
}
ctx->pbuf = get_contiguous_pixbuf (w, h, alpha);
if (!ctx->pbuf) {
g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate new pixbuf"));
return FALSE;
}
ctx->pbuf_bytes = ctx->pbuf->rowstride * ctx->pbuf->height;
if (ctx->hdr->flags & TGA_ORIGIN_UPPER || ctx->run_length_encoded)
ctx->pptr = ctx->pbuf->pixels;
else
ctx->pptr = ctx->pbuf->pixels + (ctx->pbuf->height - 1)*ctx->pbuf->rowstride;
if (ctx->hdr->type == TGA_TYPE_PSEUDOCOLOR)
ctx->rowstride = ctx->pbuf->width;
else if (ctx->hdr->type == TGA_TYPE_GRAYSCALE)
ctx->rowstride = (alpha ? ctx->pbuf->width * 2 : ctx->pbuf->width);
else if (ctx->hdr->type == TGA_TYPE_TRUECOLOR)
ctx->rowstride = ctx->pbuf->rowstride;
ctx->completed_lines = 0;
return TRUE;
}
static void parse_data_for_row_pseudocolor(TGAContext *ctx)
{
guchar *s = ctx->in->data;
guint upper_bound = ctx->pbuf->width;
guchar *p = ctx->pptr;
for (; upper_bound; upper_bound--, s++) {
*p++ = ctx->cmap->cols[*s].r;
*p++ = ctx->cmap->cols[*s].g;
*p++ = ctx->cmap->cols[*s].b;
if (ctx->hdr->cmap_bpp == 32)
*p++ = ctx->cmap->cols[*s].a;
}
}
static void swap_channels(TGAContext *ctx)
{
guchar swap;
guint count;
guchar *p = ctx->pptr;
for (count = ctx->pbuf->width; count; count--) {
swap = p[0];
p[0] = p[2];
p[2] = swap;
p += ctx->pbuf->n_channels;
}
}
static void parse_data_for_row_truecolor(TGAContext *ctx)
{
g_memmove(ctx->pptr, ctx->in->data, ctx->pbuf->rowstride);
swap_channels(ctx);
}
static void parse_data_for_row_grayscale(TGAContext *ctx)
{
guchar *s = ctx->in->data;
guint upper_bound = ctx->pbuf->width;
guchar *p = ctx->pptr;
for (; upper_bound; upper_bound--) {
p[0] = p[1] = p[2] = *s++;
if (ctx->pbuf->n_channels == 4)
p[3] = *s++;
p += ctx->pbuf->n_channels;
}
}
static gboolean parse_data_for_row(TGAContext *ctx, GError **err)
{
guint row;
if (ctx->hdr->type == TGA_TYPE_PSEUDOCOLOR)
parse_data_for_row_pseudocolor(ctx);
else if (ctx->hdr->type == TGA_TYPE_TRUECOLOR)
parse_data_for_row_truecolor(ctx);
else if (ctx->hdr->type == TGA_TYPE_GRAYSCALE)
parse_data_for_row_grayscale(ctx);
if (ctx->hdr->flags & TGA_ORIGIN_RIGHT)
pixbuf_flip_row (ctx->pbuf, ctx->pptr);
if (ctx->hdr->flags & TGA_ORIGIN_UPPER)
ctx->pptr += ctx->pbuf->rowstride;
else
ctx->pptr -= ctx->pbuf->rowstride;
ctx->pbuf_bytes_done += ctx->pbuf->rowstride;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
ctx->done = TRUE;
ctx->in = io_buffer_free_segment(ctx->in, ctx->rowstride, err);
if (!ctx->in)
return FALSE;
row = (ctx->pptr - ctx->pbuf->pixels) / ctx->pbuf->rowstride - 1;
if (ctx->ufunc)
(*ctx->ufunc) (ctx->pbuf, 0, row, ctx->pbuf->width, 1, ctx->udata);
return TRUE;
}
static void write_rle_data(TGAContext *ctx, TGAColor *color, guint *rle_count)
{
for (; *rle_count; (*rle_count)--) {
g_memmove(ctx->pptr, (guchar *) color, ctx->pbuf->n_channels);
ctx->pptr += ctx->pbuf->n_channels;
ctx->pbuf_bytes_done += ctx->pbuf->n_channels;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
return;
}
}
static guint parse_rle_data_pseudocolor(TGAContext *ctx)
{
guint rle_num, raw_num;
guchar *s, tag;
guint n;
g_return_val_if_fail(ctx->in->size > 0, 0);
s = ctx->in->data;
for (n = 0; n < ctx->in->size; ) {
tag = *s;
s++, n++;
if (tag & 0x80) {
if (n == ctx->in->size) {
return --n;
} else {
rle_num = (tag & 0x7f) + 1;
write_rle_data(ctx, &ctx->cmap->cols[*s], &rle_num);
s++, n++;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
} else {
raw_num = tag + 1;
if (n + raw_num >= ctx->in->size) {
return --n;
} else {
for (; raw_num; raw_num--) {
*ctx->pptr++ =
ctx->cmap->cols[*s].r;
*ctx->pptr++ =
ctx->cmap->cols[*s].g;
*ctx->pptr++ =
ctx->cmap->cols[*s].b;
if (ctx->pbuf->n_channels == 4)
*ctx->pptr++ = ctx->cmap->cols[*s].a;
s++, n++;
ctx->pbuf_bytes_done += ctx->pbuf->n_channels;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
}
}
}
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
ctx->done = TRUE;
return n;
}
static guint parse_rle_data_truecolor(TGAContext *ctx)
{
TGAColor col;
guint rle_num, raw_num;
guchar *s, tag;
guint n = 0;
g_return_val_if_fail(ctx->in->size > 0, 0);
s = ctx->in->data;
for (n = 0; n < ctx->in->size; ) {
tag = *s;
s++, n++;
if (tag & 0x80) {
if (n + ctx->pbuf->n_channels >= ctx->in->size) {
return --n;
} else {
rle_num = (tag & 0x7f) + 1;
col.b = *s++;
col.g = *s++;
col.r = *s++;
if (ctx->hdr->bpp == 32)
col.a = *s++;
n += ctx->pbuf->n_channels;
write_rle_data(ctx, &col, &rle_num);
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
} else {
raw_num = tag + 1;
if (n + (raw_num * ctx->pbuf->n_channels) >= ctx->in->size) {
return --n;
} else {
for (; raw_num; raw_num--) {
ctx->pptr[2] = *s++;
ctx->pptr[1] = *s++;
ctx->pptr[0] = *s++;
if (ctx->hdr->bpp == 32)
ctx->pptr[3] = *s++;
n += ctx->pbuf->n_channels;
ctx->pptr += ctx->pbuf->n_channels;
ctx->pbuf_bytes_done += ctx->pbuf->n_channels;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
}
}
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
ctx->done = TRUE;
return n;
}
static guint parse_rle_data_grayscale(TGAContext *ctx)
{
TGAColor tone;
guint rle_num, raw_num;
guchar *s, tag;
guint n;
g_return_val_if_fail(ctx->in->size > 0, 0);
s = ctx->in->data;
for (n = 0; n < ctx->in->size; ) {
tag = *s;
s++, n++;
if (tag & 0x80) {
if (n + (ctx->pbuf->n_channels == 4 ? 2 : 1) >= ctx->in->size) {
return --n;
} else {
rle_num = (tag & 0x7f) + 1;
tone.r = tone.g = tone.b = *s;
s++, n++;
if (ctx->pbuf->n_channels == 4) {
tone.a = *s++;
n++;
}
write_rle_data(ctx, &tone, &rle_num);
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
} else {
raw_num = tag + 1;
if (n + raw_num * (ctx->pbuf->n_channels == 4 ? 2 : 1) >= ctx->in->size) {
return --n;
} else {
for (; raw_num; raw_num--) {
ctx->pptr[0] = ctx->pptr[1] = ctx->pptr[2] = *s;
s++, n++;
if (ctx->pbuf->n_channels == 4) {
ctx->pptr[3] = *s++;
n++;
}
ctx->pptr += ctx->pbuf->n_channels;
ctx->pbuf_bytes_done += ctx->pbuf->n_channels;
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes) {
ctx->done = TRUE;
return n;
}
}
}
}
}
if (ctx->pbuf_bytes_done == ctx->pbuf_bytes)
ctx->done = TRUE;
return n;
}
static gboolean parse_rle_data(TGAContext *ctx, GError **err)
{
guint count = 0;
guint pbuf_count = 0;
guint bytes_done_before = ctx->pbuf_bytes_done;
if (ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR) {
count = parse_rle_data_pseudocolor(ctx);
pbuf_count = count * ctx->pbuf->n_channels;
} else if (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR) {
count = parse_rle_data_truecolor(ctx);
pbuf_count = count;
} else if (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE) {
count = parse_rle_data_grayscale(ctx);
pbuf_count = count * (ctx->pbuf->n_channels == 4 ? 2 : 3);
}
if (ctx->hdr->flags & TGA_ORIGIN_RIGHT) {
guchar *row = ctx->pbuf->pixels + (bytes_done_before / ctx->pbuf->rowstride) * ctx->pbuf->rowstride;
guchar *row_after = ctx->pbuf->pixels + (ctx->pbuf_bytes_done / ctx->pbuf->rowstride) * ctx->pbuf->rowstride;
for (; row < row_after; row += ctx->pbuf->rowstride)
pixbuf_flip_row (ctx->pbuf, row);
}
ctx->in = io_buffer_free_segment(ctx->in, count, err);
if (!ctx->in)
return FALSE;
if (ctx->done) {
/* FIXME doing the vertical flipping afterwards is not
* perfect, but doing it during the rle decoding in place
* is considerably more work.
*/
if (!(ctx->hdr->flags & TGA_ORIGIN_UPPER))
pixbuf_flip_vertically (ctx->pbuf);
}
if (ctx->ufunc)
(*ctx->ufunc) (ctx->pbuf, 0, ctx->pbuf_bytes_done / ctx->pbuf->rowstride,
ctx->pbuf->width, pbuf_count / ctx->pbuf->rowstride,
ctx->udata);
return TRUE;
}
static gboolean try_colormap(TGAContext *ctx, GError **err)
{
static guchar *p;
static guint n;
g_return_val_if_fail(ctx != NULL, FALSE);
g_return_val_if_fail(ctx->cmap_size > 0, TRUE);
ctx->cmap = g_try_malloc(sizeof(TGAColormap));
if (!ctx->cmap) {
g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate colormap structure"));
return FALSE;
}
ctx->cmap->size = LE16(ctx->hdr->cmap_n_colors);
ctx->cmap->cols = g_try_malloc(sizeof(TGAColor) * ctx->cmap->size);
if (!ctx->cmap->cols) {
g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate colormap entries"));
return FALSE;
}
p = ctx->in->data;
for (n = 0; n < ctx->cmap->size; n++) {
if ((ctx->hdr->cmap_bpp == 15) || (ctx->hdr->cmap_bpp == 16)) {
guint16 col = p[0] + (p[1] << 8);
ctx->cmap->cols[n].b = (col >> 7) & 0xf8;
ctx->cmap->cols[n].g = (col >> 2) & 0xf8;
ctx->cmap->cols[n].r = col << 3;
p += 2;
}
else if ((ctx->hdr->cmap_bpp == 24) || (ctx->hdr->cmap_bpp == 32)) {
ctx->cmap->cols[n].b = *p++;
ctx->cmap->cols[n].g = *p++;
ctx->cmap->cols[n].r = *p++;
if (ctx->hdr->cmap_bpp == 32)
ctx->cmap->cols[n].a = *p++;
} else {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
_("Unexpected bitdepth for colormap entries"));
return FALSE;
}
}
ctx->in = io_buffer_free_segment(ctx->in, ctx->cmap_size, err);
if (!ctx->in)
return FALSE;
return TRUE;
}
static gboolean try_preload(TGAContext *ctx, GError **err)
{
if (!ctx->hdr) {
if (ctx->in->size >= sizeof(TGAHeader)) {
ctx->hdr = g_try_malloc(sizeof(TGAHeader));
if (!ctx->hdr) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate TGA header memory"));
return FALSE;
}
g_memmove(ctx->hdr, ctx->in->data, sizeof(TGAHeader));
ctx->in = io_buffer_free_segment(ctx->in, sizeof(TGAHeader), err);
#ifdef DEBUG_TGA
g_print ("infolen %d "
"has_cmap %d "
"type %d "
"cmap_start %d "
"cmap_n_colors %d "
"cmap_bpp %d "
"x %d y %d width %d height %d bpp %d "
"flags %#x",
ctx->hdr->infolen,
ctx->hdr->has_cmap,
ctx->hdr->type,
LE16(ctx->hdr->cmap_start),
LE16(ctx->hdr->cmap_n_colors),
ctx->hdr->cmap_bpp,
LE16(ctx->hdr->x_origin),
LE16(ctx->hdr->y_origin),
LE16(ctx->hdr->width),
LE16(ctx->hdr->height),
ctx->hdr->bpp,
ctx->hdr->flags);
#endif
if (!ctx->in)
return FALSE;
if (LE16(ctx->hdr->width) == 0 ||
LE16(ctx->hdr->height) == 0) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
_("TGA image has invalid dimensions"));
return FALSE;
}
if ((ctx->hdr->flags & TGA_INTERLEAVE_MASK) != TGA_INTERLEAVE_NONE) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
_("TGA image type not supported"));
return FALSE;
}
switch (ctx->hdr->type) {
case TGA_TYPE_PSEUDOCOLOR:
case TGA_TYPE_RLE_PSEUDOCOLOR:
if (ctx->hdr->bpp != 8) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
_("TGA image type not supported"));
return FALSE;
}
break;
case TGA_TYPE_TRUECOLOR:
case TGA_TYPE_RLE_TRUECOLOR:
if (ctx->hdr->bpp != 24 &&
ctx->hdr->bpp != 32) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
_("TGA image type not supported"));
return FALSE;
}
break;
case TGA_TYPE_GRAYSCALE:
case TGA_TYPE_RLE_GRAYSCALE:
if (ctx->hdr->bpp != 8 &&
ctx->hdr->bpp != 16) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
_("TGA image type not supported"));
return FALSE;
}
break;
default:
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
_("TGA image type not supported"));
return FALSE;
}
if (!fill_in_context(ctx, err))
return FALSE;
} else {
return TRUE;
}
}
if (!ctx->skipped_info) {
if (ctx->in->size >= ctx->hdr->infolen) {
ctx->in = io_buffer_free_segment(ctx->in, ctx->hdr->infolen, err);
if (!ctx->in)
return FALSE;
ctx->skipped_info = TRUE;
} else {
return TRUE;
}
}
if (ctx->hdr->has_cmap && !ctx->cmap) {
if (ctx->in->size >= ctx->cmap_size) {
if (!try_colormap(ctx, err))
return FALSE;
} else {
return TRUE;
}
}
if (!ctx->prepared) {
if (ctx->pfunc)
(*ctx->pfunc) (ctx->pbuf, NULL, ctx->udata);
ctx->prepared = TRUE;
}
/* We shouldn't get here anyway. */
return TRUE;
}
static gpointer gdk_pixbuf__tga_begin_load(GdkPixbufModuleSizeFunc f0,
GdkPixbufModulePreparedFunc f1,
GdkPixbufModuleUpdatedFunc f2,
gpointer udata, GError **err)
{
TGAContext *ctx;
ctx = g_try_malloc(sizeof(TGAContext));
if (!ctx) {
g_set_error(err, GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Cannot allocate memory for TGA context struct"));
return NULL;
}
ctx->hdr = NULL;
ctx->rowstride = 0;
ctx->run_length_encoded = FALSE;
ctx->cmap = NULL;
ctx->cmap_size = 0;
ctx->pbuf = NULL;
ctx->pbuf_bytes = 0;
ctx->pbuf_bytes_done = 0;
ctx->pptr = NULL;
ctx->in = io_buffer_new(err);
if (!ctx->in) {
g_free(ctx);
return NULL;
}
ctx->skipped_info = FALSE;
ctx->prepared = FALSE;
ctx->done = FALSE;
ctx->sfunc = f0;
ctx->pfunc = f1;
ctx->ufunc = f2;
ctx->udata = udata;
return ctx;
}
static gboolean gdk_pixbuf__tga_load_increment(gpointer data,
const guchar *buffer,
guint size,
GError **err)
{
TGAContext *ctx = (TGAContext*) data;
g_return_val_if_fail(ctx != NULL, FALSE);
if (ctx->done)
return TRUE;
g_return_val_if_fail(buffer != NULL, TRUE);
ctx->in = io_buffer_append(ctx->in, buffer, size, err);
if (!ctx->in)
return FALSE;
if (!ctx->prepared) {
if (!try_preload(ctx, err))
return FALSE;
if (!ctx->prepared)
return TRUE;
if (ctx->in->size == 0)
return TRUE;
}
if (ctx->run_length_encoded) {
if (!parse_rle_data(ctx, err))
return FALSE;
} else {
while (ctx->in->size >= ctx->rowstride) {
if (ctx->completed_lines >= ctx->pbuf->height) {
g_set_error(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
_("Excess data in file"));
return FALSE;
}
if (!parse_data_for_row(ctx, err))
return FALSE;
ctx->completed_lines++;
}
}
return TRUE;
}
static gboolean gdk_pixbuf__tga_stop_load(gpointer data, GError **err)
{
TGAContext *ctx = (TGAContext *) data;
g_return_val_if_fail(ctx != NULL, FALSE);
if (ctx->hdr)
g_free (ctx->hdr);
if (ctx->cmap) {
if (ctx->cmap->cols)
g_free (ctx->cmap->cols);
g_free (ctx->cmap);
}
if (ctx->pbuf)
g_object_unref (ctx->pbuf);
if (ctx->in && ctx->in->size)
ctx->in = io_buffer_free_segment (ctx->in, ctx->in->size, err);
if (!ctx->in) {
g_free (ctx);
return FALSE;
}
io_buffer_free (ctx->in);
g_free (ctx);
return TRUE;
}
void
MODULE_ENTRY (tga, fill_vtable) (GdkPixbufModule *module)
{
module->begin_load = gdk_pixbuf__tga_begin_load;
module->stop_load = gdk_pixbuf__tga_stop_load;
module->load_increment = gdk_pixbuf__tga_load_increment;
}
void
MODULE_ENTRY (tga, fill_info) (GdkPixbufFormat *info)
{
static GdkPixbufModulePattern signature[] = {
{ " \x1\x1", "x ", 100 },
{ " \x1\x9", "x ", 100 },
{ " \x2", "xz ", 99 }, /* only 99 since .CUR also matches this */
{ " \x3", "xz ", 100 },
{ " \xa", "xz ", 100 },
{ " \xb", "xz ", 100 },
{ NULL, NULL, 0 }
};
static gchar * mime_types[] = {
"image/x-tga",
NULL
};
static gchar * extensions[] = {
"tga",
"targa",
NULL
};
info->name = "tga";
info->signature = signature;
info->description = N_("The Targa image format");
info->mime_types = mime_types;
info->extensions = extensions;
info->flags = 0;
info->license = "LGPL";
}