#include "image.h" #include "io.h" #include "log.h" #include "iolayer.h" #include #include /* =head1 NAME tga.c - implements reading and writing targa files, uses io layer. =head1 SYNOPSIS io_glue *ig = io_new_fd( fd ); i_img *im = i_readtga_wiol(ig, -1); // no limit on how much is read // or io_glue *ig = io_new_fd( fd ); return_code = i_writetga_wiol(im, ig); =head1 DESCRIPTION tga.c implements the basic functions to read and write portable targa files. It uses the iolayer and needs either a seekable source or an entire memory mapped buffer. =head1 FUNCTION REFERENCE Some of these functions are internal. =over =cut */ typedef struct { char idlength; char colourmaptype; char datatypecode; short int colourmaporigin; short int colourmaplength; char colourmapdepth; short int x_origin; short int y_origin; short width; short height; char bitsperpixel; char imagedescriptor; } tga_header; typedef enum { NoInit, Raw, Rle } rle_state; typedef struct { int compressed; int bytepp; rle_state state; unsigned char cval[4]; int len; unsigned char hdr; io_glue *ig; } tga_source; typedef struct { int compressed; int bytepp; io_glue *ig; } tga_dest; /* =item bpp_to_bytes(bpp) Convert bits per pixel into bytes per pixel bpp - bits per pixel =cut */ static int bpp_to_bytes(unsigned int bpp) { switch (bpp) { case 8: return 1; case 15: case 16: return 2; case 24: return 3; case 32: return 4; } return 0; } /* =item bpp_to_channels(bpp) Convert bits per pixel into channels in the image bpp - bits per pixel =cut */ static int bpp_to_channels(unsigned int bpp) { switch (bpp) { case 8: return 1; case 15: return 3; case 16: return 4; case 24: return 3; case 32: return 4; } return 0; } /* * Packing functions - used for (un)packing * datastructures into raw bytes. */ /* =item color_unpack(buf, bytepp, val) Unpacks bytes into colour structures, for 2 byte type the first byte coming from the file will actually be GGGBBBBB, and the second will be ARRRRRGG. "A" represents an attribute bit. The 3 byte entry contains 1 byte each of blue, green, and red. The 4 byte entry contains 1 byte each of blue, green, red, and attribute. buf - pointer to data bytepp - bytes per pixel val - pointer to color to store to =cut */ static void color_unpack(unsigned char *buf, int bytepp, i_color *val) { switch (bytepp) { case 1: val->gray.gray_color = buf[0]; break; case 2: val->rgba.r = (buf[1] & 0x7c) << 1; val->rgba.g = ((buf[1] & 0x03) << 6) | ((buf[0] & 0xe0) >> 2); val->rgba.b = (buf[0] & 0x1f) << 3; val->rgba.a = (buf[1] & 0x80) ? 255 : 0; val->rgba.r |= val->rgba.r >> 5; val->rgba.g |= val->rgba.g >> 5; val->rgba.b |= val->rgba.b >> 5; break; case 3: val->rgb.b = buf[0]; val->rgb.g = buf[1]; val->rgb.r = buf[2]; break; case 4: val->rgba.b = buf[0]; val->rgba.g = buf[1]; val->rgba.r = buf[2]; val->rgba.a = buf[3]; break; } } /* =item color_pack Packs a colour into an array of bytes, for 2 byte type the first byte will be GGGBBBBB, and the second will be ARRRRRGG. "A" represents an attribute bit. The 3 byte entry contains 1 byte each of blue, green, and red. The 4 byte entry contains 1 byte each of blue, green, red, and attribute. buf - destination buffer bitspp - bits per pixel val - color to pack =cut */ static void color_pack(unsigned char *buf, int bitspp, i_color *val) { switch (bitspp) { case 8: buf[0] = val->gray.gray_color; break; case 15: buf[0] = (val->rgba.b >> 3); buf[0] |= (val->rgba.g & 0x38) << 2; buf[1] = (val->rgba.r & 0xf8)>> 1; buf[1] |= (val->rgba.g >> 6); case 16: buf[1] |= val->rgba.a & 0x80; break; case 24: buf[0] = val->rgb.b; buf[1] = val->rgb.g; buf[2] = val->rgb.r; break; case 32: buf[0] = val->rgba.b; buf[1] = val->rgba.g; buf[2] = val->rgba.r; buf[3] = val->rgba.a; break; } } /* =item find_repeat Helper function for rle compressor to find the next triple repeat of the same pixel value in buffer. buf - buffer length - number of pixel values in buffer bytepp - number of bytes in a pixel value =cut */ static int find_repeat(unsigned char *buf, int length, int bytepp) { int i = 0; while(iidlength = headbuf[0]; header->colourmaptype = headbuf[1]; header->datatypecode = headbuf[2]; header->colourmaporigin = (headbuf[4] << 8) + headbuf[3]; header->colourmaplength = (headbuf[6] << 8) + headbuf[5]; header->colourmapdepth = headbuf[7]; header->x_origin = (headbuf[9] << 8) + headbuf[8]; header->y_origin = (headbuf[11] << 8) + headbuf[10]; header->width = (headbuf[13] << 8) + headbuf[12]; header->height = (headbuf[15] << 8) + headbuf[14]; header->bitsperpixel = headbuf[16]; header->imagedescriptor = headbuf[17]; } int tga_header_verify(unsigned char headbuf[18]) { tga_header header; tga_header_unpack(&header, headbuf); switch (header.datatypecode) { default: printf("bad typecode!\n"); return 0; case 0: case 1: /* Uncompressed, color-mapped images */ case 2: /* Uncompressed, rgb images */ case 3: /* Uncompressed, grayscale images */ case 9: /* Compressed, color-mapped images */ case 10: /* Compressed, rgb images */ case 11: /* Compressed, grayscale images */ break; } switch (header.colourmaptype) { default: printf("bad colourmaptype!\n"); return 0; case 0: case 1: break; } return 1; } /* =item tga_header_pack(header, headbuf) Packs header structure into buffer for writing. header - header structure headbuf - buffer to pack into =cut */ static void tga_header_pack(tga_header *header, unsigned char headbuf[18]) { headbuf[0] = header->idlength; headbuf[1] = header->colourmaptype; headbuf[2] = header->datatypecode; headbuf[3] = header->colourmaporigin & 0xff; headbuf[4] = header->colourmaporigin >> 8; headbuf[5] = header->colourmaplength & 0xff; headbuf[6] = header->colourmaplength >> 8; headbuf[7] = header->colourmapdepth; headbuf[8] = header->x_origin & 0xff; headbuf[9] = header->x_origin >> 8; headbuf[10] = header->y_origin & 0xff; headbuf[11] = header->y_origin >> 8; headbuf[12] = header->width & 0xff; headbuf[13] = header->width >> 8; headbuf[14] = header->height & 0xff; headbuf[15] = header->height >> 8; headbuf[16] = header->bitsperpixel; headbuf[17] = header->imagedescriptor; } /* =item tga_source_read(s, buf, pixels) Reads pixel number of pixels from source s into buffer buf. Takes care of decompressing the stream if needed. s - data source buf - destination buffer pixels - number of pixels to put into buffer =cut */ static int tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) { int cp = 0, j, k; if (!s->compressed) { if (s->ig->readcb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0; return 1; } while(cp < pixels) { int ml; if (s->len == 0) s->state = NoInit; switch (s->state) { case NoInit: if (s->ig->readcb(s->ig, &s->hdr, 1) != 1) return 0; s->len = (s->hdr &~(1<<7))+1; s->state = (s->hdr & (1<<7)) ? Rle : Raw; { /* static cnt = 0; printf("%04d %s: %d\n", cnt++, s->state==Rle?"RLE":"RAW", s->len); */ } if (s->state == Rle && s->ig->readcb(s->ig, s->cval, s->bytepp) != s->bytepp) return 0; break; case Rle: ml = i_min(s->len, pixels-cp); for(k=0; kbytepp; j++) buf[(cp+k)*s->bytepp+j] = s->cval[j]; cp += ml; s->len -= ml; break; case Raw: ml = i_min(s->len, pixels-cp); if (s->ig->readcb(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0; cp += ml; s->len -= ml; break; } } return 1; } /* =item tga_dest_write(s, buf, pixels) Writes pixels from buf to destination s. Takes care of compressing if the destination is compressed. s - data destination buf - source buffer pixels - number of pixels to put write to destination =cut */ static int tga_dest_write(tga_dest *s, unsigned char *buf, size_t pixels) { int cp = 0, j, k; if (!s->compressed) { if (s->ig->writecb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0; return 1; } while(cp < pixels) { int tlen; int nxtrip = find_repeat(buf+cp*s->bytepp, pixels-cp, s->bytepp); tlen = (nxtrip == -1) ? pixels-cp : nxtrip; while(tlen) { unsigned char clen = (tlen>128) ? 128 : tlen; clen--; if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0; clen++; if (s->ig->writecb(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0; tlen -= clen; cp += clen; } if (cp >= pixels) break; tlen = find_span(buf+cp*s->bytepp, pixels-cp, s->bytepp); if (tlen <3) continue; while (tlen) { unsigned char clen = (tlen>128) ? 128 : tlen; clen = (clen - 1) | 0x80; if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0; clen = (clen & ~0x80) + 1; if (s->ig->writecb(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0; tlen -= clen; cp += clen; } } return 1; } /* =item tga_palette_read(ig, img, bytepp, colourmaplength) Reads the colormap from a tga file and stores in the paletted image structure. ig - iolayer data source img - image structure bytepp - bytes per pixel colourmaplength - number of colours in colourmap =cut */ static int tga_palette_read(io_glue *ig, i_img *img, int bytepp, int colourmaplength) { int i; size_t palbsize; unsigned char *palbuf; i_color val; palbsize = colourmaplength*bytepp; palbuf = mymalloc(palbsize); if (ig->readcb(ig, palbuf, palbsize) != palbsize) { i_push_error(errno, "could not read targa colourmap"); return 0; } /* populate the palette of the new image */ for(i=0; iwritecb(ig, palbuf, palbsize) != palbsize) { i_push_error(errno, "could not write targa colourmap"); return 0; } myfree(palbuf); return 1; } /* =item i_readtga_wiol(ig, length) Read in an image from the iolayer data source and return the image structure to it. Returns NULL on error. ig - io_glue object length - maximum length to read from data source, before closing it -1 signifies no limit. =cut */ i_img * i_readtga_wiol(io_glue *ig, int length) { i_img* img = NULL; int x, y, i; int width, height, channels; int mapped; char *idstring = NULL; tga_source src; tga_header header; unsigned char headbuf[18]; unsigned char *databuf; unsigned char *reorderbuf; i_color *linebuf = NULL; i_clear_error(); mm_log((1,"i_readtga(ig %p, length %d)\n", ig, length)); io_glue_commit_types(ig); if (ig->readcb(ig, &headbuf, 18) != 18) { i_push_error(errno, "could not read targa header"); return NULL; } tga_header_unpack(&header, headbuf); mm_log((1,"Id length: %d\n",header.idlength)); mm_log((1,"Colour map type: %d\n",header.colourmaptype)); mm_log((1,"Image type: %d\n",header.datatypecode)); mm_log((1,"Colour map offset: %d\n",header.colourmaporigin)); mm_log((1,"Colour map length: %d\n",header.colourmaplength)); mm_log((1,"Colour map depth: %d\n",header.colourmapdepth)); mm_log((1,"X origin: %d\n",header.x_origin)); mm_log((1,"Y origin: %d\n",header.y_origin)); mm_log((1,"Width: %d\n",header.width)); mm_log((1,"Height: %d\n",header.height)); mm_log((1,"Bits per pixel: %d\n",header.bitsperpixel)); mm_log((1,"Descriptor: %d\n",header.imagedescriptor)); if (header.idlength) { idstring = mymalloc(header.idlength+1); if (ig->readcb(ig, idstring, header.idlength) != header.idlength) { i_push_error(errno, "short read on targa idstring"); return NULL; } } width = header.width; height = header.height; /* Set tags here */ switch (header.datatypecode) { case 0: /* No data in image */ i_push_error(0, "Targa image contains no image data"); if (idstring) myfree(idstring); return NULL; break; case 1: /* Uncompressed, color-mapped images */ case 9: /* Compressed, color-mapped images */ case 3: /* Uncompressed, grayscale images */ case 11: /* Compressed, grayscale images */ if (header.bitsperpixel != 8) { i_push_error(0, "Targa: mapped/grayscale image's bpp is not 8, unsupported."); if (idstring) myfree(idstring); return NULL; } src.bytepp = 1; break; case 2: /* Uncompressed, rgb images */ case 10: /* Compressed, rgb images */ if ((src.bytepp = bpp_to_bytes(header.bitsperpixel))) break; i_push_error(0, "Targa: direct color image's bpp is not 15/16/24/32 - unsupported."); if (idstring) myfree(idstring); return NULL; break; case 32: /* Compressed color-mapped, Huffman, Delta and runlength */ case 33: /* Compressed color-mapped, Huffman, Delta and runlength */ i_push_error(0, "Unsupported Targa (Huffman/delta/rle/quadtree) subformat is not supported"); if (idstring) myfree(idstring); return NULL; break; default: /* All others which we don't know which might be */ i_push_error(0, "Unknown targa format"); if (idstring) myfree(idstring); return NULL; break; } src.state = NoInit; src.len = 0; src.ig = ig; src.compressed = !!(header.datatypecode & (1<<3)); /* Determine number of channels */ mapped = 1; switch (header.datatypecode) { int tbpp; case 2: /* Uncompressed, rgb images */ case 10: /* Compressed, rgb images */ mapped = 0; case 1: /* Uncompressed, color-mapped images */ case 9: /* Compressed, color-mapped images */ if ((channels = bpp_to_channels(mapped ? header.colourmapdepth : header.bitsperpixel))) break; i_push_error(0, "Targa Image has none of 15/16/24/32 pixel layout"); if (idstring) myfree(idstring); return NULL; break; case 3: /* Uncompressed, grayscale images */ case 11: /* Compressed, grayscale images */ mapped = 0; channels = 1; break; } img = mapped ? i_img_pal_new(width, height, channels, 256) : i_img_empty_ch(NULL, width, height, channels); if (idstring) { i_tags_add(&img->tags, "tga_idstring", 0, idstring, header.idlength, 0); myfree(idstring); } if (mapped && !tga_palette_read(ig, img, bpp_to_bytes(header.colourmapdepth), header.colourmaplength) ) { i_push_error(0, "Targa Image has none of 15/16/24/32 pixel layout"); if (idstring) myfree(idstring); if (img) i_img_destroy(img); return NULL; } /* Allocate buffers */ databuf = mymalloc(width*src.bytepp); if (!mapped) linebuf = mymalloc(width*sizeof(i_color)); for(y=0; ytags, "tga_bitspp", 0, mapped?header.colourmapdepth:header.bitsperpixel); if (src.compressed) i_tags_addn(&img->tags, "compressed", 0, 1); return img; } /* =item i_writetga_wiol(img, ig) Writes an image in targa format. Returns 0 on error. img - image to store ig - io_glue object =cut */ undef_int i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idstring, size_t idlen) { static int rgb_chan[] = { 2, 1, 0, 3 }; tga_header header; tga_dest dest; unsigned char headbuf[18]; unsigned int bitspp; int mapped; /* parameters */ /* int compress = 1; char *idstring = "testing"; int wierdpack = 0; */ idlen = strlen(idstring); mapped = img->type == i_palette_type; mm_log((1,"i_writetga_wiol(img %p, ig %p, idstring %p, idlen %d, wierdpack %d, compress %d)\n", img, ig, idstring, idlen, wierdpack, compress)); mm_log((1, "virtual %d, paletted %d\n", img->virtual, mapped)); mm_log((1, "channels %d\n", img->channels)); i_clear_error(); switch (img->channels) { case 1: bitspp = 8; if (wierdpack) { mm_log((1,"wierdpack option ignored for 1 channel images\n")); wierdpack=0; } break; case 2: i_push_error(0, "Cannot store 2 channel image in targa format"); return 0; break; case 3: bitspp = wierdpack ? 15 : 24; break; case 4: bitspp = wierdpack ? 16 : 32; break; default: i_push_error(0, "Targa only handles 1,3 and 4 channel images."); return 0; } io_glue_commit_types(ig); header.idlength; header.idlength = idlen; header.colourmaptype = mapped ? 1 : 0; header.datatypecode = mapped ? 1 : img->channels == 1 ? 3 : 2; header.datatypecode += compress ? 8 : 0; mm_log((1, "datatypecode %d\n", header.datatypecode)); header.colourmaporigin = 0; header.colourmaplength = mapped ? i_colorcount(img) : 0; header.colourmapdepth = mapped ? bitspp : 0; header.x_origin = 0; header.y_origin = 0; header.width = img->xsize; header.height = img->ysize; header.bitsperpixel = mapped ? 8 : bitspp; header.imagedescriptor = (1<<5); /* normal order instead of upside down */ tga_header_pack(&header, headbuf); if (ig->writecb(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) { i_push_error(errno, "could not write targa header"); return 0; } if (idlen) { if (ig->writecb(ig, idstring, idlen) != idlen) { i_push_error(errno, "could not write targa idstring"); return 0; } } /* Make this into a constructor? */ dest.compressed = compress; dest.bytepp = mapped ? 1 : bpp_to_bytes(bitspp); dest.ig = ig; mm_log((1, "dest.compressed = %d\n", dest.compressed)); mm_log((1, "dest.bytepp = %d\n", dest.bytepp)); if (img->type == i_palette_type) { int i; int bytepp = bpp_to_bytes(bitspp); if (!tga_palette_write(ig, img, bitspp, i_colorcount(img))) return 0; if (!img->virtual && !dest.compressed) { if (ig->writecb(ig, img->idata, img->bytes) != img->bytes) { i_push_error(errno, "could not write targa image data"); return 0; } } else { int y; i_palidx *vals = mymalloc(sizeof(i_palidx)*img->xsize); for(y=0; yysize; y++) { i_gpal(img, 0, img->xsize, y, vals); tga_dest_write(&dest, vals, img->xsize); } myfree(vals); } } else { /* direct type */ int x, y; int bytepp = wierdpack ? 2 : bpp_to_bytes(bitspp); int lsize = bytepp * img->xsize; i_color *vals = mymalloc(img->xsize*sizeof(i_color)); unsigned char *buf = mymalloc(lsize); for(y=0; yysize; y++) { i_glin(img, 0, img->xsize, y, vals); for(x=0; xxsize; x++) color_pack(buf+x*bytepp, bitspp, vals+x); tga_dest_write(&dest, buf, img->xsize); } myfree(buf); myfree(vals); } ig->closecb(ig); return 1; } /* =back =head1 AUTHOR Arnar M. Hrafnkelsson =head1 SEE ALSO Imager(3) =cut */