start of bmp support (just writing so far)
[imager.git] / bmp.c
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 */
12 static
13 int 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
56 static int
57 write_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
108 static
109 int 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
185 static int
186 write_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
237 static int
238 write_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
276 static int
277 write_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
304 static int bgr_chans[] = { 2, 1, 0, };
305 static int grey_chans[] = { 0, 0, 0, };
306
307 static int
308 write_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 */
334 int
335 i_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
360 static int
361 read_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
381 static i_img *
382 read_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
440 static i_img *
441 read_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
500 i_img *
501 i_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 }