]>
Commit | Line | Data |
---|---|---|
92bda632 | 1 | #include "imageri.h" |
1ec86afa AMH |
2 | #include "log.h" |
3 | #include "iolayer.h" | |
4 | ||
5 | #include <stdlib.h> | |
6 | #include <errno.h> | |
7 | ||
8 | ||
9 | /* | |
10 | =head1 NAME | |
11 | ||
12 | tga.c - implements reading and writing targa files, uses io layer. | |
13 | ||
14 | =head1 SYNOPSIS | |
15 | ||
16 | io_glue *ig = io_new_fd( fd ); | |
17 | i_img *im = i_readtga_wiol(ig, -1); // no limit on how much is read | |
18 | // or | |
19 | io_glue *ig = io_new_fd( fd ); | |
20 | return_code = i_writetga_wiol(im, ig); | |
21 | ||
22 | =head1 DESCRIPTION | |
23 | ||
24 | tga.c implements the basic functions to read and write portable targa | |
25 | files. It uses the iolayer and needs either a seekable source or an | |
26 | entire memory mapped buffer. | |
27 | ||
28 | =head1 FUNCTION REFERENCE | |
29 | ||
30 | Some of these functions are internal. | |
31 | ||
b8c2033e | 32 | =over |
1ec86afa AMH |
33 | |
34 | =cut | |
35 | */ | |
36 | ||
37 | ||
1ec86afa AMH |
38 | |
39 | ||
40 | typedef struct { | |
b96be931 | 41 | unsigned char idlength; |
1ec86afa AMH |
42 | char colourmaptype; |
43 | char datatypecode; | |
44 | short int colourmaporigin; | |
45 | short int colourmaplength; | |
46 | char colourmapdepth; | |
47 | short int x_origin; | |
48 | short int y_origin; | |
8d14daab TC |
49 | int width; |
50 | int height; | |
1ec86afa AMH |
51 | char bitsperpixel; |
52 | char imagedescriptor; | |
53 | } tga_header; | |
54 | ||
55 | ||
76ff75b8 AMH |
56 | typedef enum { NoInit, Raw, Rle } rle_state; |
57 | ||
920aa4a5 AMH |
58 | typedef struct { |
59 | int compressed; | |
8d14daab | 60 | size_t bytepp; |
76ff75b8 | 61 | rle_state state; |
920aa4a5 AMH |
62 | unsigned char cval[4]; |
63 | int len; | |
64 | unsigned char hdr; | |
65 | io_glue *ig; | |
66 | } tga_source; | |
67 | ||
68 | ||
76ff75b8 AMH |
69 | typedef struct { |
70 | int compressed; | |
71 | int bytepp; | |
72 | io_glue *ig; | |
73 | } tga_dest; | |
74 | ||
8d14daab | 75 | #define TGA_MAX_DIM 0xFFFF |
76ff75b8 | 76 | |
febba01f AMH |
77 | /* |
78 | =item bpp_to_bytes(bpp) | |
79 | ||
80 | Convert bits per pixel into bytes per pixel | |
81 | ||
82 | bpp - bits per pixel | |
83 | ||
84 | =cut | |
85 | */ | |
86 | ||
87 | ||
920aa4a5 | 88 | static |
8d14daab | 89 | size_t |
920aa4a5 AMH |
90 | bpp_to_bytes(unsigned int bpp) { |
91 | switch (bpp) { | |
92 | case 8: | |
93 | return 1; | |
94 | case 15: | |
95 | case 16: | |
96 | return 2; | |
97 | case 24: | |
98 | return 3; | |
99 | case 32: | |
100 | return 4; | |
101 | } | |
102 | return 0; | |
103 | } | |
104 | ||
febba01f AMH |
105 | |
106 | ||
107 | /* | |
108 | =item bpp_to_channels(bpp) | |
109 | ||
c5a24cca TC |
110 | Convert bits per pixel and the number of attribute bits into channels |
111 | in the image | |
febba01f AMH |
112 | |
113 | bpp - bits per pixel | |
c5a24cca | 114 | attr_bit_count - number of attribute bits |
febba01f AMH |
115 | |
116 | =cut | |
117 | */ | |
118 | ||
920aa4a5 AMH |
119 | static |
120 | int | |
c5a24cca | 121 | bpp_to_channels(unsigned int bpp, int attr_bit_count) { |
920aa4a5 AMH |
122 | switch (bpp) { |
123 | case 8: | |
124 | return 1; | |
c5a24cca TC |
125 | case 16: |
126 | if (attr_bit_count == 1) | |
127 | return 4; | |
920aa4a5 AMH |
128 | case 15: |
129 | return 3; | |
c5a24cca TC |
130 | case 32: |
131 | if (attr_bit_count == 8) | |
132 | return 4; | |
920aa4a5 AMH |
133 | case 24: |
134 | return 3; | |
920aa4a5 AMH |
135 | } |
136 | return 0; | |
137 | } | |
138 | ||
139 | ||
140 | ||
76ff75b8 AMH |
141 | /* |
142 | * Packing functions - used for (un)packing | |
143 | * datastructures into raw bytes. | |
febba01f AMH |
144 | */ |
145 | ||
146 | ||
147 | /* | |
148 | =item color_unpack(buf, bytepp, val) | |
76ff75b8 | 149 | |
febba01f AMH |
150 | Unpacks bytes into colour structures, for 2 byte type the first byte |
151 | coming from the file will actually be GGGBBBBB, and the second will be | |
152 | ARRRRRGG. "A" represents an attribute bit. The 3 byte entry contains | |
153 | 1 byte each of blue, green, and red. The 4 byte entry contains 1 byte | |
154 | each of blue, green, red, and attribute. | |
76ff75b8 | 155 | |
febba01f AMH |
156 | buf - pointer to data |
157 | bytepp - bytes per pixel | |
158 | val - pointer to color to store to | |
920aa4a5 | 159 | |
febba01f | 160 | =cut |
920aa4a5 AMH |
161 | */ |
162 | ||
163 | static | |
164 | void | |
165 | color_unpack(unsigned char *buf, int bytepp, i_color *val) { | |
166 | switch (bytepp) { | |
167 | case 1: | |
168 | val->gray.gray_color = buf[0]; | |
169 | break; | |
170 | case 2: | |
171 | val->rgba.r = (buf[1] & 0x7c) << 1; | |
172 | val->rgba.g = ((buf[1] & 0x03) << 6) | ((buf[0] & 0xe0) >> 2); | |
173 | val->rgba.b = (buf[0] & 0x1f) << 3; | |
c5a24cca | 174 | val->rgba.a = (buf[1] & 0x80) ? 0 : 255; |
76ff75b8 AMH |
175 | val->rgba.r |= val->rgba.r >> 5; |
176 | val->rgba.g |= val->rgba.g >> 5; | |
177 | val->rgba.b |= val->rgba.b >> 5; | |
920aa4a5 AMH |
178 | break; |
179 | case 3: | |
180 | val->rgb.b = buf[0]; | |
181 | val->rgb.g = buf[1]; | |
182 | val->rgb.r = buf[2]; | |
183 | break; | |
184 | case 4: | |
185 | val->rgba.b = buf[0]; | |
186 | val->rgba.g = buf[1]; | |
187 | val->rgba.r = buf[2]; | |
188 | val->rgba.a = buf[3]; | |
189 | break; | |
920aa4a5 AMH |
190 | } |
191 | } | |
192 | ||
76ff75b8 AMH |
193 | |
194 | ||
febba01f AMH |
195 | /* |
196 | =item color_pack | |
76ff75b8 | 197 | |
febba01f AMH |
198 | Packs a colour into an array of bytes, for 2 byte type the first byte |
199 | will be GGGBBBBB, and the second will be ARRRRRGG. "A" represents an | |
76ff75b8 AMH |
200 | attribute bit. The 3 byte entry contains 1 byte each of blue, green, |
201 | and red. The 4 byte entry contains 1 byte each of blue, green, red, | |
202 | and attribute. | |
febba01f AMH |
203 | |
204 | buf - destination buffer | |
205 | bitspp - bits per pixel | |
206 | val - color to pack | |
207 | ||
208 | =cut | |
920aa4a5 AMH |
209 | */ |
210 | ||
76ff75b8 AMH |
211 | static |
212 | void | |
213 | color_pack(unsigned char *buf, int bitspp, i_color *val) { | |
214 | switch (bitspp) { | |
215 | case 8: | |
216 | buf[0] = val->gray.gray_color; | |
217 | break; | |
c5a24cca TC |
218 | case 16: |
219 | buf[0] = (val->rgba.b >> 3); | |
220 | buf[0] |= (val->rgba.g & 0x38) << 2; | |
221 | buf[1] = (val->rgba.r & 0xf8)>> 1; | |
222 | buf[1] |= (val->rgba.g >> 6); | |
223 | buf[1] |= val->rgba.a > 0x7f ? 0 : 0x80; | |
224 | break; | |
76ff75b8 AMH |
225 | case 15: |
226 | buf[0] = (val->rgba.b >> 3); | |
227 | buf[0] |= (val->rgba.g & 0x38) << 2; | |
228 | buf[1] = (val->rgba.r & 0xf8)>> 1; | |
229 | buf[1] |= (val->rgba.g >> 6); | |
76ff75b8 AMH |
230 | break; |
231 | case 24: | |
232 | buf[0] = val->rgb.b; | |
233 | buf[1] = val->rgb.g; | |
234 | buf[2] = val->rgb.r; | |
235 | break; | |
236 | case 32: | |
237 | buf[0] = val->rgba.b; | |
238 | buf[1] = val->rgba.g; | |
239 | buf[2] = val->rgba.r; | |
240 | buf[3] = val->rgba.a; | |
241 | break; | |
76ff75b8 | 242 | } |
76ff75b8 AMH |
243 | } |
244 | ||
245 | ||
febba01f AMH |
246 | /* |
247 | =item find_repeat | |
248 | ||
249 | Helper function for rle compressor to find the next triple repeat of the | |
250 | same pixel value in buffer. | |
251 | ||
252 | buf - buffer | |
253 | length - number of pixel values in buffer | |
254 | bytepp - number of bytes in a pixel value | |
76ff75b8 | 255 | |
febba01f AMH |
256 | =cut |
257 | */ | |
76ff75b8 AMH |
258 | |
259 | static | |
260 | int | |
261 | find_repeat(unsigned char *buf, int length, int bytepp) { | |
262 | int i = 0; | |
263 | ||
264 | while(i<length-1) { | |
265 | if(memcmp(buf+i*bytepp, buf+(i+1)*bytepp, bytepp) == 0) { | |
266 | if (i == length-2) return -1; | |
267 | if (memcmp(buf+(i+1)*bytepp, buf+(i+2)*bytepp,bytepp) == 0) | |
268 | return i; | |
269 | else i++; | |
270 | } | |
271 | i++; | |
272 | } | |
273 | return -1; | |
274 | } | |
275 | ||
276 | ||
febba01f AMH |
277 | /* |
278 | =item find_span | |
279 | ||
280 | Helper function for rle compressor to find the length of a span where | |
281 | the same pixel value is in the buffer. | |
282 | ||
283 | buf - buffer | |
284 | length - number of pixel values in buffer | |
285 | bytepp - number of bytes in a pixel value | |
286 | ||
287 | =cut | |
288 | */ | |
289 | ||
76ff75b8 AMH |
290 | static |
291 | int | |
292 | find_span(unsigned char *buf, int length, int bytepp) { | |
293 | int i = 0; | |
294 | while(i<length) { | |
295 | if(memcmp(buf, buf+(i*bytepp), bytepp) != 0) return i; | |
296 | i++; | |
297 | } | |
298 | return length; | |
299 | } | |
300 | ||
301 | ||
febba01f AMH |
302 | /* |
303 | =item tga_header_unpack(header, headbuf) | |
304 | ||
305 | Unpacks the header structure into from buffer and stores | |
306 | in the header structure. | |
307 | ||
308 | header - header structure | |
309 | headbuf - buffer to unpack from | |
310 | ||
311 | =cut | |
312 | */ | |
313 | ||
314 | static | |
315 | void | |
316 | tga_header_unpack(tga_header *header, unsigned char headbuf[18]) { | |
317 | header->idlength = headbuf[0]; | |
318 | header->colourmaptype = headbuf[1]; | |
319 | header->datatypecode = headbuf[2]; | |
320 | header->colourmaporigin = (headbuf[4] << 8) + headbuf[3]; | |
321 | header->colourmaplength = (headbuf[6] << 8) + headbuf[5]; | |
322 | header->colourmapdepth = headbuf[7]; | |
323 | header->x_origin = (headbuf[9] << 8) + headbuf[8]; | |
324 | header->y_origin = (headbuf[11] << 8) + headbuf[10]; | |
325 | header->width = (headbuf[13] << 8) + headbuf[12]; | |
326 | header->height = (headbuf[15] << 8) + headbuf[14]; | |
327 | header->bitsperpixel = headbuf[16]; | |
328 | header->imagedescriptor = headbuf[17]; | |
329 | } | |
330 | ||
331 | ||
63e0dc02 | 332 | /* this function should never produce diagnostics to stdout, maybe to the logfile */ |
84e51293 AMH |
333 | int |
334 | tga_header_verify(unsigned char headbuf[18]) { | |
335 | tga_header header; | |
336 | tga_header_unpack(&header, headbuf); | |
337 | switch (header.datatypecode) { | |
338 | default: | |
63e0dc02 | 339 | /*printf("bad typecode!\n");*/ |
84e51293 | 340 | return 0; |
84e51293 | 341 | case 1: /* Uncompressed, color-mapped images */ |
84e51293 AMH |
342 | case 3: /* Uncompressed, grayscale images */ |
343 | case 9: /* Compressed, color-mapped images */ | |
84e51293 | 344 | case 11: /* Compressed, grayscale images */ |
ea1136fc TC |
345 | if (header.bitsperpixel != 8) |
346 | return 0; | |
347 | break; | |
348 | case 0: | |
349 | case 2: /* Uncompressed, rgb images */ | |
350 | case 10: /* Compressed, rgb images */ | |
b14fce7a | 351 | if (header.bitsperpixel != 15 && header.bitsperpixel != 16 |
c5a24cca | 352 | && header.bitsperpixel != 24 && header.bitsperpixel != 32) |
b14fce7a TC |
353 | return 0; |
354 | break; | |
221cbd78 | 355 | } |
84e51293 AMH |
356 | |
357 | switch (header.colourmaptype) { | |
358 | default: | |
63e0dc02 | 359 | /*printf("bad colourmaptype!\n");*/ |
84e51293 | 360 | return 0; |
84e51293 | 361 | case 1: |
ea1136fc TC |
362 | if (header.datatypecode != 1 && header.datatypecode != 9) |
363 | return 0; /* only get a color map on a color mapped image */ | |
364 | case 0: | |
221cbd78 AMH |
365 | break; |
366 | } | |
ea1136fc TC |
367 | |
368 | switch (header.colourmapdepth) { | |
369 | default: | |
370 | return 0; | |
371 | case 0: /* can be 0 if no colour map */ | |
372 | case 15: | |
373 | case 16: | |
374 | case 24: | |
375 | case 32: | |
376 | break; | |
377 | } | |
84e51293 AMH |
378 | |
379 | return 1; | |
380 | } | |
381 | ||
382 | ||
febba01f AMH |
383 | /* |
384 | =item tga_header_pack(header, headbuf) | |
385 | ||
386 | Packs header structure into buffer for writing. | |
76ff75b8 | 387 | |
febba01f AMH |
388 | header - header structure |
389 | headbuf - buffer to pack into | |
76ff75b8 | 390 | |
febba01f AMH |
391 | =cut |
392 | */ | |
393 | ||
394 | static | |
395 | void | |
396 | tga_header_pack(tga_header *header, unsigned char headbuf[18]) { | |
397 | headbuf[0] = header->idlength; | |
398 | headbuf[1] = header->colourmaptype; | |
399 | headbuf[2] = header->datatypecode; | |
400 | headbuf[3] = header->colourmaporigin & 0xff; | |
401 | headbuf[4] = header->colourmaporigin >> 8; | |
402 | headbuf[5] = header->colourmaplength & 0xff; | |
403 | headbuf[6] = header->colourmaplength >> 8; | |
404 | headbuf[7] = header->colourmapdepth; | |
405 | headbuf[8] = header->x_origin & 0xff; | |
406 | headbuf[9] = header->x_origin >> 8; | |
407 | headbuf[10] = header->y_origin & 0xff; | |
408 | headbuf[11] = header->y_origin >> 8; | |
409 | headbuf[12] = header->width & 0xff; | |
410 | headbuf[13] = header->width >> 8; | |
411 | headbuf[14] = header->height & 0xff; | |
412 | headbuf[15] = header->height >> 8; | |
413 | headbuf[16] = header->bitsperpixel; | |
414 | headbuf[17] = header->imagedescriptor; | |
415 | } | |
416 | ||
417 | ||
418 | /* | |
419 | =item tga_source_read(s, buf, pixels) | |
420 | ||
421 | Reads pixel number of pixels from source s into buffer buf. Takes | |
422 | care of decompressing the stream if needed. | |
423 | ||
424 | s - data source | |
425 | buf - destination buffer | |
426 | pixels - number of pixels to put into buffer | |
427 | ||
428 | =cut | |
429 | */ | |
76ff75b8 | 430 | |
920aa4a5 AMH |
431 | static |
432 | int | |
433 | tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) { | |
434 | int cp = 0, j, k; | |
435 | if (!s->compressed) { | |
6d5c85a2 | 436 | if (i_io_read(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0; |
920aa4a5 AMH |
437 | return 1; |
438 | } | |
439 | ||
440 | while(cp < pixels) { | |
441 | int ml; | |
442 | if (s->len == 0) s->state = NoInit; | |
443 | switch (s->state) { | |
444 | case NoInit: | |
6d5c85a2 | 445 | if (i_io_read(s->ig, &s->hdr, 1) != 1) return 0; |
920aa4a5 AMH |
446 | |
447 | s->len = (s->hdr &~(1<<7))+1; | |
448 | s->state = (s->hdr & (1<<7)) ? Rle : Raw; | |
9a88a5e6 | 449 | { |
84e51293 | 450 | /* |
9a88a5e6 AMH |
451 | static cnt = 0; |
452 | printf("%04d %s: %d\n", cnt++, s->state==Rle?"RLE":"RAW", s->len); | |
84e51293 AMH |
453 | */ |
454 | } | |
6d5c85a2 | 455 | if (s->state == Rle && i_io_read(s->ig, s->cval, s->bytepp) != s->bytepp) return 0; |
920aa4a5 AMH |
456 | |
457 | break; | |
458 | case Rle: | |
b33c08f8 | 459 | ml = i_min(s->len, pixels-cp); |
920aa4a5 AMH |
460 | for(k=0; k<ml; k++) for(j=0; j<s->bytepp; j++) |
461 | buf[(cp+k)*s->bytepp+j] = s->cval[j]; | |
920aa4a5 AMH |
462 | cp += ml; |
463 | s->len -= ml; | |
464 | break; | |
465 | case Raw: | |
b33c08f8 | 466 | ml = i_min(s->len, pixels-cp); |
6d5c85a2 | 467 | if (i_io_read(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0; |
920aa4a5 AMH |
468 | cp += ml; |
469 | s->len -= ml; | |
470 | break; | |
471 | } | |
472 | } | |
473 | return 1; | |
474 | } | |
475 | ||
76ff75b8 AMH |
476 | |
477 | ||
76ff75b8 | 478 | |
febba01f AMH |
479 | /* |
480 | =item tga_dest_write(s, buf, pixels) | |
481 | ||
482 | Writes pixels from buf to destination s. Takes care of compressing if the | |
483 | destination is compressed. | |
484 | ||
485 | s - data destination | |
486 | buf - source buffer | |
487 | pixels - number of pixels to put write to destination | |
488 | ||
489 | =cut | |
76ff75b8 AMH |
490 | */ |
491 | ||
492 | static | |
493 | int | |
494 | tga_dest_write(tga_dest *s, unsigned char *buf, size_t pixels) { | |
63e0dc02 | 495 | int cp = 0; |
76ff75b8 AMH |
496 | |
497 | if (!s->compressed) { | |
6d5c85a2 | 498 | if (i_io_write(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0; |
76ff75b8 AMH |
499 | return 1; |
500 | } | |
501 | ||
502 | while(cp < pixels) { | |
503 | int tlen; | |
504 | int nxtrip = find_repeat(buf+cp*s->bytepp, pixels-cp, s->bytepp); | |
505 | tlen = (nxtrip == -1) ? pixels-cp : nxtrip; | |
506 | while(tlen) { | |
9a88a5e6 | 507 | unsigned char clen = (tlen>128) ? 128 : tlen; |
76ff75b8 | 508 | clen--; |
6d5c85a2 | 509 | if (i_io_write(s->ig, &clen, 1) != 1) return 0; |
76ff75b8 | 510 | clen++; |
6d5c85a2 | 511 | if (i_io_write(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0; |
76ff75b8 AMH |
512 | tlen -= clen; |
513 | cp += clen; | |
514 | } | |
515 | if (cp >= pixels) break; | |
516 | tlen = find_span(buf+cp*s->bytepp, pixels-cp, s->bytepp); | |
07d70837 | 517 | if (tlen <3) continue; |
76ff75b8 | 518 | while (tlen) { |
9a88a5e6 | 519 | unsigned char clen = (tlen>128) ? 128 : tlen; |
76ff75b8 | 520 | clen = (clen - 1) | 0x80; |
6d5c85a2 | 521 | if (i_io_write(s->ig, &clen, 1) != 1) return 0; |
76ff75b8 | 522 | clen = (clen & ~0x80) + 1; |
6d5c85a2 | 523 | if (i_io_write(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0; |
76ff75b8 AMH |
524 | tlen -= clen; |
525 | cp += clen; | |
526 | } | |
527 | } | |
528 | return 1; | |
529 | } | |
530 | ||
531 | ||
532 | ||
533 | ||
534 | ||
535 | ||
febba01f AMH |
536 | /* |
537 | =item tga_palette_read(ig, img, bytepp, colourmaplength) | |
538 | ||
539 | Reads the colormap from a tga file and stores in the paletted image | |
540 | structure. | |
76ff75b8 | 541 | |
febba01f AMH |
542 | ig - iolayer data source |
543 | img - image structure | |
544 | bytepp - bytes per pixel | |
545 | colourmaplength - number of colours in colourmap | |
546 | ||
547 | =cut | |
548 | */ | |
76ff75b8 | 549 | |
920aa4a5 AMH |
550 | static |
551 | int | |
552 | tga_palette_read(io_glue *ig, i_img *img, int bytepp, int colourmaplength) { | |
553 | int i; | |
554 | size_t palbsize; | |
555 | unsigned char *palbuf; | |
556 | i_color val; | |
557 | ||
558 | palbsize = colourmaplength*bytepp; | |
559 | palbuf = mymalloc(palbsize); | |
560 | ||
6d5c85a2 | 561 | if (i_io_read(ig, palbuf, palbsize) != palbsize) { |
1791cbdb | 562 | i_push_error(errno, "could not read targa colormap"); |
920aa4a5 AMH |
563 | return 0; |
564 | } | |
565 | ||
566 | /* populate the palette of the new image */ | |
567 | for(i=0; i<colourmaplength; i++) { | |
568 | color_unpack(palbuf+i*bytepp, bytepp, &val); | |
569 | i_addcolors(img, &val, 1); | |
570 | } | |
571 | myfree(palbuf); | |
572 | return 1; | |
573 | } | |
574 | ||
575 | ||
febba01f AMH |
576 | /* |
577 | =item tga_palette_write(ig, img, bitspp, colourmaplength) | |
578 | ||
579 | Stores the colormap of an image in the destination ig. | |
580 | ||
581 | ig - iolayer data source | |
582 | img - image structure | |
583 | bitspp - bits per pixel in colourmap | |
584 | colourmaplength - number of colours in colourmap | |
585 | ||
586 | =cut | |
587 | */ | |
920aa4a5 | 588 | |
76ff75b8 AMH |
589 | static |
590 | int | |
591 | tga_palette_write(io_glue *ig, i_img *img, int bitspp, int colourmaplength) { | |
592 | int i; | |
8d14daab | 593 | size_t bytepp = bpp_to_bytes(bitspp); |
76ff75b8 AMH |
594 | size_t palbsize = i_colorcount(img)*bytepp; |
595 | unsigned char *palbuf = mymalloc(palbsize); | |
596 | ||
597 | for(i=0; i<colourmaplength; i++) { | |
598 | i_color val; | |
599 | i_getcolors(img, i, &val, 1); | |
600 | color_pack(palbuf+i*bytepp, bitspp, &val); | |
601 | } | |
602 | ||
6d5c85a2 | 603 | if (i_io_write(ig, palbuf, palbsize) != palbsize) { |
1791cbdb | 604 | i_push_error(errno, "could not write targa colormap"); |
76ff75b8 AMH |
605 | return 0; |
606 | } | |
607 | myfree(palbuf); | |
608 | return 1; | |
609 | } | |
610 | ||
920aa4a5 | 611 | |
1ec86afa AMH |
612 | |
613 | /* | |
614 | =item i_readtga_wiol(ig, length) | |
615 | ||
febba01f AMH |
616 | Read in an image from the iolayer data source and return the image structure to it. |
617 | Returns NULL on error. | |
1ec86afa AMH |
618 | |
619 | ig - io_glue object | |
620 | length - maximum length to read from data source, before closing it -1 | |
621 | signifies no limit. | |
622 | ||
623 | =cut | |
624 | */ | |
625 | ||
626 | i_img * | |
627 | i_readtga_wiol(io_glue *ig, int length) { | |
07d70837 | 628 | i_img* img = NULL; |
a659442a | 629 | int x, y; |
1ec86afa | 630 | int width, height, channels; |
920aa4a5 | 631 | int mapped; |
07d70837 | 632 | char *idstring = NULL; |
1ec86afa | 633 | |
920aa4a5 | 634 | tga_source src; |
1ec86afa | 635 | tga_header header; |
1ec86afa | 636 | unsigned char headbuf[18]; |
7c58edfc | 637 | unsigned char *databuf; |
920aa4a5 AMH |
638 | |
639 | i_color *linebuf = NULL; | |
1ec86afa AMH |
640 | i_clear_error(); |
641 | ||
642 | mm_log((1,"i_readtga(ig %p, length %d)\n", ig, length)); | |
643 | ||
6d5c85a2 | 644 | if (i_io_read(ig, &headbuf, 18) != 18) { |
1ec86afa AMH |
645 | i_push_error(errno, "could not read targa header"); |
646 | return NULL; | |
647 | } | |
648 | ||
76ff75b8 | 649 | tga_header_unpack(&header, headbuf); |
1ec86afa AMH |
650 | |
651 | mm_log((1,"Id length: %d\n",header.idlength)); | |
652 | mm_log((1,"Colour map type: %d\n",header.colourmaptype)); | |
653 | mm_log((1,"Image type: %d\n",header.datatypecode)); | |
654 | mm_log((1,"Colour map offset: %d\n",header.colourmaporigin)); | |
655 | mm_log((1,"Colour map length: %d\n",header.colourmaplength)); | |
656 | mm_log((1,"Colour map depth: %d\n",header.colourmapdepth)); | |
657 | mm_log((1,"X origin: %d\n",header.x_origin)); | |
658 | mm_log((1,"Y origin: %d\n",header.y_origin)); | |
659 | mm_log((1,"Width: %d\n",header.width)); | |
660 | mm_log((1,"Height: %d\n",header.height)); | |
661 | mm_log((1,"Bits per pixel: %d\n",header.bitsperpixel)); | |
662 | mm_log((1,"Descriptor: %d\n",header.imagedescriptor)); | |
663 | ||
1ec86afa | 664 | if (header.idlength) { |
8d14daab | 665 | /* max of 256, so this is safe */ |
1ec86afa | 666 | idstring = mymalloc(header.idlength+1); |
6d5c85a2 | 667 | if (i_io_read(ig, idstring, header.idlength) != header.idlength) { |
1ec86afa AMH |
668 | i_push_error(errno, "short read on targa idstring"); |
669 | return NULL; | |
670 | } | |
1ec86afa AMH |
671 | } |
672 | ||
673 | width = header.width; | |
674 | height = header.height; | |
77157728 | 675 | |
1ec86afa AMH |
676 | |
677 | /* Set tags here */ | |
920aa4a5 | 678 | |
1ec86afa | 679 | switch (header.datatypecode) { |
1ec86afa AMH |
680 | case 0: /* No data in image */ |
681 | i_push_error(0, "Targa image contains no image data"); | |
07d70837 | 682 | if (idstring) myfree(idstring); |
1ec86afa AMH |
683 | return NULL; |
684 | break; | |
920aa4a5 AMH |
685 | case 1: /* Uncompressed, color-mapped images */ |
686 | case 9: /* Compressed, color-mapped images */ | |
687 | case 3: /* Uncompressed, grayscale images */ | |
688 | case 11: /* Compressed, grayscale images */ | |
1ec86afa | 689 | if (header.bitsperpixel != 8) { |
920aa4a5 | 690 | i_push_error(0, "Targa: mapped/grayscale image's bpp is not 8, unsupported."); |
07d70837 | 691 | if (idstring) myfree(idstring); |
1ec86afa AMH |
692 | return NULL; |
693 | } | |
920aa4a5 | 694 | src.bytepp = 1; |
1ec86afa | 695 | break; |
920aa4a5 AMH |
696 | case 2: /* Uncompressed, rgb images */ |
697 | case 10: /* Compressed, rgb images */ | |
698 | if ((src.bytepp = bpp_to_bytes(header.bitsperpixel))) | |
699 | break; | |
700 | i_push_error(0, "Targa: direct color image's bpp is not 15/16/24/32 - unsupported."); | |
07d70837 | 701 | if (idstring) myfree(idstring); |
1ec86afa AMH |
702 | return NULL; |
703 | break; | |
704 | case 32: /* Compressed color-mapped, Huffman, Delta and runlength */ | |
1ec86afa | 705 | case 33: /* Compressed color-mapped, Huffman, Delta and runlength */ |
920aa4a5 | 706 | i_push_error(0, "Unsupported Targa (Huffman/delta/rle/quadtree) subformat is not supported"); |
07d70837 | 707 | if (idstring) myfree(idstring); |
1ec86afa AMH |
708 | return NULL; |
709 | break; | |
710 | default: /* All others which we don't know which might be */ | |
711 | i_push_error(0, "Unknown targa format"); | |
07d70837 | 712 | if (idstring) myfree(idstring); |
1ec86afa AMH |
713 | return NULL; |
714 | break; | |
715 | } | |
920aa4a5 AMH |
716 | |
717 | src.state = NoInit; | |
718 | src.len = 0; | |
719 | src.ig = ig; | |
720 | src.compressed = !!(header.datatypecode & (1<<3)); | |
721 | ||
722 | /* Determine number of channels */ | |
723 | ||
724 | mapped = 1; | |
725 | switch (header.datatypecode) { | |
affbad7a TC |
726 | case 2: /* Uncompressed, rgb images */ /* FALLTHROUGH */ |
727 | case 10: /* Compressed, rgb images */ /* FALLTHROUGH */ | |
920aa4a5 AMH |
728 | mapped = 0; |
729 | case 1: /* Uncompressed, color-mapped images */ | |
730 | case 9: /* Compressed, color-mapped images */ | |
731 | if ((channels = bpp_to_channels(mapped ? | |
732 | header.colourmapdepth : | |
c5a24cca TC |
733 | header.bitsperpixel, |
734 | header.imagedescriptor & 0xF))) break; | |
920aa4a5 | 735 | i_push_error(0, "Targa Image has none of 15/16/24/32 pixel layout"); |
07d70837 | 736 | if (idstring) myfree(idstring); |
920aa4a5 AMH |
737 | return NULL; |
738 | break; | |
affbad7a | 739 | case 3: /* Uncompressed, grayscale images */ /* FALLTHROUGH */ |
920aa4a5 AMH |
740 | case 11: /* Compressed, grayscale images */ |
741 | mapped = 0; | |
742 | channels = 1; | |
743 | break; | |
b07bc64b TC |
744 | default: |
745 | i_push_error(0, "invalid or unsupported datatype code"); | |
746 | return NULL; | |
920aa4a5 | 747 | } |
77157728 TC |
748 | |
749 | if (!i_int_check_image_file_limits(width, height, channels, | |
750 | sizeof(i_sample_t))) { | |
751 | mm_log((1, "i_readtga_wiol: image size exceeds limits\n")); | |
752 | return NULL; | |
753 | } | |
920aa4a5 AMH |
754 | |
755 | img = mapped ? | |
756 | i_img_pal_new(width, height, channels, 256) : | |
757 | i_img_empty_ch(NULL, width, height, channels); | |
fe055ff6 TC |
758 | |
759 | if (!img) { | |
760 | if (idstring) | |
761 | myfree(idstring); | |
762 | return NULL; | |
763 | } | |
920aa4a5 | 764 | |
07d70837 AMH |
765 | if (idstring) { |
766 | i_tags_add(&img->tags, "tga_idstring", 0, idstring, header.idlength, 0); | |
4dfa5522 | 767 | myfree(idstring); |
07d70837 AMH |
768 | } |
769 | ||
920aa4a5 AMH |
770 | if (mapped && |
771 | !tga_palette_read(ig, | |
772 | img, | |
773 | bpp_to_bytes(header.colourmapdepth), | |
774 | header.colourmaplength) | |
775 | ) { | |
1791cbdb | 776 | /* tga_palette_read() sets a message */ |
07d70837 | 777 | if (img) i_img_destroy(img); |
920aa4a5 AMH |
778 | return NULL; |
779 | } | |
780 | ||
781 | /* Allocate buffers */ | |
8d14daab | 782 | /* width is max 0xffff, src.bytepp is max 4, so this is safe */ |
920aa4a5 | 783 | databuf = mymalloc(width*src.bytepp); |
8d14daab | 784 | /* similarly here */ |
920aa4a5 AMH |
785 | if (!mapped) linebuf = mymalloc(width*sizeof(i_color)); |
786 | ||
787 | for(y=0; y<height; y++) { | |
788 | if (!tga_source_read(&src, databuf, width)) { | |
789 | i_push_error(errno, "read for targa data failed"); | |
02576e8d | 790 | if (linebuf) myfree(linebuf); |
920aa4a5 | 791 | myfree(databuf); |
07d70837 | 792 | if (img) i_img_destroy(img); |
920aa4a5 AMH |
793 | return NULL; |
794 | } | |
7456c26c | 795 | if (mapped && header.colourmaporigin) for(x=0; x<width; x++) databuf[x] -= header.colourmaporigin; |
920aa4a5 AMH |
796 | if (mapped) i_ppal(img, 0, width, header.imagedescriptor & (1<<5) ? y : height-1-y, databuf); |
797 | else { | |
798 | for(x=0; x<width; x++) color_unpack(databuf+x*src.bytepp, src.bytepp, linebuf+x); | |
799 | i_plin(img, 0, width, header.imagedescriptor & (1<<5) ? y : height-1-y, linebuf); | |
800 | } | |
801 | } | |
802 | myfree(databuf); | |
803 | if (linebuf) myfree(linebuf); | |
07d70837 | 804 | |
fe055ff6 | 805 | i_tags_add(&img->tags, "i_format", 0, "tga", -1, 0); |
07d70837 AMH |
806 | i_tags_addn(&img->tags, "tga_bitspp", 0, mapped?header.colourmapdepth:header.bitsperpixel); |
807 | if (src.compressed) i_tags_addn(&img->tags, "compressed", 0, 1); | |
920aa4a5 | 808 | return img; |
1ec86afa AMH |
809 | } |
810 | ||
811 | ||
812 | ||
febba01f AMH |
813 | /* |
814 | =item i_writetga_wiol(img, ig) | |
1ec86afa | 815 | |
febba01f | 816 | Writes an image in targa format. Returns 0 on error. |
1ec86afa | 817 | |
febba01f AMH |
818 | img - image to store |
819 | ig - io_glue object | |
1ec86afa | 820 | |
febba01f AMH |
821 | =cut |
822 | */ | |
1ec86afa AMH |
823 | |
824 | undef_int | |
febba01f | 825 | i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idstring, size_t idlen) { |
7456c26c | 826 | tga_header header; |
76ff75b8 | 827 | tga_dest dest; |
7456c26c | 828 | unsigned char headbuf[18]; |
76ff75b8 | 829 | unsigned int bitspp; |
c5a24cca | 830 | unsigned int attr_bits = 0; |
76ff75b8 | 831 | |
76ff75b8 | 832 | int mapped; |
1ec86afa | 833 | |
76ff75b8 | 834 | /* parameters */ |
febba01f AMH |
835 | |
836 | /* | |
837 | int compress = 1; | |
838 | char *idstring = "testing"; | |
839 | int wierdpack = 0; | |
840 | */ | |
1ec86afa | 841 | |
76ff75b8 AMH |
842 | idlen = strlen(idstring); |
843 | mapped = img->type == i_palette_type; | |
1ec86afa | 844 | |
8d14daab TC |
845 | mm_log((1,"i_writetga_wiol(img %p, ig %p, idstring %p, idlen %ld, wierdpack %d, compress %d)\n", |
846 | img, ig, idstring, (long)idlen, wierdpack, compress)); | |
7456c26c AMH |
847 | mm_log((1, "virtual %d, paletted %d\n", img->virtual, mapped)); |
848 | mm_log((1, "channels %d\n", img->channels)); | |
76ff75b8 AMH |
849 | |
850 | i_clear_error(); | |
8d14daab TC |
851 | |
852 | if (img->xsize > TGA_MAX_DIM || img->ysize > TGA_MAX_DIM) { | |
853 | i_push_error(0, "image too large for TGA"); | |
854 | return 0; | |
855 | } | |
c5a24cca | 856 | |
76ff75b8 AMH |
857 | switch (img->channels) { |
858 | case 1: | |
859 | bitspp = 8; | |
860 | if (wierdpack) { | |
861 | mm_log((1,"wierdpack option ignored for 1 channel images\n")); | |
862 | wierdpack=0; | |
863 | } | |
864 | break; | |
865 | case 2: | |
866 | i_push_error(0, "Cannot store 2 channel image in targa format"); | |
867 | return 0; | |
868 | break; | |
869 | case 3: | |
870 | bitspp = wierdpack ? 15 : 24; | |
871 | break; | |
872 | case 4: | |
873 | bitspp = wierdpack ? 16 : 32; | |
c5a24cca | 874 | attr_bits = wierdpack ? 1 : 8; |
76ff75b8 AMH |
875 | break; |
876 | default: | |
877 | i_push_error(0, "Targa only handles 1,3 and 4 channel images."); | |
878 | return 0; | |
879 | } | |
7456c26c | 880 | |
7456c26c AMH |
881 | header.idlength = idlen; |
882 | header.colourmaptype = mapped ? 1 : 0; | |
883 | header.datatypecode = mapped ? 1 : img->channels == 1 ? 3 : 2; | |
7456c26c | 884 | header.datatypecode += compress ? 8 : 0; |
76ff75b8 | 885 | mm_log((1, "datatypecode %d\n", header.datatypecode)); |
7456c26c AMH |
886 | header.colourmaporigin = 0; |
887 | header.colourmaplength = mapped ? i_colorcount(img) : 0; | |
76ff75b8 | 888 | header.colourmapdepth = mapped ? bitspp : 0; |
7456c26c AMH |
889 | header.x_origin = 0; |
890 | header.y_origin = 0; | |
891 | header.width = img->xsize; | |
892 | header.height = img->ysize; | |
76ff75b8 | 893 | header.bitsperpixel = mapped ? 8 : bitspp; |
c5a24cca | 894 | header.imagedescriptor = (1<<5) | attr_bits; /* normal order instead of upside down */ |
7456c26c | 895 | |
76ff75b8 | 896 | tga_header_pack(&header, headbuf); |
7456c26c | 897 | |
6d5c85a2 | 898 | if (i_io_write(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) { |
7456c26c AMH |
899 | i_push_error(errno, "could not write targa header"); |
900 | return 0; | |
7c58edfc AMH |
901 | } |
902 | ||
7456c26c | 903 | if (idlen) { |
6d5c85a2 | 904 | if (i_io_write(ig, idstring, idlen) != idlen) { |
7456c26c AMH |
905 | i_push_error(errno, "could not write targa idstring"); |
906 | return 0; | |
1ec86afa AMH |
907 | } |
908 | } | |
7456c26c | 909 | |
76ff75b8 AMH |
910 | /* Make this into a constructor? */ |
911 | dest.compressed = compress; | |
912 | dest.bytepp = mapped ? 1 : bpp_to_bytes(bitspp); | |
913 | dest.ig = ig; | |
c95846c2 | 914 | |
76ff75b8 AMH |
915 | mm_log((1, "dest.compressed = %d\n", dest.compressed)); |
916 | mm_log((1, "dest.bytepp = %d\n", dest.bytepp)); | |
c95846c2 | 917 | |
76ff75b8 | 918 | if (img->type == i_palette_type) { |
76ff75b8 AMH |
919 | if (!tga_palette_write(ig, img, bitspp, i_colorcount(img))) return 0; |
920 | ||
921 | if (!img->virtual && !dest.compressed) { | |
6d5c85a2 | 922 | if (i_io_write(ig, img->idata, img->bytes) != img->bytes) { |
7456c26c AMH |
923 | i_push_error(errno, "could not write targa image data"); |
924 | return 0; | |
1ec86afa | 925 | } |
7456c26c AMH |
926 | } else { |
927 | int y; | |
928 | i_palidx *vals = mymalloc(sizeof(i_palidx)*img->xsize); | |
929 | for(y=0; y<img->ysize; y++) { | |
930 | i_gpal(img, 0, img->xsize, y, vals); | |
76ff75b8 | 931 | tga_dest_write(&dest, vals, img->xsize); |
1ec86afa | 932 | } |
7456c26c | 933 | myfree(vals); |
1ec86afa | 934 | } |
76ff75b8 AMH |
935 | } else { /* direct type */ |
936 | int x, y; | |
8d14daab TC |
937 | size_t bytepp = wierdpack ? 2 : bpp_to_bytes(bitspp); |
938 | size_t lsize = bytepp * img->xsize; | |
76ff75b8 AMH |
939 | i_color *vals = mymalloc(img->xsize*sizeof(i_color)); |
940 | unsigned char *buf = mymalloc(lsize); | |
941 | ||
7456c26c | 942 | for(y=0; y<img->ysize; y++) { |
76ff75b8 AMH |
943 | i_glin(img, 0, img->xsize, y, vals); |
944 | for(x=0; x<img->xsize; x++) color_pack(buf+x*bytepp, bitspp, vals+x); | |
945 | tga_dest_write(&dest, buf, img->xsize); | |
1ec86afa | 946 | } |
76ff75b8 AMH |
947 | myfree(buf); |
948 | myfree(vals); | |
1ec86afa | 949 | } |
10461f9a | 950 | |
6d5c85a2 TC |
951 | if (i_io_close(ig)) |
952 | return 0; | |
10461f9a | 953 | |
7456c26c | 954 | return 1; |
1ec86afa | 955 | } |
b8c2033e AMH |
956 | |
957 | /* | |
958 | =back | |
959 | ||
960 | =head1 AUTHOR | |
961 | ||
962 | Arnar M. Hrafnkelsson <addi@umich.edu> | |
963 | ||
964 | =head1 SEE ALSO | |
965 | ||
966 | Imager(3) | |
967 | ||
968 | =cut | |
969 | */ |