 59596e699f
			
		
	
	59596e699f
	
	
	
		
			
			2008-05-27 Tor Lillqvist <tml@novell.com> * io-*.c: Use explicit G_MODULE_EXPORT decoration when building the loader in question as a module. * gdk-pixbuf-io.c: Enable included build of the icns and jasper loaders. svn path=/trunk/; revision=20192
		
			
				
	
	
		
			1006 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1006 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- 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 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);
 | |
| 	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 rows = 0;
 | |
| 	guint count = 0;
 | |
| 	guint bytes_done_before = ctx->pbuf_bytes_done;
 | |
| 
 | |
| 	if (ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR)
 | |
| 		count = parse_rle_data_pseudocolor(ctx);
 | |
| 	else if (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR)
 | |
| 		count = parse_rle_data_truecolor(ctx);
 | |
| 	else if (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE)
 | |
| 		count = parse_rle_data_grayscale(ctx);
 | |
| 
 | |
| 	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);
 | |
| 			ctx->hdr->flags |= TGA_ORIGIN_UPPER;
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 		
 | |
| 	rows = ctx->pbuf_bytes_done / ctx->pbuf->rowstride - bytes_done_before / ctx->pbuf->rowstride;
 | |
| 	if (ctx->ufunc)
 | |
| 		(*ctx->ufunc) (ctx->pbuf, 0, bytes_done_before / ctx->pbuf->rowstride,
 | |
| 			       ctx->pbuf->width, rows,
 | |
| 			       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->flags & TGA_ORIGIN_UPPER) && 
 | |
|             ctx->run_length_encoded && ctx->pbuf) {
 | |
| 		pixbuf_flip_vertically (ctx->pbuf);
 | |
| 		if (ctx->ufunc)
 | |
| 			(*ctx->ufunc) (ctx->pbuf, 0, 0,
 | |
| 				       ctx->pbuf->width, ctx->pbuf->height,
 | |
| 			       	       ctx->udata);
 | |
| 	}
 | |
| 	g_free (ctx->hdr);
 | |
| 	if (ctx->cmap) {
 | |
| 	  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;
 | |
| }
 | |
| 
 | |
| #ifndef INCLUDE_tga
 | |
| #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
 | |
| #else
 | |
| #define MODULE_ENTRY(function) void _gdk_pixbuf__tga_ ## function
 | |
| #endif
 | |
| 
 | |
| MODULE_ENTRY (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;
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (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 = GDK_PIXBUF_FORMAT_THREADSAFE;
 | |
| 	info->license = "LGPL";
 | |
| }
 |