Code to free up resources for Imager::IO objects.
[imager.git] / tga.c
CommitLineData
1ec86afa
AMH
1#include "image.h"
2#include "io.h"
3#include "log.h"
4#include "iolayer.h"
5
6#include <stdlib.h>
7#include <errno.h>
8
9
10/*
11=head1 NAME
12
13tga.c - implements reading and writing targa files, uses io layer.
14
15=head1 SYNOPSIS
16
17 io_glue *ig = io_new_fd( fd );
18 i_img *im = i_readtga_wiol(ig, -1); // no limit on how much is read
19 // or
20 io_glue *ig = io_new_fd( fd );
21 return_code = i_writetga_wiol(im, ig);
22
23=head1 DESCRIPTION
24
25tga.c implements the basic functions to read and write portable targa
26files. It uses the iolayer and needs either a seekable source or an
27entire memory mapped buffer.
28
29=head1 FUNCTION REFERENCE
30
31Some of these functions are internal.
32
33=over 4
34
35=cut
36*/
37
38
1ec86afa
AMH
39
40
41typedef struct {
42 char idlength;
43 char colourmaptype;
44 char datatypecode;
45 short int colourmaporigin;
46 short int colourmaplength;
47 char colourmapdepth;
48 short int x_origin;
49 short int y_origin;
50 short width;
51 short height;
52 char bitsperpixel;
53 char imagedescriptor;
54} tga_header;
55
56
920aa4a5
AMH
57typedef struct {
58 int compressed;
59 int bytepp;
60 enum { NoInit, Raw, Rle } state;
61 unsigned char cval[4];
62 int len;
63 unsigned char hdr;
64 io_glue *ig;
65} tga_source;
66
67
68static
69int
70bpp_to_bytes(unsigned int bpp) {
71 switch (bpp) {
72 case 8:
73 return 1;
74 case 15:
75 case 16:
76 return 2;
77 case 24:
78 return 3;
79 case 32:
80 return 4;
81 }
82 return 0;
83}
84
85static
86int
87bpp_to_channels(unsigned int bpp) {
88 switch (bpp) {
89 case 8:
90 return 1;
91 case 15:
92 return 3;
93 case 16:
94 return 4;
95 case 24:
96 return 3;
97 case 32:
98 return 4;
99 }
100 return 0;
101}
102
103
104
105/* color_unpack
106
107Unpacks bytes into colours, for 2 byte type the first byte coming from
108the file will actually be GGGBBBBB, and the second will be ARRRRRGG.
109"A" represents an attribute bit. The 3 byte entry contains 1 byte
110each of blue, green, and red. The 4 byte entry contains 1 byte each
111of blue, green, red, and attribute.
112*/
113
114static
115void
116color_unpack(unsigned char *buf, int bytepp, i_color *val) {
117 switch (bytepp) {
118 case 1:
119 val->gray.gray_color = buf[0];
120 break;
121 case 2:
122 val->rgba.r = (buf[1] & 0x7c) << 1;
123 val->rgba.g = ((buf[1] & 0x03) << 6) | ((buf[0] & 0xe0) >> 2);
124 val->rgba.b = (buf[0] & 0x1f) << 3;
125 val->rgba.a = (buf[1] & 0x80);
126 break;
127 case 3:
128 val->rgb.b = buf[0];
129 val->rgb.g = buf[1];
130 val->rgb.r = buf[2];
131 break;
132 case 4:
133 val->rgba.b = buf[0];
134 val->rgba.g = buf[1];
135 val->rgba.r = buf[2];
136 val->rgba.a = buf[3];
137 break;
138 default:
139 }
140}
141
142/*
143 tga_source_read
144
145 Does not byte reorder, returns true on success, 0 otherwise
146*/
147
148static
149int
150tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
151 int cp = 0, j, k;
152 if (!s->compressed) {
153 if (s->ig->readcb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
154 return 1;
155 }
156
157 while(cp < pixels) {
158 int ml;
159 if (s->len == 0) s->state = NoInit;
160 switch (s->state) {
161 case NoInit:
162 if (s->ig->readcb(s->ig, &s->hdr, 1) != 1) return 0;
163
164 s->len = (s->hdr &~(1<<7))+1;
165 s->state = (s->hdr & (1<<7)) ? Rle : Raw;
166 if (s->state == Rle && s->ig->readcb(s->ig, s->cval, s->bytepp) != s->bytepp) return 0;
167
168 break;
169 case Rle:
170 ml = min(s->len, pixels-cp);
171 for(k=0; k<ml; k++) for(j=0; j<s->bytepp; j++)
172 buf[(cp+k)*s->bytepp+j] = s->cval[j];
173 // memset(buf+cp, s->cidx, ml);
174 cp += ml;
175 s->len -= ml;
176 break;
177 case Raw:
178 ml = min(s->len, pixels-cp);
179 if (s->ig->readcb(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0;
180 cp += ml;
181 s->len -= ml;
182 break;
183 }
184 }
185 return 1;
186}
187
188static
189int
190tga_palette_read(io_glue *ig, i_img *img, int bytepp, int colourmaplength) {
191 int i;
192 size_t palbsize;
193 unsigned char *palbuf;
194 i_color val;
195
196 palbsize = colourmaplength*bytepp;
197 palbuf = mymalloc(palbsize);
198
199 if (ig->readcb(ig, palbuf, palbsize) != palbsize) {
200 i_push_error(errno, "could not read targa colourmap");
201 return 0;
202 }
203
204 /* populate the palette of the new image */
205 for(i=0; i<colourmaplength; i++) {
206 color_unpack(palbuf+i*bytepp, bytepp, &val);
207 i_addcolors(img, &val, 1);
208 }
209 myfree(palbuf);
210 return 1;
211}
212
213
214
215
1ec86afa
AMH
216
217/*
218=item i_readtga_wiol(ig, length)
219
220Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
221
222 ig - io_glue object
223 length - maximum length to read from data source, before closing it -1
224 signifies no limit.
225
226=cut
227*/
228
229i_img *
230i_readtga_wiol(io_glue *ig, int length) {
231 i_img* img;
7c58edfc 232 int x, y, i;
1ec86afa 233 int width, height, channels;
920aa4a5 234 int mapped;
1ec86afa 235 char *idstring;
1ec86afa 236
920aa4a5 237 tga_source src;
1ec86afa 238 tga_header header;
1ec86afa 239 unsigned char headbuf[18];
7c58edfc 240 unsigned char *databuf;
920aa4a5
AMH
241 unsigned char *reorderbuf;
242
243 i_color *linebuf = NULL;
1ec86afa
AMH
244 i_clear_error();
245
246 mm_log((1,"i_readtga(ig %p, length %d)\n", ig, length));
247
248 io_glue_commit_types(ig);
249
250 if (ig->readcb(ig, &headbuf, 18) != 18) {
251 i_push_error(errno, "could not read targa header");
252 return NULL;
253 }
254
255 header.idlength = headbuf[0];
256 header.colourmaptype = headbuf[1];
257 header.datatypecode = headbuf[2];
920aa4a5
AMH
258 header.colourmaporigin = (headbuf[4] << 8) + headbuf[3];
259 header.colourmaplength = (headbuf[6] << 8) + headbuf[5];
1ec86afa 260 header.colourmapdepth = headbuf[7];
920aa4a5
AMH
261 header.x_origin = (headbuf[9] << 8) + headbuf[8];
262 header.y_origin = (headbuf[11] << 8) + headbuf[10];
1ec86afa
AMH
263 header.width = (headbuf[13] << 8) + headbuf[12];
264 header.height = (headbuf[15] << 8) + headbuf[14];
265 header.bitsperpixel = headbuf[16];
266 header.imagedescriptor = headbuf[17];
267
268 mm_log((1,"Id length: %d\n",header.idlength));
269 mm_log((1,"Colour map type: %d\n",header.colourmaptype));
270 mm_log((1,"Image type: %d\n",header.datatypecode));
271 mm_log((1,"Colour map offset: %d\n",header.colourmaporigin));
272 mm_log((1,"Colour map length: %d\n",header.colourmaplength));
273 mm_log((1,"Colour map depth: %d\n",header.colourmapdepth));
274 mm_log((1,"X origin: %d\n",header.x_origin));
275 mm_log((1,"Y origin: %d\n",header.y_origin));
276 mm_log((1,"Width: %d\n",header.width));
277 mm_log((1,"Height: %d\n",header.height));
278 mm_log((1,"Bits per pixel: %d\n",header.bitsperpixel));
279 mm_log((1,"Descriptor: %d\n",header.imagedescriptor));
280
281
282 if (header.idlength) {
283 idstring = mymalloc(header.idlength+1);
284 if (ig->readcb(ig, idstring, header.idlength) != header.idlength) {
285 i_push_error(errno, "short read on targa idstring");
286 return NULL;
287 }
288 myfree(idstring); /* Move this later, once this is stored in a tag */
289 }
290
291 width = header.width;
292 height = header.height;
293
294 /* Set tags here */
920aa4a5 295
1ec86afa 296 switch (header.datatypecode) {
1ec86afa
AMH
297 case 0: /* No data in image */
298 i_push_error(0, "Targa image contains no image data");
299 return NULL;
300 break;
920aa4a5
AMH
301 case 1: /* Uncompressed, color-mapped images */
302 case 9: /* Compressed, color-mapped images */
303 case 3: /* Uncompressed, grayscale images */
304 case 11: /* Compressed, grayscale images */
1ec86afa 305 if (header.bitsperpixel != 8) {
920aa4a5 306 i_push_error(0, "Targa: mapped/grayscale image's bpp is not 8, unsupported.");
1ec86afa
AMH
307 return NULL;
308 }
920aa4a5 309 src.bytepp = 1;
1ec86afa 310 break;
920aa4a5
AMH
311 case 2: /* Uncompressed, rgb images */
312 case 10: /* Compressed, rgb images */
313 if ((src.bytepp = bpp_to_bytes(header.bitsperpixel)))
314 break;
315 i_push_error(0, "Targa: direct color image's bpp is not 15/16/24/32 - unsupported.");
1ec86afa
AMH
316 return NULL;
317 break;
318 case 32: /* Compressed color-mapped, Huffman, Delta and runlength */
1ec86afa 319 case 33: /* Compressed color-mapped, Huffman, Delta and runlength */
920aa4a5 320 i_push_error(0, "Unsupported Targa (Huffman/delta/rle/quadtree) subformat is not supported");
1ec86afa
AMH
321 return NULL;
322 break;
323 default: /* All others which we don't know which might be */
324 i_push_error(0, "Unknown targa format");
325 return NULL;
326 break;
327 }
920aa4a5
AMH
328
329 src.state = NoInit;
330 src.len = 0;
331 src.ig = ig;
332 src.compressed = !!(header.datatypecode & (1<<3));
333
334 /* Determine number of channels */
335
336 mapped = 1;
337 switch (header.datatypecode) {
338 int tbpp;
339 case 2: /* Uncompressed, rgb images */
340 case 10: /* Compressed, rgb images */
341 mapped = 0;
342 case 1: /* Uncompressed, color-mapped images */
343 case 9: /* Compressed, color-mapped images */
344 if ((channels = bpp_to_channels(mapped ?
345 header.colourmapdepth :
346 header.bitsperpixel))) break;
347 i_push_error(0, "Targa Image has none of 15/16/24/32 pixel layout");
348 return NULL;
349 break;
350 case 3: /* Uncompressed, grayscale images */
351 case 11: /* Compressed, grayscale images */
352 mapped = 0;
353 channels = 1;
354 break;
355 }
356
357 img = mapped ?
358 i_img_pal_new(width, height, channels, 256) :
359 i_img_empty_ch(NULL, width, height, channels);
360
361 if (mapped &&
362 !tga_palette_read(ig,
363 img,
364 bpp_to_bytes(header.colourmapdepth),
365 header.colourmaplength)
366 ) {
367 i_push_error(0, "Targa Image has none of 15/16/24/32 pixel layout");
368 return NULL;
369 }
370
371 /* Allocate buffers */
372 databuf = mymalloc(width*src.bytepp);
373 if (!mapped) linebuf = mymalloc(width*sizeof(i_color));
374
375 for(y=0; y<height; y++) {
376 if (!tga_source_read(&src, databuf, width)) {
377 i_push_error(errno, "read for targa data failed");
378 myfree(databuf);
379 return NULL;
380 }
7456c26c 381 if (mapped && header.colourmaporigin) for(x=0; x<width; x++) databuf[x] -= header.colourmaporigin;
920aa4a5
AMH
382 if (mapped) i_ppal(img, 0, width, header.imagedescriptor & (1<<5) ? y : height-1-y, databuf);
383 else {
384 for(x=0; x<width; x++) color_unpack(databuf+x*src.bytepp, src.bytepp, linebuf+x);
385 i_plin(img, 0, width, header.imagedescriptor & (1<<5) ? y : height-1-y, linebuf);
386 }
387 }
388 myfree(databuf);
389 if (linebuf) myfree(linebuf);
390 return img;
1ec86afa
AMH
391}
392
393
394
395
396
397
398
399
400
401undef_int
7456c26c 402i_writetga_wiol(i_img *img, io_glue *ig) {
1ec86afa 403 int rc;
7456c26c
AMH
404 static int rgb_chan[] = { 2, 1, 0, 3 };
405 tga_header header;
406 unsigned char headbuf[18];
407 unsigned char *data;
408 int compress = 0;
409 char *idstring = "testing";
410 int idlen = strlen(idstring);
411 int mapped = img->type == i_palette_type;
1ec86afa 412
7456c26c 413 mm_log((1,"i_writetga_wiol(img %p, ig %p)\n", img, ig));
1ec86afa
AMH
414 i_clear_error();
415
1ec86afa
AMH
416 io_glue_commit_types(ig);
417
7456c26c
AMH
418 mm_log((1, "virtual %d, paletted %d\n", img->virtual, mapped));
419 mm_log((1, "channels %d\n", img->channels));
420
421 header.idlength;
422 header.idlength = idlen;
423 header.colourmaptype = mapped ? 1 : 0;
424 header.datatypecode = mapped ? 1 : img->channels == 1 ? 3 : 2;
425 mm_log((1, "datatypecode %d\n", header.datatypecode));
426 header.datatypecode += compress ? 8 : 0;
427 header.colourmaporigin = 0;
428 header.colourmaplength = mapped ? i_colorcount(img) : 0;
429 header.colourmapdepth = mapped ? img->channels*8 : 0;
430 header.x_origin = 0;
431 header.y_origin = 0;
432 header.width = img->xsize;
433 header.height = img->ysize;
434 header.bitsperpixel = mapped ? 8 : 8*img->channels;
435 header.imagedescriptor = (1<<5); /* normal order instead of upside down */
436
437 headbuf[0] = header.idlength;
438 headbuf[1] = header.colourmaptype;
439 headbuf[2] = header.datatypecode;
440 headbuf[3] = header.colourmaporigin & 0xff;
441 headbuf[4] = header.colourmaporigin >> 8;
442 headbuf[5] = header.colourmaplength & 0xff;
443 headbuf[6] = header.colourmaplength >> 8;
444 headbuf[7] = header.colourmapdepth;
445 headbuf[8] = header.x_origin & 0xff;
446 headbuf[9] = header.x_origin >> 8;
447 headbuf[10] = header.y_origin & 0xff;
448 headbuf[11] = header.y_origin >> 8;
449 headbuf[12] = header.width & 0xff;
450 headbuf[13] = header.width >> 8;
451 headbuf[14] = header.height & 0xff;
452 headbuf[15] = header.height >> 8;
453 headbuf[16] = header.bitsperpixel;
454 headbuf[17] = header.imagedescriptor;
455
456 if (ig->writecb(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) {
457 i_push_error(errno, "could not write targa header");
458 return 0;
7c58edfc
AMH
459 }
460
7456c26c
AMH
461 if (idlen) {
462 if (ig->writecb(ig, idstring, idlen) != idlen) {
463 i_push_error(errno, "could not write targa idstring");
464 return 0;
1ec86afa
AMH
465 }
466 }
7456c26c
AMH
467
468 if (img->type == i_palette_type) {
c95846c2
AMH
469 int i;
470 size_t palbsize = i_colorcount(img)*img->channels;
471 unsigned char *palbuf = mymalloc(palbsize);
472
473 for(i=0; i<header.colourmaplength; i++) {
474 int ch;
475 i_color val;
476 i_getcolors(img, i, &val, 1);
477 if (img->channels>1) for(ch=0; ch<img->channels; ch++)
478 palbuf[i*img->channels+ch] = val.channel[rgb_chan[ch]];
479 else for(ch=0; ch<img->channels; ch++)
480 palbuf[i] = val.channel[ch];
481 }
482
483 if (ig->writecb(ig, palbuf, palbsize) != palbsize) {
484 i_push_error(errno, "could not write targa colourmap");
485 return 0;
486 }
487 myfree(palbuf);
488
7456c26c
AMH
489 /* write palette */
490 if (!img->virtual) {
491 if (ig->writecb(ig, img->idata, img->bytes) != img->bytes) {
492 i_push_error(errno, "could not write targa image data");
493 return 0;
1ec86afa 494 }
7456c26c
AMH
495 } else {
496 int y;
497 i_palidx *vals = mymalloc(sizeof(i_palidx)*img->xsize);
498 for(y=0; y<img->ysize; y++) {
499 i_gpal(img, 0, img->xsize, y, vals);
500 if (ig->writecb(ig, vals, img->xsize) != img->xsize) {
501 i_push_error(errno, "could not write targa data to file");
502 myfree(vals);
503 return 0;
504 }
1ec86afa 505 }
7456c26c 506 myfree(vals);
1ec86afa 507 }
7456c26c
AMH
508 } else {
509 int y, lsize = img->channels * img->xsize;
510 data = mymalloc(lsize);
511 for(y=0; y<img->ysize; y++) {
c95846c2
AMH
512 if (img->channels>1) i_gsamp(img, 0, img->xsize, y, data, rgb_chan, img->channels);
513 else {
514 int gray_chan[] = {0};
515 i_gsamp(img, 0, img->xsize, y, data, gray_chan, img->channels);
516 }
7456c26c
AMH
517 if ( ig->writecb(ig, data, lsize) != lsize ) {
518 i_push_error(errno, "could not write targa data to file");
519 myfree(data);
520 return 0;
521 }
1ec86afa 522 }
7456c26c 523 myfree(data);
1ec86afa 524 }
7456c26c 525 return 1;
1ec86afa 526}