/* -*- mode: C; c-file-style: "linux" -*- */ /* * GdkPixbuf library - TGA image loader * Copyright (C) 1999 Nicola Girardi * * 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, see . * */ /* * 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 #include #include "gdk-pixbuf-private.h" #include "gdk-pixbuf-buffer-queue-private.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 _TGAHeader TGAHeader; typedef struct _TGAFooter TGAFooter; typedef struct _TGAColor TGAColor; typedef struct _TGAColormap TGAColormap; 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 _TGAColor { guchar r, g, b, a; }; struct _TGAColormap { guint n_colors; TGAColor colors[1]; }; typedef gboolean (* TGAProcessFunc) (TGAContext *ctx, GError **error); struct _TGAContext { TGAHeader *hdr; TGAColormap *cmap; guint cmap_size; GdkPixbuf *pbuf; int pbuf_x; int pbuf_y; int pbuf_y_notified; GdkPixbufBufferQueue *input; TGAProcessFunc process; GdkPixbufModuleSizeFunc sfunc; GdkPixbufModulePreparedFunc pfunc; GdkPixbufModuleUpdatedFunc ufunc; gpointer udata; }; static TGAColormap * colormap_new (guint n_colors) { TGAColormap *cmap; g_assert (n_colors <= G_MAXUINT16); cmap = g_try_malloc0 (sizeof (TGAColormap) + (MAX (n_colors, 1) - 1) * sizeof (TGAColor)); if (cmap == NULL) return NULL; cmap->n_colors = n_colors; return cmap; } static const TGAColor * colormap_get_color (TGAColormap *cmap, guint id) { static const TGAColor transparent_black = { 0, 0, 0, 0 }; if (id >= cmap->n_colors) return &transparent_black; return &cmap->colors[id]; } static void colormap_set_color (TGAColormap *cmap, guint id, const TGAColor *color) { if (id >= cmap->n_colors) return; cmap->colors[id] = *color; } static void colormap_free (TGAColormap *cmap) { g_free (cmap); } static gboolean tga_skip_rest_of_image (TGAContext *ctx, GError **err) { gdk_pixbuf_buffer_queue_flush (ctx->input, gdk_pixbuf_buffer_queue_get_size (ctx->input)); return TRUE; } static inline void tga_write_pixel (TGAContext *ctx, const TGAColor *color) { guint x = (ctx->hdr->flags & TGA_ORIGIN_RIGHT) ? ctx->pbuf->width - ctx->pbuf_x - 1 : ctx->pbuf_x; guint y = (ctx->hdr->flags & TGA_ORIGIN_UPPER) ? ctx->pbuf_y : ctx->pbuf->height - ctx->pbuf_y - 1; memcpy (ctx->pbuf->pixels + y * ctx->pbuf->rowstride + x * ctx->pbuf->n_channels, color, ctx->pbuf->n_channels); ctx->pbuf_x++; if (ctx->pbuf_x >= ctx->pbuf->width) { ctx->pbuf_x = 0; ctx->pbuf_y++; } } static gsize tga_pixels_remaining (TGAContext *ctx) { return ctx->pbuf->width * (ctx->pbuf->height - ctx->pbuf_y) - ctx->pbuf_x; } static gboolean tga_all_pixels_written (TGAContext *ctx) { return ctx->pbuf_y >= ctx->pbuf->height; } static void tga_emit_update (TGAContext *ctx) { if (!ctx->ufunc) return; /* We only notify row-by-row for now. * I was too lazy to handle line-breaks. */ if (ctx->pbuf_y_notified == ctx->pbuf_y) return; if (ctx->hdr->flags & TGA_ORIGIN_UPPER) (*ctx->ufunc) (ctx->pbuf, 0, ctx->pbuf_y_notified, ctx->pbuf->width, ctx->pbuf_y - ctx->pbuf_y_notified, ctx->udata); else (*ctx->ufunc) (ctx->pbuf, 0, ctx->pbuf->height - ctx->pbuf_y, ctx->pbuf->width, ctx->pbuf_y - ctx->pbuf_y_notified, ctx->udata); ctx->pbuf_y_notified = ctx->pbuf_y; } static gboolean tga_format_supported (guint type, guint bits_per_pixel) { switch (type) { case TGA_TYPE_PSEUDOCOLOR: case TGA_TYPE_RLE_PSEUDOCOLOR: return bits_per_pixel == 8; case TGA_TYPE_TRUECOLOR: case TGA_TYPE_RLE_TRUECOLOR: return bits_per_pixel == 16 || bits_per_pixel == 24 || bits_per_pixel == 32; case TGA_TYPE_GRAYSCALE: case TGA_TYPE_RLE_GRAYSCALE: return bits_per_pixel == 8 || bits_per_pixel == 16; default: return FALSE; } } static inline void tga_read_pixel (TGAContext *ctx, const guchar *data, TGAColor *color) { switch (ctx->hdr->type) { case TGA_TYPE_PSEUDOCOLOR: case TGA_TYPE_RLE_PSEUDOCOLOR: *color = *colormap_get_color (ctx->cmap, data[0]); break; case TGA_TYPE_TRUECOLOR: case TGA_TYPE_RLE_TRUECOLOR: if (ctx->hdr->bpp == 16) { guint16 col = data[0] + (data[1] << 8); color->r = (col >> 7) & 0xf8; color->r |= color->r >> 5; color->g = (col >> 2) & 0xf8; color->g |= color->g >> 5; color->b = col << 3; color->b |= color->b >> 5; color->a = 255; } else { color->b = data[0]; color->g = data[1]; color->r = data[2]; if (ctx->hdr->bpp == 32) color->a = data[3]; else color->a = 255; } break; case TGA_TYPE_GRAYSCALE: case TGA_TYPE_RLE_GRAYSCALE: color->r = color->g = color->b = data[0]; if (ctx->hdr->bpp == 16) color->a = data[1]; else color->a = 255; break; default: g_assert_not_reached (); } } static gboolean fill_in_context(TGAContext *ctx, GError **err) { gboolean alpha; guint w, h; g_return_val_if_fail(ctx != NULL, FALSE); ctx->cmap_size = ((ctx->hdr->cmap_bpp + 7) >> 3) * LE16(ctx->hdr->cmap_n_colors); ctx->cmap = colormap_new (LE16(ctx->hdr->cmap_n_colors)); if (!ctx->cmap) { g_set_error_literal(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Cannot allocate colormap")); return FALSE; } 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 = gdk_pixbuf_new (GDK_COLORSPACE_RGB, alpha, 8, w, h); if (!ctx->pbuf) { g_set_error_literal(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Cannot allocate new pixbuf")); return FALSE; } return TRUE; } static gboolean tga_load_image (TGAContext *ctx, GError **err) { TGAColor color; GBytes *bytes; gsize i, size, bytes_per_pixel; const guchar *data; bytes_per_pixel = (ctx->hdr->bpp + 7) / 8; size = gdk_pixbuf_buffer_queue_get_size (ctx->input) / bytes_per_pixel; size = MIN (size, tga_pixels_remaining (ctx)); bytes = gdk_pixbuf_buffer_queue_pull (ctx->input, size * bytes_per_pixel); g_assert (bytes != NULL); data = g_bytes_get_data (bytes, NULL); for (i = 0; i < size; i++) { tga_read_pixel (ctx, data, &color); tga_write_pixel (ctx, &color); data += bytes_per_pixel; } g_bytes_unref (bytes); tga_emit_update (ctx); if (tga_all_pixels_written (ctx)) ctx->process = tga_skip_rest_of_image; return TRUE; } static gboolean tga_load_rle_image (TGAContext *ctx, GError **err) { GBytes *bytes; TGAColor color; guint rle_num, raw_num; const guchar *s; guchar tag; gsize n, size, bytes_per_pixel; bytes_per_pixel = (ctx->hdr->bpp + 7) / 8; bytes = gdk_pixbuf_buffer_queue_peek (ctx->input, gdk_pixbuf_buffer_queue_get_size (ctx->input)); s = g_bytes_get_data (bytes, &size); for (n = 0; n < size; ) { tag = *s; s++, n++; if (tag & 0x80) { if (n + bytes_per_pixel > size) { --n; break; } else { rle_num = (tag & 0x7f) + 1; tga_read_pixel (ctx, s, &color); s += bytes_per_pixel; n += bytes_per_pixel; rle_num = MIN (rle_num, tga_pixels_remaining (ctx)); for (; rle_num; rle_num--) { tga_write_pixel (ctx, &color); } if (tga_all_pixels_written (ctx)) break; } } else { raw_num = tag + 1; if (n + (raw_num * bytes_per_pixel) > size) { --n; break; } else { raw_num = MIN (raw_num, tga_pixels_remaining (ctx)); for (; raw_num; raw_num--) { tga_read_pixel (ctx, s, &color); s += bytes_per_pixel; n += bytes_per_pixel; tga_write_pixel (ctx, &color); } if (tga_all_pixels_written (ctx)) break; } } } g_bytes_unref (bytes); gdk_pixbuf_buffer_queue_flush (ctx->input, n); tga_emit_update (ctx); if (tga_all_pixels_written (ctx)) ctx->process = tga_skip_rest_of_image; return TRUE; } static gboolean tga_load_colormap (TGAContext *ctx, GError **err) { GBytes *bytes; TGAColor color; const guchar *p; guint i, n_colors; if (ctx->hdr->has_cmap) { bytes = gdk_pixbuf_buffer_queue_pull (ctx->input, ctx->cmap_size); if (bytes == NULL) return TRUE; n_colors = LE16(ctx->hdr->cmap_n_colors); p = g_bytes_get_data (bytes, NULL); color.a = 255; for (i = 0; i < n_colors; i++) { if ((ctx->hdr->cmap_bpp == 15) || (ctx->hdr->cmap_bpp == 16)) { guint16 col = p[0] + (p[1] << 8); color.b = (col >> 7) & 0xf8; color.g = (col >> 2) & 0xf8; color.r = col << 3; p += 2; } else if ((ctx->hdr->cmap_bpp == 24) || (ctx->hdr->cmap_bpp == 32)) { color.b = *p++; color.g = *p++; color.r = *p++; if (ctx->hdr->cmap_bpp == 32) color.a = *p++; } else { g_set_error_literal (err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Unexpected bitdepth for colormap entries")); g_bytes_unref (bytes); return FALSE; } colormap_set_color (ctx->cmap, i, &color); } g_bytes_unref (bytes); } else { if ((ctx->hdr->type == TGA_TYPE_PSEUDOCOLOR) || (ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR)) { g_set_error_literal (err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Pseudocolor image does not contain a colormap")); return FALSE; } } if ((ctx->hdr->type == TGA_TYPE_RLE_PSEUDOCOLOR) || (ctx->hdr->type == TGA_TYPE_RLE_TRUECOLOR) || (ctx->hdr->type == TGA_TYPE_RLE_GRAYSCALE)) ctx->process = tga_load_rle_image; else ctx->process = tga_load_image; return TRUE; } static gboolean tga_read_info (TGAContext *ctx, GError **err) { if (gdk_pixbuf_buffer_queue_get_size (ctx->input) < ctx->hdr->infolen) return TRUE; gdk_pixbuf_buffer_queue_flush (ctx->input, ctx->hdr->infolen); ctx->process = tga_load_colormap; return TRUE; } static gboolean tga_load_header (TGAContext *ctx, GError **err) { GBytes *bytes; bytes = gdk_pixbuf_buffer_queue_pull (ctx->input, sizeof (TGAHeader)); if (bytes == NULL) return TRUE; ctx->hdr = g_try_malloc (sizeof (TGAHeader)); if (!ctx->hdr) { g_set_error_literal (err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Cannot allocate TGA header memory")); return FALSE; } g_memmove(ctx->hdr, g_bytes_get_data (bytes, NULL), sizeof(TGAHeader)); g_bytes_unref (bytes); #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 (LE16(ctx->hdr->width) == 0 || LE16(ctx->hdr->height) == 0) { g_set_error_literal(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_literal(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("TGA image type not supported")); return FALSE; } if (!tga_format_supported (ctx->hdr->type, ctx->hdr->bpp)) { g_set_error_literal(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("TGA image type not supported")); return FALSE; } if (!fill_in_context(ctx, err)) return FALSE; if (ctx->pfunc) (*ctx->pfunc) (ctx->pbuf, NULL, ctx->udata); ctx->process = tga_read_info; 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_literal(err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Cannot allocate memory for TGA context struct")); return NULL; } ctx->hdr = NULL; ctx->cmap = NULL; ctx->cmap_size = 0; ctx->pbuf = NULL; ctx->pbuf_x = 0; ctx->pbuf_y = 0; ctx->pbuf_y_notified = 0; ctx->input = gdk_pixbuf_buffer_queue_new (); ctx->process = tga_load_header; 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; TGAProcessFunc process; g_return_val_if_fail(buffer != NULL, TRUE); gdk_pixbuf_buffer_queue_push (ctx->input, g_bytes_new (buffer, size)); do { process = ctx->process; if (!process (ctx, err)) return FALSE; } while (process != ctx->process); return TRUE; } static gboolean gdk_pixbuf__tga_stop_load(gpointer data, GError **err) { TGAContext *ctx = (TGAContext *) data; gboolean result = TRUE; g_return_val_if_fail (ctx != NULL, FALSE); if (ctx->pbuf == NULL || tga_pixels_remaining (ctx)) { g_set_error_literal (err, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("TGA image was truncated or incomplete.")); result = FALSE; } g_free (ctx->hdr); if (ctx->cmap) colormap_free (ctx->cmap); if (ctx->pbuf) g_object_unref (ctx->pbuf); gdk_pixbuf_buffer_queue_unref (ctx->input); g_free (ctx); return result; } #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 const 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 const gchar *mime_types[] = { "image/x-tga", NULL }; static const gchar *extensions[] = { "tga", "targa", NULL }; info->name = "tga"; info->signature = (GdkPixbufModulePattern *) signature; info->description = NC_("image format", "Targa"); info->mime_types = (gchar **) mime_types; info->extensions = (gchar **) extensions; info->flags = GDK_PIXBUF_FORMAT_THREADSAFE; info->license = "LGPL"; }