start of bmp support (just writing so far)
[imager.git] / bmp.c
CommitLineData
261f91c5
TC
1#include "image.h"
2#include <stdarg.h>
3
4/* possibly this belongs in a global utilities library
5 Reads from the specified "file" the specified sizes.
6 The format codes match those used by perl's pack() function,
7 though only a few are implemented.
8 In all cases the vararg arguement is an int *.
9
10 Returns non-zero if all of the arguments were read.
11*/
12static
13int read_packed(io_glue *ig, char *format, ...) {
14 unsigned char buf[4];
15 va_list ap;
16 int *p;
17
18 va_start(ap, format);
19
20 while (*format) {
21 p = va_arg(ap, int *);
22
23 switch (*format) {
24 case 'v':
25 if (ig->readcb(ig, buf, 2) == -1)
26 return 0;
27 *p = buf[0] + (buf[1] << 8);
28 break;
29
30 case 'V':
31 if (ig->readcb(ig, buf, 4) == -1)
32 return 0;
33 *p = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24);
34 break;
35
36 case 'C':
37 if (ig->readcb(ig, buf, 1) == -1)
38 return 0;
39 *p = buf[0];
40 break;
41
42 case 'c':
43 if (ig->readcb(ig, buf, 1) == -1)
44 return 0;
45 *p = (char)buf[0];
46 break;
47
48 default:
49 m_fatal(1, "Unknown read_packed format code 0x%02x", *format);
50 }
51 ++format;
52 }
53 return 1;
54}
55
56static int
57write_packed(io_glue *ig, char *format, ...) {
58 unsigned char buf[4];
59 va_list ap;
60 int i;
61
62 va_start(ap, format);
63
64 while (*format) {
65 i = va_arg(ap, unsigned int);
66
67 switch (*format) {
68 case 'v':
69 buf[0] = i & 255;
70 buf[1] = i / 256;
71 if (ig->writecb(ig, buf, 2) == -1)
72 return 0;
73 break;
74
75 case 'V':
76 buf[0] = i & 0xFF;
77 buf[1] = (i >> 8) & 0xFF;
78 buf[2] = (i >> 16) & 0xFF;
79 buf[3] = (i >> 24) & 0xFF;
80 if (ig->writecb(ig, buf, 4) == -1)
81 return 0;
82 break;
83
84 case 'C':
85 case 'c':
86 buf[0] = i & 0xFF;
87 if (ig->writecb(ig, buf, 1) == -1)
88 return 0;
89 break;
90
91 default:
92 m_fatal(1, "Unknown read_packed format code 0x%02x", *format);
93 }
94 ++format;
95 }
96 va_end(ap);
97
98 return 1;
99}
100
101#define FILEHEAD_SIZE 14
102#define INFOHEAD_SIZE 40
103#define BI_RGB 0
104#define BI_RLE8 1
105#define BI_RLE4 2
106#define BI_BITFIELDS 3
107
108static
109int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) {
110 double xres, yres;
111 int got_xres, got_yres, aspect_only;
112 int colors_used = 0;
113 int offset = FILEHEAD_SIZE + INFOHEAD_SIZE;
114
115 got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
116 got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
117 if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
118 aspect_only = 0;
119 if (!got_xres) {
120 if (!got_yres)
121 xres = yres = 72;
122 else
123 xres = yres;
124 }
125 else {
126 if (!got_yres)
127 yres = xres;
128 }
129 if (xres <= 0 || yres <= 0)
130 xres = yres = 72;
131 if (aspect_only) {
132 /* scale so the smaller value is 72 */
133 double ratio;
134 if (xres < yres) {
135 ratio = 72.0 / xres;
136 }
137 else {
138 ratio = 72.0 / yres;
139 }
140 xres *= ratio;
141 yres *= ratio;
142 }
143 /* now to pels/meter */
144 xres *= 100.0/2.54;
145 yres *= 100.0/2.54;
146
147 if (im->type == i_palette_type) {
148 colors_used = i_colorcount(im);
149 offset += 4 * colors_used;
150 }
151
152 if (!write_packed(ig, "CCVvvVVVVvvVVVVVV", 'B', 'M', data_size+offset,
153 0, 0, offset, INFOHEAD_SIZE, im->xsize, im->ysize, 1,
154 bit_count, BI_RGB, 0, (int)xres, (int)yres,
155 colors_used, 0)){
156 i_push_error(0, "cannot write bmp header");
157 return 0;
158 }
159 if (im->type == i_palette_type) {
160 int i;
161 i_color c;
162
163 for (i = 0; i < colors_used; ++i) {
164 i_getcolors(im, i, &c, 1);
165 if (im->channels >= 3) {
166 if (!write_packed(ig, "CCCC", c.channel[2], c.channel[1],
167 c.channel[0], 0)) {
168 i_push_error(0, "cannot write palette entry");
169 return 0;
170 }
171 }
172 else {
173 if (!write_packed(ig, "CCCC", c.channel[0], c.channel[0],
174 c.channel[0], 0)) {
175 i_push_error(0, "cannot write palette entry");
176 return 0;
177 }
178 }
179 }
180 }
181
182 return 1;
183}
184
185static int
186write_1bit_data(io_glue *ig, i_img *im) {
187 i_palidx *line;
188 unsigned char *packed;
189 int byte;
190 int mask;
191 unsigned char *out;
192 int line_size = (im->xsize+7) / 8;
193 int x, y;
194
195 /* round up to nearest multiple of four */
196 line_size = (line_size + 3) / 4 * 4;
197
198 if (!write_bmphead(ig, im, 1, line_size * im->ysize))
199 return 0;
200
201 line = mymalloc(im->xsize + 8);
202 memset(line + im->xsize, 0, 8);
203
204 packed = mymalloc(line_size);
205 memset(packed, 0, line_size);
206
207 for (y = im->ysize-1; y >= 0; --y) {
208 i_gpal(im, 0, im->xsize, y, line);
209 mask = 0x80;
210 byte = 0;
211 out = packed;
212 for (x = 0; x < im->xsize; ++x) {
213 if (line[x])
214 byte |= mask;
215 if ((mask >>= 1) == 0) {
216 *out++ = byte;
217 byte = 0;
218 mask = 0x80;
219 }
220 }
221 if (mask != 0x80) {
222 *out++ = byte;
223 }
224 if (ig->writecb(ig, packed, line_size) < 0) {
225 myfree(packed);
226 myfree(line);
227 i_push_error(0, "writing 1 bit/pixel packed data");
228 return 0;
229 }
230 }
231 myfree(packed);
232 myfree(line);
233
234 return 1;
235}
236
237static int
238write_4bit_data(io_glue *ig, i_img *im) {
239 i_palidx *line;
240 unsigned char *packed;
241 unsigned char *out;
242 int line_size = (im->xsize+1) / 2;
243 int x, y;
244
245 /* round up to nearest multiple of four */
246 line_size = (line_size + 3) / 4 * 4;
247
248 if (!write_bmphead(ig, im, 4, line_size * im->ysize))
249 return 0;
250
251 line = mymalloc(im->xsize + 2);
252 memset(line + im->xsize, 0, 2);
253
254 packed = mymalloc(line_size);
255 memset(packed, 0, line_size);
256
257 for (y = im->ysize-1; y >= 0; --y) {
258 i_gpal(im, 0, im->xsize, y, line);
259 out = packed;
260 for (x = 0; x < im->xsize; x += 2) {
261 *out++ = (line[x] << 4) + line[x+1];
262 }
263 if (ig->writecb(ig, packed, line_size) < 0) {
264 myfree(packed);
265 myfree(line);
266 i_push_error(0, "writing 4 bit/pixel packed data");
267 return 0;
268 }
269 }
270 myfree(packed);
271 myfree(line);
272
273 return 1;
274}
275
276static int
277write_8bit_data(io_glue *ig, i_img *im) {
278 i_palidx *line;
279 int line_size = im->xsize;
280 int x, y;
281
282 /* round up to nearest multiple of four */
283 line_size = (line_size + 3) / 4 * 4;
284
285 if (!write_bmphead(ig, im, 8, line_size * im->ysize))
286 return 0;
287
288 line = mymalloc(im->xsize + 4);
289 memset(line + im->xsize, 0, 4);
290
291 for (y = im->ysize-1; y >= 0; --y) {
292 i_gpal(im, 0, im->xsize, y, line);
293 if (ig->writecb(ig, line, line_size) < 0) {
294 myfree(line);
295 i_push_error(0, "writing 8 bit/pixel packed data");
296 return 0;
297 }
298 }
299 myfree(line);
300
301 return 1;
302}
303
304static int bgr_chans[] = { 2, 1, 0, };
305static int grey_chans[] = { 0, 0, 0, };
306
307static int
308write_24bit_data(io_glue *ig, i_img *im) {
309 int *chans;
310 unsigned char *samples;
311 int x, y;
312 int line_size = 3 * im->xsize;
313
314 line_size = (line_size + 3) / 4 * 4;
315
316 if (!write_bmphead(ig, im, 24, line_size * im->ysize))
317 return 0;
318 chans = im->channels >= 3 ? bgr_chans : grey_chans;
319 samples = mymalloc(line_size);
320 for (y = im->ysize-1; y >= 0; --y) {
321 i_gsamp(im, 0, im->xsize, y, samples, chans, 3);
322 if (ig->writecb(ig, samples, line_size) < 0) {
323 i_push_error(0, "writing image data");
324 myfree(samples);
325 return 0;
326 }
327 }
328 myfree(samples);
329
330 return 1;
331}
332
333/* no support for writing compressed (RLE8 or RLE4) BMP files */
334int
335i_writebmp_wiol(i_img *im, io_glue *ig) {
336 io_glue_commit_types(ig);
337 i_clear_error();
338
339 /* pick a format */
340 if (im->type == i_direct_type) {
341 return write_24bit_data(ig, im);
342 }
343 else {
344 int pal_size;
345
346 /* must be paletted */
347 pal_size = i_colorcount(im);
348 if (pal_size <= 2) {
349 return write_1bit_data(ig, im);
350 }
351 else if (pal_size <= 16) {
352 return write_4bit_data(ig, im);
353 }
354 else {
355 return write_8bit_data(ig, im);
356 }
357 }
358}
359
360static int
361read_bmp_pal(io_glue *ig, i_img *im, int count) {
362 int i;
363 int r, g, b, x;
364 i_color c;
365
366 for (i = 0; i < count; ++i) {
367 if (!read_packed(ig, "CCCC", &b, &g, &r, &x)) {
368 i_push_error(0, "reading BMP palette");
369 return 0;
370 }
371 c.channel[0] = r;
372 c.channel[1] = g;
373 c.channel[2] = b;
374 if (i_addcolors(im, &c, 1) < 0)
375 return 0;
376 }
377
378 return 1;
379}
380
381static i_img *
382read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) {
383 i_img *im;
384 int x, y, lasty, yinc;
385 i_palidx *line, *p;
386 unsigned char *packed;
387 int line_size = (xsize + 7)/8;
388 int byte, bit;
389 unsigned char *in;
390
391 line_size = (line_size+3) / 4 * 4;
392
393 if (ysize > 0) {
394 y = ysize-1;
395 lasty = -1;
396 yinc = -1;
397 }
398 else {
399 /* when ysize is -ve it's a top-down image */
400 ysize = -ysize;
401 y = 0;
402 lasty = ysize;
403 yinc = 1;
404 }
405 im = i_img_pal_new(xsize, ysize, 3, 256);
406 if (!read_bmp_pal(ig, im, clr_used)) {
407 i_img_destroy(im);
408 return NULL;
409 }
410
411 packed = mymalloc(line_size);
412 line = mymalloc(xsize+8);
413 while (y != lasty) {
414 if (ig->readcb(ig, packed, line_size) != line_size) {
415 myfree(packed);
416 myfree(line);
417 i_push_error(0, "reading 1-bit bmp data");
418 i_img_destroy(im);
419 return NULL;
420 }
421 in = packed;
422 bit = 0x80;
423 p = line;
424 for (x = 0; x < xsize; ++x) {
425 *p++ = (*in & bit) ? 1 : 0;
426 bit >>= 1;
427 if (!bit) {
428 ++in;
429 bit = 0x80;
430 }
431 }
432 i_ppal(im, 0, xsize, y, line);
433 y += yinc;
434 }
435
436 return im;
437}
438#if 0
439
440static i_img *
441read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) {
442 i_img *im;
443 int x, y, lasty, yinc;
444 i_palidx *line, *p;
445 unsigned char *packed;
446 int line_size = (xsize + 1)/2;
447 unsigned char *in;
448
449 line_size = (line_size+3) / 4 * 4;
450
451 if (ysize > 0) {
452 y = ysize-1;
453 lasty = -1;
454 yinc = -1;
455 }
456 else {
457 /* when ysize is -ve it's a top-down image */
458 ysize = -ysize;
459 y = 0;
460 lasty = ysize;
461 yinc = 1;
462 }
463 im = i_img_pal_new(xsize, ysize, 3, 256);
464 if (!read_bmp_pal(ig, im, clr_used)) {
465 i_img_destroy(im);
466 return NULL;
467 }
468
469 packed = mymalloc(line_size);
470 line = mymalloc(xsize+1);
471 if (compression == BI_RGB) {
472 while (y != lasty) {
473 if (ig->readcb(ig, packed, line_size) != line_size) {
474 myfree(packed);
475 myfree(line);
476 i_push_error(0, "reading 4-bit bmp data");
477 i_img_destroy(im);
478 return NULL;
479 }
480 in = packed;
481 p = line;
482 for (x = 0; x < xsize; x+=2) {
483 *p++ = *in >> 4;
484 *p++ = *in & 0x0F;
485 ++in;
486 }
487 i_ppal(im, 0, xsize, y, line);
488 y += yinc;
489 }
490 }
491 else if (compression == BI_RLE4) {
492 return 0;
493 }
494
495 return im;
496}
497
498#endif
499
500i_img *
501i_readbmp_wiol(io_glue *ig) {
502#if 0
503 int b_magic, m_magic, filesize, dummy, infohead_size;
504 int xsize, ysize, planes, bit_count, compression, size_image, xres, yres;
505 int clr_used;
506 i_img *im;
507
508 io_glue_commit_types(ig);
509 i_clear_error();
510
511 if (!read_packed(ig, "CCVvvVVVVvvVVVVV", &b_magic, &m_magic, &filesize,
512 &dummy, &dummy, &infohead_size, &xsize, &ysize, &planes,
513 &bit_count, &compression, &size_image, &xres, &yres,
514 &clr_used, &dummy)) {
515 i_push_error(0, "file too short");
516 return 0;
517 }
518 if (b_magic != 'B' || m_magic != 'M' || infohead_size != INFOHEAD_SIZE
519 || planes != 1) {
520 i_push_error(0, "not a BMP file");
521 return 0;
522 }
523
524 switch (bit_count) {
525 case 1:
526 im = read_1bit_bmp(ig, xsize, ysize, clr_used);
527 break;
528
529 case 4:
530 im = read_4bit_bmp(ig, clr_used, compression);
531 break;
532
533 case 8:
534 im = read_8bit_bmp(ig, clr_used, compression);
535 break;
536
537 case 32:
538 case 24:
539 case 16:
540 im = read_direct_bmp(ig, clr_used, compression);
541 break;
542 }
543#endif
544 return 0;
545}