comparison tools/libgfx.c @ 1307:43b13dbbdcd1

Moved libgfx to tools/ as it's not really a very generic piece of code that belongs to the engine ..
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 19 Aug 2017 15:18:29 +0300
parents src/libgfx.c@e968b259605b
children 8f71ca1900ea
comparison
equal deleted inserted replaced
1306:696c58784635 1307:43b13dbbdcd1
1 /*
2 * Functions for reading and converting various restricted
3 * C64/etc and/or indexed/paletted graphics formats.
4 * Programmed and designed by Matti 'ccr' Hamalainen
5 * (C) Copyright 2012 Tecnic Software productions (TNSP)
6 *
7 * Please read file 'COPYING' for information on license and distribution.
8 */
9 #include "libgfx.h"
10 #include "dmfile.h"
11 #include "dmbstr.h"
12
13 #ifdef DM_USE_LIBPNG
14 #include <png.h>
15 #endif
16
17
18 int dmGFXErrorMode = DM_ERRMODE_FAIL;
19
20
21
22 BOOL dmCompareColor(const DMColor *c1, const DMColor *c2, BOOL alpha)
23 {
24 if (c1->r == c2->r &&
25 c1->g == c2->g &&
26 c1->b == c2->b)
27 return alpha ? (c1->a == c2->a) : TRUE;
28 else
29 return FALSE;
30 }
31
32
33 int dmImageGetBytesPerPixel(const int format)
34 {
35 switch (format)
36 {
37 case DM_IFMT_PALETTE : return 1;
38 case DM_IFMT_RGB : return 3;
39 case DM_IFMT_RGBA : return 4;
40 default: return -1;
41 }
42 }
43
44
45 DMImage * dmImageAlloc(const int width, const int height, const int format, const int bpp)
46 {
47 DMImage *img = dmMalloc0(sizeof(DMImage));
48 if (img == NULL)
49 return NULL;
50
51 img->width = width;
52 img->height = height;
53 img->format = format;
54 img->bpp = (bpp <= 0) ? dmImageGetBytesPerPixel(format) * 8 : bpp;
55 img->pitch = width * img->bpp;
56 img->size = img->pitch * img->height;
57 img->ctransp = -1;
58
59 if ((img->data = dmMalloc(img->size)) == NULL)
60 {
61 dmFree(img);
62 return NULL;
63 }
64
65 return img;
66 }
67
68
69 void dmImageFree(DMImage *img)
70 {
71 if (img != NULL)
72 {
73 if (!img->constpal)
74 {
75 dmFree(img->pal);
76 }
77 dmFree(img->data);
78 dmFree(img);
79 }
80 }
81
82
83 BOOL dmPaletteAlloc(DMColor **ppal, int ncolors, int ctransp)
84 {
85 int i;
86
87 if (ppal == NULL)
88 return FALSE;
89
90 // Allocate desired amount of palette
91 if ((*ppal = dmCalloc(ncolors, sizeof(DMColor))) == NULL)
92 return FALSE;
93
94 // Set alpha values to max, except for transparent color
95 for (i = 0; i < ncolors; i++)
96 {
97 (*ppal)[i].a = (i == ctransp) ? 0 : 255;
98 }
99
100 return TRUE;
101 }
102
103
104 BOOL dmImageAllocPalette(DMImage *img, int ncolors, int ctransp)
105 {
106 if (img == NULL)
107 return FALSE;
108
109 img->ncolors = ncolors;
110 img->ctransp = ctransp;
111 return dmPaletteAlloc(&(img->pal), ncolors, ctransp);
112 }
113
114
115 static BOOL dmReadPaletteData(FILE *fp, DMColor *pal, int ncolors)
116 {
117 int i;
118
119 for (i = 0; i < ncolors; i++)
120 {
121 Uint8 colR, colG, colB;
122 if (!dm_fread_byte(fp, &colR) ||
123 !dm_fread_byte(fp, &colG) ||
124 !dm_fread_byte(fp, &colB))
125 return FALSE;
126
127 pal[i].r = colR;
128 pal[i].g = colG;
129 pal[i].b = colB;
130 }
131
132 return TRUE;
133 }
134
135
136 int dmWriteImageData(DMImage *img, void *cbdata, int (*writeRowCB)(void *, const Uint8 *, const size_t), const DMImageConvSpec *spec)
137 {
138 int x, y, yscale, xscale, res = 0, rowSize, rowWidth;
139 Uint8 *row = NULL;
140
141 // Allocate memory for row buffer
142 rowWidth = img->width * spec->scaleX;
143 rowSize = rowWidth * dmImageGetBytesPerPixel(spec->format);
144
145 if ((row = dmMalloc(rowSize + 16)) == NULL)
146 {
147 res = DMERR_MALLOC;
148 goto done;
149 }
150
151 // Generate the image
152 for (y = 0; y < img->height; y++)
153 {
154 Uint8 *ptr1 = row,
155 *ptr2 = ptr1 + rowWidth,
156 *ptr3 = ptr2 + rowWidth,
157 *ptr4 = ptr3 + rowWidth;
158
159 for (x = 0; x < img->width; x++)
160 {
161 Uint8 c = img->data[(y * img->pitch) + (x * img->bpp) / 8],
162 qr, qg, qb, qa;
163
164 switch (spec->format)
165 {
166 case DM_IFMT_PALETTE:
167 for (xscale = 0; xscale < spec->scaleX; xscale++)
168 *ptr1++ = c;
169 break;
170
171 case DM_IFMT_RGBA:
172 qr = img->pal[c].r;
173 qg = img->pal[c].g;
174 qb = img->pal[c].b;
175 qa = img->pal[c].a;
176
177 if (spec->planar)
178 {
179 for (xscale = 0; xscale < spec->scaleX; xscale++)
180 {
181 *ptr1++ = qr;
182 *ptr2++ = qg;
183 *ptr3++ = qb;
184 *ptr4++ = qa;
185 }
186 }
187 else
188 {
189 for (xscale = 0; xscale < spec->scaleX; xscale++)
190 {
191 *ptr1++ = qr;
192 *ptr1++ = qg;
193 *ptr1++ = qb;
194 *ptr1++ = qa;
195 }
196 }
197 break;
198
199 case DM_IFMT_RGB:
200 qr = img->pal[c].r;
201 qg = img->pal[c].g;
202 qb = img->pal[c].b;
203
204 if (spec->planar)
205 {
206 for (xscale = 0; xscale < spec->scaleX; xscale++)
207 {
208 *ptr1++ = qr;
209 *ptr2++ = qg;
210 *ptr3++ = qb;
211 }
212 }
213 else
214 {
215 for (xscale = 0; xscale < spec->scaleX; xscale++)
216 {
217 *ptr1++ = qr;
218 *ptr1++ = qg;
219 *ptr1++ = qb;
220 }
221 }
222 break;
223 }
224 }
225
226 for (yscale = 0; yscale < spec->scaleY; yscale++)
227 {
228 if ((res = writeRowCB(cbdata, row, rowSize)) != DMERR_OK)
229 goto done;
230 }
231 }
232
233 done:
234 dmFree(row);
235 return res;
236 }
237
238
239 #define DMCOL(x) (((x) >> 4) & 0xf)
240
241 int dmWriteIFFMasterRAWPalette(FILE *fp, DMImage *img, int ncolors,
242 const char *indent, const char *type)
243 {
244 int i;
245
246 for (i = 0; i < ncolors; i++)
247 {
248 int color;
249 if (i < img->ncolors)
250 {
251 color = (DMCOL(img->pal[i].r) << 8) |
252 (DMCOL(img->pal[i].g) << 4) |
253 (DMCOL(img->pal[i].b));
254 }
255 else
256 color = 0;
257
258 fprintf(fp, "%s%s $%04X\n",
259 indent != NULL ? indent : "\t",
260 type != NULL ? type : "dc.w",
261 color);
262 }
263
264 return DMERR_OK;
265 }
266
267
268 int dmWriteRAWImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec)
269 {
270 int xc, yc, plane, res;
271 DMBitStreamContext bs;
272
273 if ((res = dmInitBitStreamFILE(&bs, fp)) != DMERR_OK)
274 return res;
275
276 if (spec->planar)
277 {
278 // Output bitplanes in planerd format (each plane of line sequentially)
279 for (yc = 0; yc < img->height; yc++)
280 {
281 for (plane = 0; plane < spec->nplanes; plane++)
282 {
283 Uint8 *sp = img->data + yc * img->pitch;
284 for (xc = 0; xc < img->width; xc++)
285 {
286 if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1))
287 return DMERR_FWRITE;
288 }
289 }
290 }
291 }
292 else
293 {
294 // Output each bitplane in sequence
295 for (plane = 0; plane < spec->nplanes; plane++)
296 {
297 for (yc = 0; yc < img->height; yc++)
298 {
299 Uint8 *sp = img->data + yc * img->pitch;
300 for (xc = 0; xc < img->width; xc++)
301 {
302 if (!dmPutBits(&bs, (sp[xc] & (1 << plane)) ? 1 : 0, 1))
303 return DMERR_FWRITE;
304 }
305 }
306 }
307 }
308
309 return dmFlushBitStream(&bs);
310 }
311
312
313 int dmWriteRAWImage(const char *filename, DMImage *img, const DMImageConvSpec *spec)
314 {
315 FILE *fp;
316 int res;
317
318 if ((fp = fopen(filename, "wb")) == NULL)
319 {
320 return dmError(DMERR_FOPEN,
321 "RAW: Could not open file '%s' for writing.\n",
322 filename);
323 }
324
325 res = dmWriteRAWImageFILE(fp, img, spec);
326
327 fclose(fp);
328 return res;
329 }
330
331
332 static int dmWritePPMRow(void *cbdata, const Uint8 *row, const size_t len)
333 {
334 if (fwrite(row, sizeof(Uint8), len, (FILE *) cbdata) == len)
335 return DMERR_OK;
336 else
337 return DMERR_FWRITE;
338 }
339
340
341 int dmWritePPMImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec)
342 {
343 DMImageConvSpec tmpSpec;
344
345 // Write PPM header
346 fprintf(fp,
347 "P6\n%d %d\n255\n",
348 img->width * spec->scaleX,
349 img->height * spec->scaleY);
350
351 // Write image data
352 memcpy(&tmpSpec, spec, sizeof(DMImageConvSpec));
353 tmpSpec.format = DM_IFMT_RGB;
354 return dmWriteImageData(img, (void *) fp, dmWritePPMRow, &tmpSpec);
355 }
356
357
358 int dmWritePPMImage(const char *filename, DMImage *img, const DMImageConvSpec *spec)
359 {
360 FILE *fp;
361 int res;
362
363 // Create output file
364 if ((fp = fopen(filename, "wb")) == NULL)
365 {
366 return dmError(DMERR_FOPEN,
367 "PPM: could not open file '%s' for writing.\n",
368 filename);
369 }
370
371 res = dmWritePPMImageFILE(fp, img, spec);
372
373 fclose(fp);
374 return res;
375 }
376
377
378 #ifdef DM_USE_LIBPNG
379 static int dmWritePNGRow(void *cbdata, const Uint8 *row, const size_t len)
380 {
381 png_structp png_ptr = cbdata;
382 (void) len;
383
384 if (setjmp(png_jmpbuf(png_ptr)))
385 return DMERR_INTERNAL;
386
387 png_write_row(png_ptr, row);
388
389 return DMERR_OK;
390 }
391
392
393 int dmWritePNGImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *spec)
394 {
395 png_structp png_ptr = NULL;
396 png_infop info_ptr = NULL;
397 int fmt, res;
398
399 // Create PNG structures
400 png_ptr = png_create_write_struct(
401 PNG_LIBPNG_VER_STRING,
402 NULL, NULL, NULL);
403
404 if (png_ptr == NULL)
405 {
406 res = dmError(DMERR_MALLOC,
407 "PNG: png_create_write_struct() failed.\n");
408 goto error;
409 }
410
411 info_ptr = png_create_info_struct(png_ptr);
412 if (info_ptr == NULL)
413 {
414 res = dmError(DMERR_INIT_FAIL,
415 "PNG: png_create_info_struct(%p) failed.\n",
416 png_ptr);
417 goto error;
418 }
419
420 if (setjmp(png_jmpbuf(png_ptr)))
421 {
422 res = dmError(DMERR_INIT_FAIL,
423 "PNG: Error during image writing..\n");
424 goto error;
425 }
426
427 res = DMERR_OK;
428 png_init_io(png_ptr, fp);
429
430 // Write PNG header info
431 switch (spec->format)
432 {
433 case DM_IFMT_PALETTE: fmt = PNG_COLOR_TYPE_PALETTE; break;
434 case DM_IFMT_RGB : fmt = PNG_COLOR_TYPE_RGB; break;
435 case DM_IFMT_RGBA : fmt = PNG_COLOR_TYPE_RGB_ALPHA; break;
436 default:
437 res = dmError(DMERR_NOT_SUPPORTED,
438 "PNG: Unsupported image format %d.\n",
439 spec->format);
440 goto error;
441 }
442
443 png_set_IHDR(png_ptr, info_ptr,
444 img->width * spec->scaleX,
445 img->height * spec->scaleY,
446 8, /* bits per component */
447 fmt,
448 PNG_INTERLACE_NONE,
449 PNG_COMPRESSION_TYPE_DEFAULT,
450 PNG_FILTER_TYPE_DEFAULT);
451
452 dmMsg(2, "PNG: %d x %d, depth=%d, type=%d\n",
453 img->width * spec->scaleX,
454 img->height * spec->scaleY,
455 8, fmt);
456
457 // Palette
458 if (spec->format == DM_IFMT_PALETTE)
459 {
460 int i;
461 png_colorp palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
462
463 if (palette == NULL)
464 {
465 res = dmError(DMERR_MALLOC,
466 "PNG: Could not allocate palette structure.");
467 goto error;
468 }
469
470 dmMemset(palette, 0, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
471
472 for (i = 0; i < img->ncolors; i++)
473 {
474 palette[i].red = img->pal[i].r;
475 palette[i].green = img->pal[i].g;
476 palette[i].blue = img->pal[i].b;
477 }
478
479 png_set_PLTE(png_ptr, info_ptr, palette, img->ncolors);
480 }
481
482 // png_set_gAMA(png_ptr, info_ptr, 2.2);
483
484 png_write_info(png_ptr, info_ptr);
485
486
487 // Write compressed image data
488 dmWriteImageData(img, (void *) png_ptr, dmWritePNGRow, spec);
489
490 // Write footer
491 png_write_end(png_ptr, NULL);
492
493 error:
494 if (png_ptr && info_ptr)
495 png_destroy_write_struct(&png_ptr, &info_ptr);
496
497 return res;
498 }
499
500
501 int dmWritePNGImage(const char *filename, DMImage *img, const DMImageConvSpec *spec)
502 {
503 int res;
504 FILE *fp;
505
506 if ((fp = fopen(filename, "wb")) == NULL)
507 {
508 return dmError(DMERR_FOPEN,
509 "PNG: could not open file '%s' for writing.\n",
510 filename);
511 }
512
513 res = dmWritePNGImageFILE(fp, img, spec);
514
515 fclose(fp);
516 return res;
517 }
518
519
520 int dmReadPNGImageFILE(FILE *fp, DMImage **pimg)
521 {
522 png_structp png_ptr = NULL;
523 png_infop info_ptr = NULL;
524 png_colorp palette = NULL;
525 png_bytep *row_pointers = NULL;
526 png_bytep trans = NULL;
527 png_uint_32 width, height;
528 int i, bit_depth, color_type, ncolors, ntrans;
529 int res = DMERR_OK;
530 DMImage *img;
531
532 // Create PNG structures
533 png_ptr = png_create_read_struct(
534 PNG_LIBPNG_VER_STRING,
535 NULL, NULL, NULL);
536
537 if (png_ptr == NULL)
538 {
539 res = dmError(DMERR_MALLOC,
540 "PNG: png_create_write_struct() failed.\n");
541 goto error;
542 }
543
544 info_ptr = png_create_info_struct(png_ptr);
545 if (info_ptr == NULL)
546 {
547 res = dmError(DMERR_INIT_FAIL,
548 "PNG: png_create_info_struct(%p) failed.\n",
549 png_ptr);
550 goto error;
551 }
552
553 if (setjmp(png_jmpbuf(png_ptr)))
554 {
555 res = dmError(DMERR_INIT_FAIL,
556 "PNG: Error during image reading.\n");
557 goto error;
558 }
559
560 png_init_io(png_ptr, fp);
561
562 // Read image information
563 png_read_info(png_ptr, info_ptr);
564
565 png_get_IHDR(png_ptr, info_ptr, &width, &height,
566 &bit_depth, &color_type, NULL, NULL, NULL);
567
568 if (width < 1 || height < 1)
569 {
570 res = dmError(DMERR_INVALID_DATA,
571 "PNG: Invalid width or height (%d x %d)\n",
572 width, height);
573 goto error;
574 }
575
576 switch (color_type)
577 {
578 case PNG_COLOR_TYPE_GRAY:
579 if (bit_depth < 8)
580 png_set_expand_gray_1_2_4_to_8(png_ptr);
581
582 if (bit_depth > 8)
583 {
584 res = dmError(DMERR_NOT_SUPPORTED,
585 "PNG: Unsupported bit depth for grayscale image: %d\n",
586 bit_depth);
587 goto error;
588 }
589 break;
590
591 case PNG_COLOR_TYPE_PALETTE:
592 png_set_packing(png_ptr);
593 break;
594
595 default:
596 res = dmError(DMERR_NOT_SUPPORTED,
597 "PNG: RGB/RGBA images not supported for loading.\n");
598 goto error;
599 }
600
601 // Allocate image
602 dmMsg(2, "PNG: %d x %d, depth=%d, type=%d\n",
603 width, height, bit_depth, color_type);
604
605 if ((*pimg = img = dmImageAlloc(width, height,
606 DM_IFMT_PALETTE,
607 // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
608 -1 /* bit_depth */)) == NULL)
609 {
610 res = dmError(DMERR_MALLOC,
611 "PNG: Could not allocate image data.\n");
612 goto error;
613 }
614
615 // ...
616 row_pointers = png_malloc(png_ptr, height * sizeof(png_bytep));
617 for (i = 0; i < img->height; i++)
618 row_pointers[i] = img->data + (i * img->pitch);
619
620 png_read_image(png_ptr, row_pointers);
621
622 png_read_end(png_ptr, NULL);
623
624 // Create palette
625 switch (color_type)
626 {
627 case PNG_COLOR_TYPE_GRAY:
628 ncolors = 256;
629 dmMsg(2, "PNG: Generating %d color grayscale palette.\n", ncolors);
630
631 if (!dmImageAllocPalette(img, ncolors, -1))
632 {
633 res = DMERR_MALLOC;
634 goto error;
635 }
636
637 for (i = 0; i < img->ncolors; i++)
638 {
639 img->pal[i].r = img->pal[i].g = img->pal[i].b = i;
640 }
641 break;
642
643 case PNG_COLOR_TYPE_PALETTE:
644 png_get_PLTE(png_ptr, info_ptr, &palette, &ncolors);
645 dmMsg(2, "PNG: Palette of %d colors found.\n", ncolors);
646 if (ncolors > 0 && palette != NULL)
647 {
648 if (!dmImageAllocPalette(img, ncolors, -1))
649 {
650 res = DMERR_MALLOC;
651 goto error;
652 }
653
654 for (i = 0; i < img->ncolors; i++)
655 {
656 img->pal[i].r = palette[i].red;
657 img->pal[i].g = palette[i].green;
658 img->pal[i].b = palette[i].blue;
659 }
660 }
661 break;
662 }
663
664 if (color_type == PNG_COLOR_TYPE_PALETTE ||
665 color_type == PNG_COLOR_TYPE_GRAY)
666 {
667 png_get_tRNS(png_ptr, info_ptr, &trans, &ntrans, NULL);
668 if (trans != NULL && ntrans > 0)
669 {
670 for (i = 0; i < img->ncolors && i < ntrans; i++)
671 {
672 img->pal[i].a = trans[i];
673 if (img->ctransp < 0 && trans[i] == 0)
674 img->ctransp = i;
675 }
676 }
677 }
678
679 error:
680 // png_free(png_ptr, palette);
681
682 if (png_ptr && info_ptr)
683 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
684
685 return res;
686 }
687
688
689 int dmReadPNGImage(const char *filename, DMImage **img)
690 {
691 int res;
692 FILE *fp;
693
694 if ((fp = fopen(filename, "rb")) == NULL)
695 {
696 return dmError(DMERR_FOPEN,
697 "PNG: Could not open file '%s' for reading.\n",
698 filename);
699 }
700
701 res = dmReadPNGImageFILE(fp, img);
702
703 fclose(fp);
704 return res;
705 }
706 #endif
707
708
709 #define DMPCX_PAL_COLORS 16 // Number of internal palette colors
710
711 typedef struct
712 {
713 Uint8 r,g,b;
714 } DMPCXColor;
715
716
717 typedef struct
718 {
719 Uint8 manufacturer, // always 0x0a
720 version, // Z-Soft PC Paintbrush version:
721 // 0 = v2.5
722 // 2 = v2.8 with palette,
723 // 3 = v2.8 without palette
724 // 4 = PC Paintbrush for Windows
725 // 5 = v3.0 or better
726 encoding, // usually 0x01 = RLE, 0x00 = uncompressed
727 bitsPerPlane; // bits per pixel per plane
728
729 Uint16 xmin, ymin, xmax, ymax;
730 Uint16 hres, vres; // resolution in DPI, can be image dimensions as well.
731 DMPCXColor colorMap[DMPCX_PAL_COLORS];
732 Uint8 reserved; // should be set to 0
733 Uint8 nplanes; // number of planes
734 Uint16 bpl; // bytes per plane LINE
735 Uint16 palInfo; // 1 = color/BW, 2 = grayscale
736 Uint16 hScreenSize, vScreenSize;
737 Uint8 filler[54];
738 } DMPCXHeader;
739
740
741 typedef struct
742 {
743 DMPCXHeader *header;
744 Uint8 *buf;
745 size_t bufLen, bufOffs;
746 FILE *fp;
747 } DMPCXData;
748
749
750 // Returns one byte from row buffer (of length len) at offset soffs,
751 // OR zero if the offset is outside buffer.
752 static inline Uint8 dmPCXGetByte(const Uint8 *row, const size_t len, const size_t soffs)
753 {
754 return (soffs < len) ? row[soffs] : 0;
755 }
756
757 static BOOL dmPCXFlush(DMPCXData *pcx)
758 {
759 BOOL ret = TRUE;
760 if (pcx->bufOffs > 0)
761 ret = fwrite(pcx->buf, sizeof(Uint8), pcx->bufOffs, pcx->fp) == pcx->bufOffs;
762
763 pcx->bufOffs = 0;
764 return ret;
765 }
766
767 static inline BOOL dmPCXPutByte(DMPCXData *pcx, const Uint8 val)
768 {
769 if (pcx->bufOffs < pcx->bufLen)
770 {
771 pcx->buf[pcx->bufOffs++] = val;
772 return TRUE;
773 }
774 else
775 return dmPCXFlush(pcx);
776 }
777
778
779 static int dmPCXPutData(DMPCXData *pcx, const Uint8 data, const int count)
780 {
781 if (count == 1 && (data & 0xC0) != 0xC0)
782 {
783 if (!dmPCXPutByte(pcx, data))
784 return DMERR_FWRITE;
785 }
786 else
787 {
788 if (!dmPCXPutByte(pcx, 0xC0 | count) ||
789 !dmPCXPutByte(pcx, data))
790 return DMERR_FWRITE;
791 }
792 return DMERR_OK;
793 }
794
795
796 static int dmWritePCXRow(void *cbdata, const Uint8 *row, const size_t len)
797 {
798 DMPCXData *pcx = (DMPCXData *) cbdata;
799 int err;
800 size_t soffs = 0;
801
802 pcx->bufOffs = 0;
803
804 for (int plane = 0; plane < pcx->header->nplanes; plane++)
805 {
806 Uint8 data = dmPCXGetByte(row, len, soffs++),
807 count = 1;
808
809 size_t blen = pcx->header->bpl * pcx->header->nplanes;
810 while (soffs < blen)
811 {
812 if (data == dmPCXGetByte(row, len, soffs) && count < 0x3F)
813 {
814 count++;
815 soffs++;
816 }
817 else
818 {
819 if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
820 return err;
821
822 data = dmPCXGetByte(row, len, soffs++);
823 count = 1;
824 }
825 }
826
827 if ((err = dmPCXPutData(pcx, data, count)) != DMERR_OK)
828 return err;
829
830 if (!dmPCXFlush(pcx))
831 return DMERR_FWRITE;
832 }
833
834
835 return DMERR_OK;
836 }
837
838
839 int dmWritePCXImageFILE(FILE *fp, DMImage *img, const DMImageConvSpec *pspec)
840 {
841 DMPCXData pcx;
842 DMPCXHeader hdr;
843 DMImageConvSpec spec;
844 int res;
845
846 // Always force planar for PCX
847 memcpy(&spec, pspec, sizeof(DMImageConvSpec));
848 spec.planar = TRUE;
849
850 // Create output file
851 pcx.buf = NULL;
852 pcx.header = &hdr;
853 pcx.fp = fp;
854
855 // Create PCX header
856 dmMemset(&hdr, 0, sizeof(hdr));
857 if (spec.paletted && img->pal != NULL)
858 {
859 for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : img->ncolors); i++)
860 {
861 hdr.colorMap[i].r = img->pal[i].r;
862 hdr.colorMap[i].g = img->pal[i].g;
863 hdr.colorMap[i].b = img->pal[i].b;
864 }
865 }
866 hdr.manufacturer = 10;
867 hdr.version = 5;
868 hdr.encoding = 1;
869 hdr.hres = img->width * spec.scaleX;
870 hdr.vres = img->height * spec.scaleY;
871 hdr.xmin = hdr.ymin = 0;
872 hdr.xmax = (img->width * spec.scaleX) - 1;
873 hdr.ymax = (img->height * spec.scaleY) - 1;
874 hdr.palInfo = 1;
875 hdr.hScreenSize = hdr.hres;
876 hdr.vScreenSize = hdr.vres;
877
878 // TODO XXX .. maybe actually compute these asdf
879 hdr.bitsPerPlane = 8;
880 hdr.nplanes = dmImageGetBytesPerPixel(spec.format);
881
882 res = img->width * spec.scaleX;
883 hdr.bpl = res / 2;
884 if (res % 2) hdr.bpl++;
885 hdr.bpl *= 2;
886
887 dmMsg(2,
888 "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
889 hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
890 hdr.hres, hdr.vres,
891 hdr.hScreenSize, hdr.vScreenSize);
892
893 dmMsg(2, "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s, planar=%s\n",
894 hdr.nplanes, hdr.bitsPerPlane, hdr.bpl,
895 spec.paletted ? "yes" : "no",
896 spec.planar ? "yes" : "no"
897 );
898
899 // TODO XXX this is also bogus
900 pcx.bufLen = hdr.bpl * 4;
901 if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
902 {
903 res = dmError(DMERR_MALLOC,
904 "PCX: Could not allocate %d bytes for RLE compression buffer.\n",
905 pcx.bufLen);
906 goto error;
907 }
908
909 // Write PCX header
910 if (!dm_fwrite_byte(pcx.fp, hdr.manufacturer) ||
911 !dm_fwrite_byte(pcx.fp, hdr.version) ||
912 !dm_fwrite_byte(pcx.fp, hdr.encoding) ||
913 !dm_fwrite_byte(pcx.fp, hdr.bitsPerPlane))
914 {
915 res = dmError(DMERR_FWRITE,
916 "PCX: Could not write basic header data.\n");
917 goto error;
918 }
919
920 if (!dm_fwrite_le16(pcx.fp, hdr.xmin) ||
921 !dm_fwrite_le16(pcx.fp, hdr.ymin) ||
922 !dm_fwrite_le16(pcx.fp, hdr.xmax) ||
923 !dm_fwrite_le16(pcx.fp, hdr.ymax) ||
924 !dm_fwrite_le16(pcx.fp, hdr.hres) ||
925 !dm_fwrite_le16(pcx.fp, hdr.vres))
926 {
927 res = dmError(DMERR_FWRITE,
928 "PCX: Could not write image dimensions.\n");
929 goto error;
930 }
931
932 if (!dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)))
933 {
934 res = dmError(DMERR_FWRITE,
935 "PCX: Could not write colormap.\n");
936 goto error;
937 }
938
939 if (!dm_fwrite_byte(pcx.fp, hdr.reserved) ||
940 !dm_fwrite_byte(pcx.fp, hdr.nplanes) ||
941 !dm_fwrite_le16(pcx.fp, hdr.bpl) ||
942 !dm_fwrite_le16(pcx.fp, hdr.palInfo) ||
943 !dm_fwrite_le16(pcx.fp, hdr.hScreenSize) ||
944 !dm_fwrite_le16(pcx.fp, hdr.vScreenSize) ||
945 !dm_fwrite_str(pcx.fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler)))
946 {
947 res = dmError(DMERR_FWRITE,
948 "PCX: Could not write header remainder.\n");
949 goto error;
950 }
951
952 // Write image data
953 res = dmWriteImageData(img, (void *) &pcx, dmWritePCXRow, &spec);
954
955 // Write VGA palette
956 if (spec.paletted)
957 {
958 int i;
959 dmMsg(2, "PCX: Writing palette of %d active entries.\n", img->ncolors);
960
961 dm_fwrite_byte(pcx.fp, 0x0C);
962
963 for (i = 0; i < img->ncolors; i++)
964 {
965 if (!dm_fwrite_byte(pcx.fp, img->pal[i].r) ||
966 !dm_fwrite_byte(pcx.fp, img->pal[i].g) ||
967 !dm_fwrite_byte(pcx.fp, img->pal[i].b))
968 {
969 res = dmError(DMERR_FWRITE,
970 "PCX: Could not write palette data.\n");
971 goto error;
972 }
973 }
974
975 // Pad the palette, if necessary
976 for (; i < 256; i++)
977 {
978 if (!dm_fwrite_byte(pcx.fp, 0) ||
979 !dm_fwrite_byte(pcx.fp, 0) ||
980 !dm_fwrite_byte(pcx.fp, 0))
981 {
982 res = dmError(DMERR_FWRITE,
983 "PCX: Could not write palette data.\n");
984 goto error;
985 }
986 }
987 }
988
989 error:
990 dmFree(pcx.buf);
991 return res;
992 }
993
994
995 int dmWritePCXImage(const char *filename, DMImage *img, const DMImageConvSpec *spec)
996 {
997 FILE *fp;
998 int res;
999
1000 if ((fp = fopen(filename, "wb")) == NULL)
1001 {
1002 return dmError(DMERR_FOPEN,
1003 "PCX: Could not open file '%s' for writing.\n",
1004 filename);
1005 }
1006
1007 res = dmWritePCXImageFILE(fp, img, spec);
1008
1009 fclose(fp);
1010 return res;
1011 }
1012
1013
1014 static BOOL dmPCXDecodeRLERow(FILE *fp, Uint8 *buf, const size_t bufLen)
1015 {
1016 size_t offs = 0;
1017 do
1018 {
1019 int count;
1020 Uint8 data;
1021
1022 if (!dm_fread_byte(fp, &data))
1023 return FALSE;
1024
1025 if ((data & 0xC0) == 0xC0)
1026 {
1027 BOOL skip = FALSE;
1028 count = data & 0x3F;
1029 if (count == 0)
1030 {
1031 switch (dmGFXErrorMode)
1032 {
1033 case DM_ERRMODE_RECOV_1:
1034 // Use as literal
1035 skip = TRUE;
1036 count = 1;
1037 break;
1038
1039 case DM_ERRMODE_RECOV_2:
1040 // Ignore completely
1041 skip = TRUE;
1042 break;
1043
1044 case DM_ERRMODE_FAIL:
1045 default:
1046 // Error out on "invalid" data
1047 return FALSE;
1048 }
1049 }
1050
1051 if (!skip && !dm_fread_byte(fp, &data))
1052 return FALSE;
1053 }
1054 else
1055 count = 1;
1056
1057 while (count-- && offs < bufLen)
1058 buf[offs++] = data;
1059
1060 // Check for remaining output count, error out if we wish to
1061 if (count > 0 && dmGFXErrorMode == DM_ERRMODE_FAIL)
1062 return FALSE;
1063
1064 } while (offs < bufLen);
1065
1066 return TRUE;
1067 }
1068
1069
1070 int dmReadPCXImageFILE(FILE *fp, DMImage **pimg)
1071 {
1072 DMImage *img;
1073 DMPCXData pcx;
1074 DMPCXHeader hdr;
1075 int res = 0;
1076 BOOL isPaletted;
1077 pcx.buf = NULL;
1078
1079 // Read PCX header
1080 if (!dm_fread_byte(fp, &hdr.manufacturer) ||
1081 !dm_fread_byte(fp, &hdr.version) ||
1082 !dm_fread_byte(fp, &hdr.encoding) ||
1083 !dm_fread_byte(fp, &hdr.bitsPerPlane) ||
1084 !dm_fread_le16(fp, &hdr.xmin) ||
1085 !dm_fread_le16(fp, &hdr.ymin) ||
1086 !dm_fread_le16(fp, &hdr.xmax) ||
1087 !dm_fread_le16(fp, &hdr.ymax) ||
1088 !dm_fread_le16(fp, &hdr.hres) ||
1089 !dm_fread_le16(fp, &hdr.vres) ||
1090 !dm_fread_str(fp, (Uint8 *) &hdr.colorMap, sizeof(hdr.colorMap)) ||
1091 !dm_fread_byte(fp, &hdr.reserved) ||
1092 !dm_fread_byte(fp, &hdr.nplanes) ||
1093 !dm_fread_le16(fp, &hdr.bpl) ||
1094 !dm_fread_le16(fp, &hdr.palInfo) ||
1095 !dm_fread_le16(fp, &hdr.hScreenSize) ||
1096 !dm_fread_le16(fp, &hdr.vScreenSize) ||
1097 !dm_fread_str(fp, (Uint8 *) &hdr.filler, sizeof(hdr.filler)))
1098 {
1099 res = dmError(DMERR_FREAD,
1100 "PCX: Could not read image header data.\n");
1101 goto error;
1102 }
1103
1104 if (hdr.manufacturer != 10 ||
1105 hdr.version > 5 ||
1106 hdr.encoding != 1)
1107 {
1108 res = dmError(DMERR_NOT_SUPPORTED,
1109 "PCX: Not a PCX file, or unsupported variant.\n");
1110 goto error;
1111 }
1112
1113 if (hdr.nplanes == 4 && hdr.bitsPerPlane == 4)
1114 {
1115 dmMsg(2,
1116 "PCX: Probably invalid combination of nplanes and bpp, attempting to fix ..\n");
1117
1118 hdr.bitsPerPlane = 1;
1119 }
1120
1121 isPaletted = (hdr.bitsPerPlane * hdr.nplanes) <= 8;
1122
1123 dmMsg(2,
1124 "PCX: xmin=%d, ymin=%d, xmax=%d, ymax=%d, res=%dx%d, scr=%dx%d\n",
1125 hdr.xmin, hdr.ymin, hdr.xmax, hdr.ymax,
1126 hdr.hres, hdr.vres,
1127 hdr.hScreenSize, hdr.vScreenSize);
1128
1129 dmMsg(2,
1130 "PCX: nplanes=%d, bpp=%d, bpl=%d, isPaletted=%s\n",
1131 hdr.nplanes, hdr.bitsPerPlane, hdr.bpl, isPaletted ? "yes" : "no");
1132
1133 if (hdr.nplanes < 1 || hdr.nplanes > 8)
1134 {
1135 res = dmError(DMERR_NOT_SUPPORTED,
1136 "PCX: Unsupported number of bitplanes %d.\n",
1137 hdr.nplanes);
1138 goto error;
1139 }
1140
1141 if (!isPaletted)
1142 {
1143 res = dmError(DMERR_NOT_SUPPORTED,
1144 "PCX: Non-indexed (truecolour) PCX images not supported for loading.\n");
1145 goto error;
1146 }
1147
1148 // Allocate image
1149 if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1,
1150 isPaletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
1151 // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
1152 // isPaletted ? (hdr.bitsPerPlane * hdr.nplanes) : -1
1153 -1
1154 )) == NULL)
1155 {
1156 res = dmError(DMERR_MALLOC,
1157 "PCX: Could not allocate image structure.\n");
1158 goto error;
1159 }
1160
1161 // Sanity check bytes per line value
1162 if (hdr.bpl < (img->width * hdr.bitsPerPlane) / 8)
1163 {
1164 res = dmError(DMERR_MALLOC,
1165 "PCX: The bytes per plane line value %d is smaller than width*bpp/8 = %d!\n",
1166 hdr.bpl, (img->width * hdr.bitsPerPlane) / 8);
1167 goto error;
1168 }
1169
1170 pcx.bufLen = hdr.nplanes * hdr.bpl;
1171 if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
1172 {
1173 res = dmError(DMERR_MALLOC,
1174 "PCX: Could not allocate RLE buffer.\n");
1175 goto error;
1176 }
1177
1178 dmMsg(2,
1179 "PCX: bufLen=%d\n",
1180 pcx.bufLen);
1181
1182 // Read image data
1183 Uint8 *dp = img->data;
1184 for (int yc = 0; yc < img->height; yc++)
1185 {
1186 // Decode row of RLE'd data
1187 if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen))
1188 {
1189 res = dmError(DMERR_INVALID_DATA,
1190 "PCX: Error decoding RLE compressed data.\n");
1191 goto error;
1192 }
1193
1194 // Decode bitplanes
1195 switch (hdr.bitsPerPlane)
1196 {
1197 case 32:
1198 case 24:
1199 case 16:
1200 case 8:
1201 {
1202 // Actually bytes and bits per plane per pixel ..
1203 const int bytesPerPlane = hdr.bitsPerPlane / 8;
1204
1205 for (int nplane = 0; nplane < hdr.nplanes; nplane++)
1206 {
1207 Uint8 *dptr = dp + (nplane * bytesPerPlane),
1208 *sptr = pcx.buf + (hdr.bpl * nplane);
1209
1210 memcpy(dptr, sptr, img->width * bytesPerPlane);
1211 }
1212 }
1213 break;
1214
1215 case 1:
1216 memset(dp, 0, img->width);
1217
1218 for (int nplane = 0; nplane < hdr.nplanes; nplane++)
1219 {
1220 Uint8 *sptr = pcx.buf + (hdr.bpl * nplane);
1221
1222 for (int xc = 0; xc < img->width; xc++)
1223 {
1224 const int px = 7 - (xc & 7);
1225 dp[xc] |= ((sptr[xc / 8] & (1 << px)) >> px) << nplane;
1226 }
1227 }
1228 break;
1229
1230 default:
1231 res = dmError(DMERR_NOT_SUPPORTED,
1232 "PCX: Unsupported number of bits per plane %d.\n",
1233 hdr.bitsPerPlane);
1234 goto error;
1235 }
1236
1237 dp += img->pitch;
1238 }
1239
1240 // Read additional VGA palette, if available
1241 if (isPaletted)
1242 {
1243 int ncolors;
1244 Uint8 tmpb;
1245 BOOL read;
1246
1247 if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C)
1248 {
1249 read = FALSE;
1250 ncolors = DMPCX_PAL_COLORS;
1251 }
1252 else
1253 {
1254 read = TRUE;
1255 ncolors = 256;
1256 }
1257
1258 if (!dmImageAllocPalette(img, ncolors, -1))
1259 {
1260 res = dmError(DMERR_MALLOC,
1261 "PCX: Could not allocate palette data!\n");
1262 goto error;
1263 }
1264
1265 if (read)
1266 {
1267 // Okay, attempt to read the palette data
1268 dmMsg(2, "PCX: Reading palette of %d colors\n", ncolors);
1269 if (!dmReadPaletteData(fp, img->pal, ncolors))
1270 {
1271 res = dmError(DMERR_FREAD,
1272 "PCX: Error reading palette.\n");
1273 goto error;
1274 }
1275 }
1276 else
1277 {
1278 // If the extra palette is not available, copy the colors from
1279 // the header palette to our internal palette structure.
1280 dmMsg(2, "PCX: Initializing palette from header of %d colors\n", ncolors);
1281 for (int i = 0; i < (img->ncolors > DMPCX_PAL_COLORS ? DMPCX_PAL_COLORS : img->ncolors); i++)
1282 {
1283 img->pal[i].r = hdr.colorMap[i].r;
1284 img->pal[i].g = hdr.colorMap[i].g;
1285 img->pal[i].b = hdr.colorMap[i].b;
1286 }
1287 }
1288 }
1289
1290 error:
1291 dmFree(pcx.buf);
1292 return res;
1293 }
1294
1295
1296 int dmReadPCXImage(const char *filename, DMImage **pimg)
1297 {
1298 FILE *fp;
1299 int res;
1300
1301 if ((fp = fopen(filename, "rb")) == NULL)
1302 {
1303 return dmError(DMERR_FOPEN,
1304 "PCX: Could not open file '%s' for reading.\n",
1305 filename);
1306 }
1307
1308 res = dmReadPCXImageFILE(fp, pimg);
1309
1310 fclose(fp);
1311 return res;
1312 }
1313
1314
1315 #define IFF_ID_FORM 0x464F524D // "FORM"
1316 #define IFF_ID_ILBM 0x494C424D // "ILBM"
1317 #define IFF_ID_PBM 0x50424D20 // "PBM "
1318 #define IFF_ID_BMHD 0x424D4844 // "BMHD"
1319 #define IFF_ID_CMAP 0x434D4150 // "CMAP"
1320 #define IFF_ID_BODY 0x424F4459 // "BODY"
1321 #define IFF_ID_CAMG 0x43414D47 // "CAMG"
1322
1323 #define IFF_MASK_NONE 0
1324 #define IFF_MASK_HAS_MASK 1
1325 #define IFF_MASK_TRANSP 2
1326 #define IFF_MASK_LASSO 3
1327
1328 #define IFF_COMP_NONE 0
1329 #define IFF_COMP_BYTERUN1 1
1330
1331 #define IFF_CAMG_LACE 0x00000004
1332 #define IFF_CAMG_HALFBRITE 0x00000080
1333 #define IFF_CAMG_HAM 0x00000800
1334
1335 typedef struct
1336 {
1337 Uint32 id;
1338 Uint32 size;
1339 int count;
1340 char str[6];
1341 } DMIFFChunk;
1342
1343
1344 typedef struct
1345 {
1346 Uint16 w, h;
1347 Sint16 x, y;
1348 Uint8 nplanes;
1349 Uint8 masking;
1350 Uint8 compression;
1351 Uint8 pad1;
1352 Uint16 transp;
1353 Uint8 xasp, yasp;
1354 Sint16 pagew, pageh;
1355 } DMIFFBMHD;
1356
1357
1358 typedef struct
1359 {
1360 DMIFFChunk chBMHD, chCMAP, chBODY;
1361 DMIFFBMHD bmhd;
1362 Uint32 camg;
1363 int ncolors;
1364 DMColor *pal;
1365 BOOL planar;
1366 } DMIFF;
1367
1368
1369 static BOOL dmReadIFFChunk(FILE *fp, DMIFFChunk *chunk)
1370 {
1371 if (!dm_fread_be32(fp, &chunk->id) ||
1372 !dm_fread_be32(fp, &chunk->size))
1373 {
1374 dmError(DMERR_FREAD,
1375 "ILBM: Could not read IFF chunk header.\n");
1376 return FALSE;
1377 }
1378 else
1379 return TRUE;
1380 }
1381
1382 static char * dmGetIFFChunkID(DMIFFChunk *chunk)
1383 {
1384 chunk->str[0] = (chunk->id >> 24) & 0xff;
1385 chunk->str[1] = (chunk->id >> 16) & 0xff;
1386 chunk->str[2] = (chunk->id >> 8) & 0xff;
1387 chunk->str[3] = (chunk->id) & 0xff;
1388 chunk->str[4] = 0;
1389 return chunk->str;
1390 }
1391
1392 static int dmSkipIFFChunkRest(FILE *fp, const DMIFFChunk *chunk, const Uint32 used)
1393 {
1394 if (chunk->size > used)
1395 {
1396 dmMsg(4, "ILBM: Skipping %d bytes (%d of %d consumed)\n",
1397 chunk->size - used, used, chunk->size);
1398
1399 if (fseeko(fp, chunk->size - used, SEEK_CUR) != 0)
1400 {
1401 return dmError(DMERR_FSEEK,
1402 "ILBM: Failed to skip chunk end.\n");
1403 }
1404 else
1405 return DMERR_OK;
1406 }
1407 else
1408 return DMERR_OK;
1409 }
1410
1411 static int dmCheckIFFChunk(DMIFFChunk *dest, DMIFFChunk *chunk,
1412 const BOOL multi, const Uint32 minSize)
1413 {
1414 if (dest->count > 0 && !multi)
1415 {
1416 return dmError(DMERR_INVALID_DATA,
1417 "ILBM: Multiple instances of chunk %s found.\n",
1418 dmGetIFFChunkID(chunk));
1419 }
1420
1421 dest->count++;
1422
1423 if (chunk->size < minSize)
1424 {
1425 return dmError(DMERR_OUT_OF_DATA,
1426 "ILBM: Chunk is too small.\n");
1427 }
1428
1429 return DMERR_OK;
1430 }
1431
1432
1433 static BOOL dmIFFDecodeByteRun1Row(FILE *fp, Uint8 *buf, const size_t bufLen)
1434 {
1435 size_t offs = 0;
1436 do
1437 {
1438 Sint8 dcount;
1439 Uint8 data;
1440
1441 if (!dm_fread_byte(fp, (Uint8 *) &dcount))
1442 return FALSE;
1443
1444 if (dcount == -128)
1445 {
1446 if (!dm_fread_byte(fp, &data))
1447 return FALSE;
1448 }
1449 else
1450 if (dcount < 0)
1451 {
1452 int count = (-dcount) + 1;
1453 if (!dm_fread_byte(fp, &data))
1454 return FALSE;
1455
1456 while (count-- && offs < bufLen)
1457 buf[offs++] = data;
1458 }
1459 else
1460 {
1461 int count = dcount + 1;
1462 while (count-- && offs < bufLen)
1463 {
1464 if (!dm_fread_byte(fp, &data))
1465 return FALSE;
1466
1467 buf[offs++] = data;
1468 }
1469 }
1470 } while (offs < bufLen);
1471
1472 return TRUE;
1473 }
1474
1475
1476 static BOOL dmIFFReadOneRow(FILE *fp, DMIFF *iff, Uint8 *buf, const size_t bufLen)
1477 {
1478 if (iff->bmhd.compression == IFF_COMP_BYTERUN1)
1479 return dmIFFDecodeByteRun1Row(fp, buf, bufLen);
1480 else
1481 return dm_fread_str(fp, buf, bufLen);
1482 }
1483
1484
1485 void dmDecodeBitPlane(Uint8 *dp, Uint8 *src, const int width, const int nplane)
1486 {
1487 int xc;
1488 for (xc = 0; xc < width; xc++)
1489 {
1490 const Uint8 data = (src[xc / 8] >> (7 - (xc & 7))) & 1;
1491 dp[xc] |= (data << nplane);
1492 }
1493 }
1494
1495
1496 int dmDecodeILBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read)
1497 {
1498 DMImage *img;
1499 Uint8 *buf;
1500 size_t bufLen;
1501 int yc, res = DMERR_OK;
1502
1503 *read = 0;
1504
1505 // Allocate image
1506 if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h,
1507 iff->bmhd.nplanes <= 8 ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
1508 // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
1509 //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
1510 -1
1511 )) == NULL)
1512 return DMERR_MALLOC;
1513
1514 // Allocate planar decoding buffer
1515 bufLen = ((img->width + 15) / 16) * 2;
1516 if ((buf = dmMalloc(bufLen)) == NULL)
1517 return DMERR_MALLOC;
1518
1519 dmMsg(2, "ILBM: plane row size %d bytes.\n", bufLen);
1520
1521 // Decode the chunk
1522 for (yc = 0; yc < img->height; yc++)
1523 {
1524 int plane;
1525 const int nplanes = iff->bmhd.nplanes;
1526 Uint8 *dp = img->data + (yc * img->pitch);
1527
1528 dmMemset(dp, 0, img->pitch);
1529
1530 for (plane = 0; plane < nplanes; plane++)
1531 {
1532 // Decompress or read data
1533 if (!dmIFFReadOneRow(fp, iff, buf, bufLen))
1534 {
1535 res = dmError(DMERR_FREAD,
1536 "ILBM: Error in reading image plane #%d @ %d.\n",
1537 plane, yc);
1538 goto error;
1539 }
1540
1541 // Decode bitplane
1542 dmDecodeBitPlane(dp, buf, img->width, plane);
1543
1544 *read += bufLen;
1545 }
1546
1547 // Read mask data
1548 if (iff->bmhd.masking == IFF_MASK_HAS_MASK)
1549 {
1550 int xc;
1551
1552 // Decompress or read data
1553 if (!dmIFFReadOneRow(fp, iff, buf, bufLen))
1554 {
1555 res = dmError(DMERR_FREAD,
1556 "ILBM: Error in reading mask plane.\n");
1557 goto error;
1558 }
1559
1560 // Decode mask
1561 for (xc = 0; xc < img->width; xc++)
1562 {
1563 const Uint8 data = (buf[xc / 8] >> (7 - (xc & 7))) & 1;
1564
1565 // Black out any pixels with mask bit 0
1566 if (!data)
1567 dp[xc] = 0;
1568 }
1569
1570 *read += bufLen;
1571 }
1572 }
1573
1574 error:
1575 dmFree(buf);
1576 return res;
1577 }
1578
1579
1580 int dmDecodePBMBody(FILE *fp, DMIFF *iff, DMImage **pimg, Uint32 *read)
1581 {
1582 DMImage *img;
1583 int yc, res = DMERR_OK;
1584
1585 *read = 0;
1586
1587 // Allocate image
1588 if ((*pimg = img = dmImageAlloc(iff->bmhd.w, iff->bmhd.h,
1589 iff->bmhd.nplanes <= 8 ? DM_IFMT_PALETTE : DM_IFMT_RGBA,
1590 // XXX TODO? When/if we ever handle < 8bit indexed correctly, we can use the actual bpp
1591 //iff->bmhd.nplanes <= 8 ? iff->bmhd.nplanes : -1
1592 -1
1593 )) == NULL)
1594 return DMERR_MALLOC;
1595
1596 // Decode the chunk
1597 for (yc = 0; yc < img->height; yc++)
1598 {
1599 Uint8 *dp = img->data + (yc * img->pitch);
1600
1601 if (!dmIFFReadOneRow(fp, iff, dp, img->width))
1602 {
1603 res = dmError(DMERR_FREAD,
1604 "ILBM: Error in reading image row #%d.\n", yc);
1605 goto error;
1606 }
1607
1608 *read += img->width;
1609 }
1610
1611 error:
1612 return res;
1613 }
1614
1615
1616 int dmReadILBMImageFILE(FILE *fp, DMImage **pimg)
1617 {
1618 Uint32 idILBM;
1619 DMIFFChunk chunk;
1620 DMIFF iff;
1621 Uint32 read;
1622 BOOL parsed = FALSE;
1623 int i, res = DMERR_OK;
1624
1625 dmMemset(&iff, 0, sizeof(iff));
1626
1627 // Read IFF FORM header
1628 if (!dmReadIFFChunk(fp, &chunk) ||
1629 chunk.id != IFF_ID_FORM ||
1630 chunk.size < 32)
1631 {
1632 return dmError(DMERR_NOT_SUPPORTED,
1633 "ILBM: Not a IFF file (%08X vs %08X / %d).\n",
1634 chunk.id, IFF_ID_FORM, chunk.size);
1635 }
1636
1637 // Check IFF ILBM signature
1638 if (!dm_fread_be32(fp, &idILBM) ||
1639 (idILBM != IFF_ID_ILBM && idILBM != IFF_ID_PBM))
1640 {
1641 return dmError(DMERR_INVALID_DATA,
1642 "ILBM: Not a ILBM file.\n");
1643 }
1644
1645 iff.planar = (idILBM == IFF_ID_ILBM);
1646
1647 while (!parsed && !feof(fp))
1648 {
1649 if (!dmReadIFFChunk(fp, &chunk))
1650 {
1651 return dmError(DMERR_FREAD,
1652 "ILBM: Error reading IFF ILBM data.\n");
1653 }
1654
1655 switch (chunk.id)
1656 {
1657 case IFF_ID_BMHD:
1658 // Check for multiple occurences of BMHD
1659 if ((res = dmCheckIFFChunk(&iff.chBMHD, &chunk, FALSE, sizeof(iff.bmhd))) != DMERR_OK)
1660 return res;
1661
1662 // Read BMHD data
1663 if (!dm_fread_be16(fp, &iff.bmhd.w) ||
1664 !dm_fread_be16(fp, &iff.bmhd.h) ||
1665 !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.x) ||
1666 !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.y) ||
1667 !dm_fread_byte(fp, &iff.bmhd.nplanes) ||
1668 !dm_fread_byte(fp, &iff.bmhd.masking) ||
1669 !dm_fread_byte(fp, &iff.bmhd.compression) ||
1670 !dm_fread_byte(fp, &iff.bmhd.pad1) ||
1671 !dm_fread_be16(fp, &iff.bmhd.transp) ||
1672 !dm_fread_byte(fp, &iff.bmhd.xasp) ||
1673 !dm_fread_byte(fp, &iff.bmhd.yasp) ||
1674 !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pagew) ||
1675 !dm_fread_be16(fp, (Uint16 *) &iff.bmhd.pageh))
1676 {
1677 return dmError(DMERR_FREAD,
1678 "ILBM: Error reading BMHD chunk.\n");
1679 }
1680
1681 dmMsg(2, "ILBM: BMHD %d x %d @ %d, %d : nplanes=%d, comp=%d, mask=%d\n",
1682 iff.bmhd.w, iff.bmhd.h, iff.bmhd.x, iff.bmhd.y,
1683 iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking);
1684
1685 // Sanity check
1686 if (iff.bmhd.nplanes < 1 || iff.bmhd.nplanes > 8 ||
1687 (iff.bmhd.compression != IFF_COMP_NONE &&
1688 iff.bmhd.compression != IFF_COMP_BYTERUN1) ||
1689 (iff.bmhd.masking != IFF_MASK_NONE &&
1690 iff.bmhd.masking != IFF_MASK_HAS_MASK &&
1691 iff.bmhd.masking != IFF_MASK_TRANSP))
1692 {
1693 return dmError(DMERR_NOT_SUPPORTED,
1694 "ILBM: Unsupported features, refusing to load.\n");
1695 }
1696
1697 if ((res = dmSkipIFFChunkRest(fp, &chunk, sizeof(iff.bmhd))) != DMERR_OK)
1698 return res;
1699 break;
1700
1701
1702 case IFF_ID_CMAP:
1703 // Check for multiple occurences of CMAP
1704 if ((res = dmCheckIFFChunk(&iff.chCMAP, &chunk, FALSE, 3)) != DMERR_OK)
1705 return res;
1706
1707 // Check for sanity
1708 if (chunk.size % 3 != 0)
1709 {
1710 // Non-fatal
1711 dmError(DMERR_INVALID_DATA,
1712 "ILBM: CMAP chunk size not divisible by 3, possibly broken file.\n");
1713 }
1714
1715 iff.ncolors = chunk.size / 3;
1716 dmMsg(2, "ILBM: CMAP %d entries (%d bytes)\n",
1717 iff.ncolors, chunk.size, 1 << iff.bmhd.nplanes);
1718
1719 if (iff.bmhd.nplanes > 0 && iff.ncolors != 1 << iff.bmhd.nplanes)
1720 dmMsg(2, "ILBM: Expected %d entries in CMAP.\n", 1 << iff.bmhd.nplanes);
1721
1722 // Read palette
1723 if (iff.ncolors > 0)
1724 {
1725 if (!dmPaletteAlloc(&iff.pal, iff.ncolors,
1726 (iff.bmhd.masking == IFF_MASK_TRANSP) ? iff.bmhd.transp : -1))
1727 {
1728 return dmError(DMERR_MALLOC,
1729 "ILBM: Could not allocate palette data.\n");
1730 }
1731 if (!dmReadPaletteData(fp, iff.pal, iff.ncolors))
1732 {
1733 return dmError(DMERR_FREAD,
1734 "ILBM: Error reading CMAP.\n");
1735 }
1736 }
1737
1738 if (iff.chBMHD.count && iff.chBODY.count)
1739 parsed = TRUE;
1740 break;
1741
1742 case IFF_ID_BODY:
1743 // Check for multiple occurences of CMAP
1744 if ((res = dmCheckIFFChunk(&iff.chBODY, &chunk, FALSE, 1)) != DMERR_OK)
1745 return res;
1746
1747 // Check for sanity
1748 if (!iff.chBMHD.count)
1749 {
1750 return dmError(DMERR_INVALID_DATA,
1751 "ILBM: BODY chunk before BMHD?\n");
1752 }
1753
1754 dmMsg(2, "ILBM: BODY chunk size %d bytes\n", chunk.size);
1755
1756 // Decode the body
1757 if (iff.planar)
1758 {
1759 if ((res = dmDecodeILBMBody(fp, &iff, pimg, &read)) != DMERR_OK)
1760 return res;
1761 }
1762 else
1763 {
1764 if ((res = dmDecodePBMBody(fp, &iff, pimg, &read)) != DMERR_OK)
1765 return res;
1766 }
1767
1768 if ((res = dmSkipIFFChunkRest(fp, &chunk, read)) != DMERR_OK)
1769 return res;
1770
1771 if (iff.chCMAP.count)
1772 parsed = TRUE;
1773 break;
1774
1775
1776 case IFF_ID_CAMG:
1777 if (!dm_fread_be32(fp, &iff.camg))
1778 {
1779 return dmError(DMERR_FREAD,
1780 "ILBM: Error reading CAMG chunk.\n");
1781 }
1782
1783 dmMsg(2, "ILBM: CAMG value 0x%08x\n", iff.camg);
1784
1785 if ((iff.camg & IFF_CAMG_HAM))
1786 {
1787 return dmError(DMERR_NOT_SUPPORTED,
1788 "ILBM: HAM files are not supported.\n");
1789 }
1790
1791 if ((res = dmSkipIFFChunkRest(fp, &chunk, sizeof(Uint32))) != DMERR_OK)
1792 return res;
1793 break;
1794
1795
1796 default:
1797 {
1798 dmMsg(4, "Unknown chunk ID '%s', size %d\n",
1799 dmGetIFFChunkID(&chunk), chunk.size);
1800
1801 if (fseeko(fp, chunk.size, SEEK_CUR) != 0)
1802 {
1803 return dmError(DMERR_FSEEK,
1804 "ILBM: Error skipping in file.");
1805 }
1806 }
1807 break;
1808 }
1809
1810 if (chunk.size & 1)
1811 fgetc(fp);
1812 }
1813
1814 // Set colormap after finishing
1815 if (iff.pal != NULL && iff.ncolors > 0 && *pimg != NULL)
1816 {
1817 // If halfbrite is used, duplicate the palette
1818 if (iff.camg & IFF_CAMG_HALFBRITE)
1819 {
1820 void *ptmp;
1821
1822 if (iff.ncolors > 128)
1823 {
1824 return dmError(DMERR_NOT_SUPPORTED,
1825 "ILBM: Halfbrite enabled, but ncolors > 128.\n");
1826 }
1827
1828 if ((ptmp = dmRealloc(iff.pal, sizeof(DMColor) * iff.ncolors * 2)) == NULL)
1829 {
1830 dmFree(iff.pal);
1831 iff.pal = NULL;
1832 return DMERR_MALLOC;
1833 }
1834 else
1835 iff.pal = ptmp;
1836
1837 for (i = 0; i < iff.ncolors; i++)
1838 {
1839 int i2 = iff.ncolors + i;
1840 iff.pal[i2].r = iff.pal[i].r / 2;
1841 iff.pal[i2].g = iff.pal[i].g / 2;
1842 iff.pal[i2].b = iff.pal[i].b / 2;
1843 }
1844 }
1845
1846 (*pimg)->ncolors = iff.ncolors;
1847 (*pimg)->pal = iff.pal;
1848 }
1849
1850 return res;
1851 }
1852
1853
1854 int dmReadILBMImage(const char *filename, DMImage **pimg)
1855 {
1856 FILE *fp;
1857 int res;
1858
1859 if ((fp = fopen(filename, "rb")) == NULL)
1860 {
1861 return dmError(DMERR_FOPEN,
1862 "ILBM: Could not open file '%s' for reading.\n",
1863 filename);
1864 }
1865
1866 res = dmReadILBMImageFILE(fp, pimg);
1867
1868 fclose(fp);
1869 return res;
1870 }
1871
1872
1873
1874
1875 static int fmtProbePNG(const Uint8 *buf, const size_t len)
1876 {
1877 if (len > 64 && buf[0] == 0x89 &&
1878 buf[1] == 'P' && buf[2] == 'N' && buf[3] == 'G' &&
1879 buf[4] == 0x0d && buf[5] == 0x0a)
1880 {
1881 if (buf[12] == 'I' && buf[13] == 'H' &&
1882 buf[14] == 'D' && buf[15] == 'R')
1883 return DM_PROBE_SCORE_MAX;
1884 else
1885 return DM_PROBE_SCORE_GOOD;
1886 }
1887
1888 return DM_PROBE_SCORE_FALSE;
1889 }
1890
1891
1892 static int fmtProbePCX(const Uint8 *buf, const size_t len)
1893 {
1894 if (len > 128 + 32 &&
1895
1896 (buf[1] == 5 || buf[1] == 2 || buf[1] == 3) &&
1897 buf[2] == 1 &&
1898 (buf[3] == 8 || buf[3] == 4 || buf[3] == 3 || buf[3] == 1) &&
1899 buf[65] >= 1 && buf[65] <= 4)
1900 return DM_PROBE_SCORE_GOOD;
1901
1902 return DM_PROBE_SCORE_FALSE;
1903 }
1904
1905
1906 static int fmtProbeILBM(const Uint8 *buf, const size_t len)
1907 {
1908 if (len > 32 &&
1909 buf[ 0] == 'F' && buf[ 1] == 'O' &&
1910 buf[ 2] == 'R' && buf[ 3] == 'M' && (
1911 (buf[ 8] == 'I' && buf[ 9] == 'L' && buf[10] == 'B' && buf[11] == 'M') ||
1912 (buf[ 8] == 'P' && buf[ 9] == 'B' && buf[10] == 'M' && buf[11] == 0x20)
1913 ))
1914 {
1915 if (buf[12] == 'B' && buf[13] == 'M' &&
1916 buf[14] == 'H' && buf[15] == 'D')
1917 return DM_PROBE_SCORE_MAX;
1918 else
1919 return DM_PROBE_SCORE_GOOD;
1920 }
1921
1922 return DM_PROBE_SCORE_FALSE;
1923 }
1924
1925
1926 DMImageFormat dmImageFormatList[IMGFMT_LAST] =
1927 {
1928 {
1929 "PNG", "Portable Network Graphics",
1930 fmtProbePNG,
1931 #ifdef DM_USE_LIBPNG
1932 dmReadPNGImage, dmReadPNGImageFILE,
1933 dmWritePNGImage, dmWritePNGImageFILE,
1934 #else
1935 NULL, NULL,
1936 NULL, NULL,
1937 #endif
1938 },
1939 {
1940 "PPM", "Portable PixMap",
1941 NULL,
1942 NULL, NULL,
1943 dmWritePPMImage, dmWritePPMImageFILE,
1944 },
1945 {
1946 "PCX", "Z-Soft Paintbrush",
1947 fmtProbePCX,
1948 dmReadPCXImage, dmReadPCXImageFILE,
1949 dmWritePCXImage, dmWritePCXImageFILE,
1950 },
1951 {
1952 "ILBM", "IFF ILBM",
1953 fmtProbeILBM,
1954 dmReadILBMImage, dmReadILBMImageFILE,
1955 NULL, NULL,
1956 },
1957 {
1958 "RAW", "Plain bitplaned (planar or non-planar) RAW",
1959 NULL,
1960 NULL, NULL,
1961 dmWriteRAWImage, dmWriteRAWImageFILE,
1962 },
1963 {
1964 "ARAW", "IFFMaster Amiga RAW",
1965 NULL,
1966 NULL, NULL,
1967 dmWriteRAWImage, dmWriteRAWImageFILE,
1968 }
1969 };
1970
1971
1972 int dmImageProbeGeneric(const Uint8 *buf, const size_t len, DMImageFormat **pfmt, int *index)
1973 {
1974 int i, scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;
1975
1976 for (i = 0; i < IMGFMT_LAST; i++)
1977 {
1978 DMImageFormat *fmt = &dmImageFormatList[i];
1979 if (fmt->probe != NULL)
1980 {
1981 int score = fmt->probe(buf, len);
1982 if (score > scoreMax)
1983 {
1984 scoreMax = score;
1985 scoreIndex = i;
1986 }
1987 }
1988 }
1989
1990 if (scoreIndex >= 0)
1991 {
1992 *pfmt = &dmImageFormatList[scoreIndex];
1993 *index = scoreIndex;
1994 return scoreMax;
1995 }
1996 else
1997 return DM_PROBE_SCORE_FALSE;
1998 }