/* --------------------------------------------------------------------------- * Truevision Targa Reader/Writer * Copyright (C) 2001-2003, Emil Mikulic. * * Source and binary redistribution of this code, with or without changes, for * free or for profit, is allowed as long as this copyright notice is kept * intact. Modified versions must be clearly marked as modified. * * This code is provided without any warranty. The copyright holder is * not liable for anything bad that might happen as a result of the * code. * -------------------------------------------------------------------------*/ /* * preso da http://dmr.ath.cx/gfx/targa/ * modificato da gmt * */ /*@unused@*/ static const char rcsid[] = "$Id: targa.c,v 1.7 2003/06/21 09:30:53 emikulic Exp $"; #define TGA_KEEP_MACROS /* BIT, htole16, letoh16 */ #include "targa.h" #include #include /* memcpy, memcmp */ #define SANE_DEPTH(x) ((x) == 8 || (x) == 16 || (x) == 24 || (x) == 32) #define UNMAP_DEPTH(x) ((x) == 16 || (x) == 24 || (x) == 32) static const char tga_id[] = "\0\0\0\0" /* extension area offset */ "\0\0\0\0" /* developer directory offset */ "TRUEVISION-XFILE."; static const size_t tga_id_length = 26; /* tga_id + \0 */ /* helpers */ static tga_result tga_read_rle(tga_image *dest, FILE *fp); static tga_result tga_write_row_RLE(FILE *fp, const tga_image *src, const uint8_t *row); typedef enum { RAW, RLE } packet_type; static packet_type rle_packet_type(const uint8_t *row, const uint16_t pos, const uint16_t width, const uint16_t bpp); static uint8_t rle_packet_len(const uint8_t *row, const uint16_t pos, const uint16_t width, const uint16_t bpp, const packet_type type); uint8_t tga_get_attribute_bits(const tga_image *tga) { return tga->image_descriptor & TGA_ATTRIB_BITS; } int tga_is_right_to_left(const tga_image *tga) { return (tga->image_descriptor & TGA_R_TO_L_BIT) != 0; } int tga_is_top_to_bottom(const tga_image *tga) { return (tga->image_descriptor & TGA_T_TO_B_BIT) != 0; } int tga_is_colormapped(const tga_image *tga) { return ( tga->image_type == TGA_IMAGE_TYPE_COLORMAP || tga->image_type == TGA_IMAGE_TYPE_COLORMAP_RLE); } int tga_is_rle(const tga_image *tga) { return ( tga->image_type == TGA_IMAGE_TYPE_COLORMAP_RLE || tga->image_type == TGA_IMAGE_TYPE_BGR_RLE || tga->image_type == TGA_IMAGE_TYPE_MONO_RLE); } int tga_is_mono(const tga_image *tga) { return ( tga->image_type == TGA_IMAGE_TYPE_MONO || tga->image_type == TGA_IMAGE_TYPE_MONO_RLE); } /* --------------------------------------------------------------------------- * Convert the numerical into a verbose error string. * * Returns: an error string */ const char *tga_error(const tga_result errcode) { switch (errcode) { case TGA_NOERR: return "no error"; case TGAERR_FOPEN: return "error opening file"; case TGAERR_EOF: return "premature end of file"; case TGAERR_WRITE: return "error writing to file"; case TGAERR_CMAP_TYPE: return "invalid color map type"; case TGAERR_IMG_TYPE: return "invalid image type"; case TGAERR_NO_IMG: return "no image data included"; case TGAERR_CMAP_MISSING: return "color-mapped image without color map"; case TGAERR_CMAP_PRESENT: return "non-color-mapped image with extraneous color map"; case TGAERR_CMAP_LENGTH: return "color map has zero length"; case TGAERR_CMAP_DEPTH: return "invalid color map depth"; case TGAERR_ZERO_SIZE: return "the image dimensions are zero"; case TGAERR_PIXEL_DEPTH: return "invalid pixel depth"; case TGAERR_NO_MEM: return "out of memory"; case TGAERR_NOT_CMAP: return "image is not color mapped"; case TGAERR_RLE: return "RLE data is corrupt"; case TGAERR_INDEX_RANGE: return "color map index out of range"; case TGAERR_MONO: return "image is mono"; default: return "unknown error code"; } } /* --------------------------------------------------------------------------- * Read a Targa image from a file named to . This is just a * wrapper around tga_read_from_FILE(). * * Returns: TGA_NOERR on success, or a matching TGAERR_* code on failure. */ tga_result tga_read(tga_image *dest, const char *filename) { tga_result result; FILE *fp = fopen(filename, "rb"); if (fp == NULL) return TGAERR_FOPEN; result = tga_read_from_FILE(dest, fp); fclose(fp); return result; } /* --------------------------------------------------------------------------- * Read a Targa image from to . * * Returns: TGA_NOERR on success, or a TGAERR_* code on failure. In the * case of failure, the contents of dest are not guaranteed to be * valid. */ tga_result tga_read_from_FILE(tga_image *dest, FILE *fp) { #define BARF(errcode) \ { \ tga_free_buffers(dest); \ return errcode; \ } #define READ(destptr, size) \ if (fread(destptr, size, 1, fp) != 1) \ BARF(TGAERR_EOF) #define READ16(dest) \ { \ if (fread(&(dest), 2, 1, fp) != 1) \ BARF(TGAERR_EOF); \ dest = letoh16(dest); \ } dest->image_id = NULL; dest->color_map_data = NULL; dest->image_data = NULL; READ(&dest->image_id_length, 1); READ(&dest->color_map_type, 1); if (dest->color_map_type != TGA_COLOR_MAP_ABSENT && dest->color_map_type != TGA_COLOR_MAP_PRESENT) BARF(TGAERR_CMAP_TYPE); READ(&dest->image_type, 1); if (dest->image_type == TGA_IMAGE_TYPE_NONE) BARF(TGAERR_NO_IMG); if (dest->image_type != TGA_IMAGE_TYPE_COLORMAP && dest->image_type != TGA_IMAGE_TYPE_BGR && dest->image_type != TGA_IMAGE_TYPE_MONO && dest->image_type != TGA_IMAGE_TYPE_COLORMAP_RLE && dest->image_type != TGA_IMAGE_TYPE_BGR_RLE && dest->image_type != TGA_IMAGE_TYPE_MONO_RLE) BARF(TGAERR_IMG_TYPE); if (tga_is_colormapped(dest) && dest->color_map_type == TGA_COLOR_MAP_ABSENT) BARF(TGAERR_CMAP_MISSING); if (!tga_is_colormapped(dest) && dest->color_map_type == TGA_COLOR_MAP_PRESENT) BARF(TGAERR_CMAP_PRESENT); READ16(dest->color_map_origin); READ16(dest->color_map_length); READ(&dest->color_map_depth, 1); if (dest->color_map_type == TGA_COLOR_MAP_PRESENT) { if (dest->color_map_length == 0) BARF(TGAERR_CMAP_LENGTH); if (!UNMAP_DEPTH(dest->color_map_depth)) BARF(TGAERR_CMAP_DEPTH); } READ16(dest->origin_x); READ16(dest->origin_y); READ16(dest->width); READ16(dest->height); if (dest->width == 0 || dest->height == 0) BARF(TGAERR_ZERO_SIZE); READ(&dest->pixel_depth, 1); if (!SANE_DEPTH(dest->pixel_depth) || (dest->pixel_depth != 8 && tga_is_colormapped(dest))) BARF(TGAERR_PIXEL_DEPTH); READ(&dest->image_descriptor, 1); if (dest->image_id_length > 0) { dest->image_id = (uint8_t *)malloc(dest->image_id_length); if (dest->image_id == NULL) BARF(TGAERR_NO_MEM); READ(dest->image_id, dest->image_id_length); } if (dest->color_map_type == TGA_COLOR_MAP_PRESENT) { dest->color_map_data = (uint8_t *)malloc( (dest->color_map_origin + dest->color_map_length) * dest->color_map_depth / 8); if (dest->color_map_data == NULL) BARF(TGAERR_NO_MEM); READ(dest->color_map_data + (dest->color_map_origin * dest->color_map_depth / 8), dest->color_map_length * dest->color_map_depth / 8); } dest->image_data = (uint8_t *)malloc( dest->width * dest->height * dest->pixel_depth / 8); if (dest->image_data == NULL) BARF(TGAERR_NO_MEM); if (tga_is_rle(dest)) { /* read RLE */ tga_result result = tga_read_rle(dest, fp); if (result != TGA_NOERR) BARF(result); } else { /* uncompressed */ READ(dest->image_data, dest->width * dest->height * dest->pixel_depth / 8); } return TGA_NOERR; #undef BARF #undef READ #undef READ16 } /* --------------------------------------------------------------------------- * Helper function for tga_read_from_FILE(). Decompresses RLE image data from * . Assumes header fields are set correctly. */ static tga_result tga_read_rle(tga_image *dest, FILE *fp) { #define RLE_BIT BIT(7) #define READ(dest, size) \ if (fread(dest, size, 1, fp) != 1) \ return TGAERR_EOF uint8_t *pos; uint32_t p_loaded = 0, p_expected = dest->width * dest->height; uint8_t bpp = dest->pixel_depth / 8; /* bytes per pixel */ pos = dest->image_data; while ((p_loaded < p_expected) && !feof(fp)) { uint8_t b; READ(&b, 1); if (b & RLE_BIT) { /* is an RLE packet */ uint8_t count, tmp[4], i; count = (b & ~RLE_BIT) + 1; READ(tmp, bpp); for (i = 0; i < count; i++) { p_loaded++; if (p_loaded > p_expected) return TGAERR_RLE; memcpy(pos, tmp, bpp); pos += bpp; } } else /* RAW packet */ { uint8_t count; count = (b & ~RLE_BIT) + 1; if (p_loaded + count > p_expected) return TGAERR_RLE; p_loaded += count; READ(pos, bpp * count); pos += count * bpp; } } return TGA_NOERR; #undef RLE_BIT #undef READ } /* --------------------------------------------------------------------------- * Write a Targa image to a file named from . This is just a * wrapper around tga_write_to_FILE(). * * Returns: TGA_NOERR on success, or a matching TGAERR_* code on failure. */ tga_result tga_write(const char *filename, const tga_image *src) { tga_result result; FILE *fp = fopen(filename, "wb"); if (fp == NULL) return TGAERR_FOPEN; result = tga_write_to_FILE(fp, src); fclose(fp); return result; } /* --------------------------------------------------------------------------- * Write one row of an image to using RLE. This is a helper function * called from tga_write_to_FILE(). It assumes that has its header * fields set up correctly. */ #define PIXEL(ofs) (row + (ofs)*bpp) static tga_result tga_write_row_RLE(FILE *fp, const tga_image *src, const uint8_t *row) { #define WRITE(src, size) \ if (fwrite(src, size, 1, fp) != 1) \ return TGAERR_WRITE uint16_t pos = 0; uint16_t bpp = src->pixel_depth / 8; while (pos < src->width) { packet_type type = rle_packet_type(row, pos, src->width, bpp); uint8_t len = rle_packet_len(row, pos, src->width, bpp, type); uint8_t packet_header; packet_header = len - 1; if (type == RLE) packet_header |= BIT(7); WRITE(&packet_header, 1); if (type == RLE) { WRITE(PIXEL(pos), bpp); } else /* type == RAW */ { WRITE(PIXEL(pos), bpp * len); } pos += len; } return TGA_NOERR; #undef WRITE } /* --------------------------------------------------------------------------- * Determine whether the next packet should be RAW or RLE for maximum * efficiency. This is a helper function called from rle_packet_len() and * tga_write_row_RLE(). */ #define SAME(ofs1, ofs2) (memcmp(PIXEL(ofs1), PIXEL(ofs2), bpp) == 0) static packet_type rle_packet_type(const uint8_t *row, const uint16_t pos, const uint16_t width, const uint16_t bpp) { if (pos == width - 1) return RAW; /* one pixel */ if (SAME(pos, pos + 1)) /* dupe pixel */ { if (bpp > 1) return RLE; /* inefficient for bpp=1 */ /* three repeats makes the bpp=1 case efficient enough */ if ((pos < width - 2) && SAME(pos + 1, pos + 2)) return RLE; } return RAW; } /* --------------------------------------------------------------------------- * Find the length of the current RLE packet. This is a helper function * called from tga_write_row_RLE(). */ static uint8_t rle_packet_len(const uint8_t *row, const uint16_t pos, const uint16_t width, const uint16_t bpp, const packet_type type) { uint8_t len = 2; if (pos == width - 1) return 1; if (pos == width - 2) return 2; if (type == RLE) { while (pos + len < width) { if (SAME(pos, pos + len)) len++; else return len; if (len == 128) return 128; } } else /* type == RAW */ { while (pos + len < width) { if (rle_packet_type(row, pos + len, width, bpp) == RAW) len++; else return len; if (len == 128) return 128; } } return len; /* hit end of row (width) */ } #undef SAME #undef PIXEL /* --------------------------------------------------------------------------- * Writes a Targa image to from . * * Returns: TGA_NOERR on success, or a TGAERR_* code on failure. * On failure, the contents of the file are not guaranteed * to be valid. */ tga_result tga_write_to_FILE(FILE *fp, const tga_image *src) { #define WRITE(srcptr, size) \ if (fwrite(srcptr, size, 1, fp) != 1) \ return TGAERR_WRITE #define WRITE16(src) \ { \ uint16_t _temp = htole16(src); \ if (fwrite(&_temp, 2, 1, fp) != 1) \ return TGAERR_WRITE; \ } WRITE(&src->image_id_length, 1); if (src->color_map_type != TGA_COLOR_MAP_ABSENT && src->color_map_type != TGA_COLOR_MAP_PRESENT) return TGAERR_CMAP_TYPE; WRITE(&src->color_map_type, 1); if (src->image_type == TGA_IMAGE_TYPE_NONE) return TGAERR_NO_IMG; if (src->image_type != TGA_IMAGE_TYPE_COLORMAP && src->image_type != TGA_IMAGE_TYPE_BGR && src->image_type != TGA_IMAGE_TYPE_MONO && src->image_type != TGA_IMAGE_TYPE_COLORMAP_RLE && src->image_type != TGA_IMAGE_TYPE_BGR_RLE && src->image_type != TGA_IMAGE_TYPE_MONO_RLE) return TGAERR_IMG_TYPE; WRITE(&src->image_type, 1); if (tga_is_colormapped(src) && src->color_map_type == TGA_COLOR_MAP_ABSENT) return TGAERR_CMAP_MISSING; if (!tga_is_colormapped(src) && src->color_map_type == TGA_COLOR_MAP_PRESENT) return TGAERR_CMAP_PRESENT; if (src->color_map_type == TGA_COLOR_MAP_PRESENT) { if (src->color_map_length == 0) return TGAERR_CMAP_LENGTH; if (!UNMAP_DEPTH(src->color_map_depth)) return TGAERR_CMAP_DEPTH; } WRITE16(src->color_map_origin); WRITE16(src->color_map_length); WRITE(&src->color_map_depth, 1); WRITE16(src->origin_x); WRITE16(src->origin_y); if (src->width == 0 || src->height == 0) return TGAERR_ZERO_SIZE; WRITE16(src->width); WRITE16(src->height); if (!SANE_DEPTH(src->pixel_depth) || (src->pixel_depth != 8 && tga_is_colormapped(src))) return TGAERR_PIXEL_DEPTH; WRITE(&src->pixel_depth, 1); WRITE(&src->image_descriptor, 1); if (src->image_id_length > 0) WRITE(&src->image_id, src->image_id_length); if (src->color_map_type == TGA_COLOR_MAP_PRESENT) WRITE(src->color_map_data + (src->color_map_origin * src->color_map_depth / 8), src->color_map_length * src->color_map_depth / 8); if (tga_is_rle(src)) { uint16_t row; for (row = 0; row < src->height; row++) { tga_result result = tga_write_row_RLE(fp, src, src->image_data + row * src->width * src->pixel_depth / 8); if (result != TGA_NOERR) return result; } } else { /* uncompressed */ WRITE(src->image_data, src->width * src->height * src->pixel_depth / 8); } WRITE(tga_id, tga_id_length); return TGA_NOERR; #undef WRITE #undef WRITE16 } /* Convenient writing functions --------------------------------------------*/ /* * This is just a helper function to initialise the header fields in a * tga_image struct. */ static void init_tga_image(tga_image *img, uint8_t *image, const uint16_t width, const uint16_t height, const uint8_t depth) { img->image_id_length = 0; img->color_map_type = TGA_COLOR_MAP_ABSENT; img->image_type = TGA_IMAGE_TYPE_NONE; /* override this below! */ img->color_map_origin = 0; img->color_map_length = 0; img->color_map_depth = 0; img->origin_x = 0; img->origin_y = 0; img->width = width; img->height = height; img->pixel_depth = depth; img->image_descriptor = TGA_T_TO_B_BIT; img->image_id = NULL; img->color_map_data = NULL; img->image_data = image; } tga_result tga_write_mono(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height) { tga_image img; init_tga_image(&img, image, width, height, 8); img.image_type = TGA_IMAGE_TYPE_MONO; return tga_write(filename, &img); } tga_result tga_write_mono_rle(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height) { tga_image img; init_tga_image(&img, image, width, height, 8); img.image_type = TGA_IMAGE_TYPE_MONO_RLE; return tga_write(filename, &img); } tga_result tga_write_bgr(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height, const uint8_t depth) { tga_image img; init_tga_image(&img, image, width, height, depth); img.image_type = TGA_IMAGE_TYPE_BGR; return tga_write(filename, &img); } tga_result tga_write_bgr_rle(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height, const uint8_t depth) { tga_image img; init_tga_image(&img, image, width, height, depth); img.image_type = TGA_IMAGE_TYPE_BGR_RLE; return tga_write(filename, &img); } /* Note: this function will MODIFY */ tga_result tga_write_rgb(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height, const uint8_t depth) { tga_image img; init_tga_image(&img, image, width, height, depth); img.image_type = TGA_IMAGE_TYPE_BGR; (void)tga_swap_red_blue(&img); return tga_write(filename, &img); } /* Note: this function will MODIFY */ tga_result tga_write_rgb_rle(const char *filename, uint8_t *image, const uint16_t width, const uint16_t height, const uint8_t depth) { tga_image img; init_tga_image(&img, image, width, height, depth); img.image_type = TGA_IMAGE_TYPE_BGR_RLE; (void)tga_swap_red_blue(&img); return tga_write(filename, &img); } /* Convenient manipulation functions ---------------------------------------*/ /* --------------------------------------------------------------------------- * Horizontally flip the image in place. Reverses the right-to-left bit in * the image descriptor. */ tga_result tga_flip_horiz(tga_image *img) { uint16_t row; size_t bpp; uint8_t *left, *right; int r_to_l; if (!SANE_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH; bpp = (size_t)(img->pixel_depth / 8); /* bytes per pixel */ for (row = 0; row < img->height; row++) { left = img->image_data + row * img->width * bpp; right = left + (img->width - 1) * bpp; /* reverse from left to right */ while (left < right) { uint8_t buffer[4]; /* swap */ memcpy(buffer, left, bpp); memcpy(left, right, bpp); memcpy(right, buffer, bpp); left += bpp; right -= bpp; } } /* Correct image_descriptor's left-to-right-ness. */ r_to_l = tga_is_right_to_left(img); img->image_descriptor &= ~TGA_R_TO_L_BIT; /* mask out r-to-l bit */ if (!r_to_l) /* was l-to-r, need to set r_to_l */ img->image_descriptor |= TGA_R_TO_L_BIT; /* else bit is already rubbed out */ return TGA_NOERR; } /* --------------------------------------------------------------------------- * Vertically flip the image in place. Reverses the top-to-bottom bit in * the image descriptor. */ tga_result tga_flip_vert(tga_image *img) { uint16_t col; size_t bpp, line; uint8_t *top, *bottom; int t_to_b; if (!SANE_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH; bpp = (size_t)(img->pixel_depth / 8); /* bytes per pixel */ line = bpp * img->width; /* bytes per line */ for (col = 0; col < img->width; col++) { top = img->image_data + col * bpp; bottom = top + (img->height - 1) * line; /* reverse from top to bottom */ while (top < bottom) { uint8_t buffer[4]; /* swap */ memcpy(buffer, top, bpp); memcpy(top, bottom, bpp); memcpy(bottom, buffer, bpp); top += line; bottom -= line; } } /* Correct image_descriptor's top-to-bottom-ness. */ t_to_b = tga_is_top_to_bottom(img); img->image_descriptor &= ~TGA_T_TO_B_BIT; /* mask out t-to-b bit */ if (!t_to_b) /* was b-to-t, need to set t_to_b */ img->image_descriptor |= TGA_T_TO_B_BIT; /* else bit is already rubbed out */ return TGA_NOERR; } /* --------------------------------------------------------------------------- * Convert a color-mapped image to unmapped BGR. Reallocates image_data to a * bigger size, then converts the image backwards to avoid using a secondary * buffer. Alters the necessary header fields and deallocates the color map. */ tga_result tga_color_unmap(tga_image *img) { uint8_t bpp = img->color_map_depth / 8; /* bytes per pixel */ int pos; void *tmp; if (!tga_is_colormapped(img)) return TGAERR_NOT_CMAP; if (img->pixel_depth != 8) return TGAERR_PIXEL_DEPTH; if (!SANE_DEPTH(img->color_map_depth)) return TGAERR_CMAP_DEPTH; tmp = realloc(img->image_data, img->width * img->height * bpp); if (tmp == NULL) return TGAERR_NO_MEM; img->image_data = (uint8_t *)tmp; for (pos = img->width * img->height - 1; pos >= 0; pos--) { uint8_t c_index = img->image_data[pos]; uint8_t *c_bgr = img->color_map_data + (c_index * bpp); if (c_index >= img->color_map_origin + img->color_map_length) return TGAERR_INDEX_RANGE; memcpy(img->image_data + (pos * bpp), c_bgr, (size_t)bpp); } /* clean up */ img->image_type = TGA_IMAGE_TYPE_BGR; img->pixel_depth = img->color_map_depth; free(img->color_map_data); img->color_map_data = NULL; img->color_map_type = TGA_COLOR_MAP_ABSENT; img->color_map_origin = 0; img->color_map_length = 0; img->color_map_depth = 0; return TGA_NOERR; } /* --------------------------------------------------------------------------- * Return a pointer to a given pixel. Accounts for image orientation (T_TO_B, * R_TO_L, etc). Returns NULL if the pixel is out of range. */ uint8_t *tga_find_pixel(const tga_image *img, uint16_t x, uint16_t y) { if (x >= img->width || y >= img->height) return NULL; if (!tga_is_top_to_bottom(img)) y = img->height - 1 - y; if (tga_is_right_to_left(img)) x = img->width - 1 - x; return img->image_data + (x + y * img->width) * img->pixel_depth / 8; } /* --------------------------------------------------------------------------- * Unpack the pixel at the src pointer according to bits. Any of b,g,r,a can * be set to NULL if not wanted. Returns TGAERR_PIXEL_DEPTH if a stupid * number of bits is given. */ tga_result tga_unpack_pixel(const uint8_t *src, const uint8_t bits, uint8_t *b, uint8_t *g, uint8_t *r, uint8_t *a) { switch (bits) { case 32: if (b) *b = src[0]; if (g) *g = src[1]; if (r) *r = src[2]; if (a) *a = src[3]; break; case 24: if (b) *b = src[0]; if (g) *g = src[1]; if (r) *r = src[2]; if (a) *a = 0; break; case 16: { uint16_t src16 = (uint16_t)(src[1] << 8) | (uint16_t)src[0]; #define FIVE_BITS (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4)) if (b) *b = ((src16)&FIVE_BITS) << 3; if (g) *g = ((src16 >> 5) & FIVE_BITS) << 3; if (r) *r = ((src16 >> 10) & FIVE_BITS) << 3; if (a) *a = (uint8_t)((src16 & BIT(15)) ? 255 : 0); #undef FIVE_BITS break; } case 8: if (b) *b = *src; if (g) *g = *src; if (r) *r = *src; if (a) *a = 0; break; default: return TGAERR_PIXEL_DEPTH; } return TGA_NOERR; } /* --------------------------------------------------------------------------- * Pack the pixel at the dest pointer according to bits. Returns * TGAERR_PIXEL_DEPTH if a stupid number of bits is given. */ tga_result tga_pack_pixel(uint8_t *dest, const uint8_t bits, const uint8_t b, const uint8_t g, const uint8_t r, const uint8_t a) { switch (bits) { case 32: dest[0] = b; dest[1] = g; dest[2] = r; dest[3] = a; break; case 24: dest[0] = b; dest[1] = g; dest[2] = r; break; case 16: { uint16_t tmp; #define FIVE_BITS (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4)) tmp = (b >> 3) & FIVE_BITS; tmp |= ((g >> 3) & FIVE_BITS) << 5; tmp |= ((r >> 3) & FIVE_BITS) << 10; if (a > 127) tmp |= BIT(15); #undef FIVE_BITS dest[0] = (uint8_t)(tmp & 0x00FF); dest[1] = (uint8_t)((tmp & 0xFF00) >> 8); break; } default: return TGAERR_PIXEL_DEPTH; } return TGA_NOERR; } /* --------------------------------------------------------------------------- * Desaturate the specified Targa using the specified coefficients: * output = ( red * cr + green * cg + blue * cb ) / dv */ tga_result tga_desaturate(tga_image *img, const int cr, const int cg, const int cb, const int dv) { uint8_t bpp = img->pixel_depth / 8; /* bytes per pixel */ uint8_t *dest, *src, *tmp; if (tga_is_mono(img)) return TGAERR_MONO; if (tga_is_colormapped(img)) { tga_result result = tga_color_unmap(img); if (result != TGA_NOERR) return result; } if (!UNMAP_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH; dest = img->image_data; for (src = img->image_data; src < img->image_data + img->width * img->height * bpp; src += bpp) { uint8_t b, g, r; (void)tga_unpack_pixel(src, img->pixel_depth, &b, &g, &r, NULL); *dest = (uint8_t)(((int)b * cb + (int)g * cg + (int)r * cr) / dv); dest++; } /* shrink */ tmp = realloc(img->image_data, img->width * img->height); if (tmp == NULL) return TGAERR_NO_MEM; img->image_data = tmp; img->pixel_depth = 8; img->image_type = TGA_IMAGE_TYPE_MONO; return TGA_NOERR; } tga_result tga_desaturate_rec_601_1(tga_image *img) { return tga_desaturate(img, 2989, 5866, 1145, 10000); } tga_result tga_desaturate_rec_709(tga_image *img) { return tga_desaturate(img, 2126, 7152, 722, 10000); } tga_result tga_desaturate_itu(tga_image *img) { return tga_desaturate(img, 2220, 7067, 713, 10000); } tga_result tga_desaturate_avg(tga_image *img) { return tga_desaturate(img, 1, 1, 1, 3); } /* --------------------------------------------------------------------------- * Convert an image to the given pixel depth. (one of 32, 24, 16) Avoids * using a secondary buffer to do the conversion. */ tga_result tga_convert_depth(tga_image *img, const uint8_t bits) { size_t src_size, dest_size; uint8_t src_bpp, dest_bpp; uint8_t *src, *dest; if (!UNMAP_DEPTH(bits) || !SANE_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH; if (tga_is_colormapped(img)) { tga_result result = tga_color_unmap(img); if (result != TGA_NOERR) return result; } if (img->pixel_depth == bits) return TGA_NOERR; /* no op, no err */ src_bpp = img->pixel_depth / 8; dest_bpp = bits / 8; src_size = (size_t)(img->width * img->height * src_bpp); dest_size = (size_t)(img->width * img->height * dest_bpp); if (src_size > dest_size) { void *tmp; /* convert forwards */ dest = img->image_data; for (src = img->image_data; src < img->image_data + img->width * img->height * src_bpp; src += src_bpp) { uint8_t r, g, b, a; (void)tga_unpack_pixel(src, img->pixel_depth, &r, &g, &b, &a); (void)tga_pack_pixel(dest, bits, r, g, b, a); dest += dest_bpp; } /* shrink */ tmp = realloc(img->image_data, img->width * img->height * dest_bpp); if (tmp == NULL) return TGAERR_NO_MEM; img->image_data = tmp; } else { /* expand */ void *tmp = realloc(img->image_data, img->width * img->height * dest_bpp); if (tmp == NULL) return TGAERR_NO_MEM; img->image_data = (uint8_t *)tmp; /* convert backwards */ dest = img->image_data + (img->width * img->height - 1) * dest_bpp; for (src = img->image_data + (img->width * img->height - 1) * src_bpp; src >= img->image_data; src -= src_bpp) { uint8_t r, g, b, a; (void)tga_unpack_pixel(src, img->pixel_depth, &r, &g, &b, &a); (void)tga_pack_pixel(dest, bits, r, g, b, a); dest -= dest_bpp; } } img->pixel_depth = bits; return TGA_NOERR; } /* --------------------------------------------------------------------------- * Swap red and blue (RGB becomes BGR and vice verse). (in-place) */ tga_result tga_swap_red_blue(tga_image *img) { uint8_t *ptr; uint8_t bpp = img->pixel_depth / 8; if (!UNMAP_DEPTH(img->pixel_depth)) return TGAERR_PIXEL_DEPTH; for (ptr = img->image_data; ptr < img->image_data + (img->width * img->height - 1) * bpp; ptr += bpp) { uint8_t r, g, b, a; (void)tga_unpack_pixel(ptr, img->pixel_depth, &b, &g, &r, &a); (void)tga_pack_pixel(ptr, img->pixel_depth, r, g, b, a); } return TGA_NOERR; } /* --------------------------------------------------------------------------- * Free the image_id, color_map_data and image_data buffers of the specified * tga_image, if they're not already NULL. */ void tga_free_buffers(tga_image *img) { if (img->image_id != NULL) { free(img->image_id); img->image_id = NULL; } if (img->color_map_data != NULL) { free(img->color_map_data); img->color_map_data = NULL; } if (img->image_data != NULL) { free(img->image_data); img->image_data = NULL; } } /* vim:set tabstop=4 shiftwidth=4 textwidth=78 expandtab: */