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