Commit | Line | Data |
---|---|---|
261f91c5 TC |
1 | #include "image.h" |
2 | #include <stdarg.h> | |
3 | ||
705fd961 TC |
4 | /* |
5 | =head1 NAME | |
261f91c5 | 6 | |
705fd961 TC |
7 | bmp.c - read and write windows BMP files |
8 | ||
9 | =head1 SYNOPSIS | |
10 | ||
11 | i_img *im; | |
12 | io_glue *ig; | |
13 | ||
14 | if (!i_writebmp_wiol(im, ig)) { | |
15 | ... error ... | |
16 | } | |
17 | im = i_readbmp(ig); | |
18 | ||
19 | =head1 DESCRIPTION | |
20 | ||
21 | Reads and writes Windows BMP files. | |
22 | ||
23 | =over | |
24 | ||
25 | =cut | |
26 | */ | |
27 | ||
28 | #define FILEHEAD_SIZE 14 | |
29 | #define INFOHEAD_SIZE 40 | |
30 | #define BI_RGB 0 | |
31 | #define BI_RLE8 1 | |
32 | #define BI_RLE4 2 | |
33 | #define BI_BITFIELDS 3 | |
34 | #define BMPRLE_ENDOFLINE 0 | |
35 | #define BMPRLE_ENDOFBMP 1 | |
36 | #define BMPRLE_DELTA 2 | |
37 | ||
38 | static int read_packed(io_glue *ig, char *format, ...); | |
39 | static int write_packed(io_glue *ig, char *format, ...); | |
40 | static int write_bmphead(io_glue *ig, i_img *im, int bit_count, | |
41 | int data_size); | |
42 | static int write_1bit_data(io_glue *ig, i_img *im); | |
43 | static int write_4bit_data(io_glue *ig, i_img *im); | |
44 | static int write_8bit_data(io_glue *ig, i_img *im); | |
45 | static int write_24bit_data(io_glue *ig, i_img *im); | |
46 | static int read_bmp_pal(io_glue *ig, i_img *im, int count); | |
47 | static i_img *read_1bit_bmp(io_glue *ig, int xsize, int ysize, | |
48 | int clr_used); | |
49 | static i_img *read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, | |
50 | int compression); | |
51 | static i_img *read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, | |
52 | int compression); | |
53 | static i_img *read_direct_bmp(io_glue *ig, int xsize, int ysize, | |
54 | int bit_count, int clr_used, int compression); | |
55 | ||
56 | /* | |
57 | =item i_writebmp_wiol(im, io_glue) | |
58 | ||
59 | Writes the image as a BMP file. Uses 1-bit, 4-bit, 8-bit or 24-bit | |
60 | formats depending on the image. | |
61 | ||
62 | Never compresses the image. | |
63 | ||
64 | =cut | |
65 | */ | |
66 | int | |
67 | i_writebmp_wiol(i_img *im, io_glue *ig) { | |
68 | io_glue_commit_types(ig); | |
69 | i_clear_error(); | |
70 | ||
71 | /* pick a format */ | |
72 | if (im->type == i_direct_type) { | |
73 | return write_24bit_data(ig, im); | |
74 | } | |
75 | else { | |
76 | int pal_size; | |
77 | ||
78 | /* must be paletted */ | |
79 | pal_size = i_colorcount(im); | |
80 | if (pal_size <= 2) { | |
81 | return write_1bit_data(ig, im); | |
82 | } | |
83 | else if (pal_size <= 16) { | |
84 | return write_4bit_data(ig, im); | |
85 | } | |
86 | else { | |
87 | return write_8bit_data(ig, im); | |
88 | } | |
89 | } | |
90 | } | |
91 | ||
92 | /* | |
93 | =item i_readbmp_wiol(ig) | |
94 | ||
95 | Reads a Windows format bitmap from the given file. | |
96 | ||
97 | Handles BI_RLE4 and BI_RLE8 compressed images. Attempts to handle | |
98 | BI_BITFIELDS images too, but I need a test image. | |
99 | ||
100 | =cut | |
101 | */ | |
102 | ||
103 | i_img * | |
104 | i_readbmp_wiol(io_glue *ig) { | |
105 | int b_magic, m_magic, filesize, dummy, infohead_size; | |
106 | int xsize, ysize, planes, bit_count, compression, size_image, xres, yres; | |
107 | int clr_used, clr_important, offbits; | |
108 | i_img *im; | |
109 | ||
110 | io_glue_commit_types(ig); | |
111 | i_clear_error(); | |
112 | ||
113 | if (!read_packed(ig, "CCVvvVVVVvvVVVVVV", &b_magic, &m_magic, &filesize, | |
114 | &dummy, &dummy, &offbits, &infohead_size, | |
115 | &xsize, &ysize, &planes, | |
116 | &bit_count, &compression, &size_image, &xres, &yres, | |
117 | &clr_used, &clr_important)) { | |
118 | i_push_error(0, "file too short"); | |
119 | return 0; | |
120 | } | |
121 | if (b_magic != 'B' || m_magic != 'M' || infohead_size != INFOHEAD_SIZE | |
122 | || planes != 1) { | |
123 | i_push_error(0, "not a BMP file"); | |
124 | return 0; | |
125 | } | |
126 | ||
127 | switch (bit_count) { | |
128 | case 1: | |
129 | im = read_1bit_bmp(ig, xsize, ysize, clr_used); | |
130 | break; | |
131 | ||
132 | case 4: | |
133 | im = read_4bit_bmp(ig, xsize, ysize, clr_used, compression); | |
134 | break; | |
135 | ||
136 | case 8: | |
137 | im = read_8bit_bmp(ig, xsize, ysize, clr_used, compression); | |
138 | break; | |
139 | ||
140 | case 32: | |
141 | case 24: | |
142 | case 16: | |
143 | im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression); | |
144 | break; | |
efdc2568 TC |
145 | |
146 | default: | |
147 | i_push_errorf(0, "unknown bit count for BMP file (%d)", bit_count); | |
148 | return NULL; | |
705fd961 TC |
149 | } |
150 | ||
151 | /* store the resolution */ | |
152 | if (xres && !yres) | |
153 | yres = xres; | |
154 | else if (yres && !xres) | |
155 | xres = yres; | |
156 | if (xres) { | |
157 | i_tags_set_float(&im->tags, "i_xres", 0, xres * 0.0254); | |
158 | i_tags_set_float(&im->tags, "i_yres", 0, yres * 0.0254); | |
159 | } | |
160 | i_tags_addn(&im->tags, "bmp_compression", 0, compression); | |
161 | i_tags_addn(&im->tags, "bmp_important_colors", 0, clr_important); | |
162 | ||
163 | return im; | |
164 | } | |
165 | ||
166 | /* | |
167 | =back | |
168 | ||
169 | =head1 IMPLEMENTATION FUNCTIONS | |
170 | ||
171 | Internal functions used in the implementation. | |
172 | ||
173 | =over | |
174 | ||
175 | =item read_packed(ig, format, ...) | |
176 | ||
177 | Reads from the specified "file" the specified sizes. The format codes | |
178 | match those used by perl's pack() function, though only a few are | |
179 | implemented. In all cases the vararg arguement is an int *. | |
180 | ||
181 | Returns non-zero if all of the arguments were read. | |
182 | ||
183 | =cut | |
261f91c5 TC |
184 | */ |
185 | static | |
186 | int read_packed(io_glue *ig, char *format, ...) { | |
187 | unsigned char buf[4]; | |
188 | va_list ap; | |
189 | int *p; | |
190 | ||
191 | va_start(ap, format); | |
192 | ||
193 | while (*format) { | |
194 | p = va_arg(ap, int *); | |
195 | ||
196 | switch (*format) { | |
197 | case 'v': | |
198 | if (ig->readcb(ig, buf, 2) == -1) | |
199 | return 0; | |
200 | *p = buf[0] + (buf[1] << 8); | |
201 | break; | |
202 | ||
203 | case 'V': | |
204 | if (ig->readcb(ig, buf, 4) == -1) | |
205 | return 0; | |
206 | *p = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); | |
207 | break; | |
208 | ||
209 | case 'C': | |
210 | if (ig->readcb(ig, buf, 1) == -1) | |
211 | return 0; | |
212 | *p = buf[0]; | |
213 | break; | |
214 | ||
215 | case 'c': | |
216 | if (ig->readcb(ig, buf, 1) == -1) | |
217 | return 0; | |
218 | *p = (char)buf[0]; | |
219 | break; | |
220 | ||
705fd961 TC |
221 | case '3': /* extension - 24-bit number */ |
222 | if (ig->readcb(ig, buf, 3) == -1) | |
223 | return 0; | |
224 | *p = buf[0] + (buf[1] << 8) + (buf[2] << 16); | |
225 | break; | |
226 | ||
261f91c5 TC |
227 | default: |
228 | m_fatal(1, "Unknown read_packed format code 0x%02x", *format); | |
229 | } | |
230 | ++format; | |
231 | } | |
232 | return 1; | |
233 | } | |
234 | ||
705fd961 TC |
235 | /* |
236 | =item write_packed(ig, format, ...) | |
237 | ||
238 | Writes packed data to the specified io_glue. | |
239 | ||
240 | Returns non-zero on success. | |
241 | ||
242 | =cut | |
243 | */ | |
244 | ||
261f91c5 TC |
245 | static int |
246 | write_packed(io_glue *ig, char *format, ...) { | |
247 | unsigned char buf[4]; | |
248 | va_list ap; | |
249 | int i; | |
250 | ||
251 | va_start(ap, format); | |
252 | ||
253 | while (*format) { | |
254 | i = va_arg(ap, unsigned int); | |
255 | ||
256 | switch (*format) { | |
257 | case 'v': | |
258 | buf[0] = i & 255; | |
259 | buf[1] = i / 256; | |
260 | if (ig->writecb(ig, buf, 2) == -1) | |
261 | return 0; | |
262 | break; | |
263 | ||
264 | case 'V': | |
265 | buf[0] = i & 0xFF; | |
266 | buf[1] = (i >> 8) & 0xFF; | |
267 | buf[2] = (i >> 16) & 0xFF; | |
268 | buf[3] = (i >> 24) & 0xFF; | |
269 | if (ig->writecb(ig, buf, 4) == -1) | |
270 | return 0; | |
271 | break; | |
272 | ||
273 | case 'C': | |
274 | case 'c': | |
275 | buf[0] = i & 0xFF; | |
276 | if (ig->writecb(ig, buf, 1) == -1) | |
277 | return 0; | |
278 | break; | |
279 | ||
280 | default: | |
281 | m_fatal(1, "Unknown read_packed format code 0x%02x", *format); | |
282 | } | |
283 | ++format; | |
284 | } | |
285 | va_end(ap); | |
286 | ||
287 | return 1; | |
288 | } | |
289 | ||
705fd961 TC |
290 | /* |
291 | =item write_bmphead(ig, im, bit_count, data_size) | |
292 | ||
293 | Writes a Windows BMP header to the file. | |
294 | ||
295 | Returns non-zero on success. | |
296 | ||
297 | =cut | |
298 | */ | |
261f91c5 TC |
299 | |
300 | static | |
301 | int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { | |
302 | double xres, yres; | |
303 | int got_xres, got_yres, aspect_only; | |
304 | int colors_used = 0; | |
305 | int offset = FILEHEAD_SIZE + INFOHEAD_SIZE; | |
306 | ||
307 | got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres); | |
308 | got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres); | |
309 | if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only)) | |
310 | aspect_only = 0; | |
311 | if (!got_xres) { | |
312 | if (!got_yres) | |
313 | xres = yres = 72; | |
314 | else | |
315 | xres = yres; | |
316 | } | |
317 | else { | |
318 | if (!got_yres) | |
319 | yres = xres; | |
320 | } | |
321 | if (xres <= 0 || yres <= 0) | |
322 | xres = yres = 72; | |
323 | if (aspect_only) { | |
324 | /* scale so the smaller value is 72 */ | |
325 | double ratio; | |
326 | if (xres < yres) { | |
327 | ratio = 72.0 / xres; | |
328 | } | |
329 | else { | |
330 | ratio = 72.0 / yres; | |
331 | } | |
332 | xres *= ratio; | |
333 | yres *= ratio; | |
334 | } | |
335 | /* now to pels/meter */ | |
336 | xres *= 100.0/2.54; | |
337 | yres *= 100.0/2.54; | |
338 | ||
339 | if (im->type == i_palette_type) { | |
340 | colors_used = i_colorcount(im); | |
341 | offset += 4 * colors_used; | |
342 | } | |
343 | ||
344 | if (!write_packed(ig, "CCVvvVVVVvvVVVVVV", 'B', 'M', data_size+offset, | |
345 | 0, 0, offset, INFOHEAD_SIZE, im->xsize, im->ysize, 1, | |
705fd961 TC |
346 | bit_count, BI_RGB, 0, (int)(xres+0.5), (int)(yres+0.5), |
347 | colors_used, colors_used)){ | |
261f91c5 TC |
348 | i_push_error(0, "cannot write bmp header"); |
349 | return 0; | |
350 | } | |
351 | if (im->type == i_palette_type) { | |
352 | int i; | |
353 | i_color c; | |
354 | ||
355 | for (i = 0; i < colors_used; ++i) { | |
356 | i_getcolors(im, i, &c, 1); | |
357 | if (im->channels >= 3) { | |
358 | if (!write_packed(ig, "CCCC", c.channel[2], c.channel[1], | |
359 | c.channel[0], 0)) { | |
360 | i_push_error(0, "cannot write palette entry"); | |
361 | return 0; | |
362 | } | |
363 | } | |
364 | else { | |
365 | if (!write_packed(ig, "CCCC", c.channel[0], c.channel[0], | |
366 | c.channel[0], 0)) { | |
367 | i_push_error(0, "cannot write palette entry"); | |
368 | return 0; | |
369 | } | |
370 | } | |
371 | } | |
372 | } | |
373 | ||
374 | return 1; | |
375 | } | |
376 | ||
705fd961 TC |
377 | /* |
378 | =item write_1bit_data(ig, im) | |
379 | ||
380 | Writes the image data as a 1-bit/pixel image. | |
381 | ||
382 | Returns non-zero on success. | |
383 | ||
384 | =cut | |
385 | */ | |
261f91c5 TC |
386 | static int |
387 | write_1bit_data(io_glue *ig, i_img *im) { | |
388 | i_palidx *line; | |
389 | unsigned char *packed; | |
390 | int byte; | |
391 | int mask; | |
392 | unsigned char *out; | |
393 | int line_size = (im->xsize+7) / 8; | |
394 | int x, y; | |
395 | ||
396 | /* round up to nearest multiple of four */ | |
397 | line_size = (line_size + 3) / 4 * 4; | |
398 | ||
399 | if (!write_bmphead(ig, im, 1, line_size * im->ysize)) | |
400 | return 0; | |
401 | ||
402 | line = mymalloc(im->xsize + 8); | |
403 | memset(line + im->xsize, 0, 8); | |
404 | ||
405 | packed = mymalloc(line_size); | |
406 | memset(packed, 0, line_size); | |
407 | ||
408 | for (y = im->ysize-1; y >= 0; --y) { | |
409 | i_gpal(im, 0, im->xsize, y, line); | |
410 | mask = 0x80; | |
411 | byte = 0; | |
412 | out = packed; | |
413 | for (x = 0; x < im->xsize; ++x) { | |
414 | if (line[x]) | |
415 | byte |= mask; | |
416 | if ((mask >>= 1) == 0) { | |
417 | *out++ = byte; | |
418 | byte = 0; | |
419 | mask = 0x80; | |
420 | } | |
421 | } | |
422 | if (mask != 0x80) { | |
423 | *out++ = byte; | |
424 | } | |
425 | if (ig->writecb(ig, packed, line_size) < 0) { | |
426 | myfree(packed); | |
427 | myfree(line); | |
428 | i_push_error(0, "writing 1 bit/pixel packed data"); | |
429 | return 0; | |
430 | } | |
431 | } | |
432 | myfree(packed); | |
433 | myfree(line); | |
434 | ||
435 | return 1; | |
436 | } | |
437 | ||
705fd961 TC |
438 | /* |
439 | =item write_4bit_data(ig, im) | |
440 | ||
441 | Writes the image data as a 4-bit/pixel image. | |
442 | ||
443 | Returns non-zero on success. | |
444 | ||
445 | =cut | |
446 | */ | |
261f91c5 TC |
447 | static int |
448 | write_4bit_data(io_glue *ig, i_img *im) { | |
449 | i_palidx *line; | |
450 | unsigned char *packed; | |
451 | unsigned char *out; | |
452 | int line_size = (im->xsize+1) / 2; | |
453 | int x, y; | |
454 | ||
455 | /* round up to nearest multiple of four */ | |
456 | line_size = (line_size + 3) / 4 * 4; | |
457 | ||
458 | if (!write_bmphead(ig, im, 4, line_size * im->ysize)) | |
459 | return 0; | |
460 | ||
461 | line = mymalloc(im->xsize + 2); | |
462 | memset(line + im->xsize, 0, 2); | |
463 | ||
464 | packed = mymalloc(line_size); | |
465 | memset(packed, 0, line_size); | |
466 | ||
467 | for (y = im->ysize-1; y >= 0; --y) { | |
468 | i_gpal(im, 0, im->xsize, y, line); | |
469 | out = packed; | |
470 | for (x = 0; x < im->xsize; x += 2) { | |
471 | *out++ = (line[x] << 4) + line[x+1]; | |
472 | } | |
473 | if (ig->writecb(ig, packed, line_size) < 0) { | |
474 | myfree(packed); | |
475 | myfree(line); | |
476 | i_push_error(0, "writing 4 bit/pixel packed data"); | |
477 | return 0; | |
478 | } | |
479 | } | |
480 | myfree(packed); | |
481 | myfree(line); | |
482 | ||
483 | return 1; | |
484 | } | |
485 | ||
705fd961 TC |
486 | /* |
487 | =item write_8bit_data(ig, im) | |
488 | ||
489 | Writes the image data as a 8-bit/pixel image. | |
490 | ||
491 | Returns non-zero on success. | |
492 | ||
493 | =cut | |
494 | */ | |
261f91c5 TC |
495 | static int |
496 | write_8bit_data(io_glue *ig, i_img *im) { | |
497 | i_palidx *line; | |
498 | int line_size = im->xsize; | |
499 | int x, y; | |
500 | ||
501 | /* round up to nearest multiple of four */ | |
502 | line_size = (line_size + 3) / 4 * 4; | |
503 | ||
504 | if (!write_bmphead(ig, im, 8, line_size * im->ysize)) | |
505 | return 0; | |
506 | ||
507 | line = mymalloc(im->xsize + 4); | |
508 | memset(line + im->xsize, 0, 4); | |
509 | ||
510 | for (y = im->ysize-1; y >= 0; --y) { | |
511 | i_gpal(im, 0, im->xsize, y, line); | |
512 | if (ig->writecb(ig, line, line_size) < 0) { | |
513 | myfree(line); | |
514 | i_push_error(0, "writing 8 bit/pixel packed data"); | |
515 | return 0; | |
516 | } | |
517 | } | |
518 | myfree(line); | |
519 | ||
520 | return 1; | |
521 | } | |
522 | ||
523 | static int bgr_chans[] = { 2, 1, 0, }; | |
524 | static int grey_chans[] = { 0, 0, 0, }; | |
525 | ||
705fd961 TC |
526 | /* |
527 | =item write_24bit_data(ig, im) | |
528 | ||
529 | Writes the image data as a 24-bit/pixel image. | |
530 | ||
531 | Returns non-zero on success. | |
532 | ||
533 | =cut | |
534 | */ | |
261f91c5 TC |
535 | static int |
536 | write_24bit_data(io_glue *ig, i_img *im) { | |
537 | int *chans; | |
538 | unsigned char *samples; | |
539 | int x, y; | |
540 | int line_size = 3 * im->xsize; | |
541 | ||
542 | line_size = (line_size + 3) / 4 * 4; | |
543 | ||
544 | if (!write_bmphead(ig, im, 24, line_size * im->ysize)) | |
545 | return 0; | |
546 | chans = im->channels >= 3 ? bgr_chans : grey_chans; | |
547 | samples = mymalloc(line_size); | |
548 | for (y = im->ysize-1; y >= 0; --y) { | |
549 | i_gsamp(im, 0, im->xsize, y, samples, chans, 3); | |
550 | if (ig->writecb(ig, samples, line_size) < 0) { | |
551 | i_push_error(0, "writing image data"); | |
552 | myfree(samples); | |
553 | return 0; | |
554 | } | |
555 | } | |
556 | myfree(samples); | |
557 | ||
558 | return 1; | |
559 | } | |
560 | ||
705fd961 TC |
561 | /* |
562 | =item read_bmp_pal(ig, im, count) | |
261f91c5 | 563 | |
705fd961 | 564 | Reads count palette entries from the file and add them to the image. |
261f91c5 | 565 | |
705fd961 | 566 | Returns non-zero on success. |
261f91c5 | 567 | |
705fd961 TC |
568 | =cut |
569 | */ | |
261f91c5 TC |
570 | static int |
571 | read_bmp_pal(io_glue *ig, i_img *im, int count) { | |
572 | int i; | |
573 | int r, g, b, x; | |
574 | i_color c; | |
575 | ||
576 | for (i = 0; i < count; ++i) { | |
577 | if (!read_packed(ig, "CCCC", &b, &g, &r, &x)) { | |
578 | i_push_error(0, "reading BMP palette"); | |
579 | return 0; | |
580 | } | |
581 | c.channel[0] = r; | |
582 | c.channel[1] = g; | |
583 | c.channel[2] = b; | |
584 | if (i_addcolors(im, &c, 1) < 0) | |
585 | return 0; | |
586 | } | |
587 | ||
588 | return 1; | |
589 | } | |
590 | ||
705fd961 TC |
591 | /* |
592 | =item read_1bit_bmp(ig, xsize, ysize, clr_used) | |
593 | ||
594 | Reads in the palette and image data for a 1-bit/pixel image. | |
595 | ||
596 | Returns the image or NULL. | |
597 | ||
598 | =cut | |
599 | */ | |
261f91c5 TC |
600 | static i_img * |
601 | read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { | |
602 | i_img *im; | |
603 | int x, y, lasty, yinc; | |
604 | i_palidx *line, *p; | |
605 | unsigned char *packed; | |
606 | int line_size = (xsize + 7)/8; | |
607 | int byte, bit; | |
608 | unsigned char *in; | |
609 | ||
610 | line_size = (line_size+3) / 4 * 4; | |
611 | ||
612 | if (ysize > 0) { | |
613 | y = ysize-1; | |
614 | lasty = -1; | |
615 | yinc = -1; | |
616 | } | |
617 | else { | |
618 | /* when ysize is -ve it's a top-down image */ | |
619 | ysize = -ysize; | |
620 | y = 0; | |
621 | lasty = ysize; | |
622 | yinc = 1; | |
623 | } | |
624 | im = i_img_pal_new(xsize, ysize, 3, 256); | |
705fd961 TC |
625 | if (!clr_used) |
626 | clr_used = 2; | |
261f91c5 TC |
627 | if (!read_bmp_pal(ig, im, clr_used)) { |
628 | i_img_destroy(im); | |
629 | return NULL; | |
630 | } | |
631 | ||
632 | packed = mymalloc(line_size); | |
633 | line = mymalloc(xsize+8); | |
634 | while (y != lasty) { | |
635 | if (ig->readcb(ig, packed, line_size) != line_size) { | |
636 | myfree(packed); | |
637 | myfree(line); | |
638 | i_push_error(0, "reading 1-bit bmp data"); | |
639 | i_img_destroy(im); | |
640 | return NULL; | |
641 | } | |
642 | in = packed; | |
643 | bit = 0x80; | |
644 | p = line; | |
645 | for (x = 0; x < xsize; ++x) { | |
646 | *p++ = (*in & bit) ? 1 : 0; | |
647 | bit >>= 1; | |
648 | if (!bit) { | |
649 | ++in; | |
650 | bit = 0x80; | |
651 | } | |
652 | } | |
653 | i_ppal(im, 0, xsize, y, line); | |
654 | y += yinc; | |
655 | } | |
656 | ||
657 | return im; | |
658 | } | |
261f91c5 | 659 | |
705fd961 TC |
660 | /* |
661 | =item read_4bit_bmp(ig, xsize, ysize, clr_used, compression) | |
662 | ||
663 | Reads in the palette and image data for a 4-bit/pixel image. | |
664 | ||
665 | Returns the image or NULL. | |
666 | ||
667 | Hopefully this will be combined with the following function at some | |
668 | point. | |
669 | ||
670 | =cut | |
671 | */ | |
261f91c5 | 672 | static i_img * |
705fd961 TC |
673 | read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, |
674 | int compression) { | |
261f91c5 TC |
675 | i_img *im; |
676 | int x, y, lasty, yinc; | |
677 | i_palidx *line, *p; | |
678 | unsigned char *packed; | |
679 | int line_size = (xsize + 1)/2; | |
680 | unsigned char *in; | |
705fd961 | 681 | int size, i; |
261f91c5 TC |
682 | |
683 | line_size = (line_size+3) / 4 * 4; | |
684 | ||
685 | if (ysize > 0) { | |
686 | y = ysize-1; | |
687 | lasty = -1; | |
688 | yinc = -1; | |
689 | } | |
690 | else { | |
691 | /* when ysize is -ve it's a top-down image */ | |
692 | ysize = -ysize; | |
693 | y = 0; | |
694 | lasty = ysize; | |
695 | yinc = 1; | |
696 | } | |
697 | im = i_img_pal_new(xsize, ysize, 3, 256); | |
705fd961 TC |
698 | if (!clr_used) |
699 | clr_used = 16; | |
261f91c5 TC |
700 | if (!read_bmp_pal(ig, im, clr_used)) { |
701 | i_img_destroy(im); | |
702 | return NULL; | |
703 | } | |
704 | ||
705fd961 TC |
705 | if (line_size < 260) |
706 | packed = mymalloc(260); | |
707 | else | |
708 | packed = mymalloc(line_size); | |
261f91c5 TC |
709 | line = mymalloc(xsize+1); |
710 | if (compression == BI_RGB) { | |
711 | while (y != lasty) { | |
712 | if (ig->readcb(ig, packed, line_size) != line_size) { | |
713 | myfree(packed); | |
714 | myfree(line); | |
715 | i_push_error(0, "reading 4-bit bmp data"); | |
716 | i_img_destroy(im); | |
717 | return NULL; | |
718 | } | |
719 | in = packed; | |
720 | p = line; | |
721 | for (x = 0; x < xsize; x+=2) { | |
722 | *p++ = *in >> 4; | |
723 | *p++ = *in & 0x0F; | |
724 | ++in; | |
725 | } | |
726 | i_ppal(im, 0, xsize, y, line); | |
727 | y += yinc; | |
728 | } | |
729 | } | |
730 | else if (compression == BI_RLE4) { | |
705fd961 TC |
731 | int read_size; |
732 | int want_high; | |
733 | int count; | |
734 | ||
735 | x = 0; | |
736 | while (1) { | |
737 | /* there's always at least 2 bytes in a sequence */ | |
738 | if (ig->readcb(ig, packed, 2) != 2) { | |
739 | myfree(packed); | |
740 | myfree(line); | |
741 | i_push_error(0, "missing data during decompression"); | |
742 | i_img_destroy(im); | |
743 | return NULL; | |
744 | } | |
745 | else if (packed[0]) { | |
746 | line[0] = packed[1] >> 4; | |
747 | line[1] = packed[1] & 0x0F; | |
748 | for (i = 0; i < packed[0]; i += 2) { | |
749 | if (i < packed[0]-1) | |
750 | i_ppal(im, x, x+2, y, line); | |
751 | else | |
752 | i_ppal(im, x, x+(packed[0]-i), y, line); | |
753 | x += 2; | |
754 | } | |
755 | } else { | |
756 | switch (packed[1]) { | |
757 | case BMPRLE_ENDOFLINE: | |
758 | x = 0; | |
759 | y += yinc; | |
760 | break; | |
761 | ||
762 | case BMPRLE_ENDOFBMP: | |
763 | free(packed); | |
764 | free(line); | |
765 | return im; | |
766 | ||
767 | case BMPRLE_DELTA: | |
768 | if (ig->readcb(ig, packed, 2) != 2) { | |
769 | myfree(packed); | |
770 | myfree(line); | |
771 | i_push_error(0, "missing data during decompression"); | |
772 | i_img_destroy(im); | |
773 | return NULL; | |
774 | } | |
775 | x += packed[0]; | |
776 | y += yinc * packed[1]; | |
777 | break; | |
778 | ||
779 | default: | |
780 | count = packed[1]; | |
781 | size = (count + 1) / 2; | |
782 | read_size = (size+1) / 2 * 2; | |
783 | if (ig->readcb(ig, packed, read_size) != read_size) { | |
784 | myfree(packed); | |
785 | myfree(line); | |
786 | i_push_error(0, "missing data during decompression"); | |
787 | /*i_img_destroy(im);*/ | |
788 | return im; | |
789 | } | |
790 | for (i = 0; i < size; ++i) { | |
791 | line[0] = packed[i] >> 4; | |
792 | line[1] = packed[i] & 0xF; | |
793 | i_ppal(im, x, x+2, y, line); | |
794 | x += 2; | |
795 | } | |
796 | break; | |
797 | } | |
798 | } | |
799 | } | |
800 | } | |
801 | else { /*if (compression == BI_RLE4) {*/ | |
802 | myfree(packed); | |
803 | myfree(line); | |
804 | i_push_error(0, "bad compression for 4-bit image"); | |
805 | i_img_destroy(im); | |
806 | return NULL; | |
261f91c5 TC |
807 | } |
808 | ||
809 | return im; | |
810 | } | |
811 | ||
705fd961 TC |
812 | /* |
813 | =item read_8bit_bmp(ig, xsize, ysize, clr_used, compression) | |
261f91c5 | 814 | |
705fd961 TC |
815 | Reads in the palette and image data for a 8-bit/pixel image. |
816 | ||
817 | Returns the image or NULL. | |
818 | ||
819 | =cut | |
820 | */ | |
821 | static i_img * | |
822 | read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, | |
823 | int compression) { | |
261f91c5 | 824 | i_img *im; |
705fd961 TC |
825 | int x, y, lasty, yinc; |
826 | i_palidx *line, *p; | |
827 | int line_size = xsize; | |
828 | unsigned char *in; | |
261f91c5 | 829 | |
705fd961 TC |
830 | line_size = (line_size+3) / 4 * 4; |
831 | ||
832 | if (ysize > 0) { | |
833 | y = ysize-1; | |
834 | lasty = -1; | |
835 | yinc = -1; | |
261f91c5 | 836 | } |
705fd961 TC |
837 | else { |
838 | /* when ysize is -ve it's a top-down image */ | |
839 | ysize = -ysize; | |
840 | y = 0; | |
841 | lasty = ysize; | |
842 | yinc = 1; | |
261f91c5 | 843 | } |
705fd961 TC |
844 | im = i_img_pal_new(xsize, ysize, 3, 256); |
845 | if (!clr_used) | |
846 | clr_used = 256; | |
847 | if (!read_bmp_pal(ig, im, clr_used)) { | |
848 | i_img_destroy(im); | |
849 | return NULL; | |
850 | } | |
851 | ||
852 | line = mymalloc(line_size); | |
853 | if (compression == BI_RGB) { | |
854 | while (y != lasty) { | |
855 | if (ig->readcb(ig, line, line_size) != line_size) { | |
856 | myfree(line); | |
857 | i_push_error(0, "reading 8-bit bmp data"); | |
858 | i_img_destroy(im); | |
859 | return NULL; | |
860 | } | |
861 | i_ppal(im, 0, xsize, y, line); | |
862 | y += yinc; | |
863 | } | |
864 | } | |
865 | else if (compression == BI_RLE8) { | |
866 | int read_size; | |
867 | int want_high; | |
868 | int count; | |
869 | unsigned char packed[2]; | |
870 | ||
871 | x = 0; | |
872 | while (1) { | |
873 | /* there's always at least 2 bytes in a sequence */ | |
874 | if (ig->readcb(ig, packed, 2) != 2) { | |
875 | myfree(line); | |
876 | i_push_error(0, "missing data during decompression"); | |
877 | i_img_destroy(im); | |
878 | return NULL; | |
879 | } | |
880 | if (packed[0]) { | |
881 | memset(line, packed[1], packed[0]); | |
882 | i_ppal(im, x, x+packed[0], y, line); | |
883 | x += packed[0]; | |
884 | } else { | |
885 | switch (packed[1]) { | |
886 | case BMPRLE_ENDOFLINE: | |
887 | x = 0; | |
888 | y += yinc; | |
889 | break; | |
890 | ||
891 | case BMPRLE_ENDOFBMP: | |
892 | free(line); | |
893 | return im; | |
894 | ||
895 | case BMPRLE_DELTA: | |
896 | if (ig->readcb(ig, packed, 2) != 2) { | |
897 | myfree(line); | |
898 | i_push_error(0, "missing data during decompression"); | |
899 | i_img_destroy(im); | |
900 | return NULL; | |
901 | } | |
902 | x += packed[0]; | |
903 | y += yinc * packed[1]; | |
904 | break; | |
905 | ||
906 | default: | |
907 | count = packed[1]; | |
908 | read_size = (count+1) / 2 * 2; | |
909 | if (ig->readcb(ig, line, read_size) != read_size) { | |
910 | myfree(line); | |
911 | i_push_error(0, "missing data during decompression"); | |
912 | i_img_destroy(im); | |
913 | return NULL; | |
914 | } | |
915 | i_ppal(im, x, x+count, y, line); | |
916 | x += count; | |
917 | break; | |
918 | } | |
919 | } | |
920 | } | |
921 | } | |
922 | else { | |
923 | myfree(line); | |
924 | i_push_errorf(0, "unknown 8-bit BMP compression %d", compression); | |
925 | i_img_destroy(im); | |
926 | return NULL; | |
927 | } | |
928 | ||
929 | return im; | |
930 | } | |
931 | ||
932 | struct bm_masks { | |
933 | unsigned masks[3]; | |
934 | int shifts[3]; | |
935 | }; | |
936 | static struct bm_masks std_masks[] = | |
937 | { | |
938 | { /* 16-bit */ | |
939 | { 0770000, 00007700, 00000077, }, | |
940 | { 10, 4, -2, }, | |
941 | }, | |
942 | { /* 24-bit */ | |
943 | { 0xFF0000, 0x00FF00, 0x0000FF, }, | |
944 | { 16, 8, 0, }, | |
945 | }, | |
946 | { /* 32-bit */ | |
947 | { 0xFF0000, 0x00FF00, 0x0000FF, }, | |
948 | { 16, 8, 0, }, | |
949 | }, | |
950 | }; | |
951 | ||
952 | /* | |
953 | =item read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression) | |
954 | ||
955 | Skips the palette and reads in the image data for a direct colour image. | |
956 | ||
957 | Returns the image or NULL. | |
958 | ||
959 | =cut | |
960 | */ | |
961 | static i_img * | |
962 | read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, | |
963 | int clr_used, int compression) { | |
964 | i_img *im; | |
965 | int x, y, lasty, yinc; | |
966 | i_color *line, *p; | |
967 | unsigned char *in; | |
968 | int pix_size = bit_count / 8; | |
969 | int line_size = xsize * pix_size; | |
970 | struct bm_masks masks; | |
971 | char unpack_code[2] = ""; | |
972 | int i; | |
973 | int extras; | |
974 | char junk[4]; | |
261f91c5 | 975 | |
705fd961 TC |
976 | unpack_code[0] = *("v3V"+pix_size-2); |
977 | unpack_code[1] = '\0'; | |
261f91c5 | 978 | |
705fd961 TC |
979 | line_size = (line_size+3) / 4 * 4; |
980 | extras = line_size - xsize * pix_size; | |
261f91c5 | 981 | |
705fd961 TC |
982 | if (ysize > 0) { |
983 | y = ysize-1; | |
984 | lasty = -1; | |
985 | yinc = -1; | |
986 | } | |
987 | else { | |
988 | /* when ysize is -ve it's a top-down image */ | |
989 | ysize = -ysize; | |
990 | y = 0; | |
991 | lasty = ysize; | |
992 | yinc = 1; | |
993 | } | |
994 | line = mymalloc(line_size); | |
995 | if (compression == BI_RGB) { | |
996 | masks = std_masks[pix_size-2]; | |
997 | ||
998 | /* there's a potential "palette" after the header */ | |
999 | for (i = 0; i < clr_used; ++clr_used) { | |
1000 | char buf[4]; | |
1001 | if (ig->readcb(ig, buf, 4) != 4) { | |
1002 | i_push_error(0, "skipping colors"); | |
1003 | return 0; | |
1004 | } | |
1005 | } | |
1006 | } | |
1007 | else if (compression == BI_BITFIELDS) { | |
1008 | int pos, bit; | |
1009 | for (i = 0; i < 3; ++i) { | |
1010 | if (!read_packed(ig, "V", masks.masks+i)) { | |
1011 | i_push_error(0, "reading pixel masks"); | |
1012 | return 0; | |
1013 | } | |
1014 | /* work out a shift for the mask */ | |
1015 | pos = 0; | |
1016 | bit = masks.masks[i] & -masks.masks[i]; | |
1017 | while (bit) { | |
1018 | ++pos; | |
1019 | bit >>= 1; | |
1020 | } | |
1021 | masks.shifts[i] = pos - 8; | |
1022 | } | |
1023 | } | |
261f91c5 | 1024 | |
705fd961 TC |
1025 | im = i_img_empty(NULL, xsize, ysize); |
1026 | ||
1027 | line = mymalloc(sizeof(i_color) * xsize); | |
1028 | while (y != lasty) { | |
1029 | p = line; | |
1030 | for (x = 0; x < xsize; ++x) { | |
1031 | unsigned pixel; | |
1032 | if (!read_packed(ig, unpack_code, &pixel)) { | |
1033 | i_push_error(0, "reading image data"); | |
1034 | myfree(line); | |
1035 | i_img_destroy(im); | |
1036 | return NULL; | |
1037 | } | |
1038 | for (i = 0; i < 3; ++i) { | |
1039 | if (masks.shifts[i] > 0) | |
1040 | p->channel[i] = (pixel & masks.masks[i]) >> masks.shifts[i]; | |
1041 | else | |
1042 | p->channel[i] = (pixel & masks.masks[i]) << -masks.shifts[i]; | |
1043 | } | |
1044 | ++p; | |
1045 | } | |
1046 | i_plin(im, 0, xsize, y, line); | |
1047 | if (extras) | |
1048 | ig->readcb(ig, junk, extras); | |
1049 | y += yinc; | |
261f91c5 | 1050 | } |
705fd961 TC |
1051 | myfree(line); |
1052 | ||
1053 | return im; | |
261f91c5 | 1054 | } |
705fd961 TC |
1055 | |
1056 | /* | |
1057 | =head1 SEE ALSO | |
1058 | ||
1059 | Imager(3) | |
1060 | ||
1061 | =head1 AUTHOR | |
1062 | ||
1063 | Tony Cook <tony@develop-help.com> | |
1064 | ||
1065 | =head1 RESTRICTIONS | |
1066 | ||
1067 | Cannot save as compressed BMP. | |
1068 | ||
1069 | =head1 BUGS | |
1070 | ||
1071 | Doesn't handle OS/2 bitmaps. | |
1072 | ||
1073 | 16-bit/pixel images haven't been tested. (I need an image). | |
1074 | ||
1075 | BI_BITFIELDS compression hasn't been tested (I need an image). | |
1076 | ||
1077 | =cut | |
1078 | */ |