/* -*- Mode: C; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ /* * GImageView * Copyright (C) 2001 Takuro Ashie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id: tga.c,v 1.9 2004/09/21 08:44:27 makeinu Exp $ */ /* * These codes are mostly taken from Gimp. * Gimp Copyright (C) 1995 Spencer Kimball and Peter Mattis * Targa plugin code Copyright (C) 1997 Raphael FRANCOIS and Gordon Matzigkeit */ /* * TGA File Format Specification, Version 2.0: * */ #include #include #include #include #include #include "gimv_image.h" #include "intl.h" #include "tga.h" #include "gimv_plugin.h" static gint rle_read (GimvImageLoader *loader, guchar *buffer, tga_info *info); static void flip_line (guchar *buffer, tga_info *info); static void upsample (guchar *dest, guchar *src, guint width, guint bytes); static void bgr2rgb (guchar *dest, guchar *src, guint width, guint bytes, guint alpha); static void read_line (GimvImageLoader *loader, guchar *row, guchar *buffer, tga_info *info, guint bytes); static GimvImage *tga_read_image (GimvImageLoader *loader, tga_info *info); static GimvImageLoaderPlugin gimv_tga_loader[] = { { if_version: GIMV_IMAGE_LOADER_IF_VERSION, id: "TGA", priority_hint: GIMV_IMAGE_LOADER_PRIORITY_CAN_CANCEL, check_type: NULL, get_info: NULL, loader: tga_load, } }; static const gchar *tga_extensions[] = { "tga", }; static GimvMimeTypeEntry tga_mime_types[] = { { mime_type: "image/x-tga", description: "Targa Image", extensions: tga_extensions, extensions_len: sizeof (tga_extensions) / sizeof (gchar *), icon: NULL, }, { mime_type: "image/x-targa", description: "Targa Image", extensions: tga_extensions, extensions_len: sizeof (tga_extensions) / sizeof (gchar *), icon: NULL, }, }; GIMV_PLUGIN_GET_IMPL(gimv_tga_loader, GIMV_PLUGIN_IMAGE_LOADER) GIMV_PLUGIN_GET_MIME_TYPE(tga_mime_types) GimvPluginInfo gimv_plugin_info = { if_version: GIMV_PLUGIN_IF_VERSION, name: N_("TGA Image Loader"), version: "0.1.1", author: N_("Takuro Ashie"), description: NULL, get_implement: gimv_plugin_get_impl, get_mime_type: gimv_plugin_get_mime_type, get_prefs_ui: NULL, }; /* TRUEVISION-XFILE magic signature string */ static guchar magic[18] = { 0x54, 0x52, 0x55, 0x45, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x2d, 0x58, 0x46, 0x49, 0x4c, 0x45, 0x2e, 0x0 }; GimvImage * tga_load (GimvImageLoader *loader, gpointer data) { GimvIO *gio; tga_info info; guchar header[18]; guchar footer[26]; guchar extension[495]; GimvImage *image; glong offset; GimvIOStatus status; guint bytes_read; g_return_val_if_fail (loader, NULL); gio = gimv_image_loader_get_gio (loader); if (!gio) return NULL; status = gimv_io_seek (gio, -26L, SEEK_END); if (status == GIMV_IO_STATUS_NORMAL) { /* Is file big enough for a footer? */ status = gimv_io_read (gio, footer, sizeof (footer), &bytes_read); if (status != GIMV_IO_STATUS_NORMAL) { return NULL; } else if (memcmp (footer + 8, magic, sizeof (magic)) == 0) { /* Check the signature. */ offset= footer[0] + (footer[1] * 256) + (footer[2] * 65536) + (footer[3] * 16777216); status = gimv_io_seek (gio, offset, SEEK_SET); if (status != GIMV_IO_STATUS_NORMAL) return NULL; status = gimv_io_read (gio, extension, sizeof (extension), &bytes_read); if (status != GIMV_IO_STATUS_NORMAL) return NULL; /* Eventually actually handle version 2 TGA here */ } } status = gimv_io_seek (gio, 0, SEEK_SET); if (status != GIMV_IO_STATUS_NORMAL) return NULL; status = gimv_io_read (gio, header, sizeof (header), &bytes_read); if (status != GIMV_IO_STATUS_NORMAL) return NULL; switch (header[2]) { case 1: info.imageType = TGA_TYPE_MAPPED; info.imageCompression = TGA_COMP_NONE; break; case 2: info.imageType = TGA_TYPE_COLOR; info.imageCompression = TGA_COMP_NONE; break; case 3: info.imageType = TGA_TYPE_GRAY; info.imageCompression = TGA_COMP_NONE; break; case 9: info.imageType = TGA_TYPE_MAPPED; info.imageCompression = TGA_COMP_RLE; break; case 10: info.imageType = TGA_TYPE_COLOR; info.imageCompression = TGA_COMP_RLE; break; case 11: info.imageType = TGA_TYPE_GRAY; info.imageCompression = TGA_COMP_RLE; break; default: info.imageType = 0; } info.idLength = header[0]; info.colorMapType = header[1]; info.colorMapIndex = header[3] + header[4] * 256; info.colorMapLength = header[5] + header[6] * 256; info.colorMapSize = header[7]; info.xOrigin = header[8] + header[9] * 256; info.yOrigin = header[10] + header[11] * 256; info.width = header[12] + header[13] * 256; info.height = header[14] + header[15] * 256; info.bpp = header[16]; info.bytes = (info.bpp + 7) / 8; info.alphaBits = header[17] & 0x0f; /* Just the low 4 bits */ info.flipHoriz = (header[17] & 0x10) ? 1 : 0; info.flipVert = (header[17] & 0x20) ? 0 : 1; switch (info.imageType) { case TGA_TYPE_MAPPED: if (info.bpp != 8) return NULL; break; case TGA_TYPE_COLOR: if (info.alphaBits == 0 && info.bpp == 32) { info.alphaBits = 8; } if (info.bpp != 16 && info.bpp != 24 && (info.alphaBits != 8 || info.bpp != 32)) { return NULL; } break; case TGA_TYPE_GRAY: if (info.alphaBits == 0 && info.bpp == 16) { info.alphaBits = 8; } if (info.bpp != 8 && (info.alphaBits != 8 || info.bpp != 16)) { return NULL; } break; default: return NULL; } /* Plausible but unhandled formats */ if (info.bytes * 8 != info.bpp) return NULL; /* Check that we have a color map only when we need it. */ if (info.imageType == TGA_TYPE_MAPPED && info.colorMapType != 1) { return NULL; } else if (info.imageType != TGA_TYPE_MAPPED && info.colorMapType != 0) { return NULL; } /* Skip the image ID field. */ if (info.idLength) { status = gimv_io_seek (gio, info.idLength, SEEK_CUR); if (status != GIMV_IO_STATUS_NORMAL) return NULL; } image = tga_read_image (loader, &info); return image; } static gint rle_read (GimvImageLoader *loader, guchar *buffer, tga_info *info) { GimvIO *gio; static gint repeat = 0; static gint direct = 0; static guchar sample[4]; gint head; gint x, k; guint bytes_read; GimvIOStatus status; gio = gimv_image_loader_get_gio (loader); g_return_val_if_fail (gio, EOF); for (x = 0; x < info->width; x++) { if (repeat == 0 && direct == 0) { head = gimv_io_getc (gio, &status); if (head == EOF) { return EOF; } else if (head >= 128) { repeat = head - 127; status = gimv_io_read (gio, sample, info->bytes, &bytes_read); if (bytes_read < 1) return EOF; } else { direct = head + 1; } } if (repeat > 0) { for (k = 0; k < info->bytes; ++k) { buffer[k] = sample[k]; } repeat--; } else { /* direct > 0 */ status = gimv_io_read (gio, buffer, info->bytes, &bytes_read); if (bytes_read < 1) return EOF; direct--; } buffer += info->bytes; } return 0; } static void flip_line (guchar *buffer, tga_info *info) { guchar temp; guchar *alt; gint x, s; alt = buffer + (info->bytes * (info->width - 1)); for (x = 0; x * 2 <= info->width; x++) { for (s = 0; s < info->bytes; ++s) { temp = buffer[s]; buffer[s] = alt[s]; alt[s] = temp; } buffer += info->bytes; alt -= info->bytes; } } /* Some people write 16-bit RGB TGA files. The spec would probably allow 27-bit RGB too, for what it's worth, but I won't fix that unless someone actually provides an existence proof */ static void upsample (guchar *dest, guchar *src, guint width, guint bytes) { guint x; for (x = 0; x < width; x++) { dest[0] = ((src[1] << 1) & 0xf8); dest[0] += (dest[0] >> 5); dest[1] = ((src[0] & 0xe0) >> 2) + ((src[1] & 0x03) << 6); dest[1] += (dest[1] >> 5); dest[2] = ((src[0] << 3) & 0xf8); dest[2] += (dest[2] >> 5); dest += 3; src += bytes; } } static void bgr2rgb (guchar *dest, guchar *src, guint width, guint bytes, guint alpha) { guint x; if (alpha) { for (x = 0; x < width; x++) { *(dest++) = src[2]; *(dest++) = src[1]; *(dest++) = src[0]; *(dest++) = src[3]; src += bytes; } } else { for (x = 0; x < width; x++) { *(dest++) = src[2]; *(dest++) = src[1]; *(dest++) = src[0]; src += bytes; } } } static void read_line (GimvImageLoader *loader, guchar *row, guchar *buffer, tga_info *info, guint bytes) { GimvIO *gio; GimvIOStatus status; guint bytes_read; gio = gimv_image_loader_get_gio (loader); g_return_if_fail (gio); if (info->imageCompression == TGA_COMP_RLE) { rle_read (loader, buffer, info); } else { status = gimv_io_read (gio, buffer, info->bytes * info->width, &bytes_read); } if (info->flipHoriz) { flip_line (buffer, info); } if (info->imageType == TGA_TYPE_COLOR) { if (info->bpp == 16) { upsample (row, buffer, info->width, info->bytes); } else { bgr2rgb (row, buffer, info->width, info->bytes, info->alphaBits); } } else { memcpy (row, buffer, info->width * bytes); } } static GimvImage * tga_read_image (GimvImageLoader *loader, tga_info *info) { GimvIO *gio; GimvImage *image; guchar *data, *buffer, *row; gint i, y; gboolean alpha = FALSE; gint max_tileheight, tileheight; gint bytes; guint cmap_bytes; guchar tga_cmap[4 * 256], cmap[3 * 256]; GimvIOStatus status; guint bytes_read; gint bytes_count = 0, bytes_count_tmp = 0, step = 65536; glong pos; gio = gimv_image_loader_get_gio (loader); g_return_val_if_fail (gio, NULL); /* Handle colormap */ if (info->colorMapType == 1) { cmap_bytes= (info->colorMapSize + 7 ) / 8; if (cmap_bytes > 4) return NULL; status = gimv_io_read (gio, tga_cmap, info->colorMapLength * cmap_bytes, &bytes_read); if (status == GIMV_IO_STATUS_NORMAL) { if (info->colorMapSize == 32) bgr2rgb(cmap, tga_cmap, info->colorMapLength, cmap_bytes, 1); else if (info->colorMapSize == 24) bgr2rgb(cmap, tga_cmap, info->colorMapLength, cmap_bytes, 0); else if (info->colorMapSize == 16) upsample(cmap, tga_cmap, info->colorMapLength, cmap_bytes); } else { return NULL; } } if (!gimv_image_loader_progress_update (loader)) return NULL; /* Allocate the data. */ /* * FIXME!! When this loader is implement as plugin, * it shuold be supported progressive loading correctly. */ max_tileheight = info->height; if (gimv_image_can_alpha (NULL) && info->bytes == 4) { bytes = 4; alpha = TRUE; } else { bytes = 3; } data = (guchar *) g_malloc (info->width * max_tileheight * bytes); buffer = (guchar *) g_malloc (info->width * info->bytes); if (info->flipVert) { for (i = 0; i < info->height; i += tileheight) { tileheight = i ? max_tileheight : (info->height % max_tileheight); if (tileheight == 0) tileheight = max_tileheight; for (y = 1; y <= tileheight; ++y) { row = data + (info->width * bytes * (tileheight - y)); read_line(loader, row, buffer, info, bytes); gimv_io_tell (gio, &pos); bytes_count_tmp = pos / step; if (bytes_count_tmp > bytes_count) { bytes_count = bytes_count_tmp; if (!gimv_image_loader_progress_update (loader)) goto ERROR; } } } } else { for (i = 0; i < info->height; i += max_tileheight) { tileheight = MIN (max_tileheight, info->height - i); for (y = 0; y < tileheight; ++y) { row = data + (info->width * bytes * y); read_line(loader, row, buffer, info, bytes); gimv_io_tell (gio, &pos); bytes_count_tmp = pos / step; if (bytes_count_tmp > bytes_count) { bytes_count = bytes_count_tmp; if (!gimv_image_loader_progress_update (loader)) goto ERROR; } } } } g_free (buffer); image = gimv_image_create_from_data (data, info->width, info->height, alpha); return image; ERROR: g_free (buffer); g_free (data); return NULL; }