ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bmpfile.c
Revision: 2.13
Committed: Fri Mar 18 21:04:05 2005 UTC (19 years, 1 month ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.12: +21 -25 lines
Log Message:
Fixed bug in header size computation related to color palette

File Contents

# User Rev Content
1 greg 2.1 #ifndef lint
2 greg 2.13 static const char RCSid[] = "$Id: bmpfile.c,v 2.12 2004/09/14 02:53:50 greg Exp $";
3 greg 2.1 #endif
4     /*
5     * Windows and OS/2 BMP file support
6     */
7    
8     #include <stdio.h>
9     #include <stdlib.h>
10     #include <string.h>
11     #include "bmpfile.h"
12    
13 greg 2.9 #ifdef getc_unlocked /* avoid horrendous overhead of flockfile */
14 greg 2.12 #undef getc
15     #undef putc
16 greg 2.9 #define getc getc_unlocked
17     #define putc putc_unlocked
18     #endif
19    
20 greg 2.1 /* get corresponding error message */
21     const char *
22     BMPerrorMessage(int ec)
23     {
24     switch (ec) {
25     case BIR_OK:
26     return "No error";
27     case BIR_EOF:
28     return "End of BMP image";
29     case BIR_TRUNCATED:
30     return "Truncated BMP image";
31     case BIR_UNSUPPORTED:
32     return "Unsupported BMP feature";
33 greg 2.3 case BIR_RLERROR:
34     return "BMP runlength encoding error";
35 greg 2.1 case BIR_SEEKERR:
36     return "BMP seek error";
37     }
38     return "Unknown BMP error";
39     }
40    
41 greg 2.3 /* check than header is sensible */
42     static int
43     BMPheaderOK(const BMPHeader *hdr)
44     {
45     if (!hdr)
46     return 0;
47 schorsch 2.4 if ((hdr->width <= 0) | (hdr->height <= 0))
48 greg 2.3 return 0;
49     switch (hdr->bpp) { /* check compression */
50     case 1:
51     case 24:
52     if (hdr->compr != BI_UNCOMPR)
53     return 0;
54     break;
55     case 16:
56     case 32:
57 schorsch 2.4 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_BITFIELDS))
58 greg 2.3 return 0;
59     break;
60     case 4:
61 schorsch 2.4 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE4))
62 greg 2.3 return 0;
63     break;
64     case 8:
65 schorsch 2.4 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE8))
66 greg 2.3 return 0;
67     break;
68     default:
69     return 0;
70     }
71     if (hdr->compr == BI_BITFIELDS && (BMPbitField(hdr)[0] &
72     BMPbitField(hdr)[1] & BMPbitField(hdr)[2]))
73     return 0;
74 greg 2.13 if (hdr->bpp > 8) {
75     if (hdr->nColors != 0)
76     return 0;
77     } else {
78     if ((hdr->nColors < 0) | (hdr->nColors > 1<<hdr->bpp))
79     return 0;
80     if ((hdr->impColors < 0) | (hdr->impColors > hdr->nColors))
81     return 0;
82     }
83 greg 2.3 return 1;
84     }
85 greg 2.1
86 greg 2.3 /* compute uncompressed scan size */
87 schorsch 2.4 #define getScanSiz(h) ( ((((h)->bpp*(h)->width+7) >>3) + 3) & ~03 )
88 greg 2.1
89     /* get next byte from reader */
90     #define rdbyte(c,br) ((br)->fpos += (c=(*(br)->cget)((br)->c_data))!=EOF, c)
91    
92     /* read n bytes */
93     static int
94     rdbytes(char *bp, uint32 n, BMPReader *br)
95     {
96     int c;
97    
98     while (n--) {
99     if (rdbyte(c, br) == EOF)
100     return BIR_TRUNCATED;
101     *bp++ = c;
102     }
103     return BIR_OK;
104     }
105    
106     /* read 32-bit integer in littlendian order */
107     static int32
108     rdint32(BMPReader *br)
109     {
110     int32 i;
111     int c;
112    
113     i = rdbyte(c, br);
114     i |= rdbyte(c, br) << 8;
115     i |= rdbyte(c, br) << 16;
116     i |= rdbyte(c, br) << 24;
117     return i; /* -1 on EOF */
118     }
119    
120     /* read 16-bit unsigned integer in littlendian order */
121     static int
122     rduint16(BMPReader *br)
123     {
124     int i;
125     int c;
126    
127     i = rdbyte(c, br);
128     i |= rdbyte(c, br) << 8;
129     return i; /* -1 on EOF */
130     }
131    
132     /* seek on reader or return 0 (BIR_OK) on success */
133     static int
134     rdseek(uint32 pos, BMPReader *br)
135     {
136     if (pos == br->fpos)
137     return BIR_OK;
138     if (br->seek == NULL || (*br->seek)(pos, br->c_data) != 0)
139     return BIR_SEEKERR;
140     br->fpos = pos;
141     return BIR_OK;
142     }
143    
144     /* open BMP stream for reading and get first scanline */
145     BMPReader *
146     BMPopenReader(int (*cget)(void *), int (*seek)(uint32, void *), void *c_data)
147     {
148     BMPReader *br;
149 greg 2.13 uint32 bmPos, hdrSiz, palSiz;
150     int magic[2]; /* check magic number */
151 greg 2.1
152     if (cget == NULL)
153     return NULL;
154     magic[0] = (*cget)(c_data);
155     if (magic[0] != 'B')
156     return NULL;
157     magic[1] = (*cget)(c_data);
158     if (magic[1] != 'M' && magic[1] != 'A')
159     return NULL;
160     br = (BMPReader *)calloc(1, sizeof(BMPReader));
161     if (br == NULL)
162     return NULL;
163     br->cget = cget;
164     br->seek = seek;
165     br->c_data = c_data;
166     br->hdr = (BMPHeader *)malloc(sizeof(BMPHeader));
167     if (br->hdr == NULL)
168     goto err;
169     br->fpos = 2;
170     /* read & verify file header */
171     (void)rdint32(br); /* file size */
172     (void)rdint32(br); /* reserved word */
173     bmPos = rdint32(br); /* offset to bitmap */
174 greg 2.3 hdrSiz = 2 + 3*4 + rdint32(br); /* header size */
175 greg 2.1 if (hdrSiz < 2 + 6*4 + 2*2 + 6*4)
176     goto err;
177     br->hdr->width = rdint32(br); /* bitmap width */
178     br->hdr->height = rdint32(br); /* bitmap height */
179 schorsch 2.2 if (((br->hdr->width <= 0) | (br->hdr->height == 0)))
180 greg 2.1 goto err;
181     if ((br->hdr->yIsDown = br->hdr->height < 0))
182     br->hdr->height = -br->hdr->height;
183     if (rduint16(br) != 1) /* number of planes */
184     goto err;
185     br->hdr->bpp = rduint16(br); /* bits per pixel */
186     br->hdr->compr = rdint32(br); /* compression mode */
187     (void)rdint32(br); /* bitmap size */
188     br->hdr->hRes = rdint32(br); /* horizontal resolution */
189     br->hdr->vRes = rdint32(br); /* vertical resolution */
190     br->hdr->nColors = rdint32(br); /* # colors used */
191     br->hdr->impColors = rdint32(br); /* # important colors */
192     if (br->hdr->impColors < 0)
193     goto err; /* catch premature EOF */
194 greg 2.3 if (!BMPheaderOK(br->hdr))
195     goto err;
196 greg 2.13 palSiz = sizeof(RGBquad)*br->hdr->nColors;
197     if (br->hdr->impColors <= 0)
198     br->hdr->impColors = br->hdr->nColors;
199 greg 2.1 /* extend header */
200 greg 2.13 if (bmPos < hdrSiz + palSiz)
201 greg 2.1 goto err;
202 greg 2.13 br->hdr->infoSiz = bmPos - (hdrSiz + palSiz);
203     if (br->hdr->nColors > 0 || br->hdr->infoSiz > 0) {
204 greg 2.1 br->hdr = (BMPHeader *)realloc((void *)br->hdr,
205     sizeof(BMPHeader) +
206 greg 2.13 palSiz + br->hdr->infoSiz);
207 greg 2.1 if (br->hdr == NULL)
208     goto err;
209     }
210 greg 2.3 /* read colors or fields */
211     if (br->hdr->compr == BI_BITFIELDS) {
212     BMPbitField(br->hdr)[0] = (uint32)rdint32(br);
213     BMPbitField(br->hdr)[1] = (uint32)rdint32(br);
214     BMPbitField(br->hdr)[2] = (uint32)rdint32(br);
215 greg 2.13 } else if (rdbytes((char *)br->hdr->palette, palSiz, br) != BIR_OK)
216 greg 2.1 goto err;
217     /* read add'l information */
218     if (rdbytes(BMPinfo(br->hdr), br->hdr->infoSiz, br) != BIR_OK)
219     goto err;
220     /* read first scanline */
221     br->scanline = (uint8 *)calloc(getScanSiz(br->hdr), sizeof(uint8));
222     if (br->scanline == NULL)
223     goto err;
224     br->yscan = -1;
225 schorsch 2.4 if (seek != NULL && ((br->hdr->compr == BI_RLE8) |
226     (br->hdr->compr == BI_RLE4))) {
227 greg 2.1 BMPReader *newbr = (BMPReader *)realloc((void *)br,
228     sizeof(BMPReader) +
229     sizeof(br->scanpos[0]) *
230     br->hdr->height);
231     if (newbr == NULL)
232     goto err;
233     br = newbr;
234     memset((void *)(br->scanpos + 1), 0,
235     sizeof(br->scanpos[0])*br->hdr->height);
236     }
237     br->scanpos[0] = br->fpos;
238 greg 2.10 if (BMPreadScanline(br) == BIR_OK)
239     return br;
240 greg 2.1 err:
241     if (br->hdr != NULL)
242     free((void *)br->hdr);
243     if (br->scanline != NULL)
244     free((void *)br->scanline);
245     free((void *)br);
246     return NULL;
247     }
248    
249     /* determine if image is grayscale */
250     int
251     BMPisGrayscale(const BMPHeader *hdr)
252     {
253     const RGBquad *rgbp;
254     int n;
255    
256     if (hdr == NULL)
257     return -1;
258     if (hdr->bpp > 8) /* assume they had a reason for it */
259     return 0;
260 greg 2.6 for (rgbp = hdr->palette, n = hdr->nColors; n-- > 0; rgbp++)
261 schorsch 2.2 if (((rgbp->r != rgbp->g) | (rgbp->g != rgbp->b)))
262 greg 2.1 return 0;
263     return 1; /* all colors neutral in map */
264     }
265    
266     /* read and decode next BMP scanline */
267     int
268     BMPreadScanline(BMPReader *br)
269     {
270 greg 2.3 int n;
271     int8 *sp;
272    
273 greg 2.1 if (br->yscan + 1 >= br->hdr->height)
274     return BIR_EOF;
275     br->yscan++; /* prepare to read */
276 greg 2.3 n = getScanSiz(br->hdr); /* reading uncompressed data? */
277     if (br->hdr->compr == BI_UNCOMPR || br->hdr->compr == BI_BITFIELDS)
278     return rdbytes((char *)br->scanline, n, br);
279     /*
280 greg 2.10 * RLE4/RLE8 Decoding
281 greg 2.3 *
282     * Certain aspects of this scheme are completely insane, so
283     * we don't support them. Fortunately, they rarely appear.
284 greg 2.6 * One is the mid-file EOD (0x0001) and another is the ill-conceived
285 greg 2.3 * "delta" (0x0002), which is like a "goto" statement for bitmaps.
286 greg 2.10 * Whoever thought this up should be wrestled to the ground and told
287     * why it's impossible to support such a scheme in any reasonable way.
288 greg 2.3 * Also, RLE4 mode allows runs to stop halfway through a byte,
289     * which is likewise uncodeable, so we don't even try.
290     * Finally, the scanline break is ambiguous -- we assume here that
291     * it is required at the end of each scanline, though I haven't
292     * found anywhere this is written. Otherwise, we would read to
293     * the end of the scanline, assuming the next bit of data belongs
294     * the following scan. If a break follows the last pixel, as it
295     * seems to in the files I've tested out of Photoshop, you end up
296 greg 2.6 * painting every other line black. Also, I assume any skipped
297 greg 2.3 * pixels are painted with color 0, which is often black. Nowhere
298     * is it specified what we should assume for missing pixels. This
299     * is undoubtedly the most brain-dead format I've ever encountered.
300     */
301     sp = br->scanline;
302 greg 2.10 n = br->hdr->width;
303     if (br->hdr->compr == BI_RLE4)
304     n = (n + 1) >> 1;
305 greg 2.3 while (n > 0) {
306     int skipOdd, len, val;
307    
308     if (rdbyte(len, br) == EOF)
309     return BIR_TRUNCATED;
310     if (len > 0) { /* got a run */
311     if (br->hdr->compr == BI_RLE4) {
312     if (len & 1)
313     return BIR_UNSUPPORTED;
314     len >>= 1;
315     }
316     if (len > n)
317     return BIR_RLERROR;
318     if (rdbyte(val, br) == EOF)
319     return BIR_TRUNCATED;
320     n -= len;
321     while (len--)
322     *sp++ = val;
323     continue;
324     }
325 greg 2.10 /* check for escape */
326 greg 2.3 switch (rdbyte(len, br)) {
327     case EOF:
328     return BIR_TRUNCATED;
329     case 0: /* end of line */
330     while (n--)
331     *sp++ = 0;
332 greg 2.6 /* leaves n == -1 as flag for test after loop */
333 greg 2.3 continue;
334     case 1: /* end of bitmap */
335     case 2: /* delta */
336     return BIR_UNSUPPORTED;
337     }
338     /* absolute mode */
339     if (br->hdr->compr == BI_RLE4) {
340     if (len & 1)
341     return BIR_UNSUPPORTED;
342     len >>= 1;
343     }
344     skipOdd = len & 1;
345     if (len > n)
346     return BIR_RLERROR;
347     n -= len;
348     while (len--) {
349     if (rdbyte(val, br) == EOF)
350     return BIR_TRUNCATED;
351     *sp++ = val;
352     }
353     if (skipOdd && rdbyte(val, br) == EOF)
354     return BIR_TRUNCATED;
355     }
356     /* verify break at end of line */
357 greg 2.6 if (!n && (rdbyte(n, br) != 0 || (rdbyte(n, br) != 0 &&
358     (n != 1 || br->yscan != br->hdr->height-1))))
359 greg 2.3 return BIR_RLERROR;
360     if (br->seek != NULL) /* record next scanline position */
361 greg 2.1 br->scanpos[br->yscan + 1] = br->fpos;
362     return BIR_OK;
363     }
364    
365     /* read a specific scanline */
366     int
367     BMPseekScanline(int y, BMPReader *br)
368     {
369     int rv;
370     /* check arguments */
371     if (br == NULL)
372     return BIR_EOF;
373     if (y < 0)
374     return BIR_SEEKERR;
375     if (y >= br->hdr->height)
376     return BIR_EOF;
377     /* already read? */
378     if (y == br->yscan)
379     return BIR_OK;
380     /* shall we seek? */
381     if (y != br->yscan + 1 && br->seek != NULL) {
382     int yseek;
383     uint32 seekp;
384 greg 2.3 if (br->hdr->compr == BI_UNCOMPR ||
385     br->hdr->compr == BI_BITFIELDS) {
386 greg 2.1 yseek = y;
387     seekp = br->scanpos[0] + y*getScanSiz(br->hdr);
388     } else {
389     yseek = br->yscan + 1;
390     while (yseek < y && br->scanpos[yseek+1] != 0)
391     ++yseek;
392     if (y < yseek && br->scanpos[yseek=y] == 0)
393     return BIR_SEEKERR;
394     seekp = br->scanpos[yseek];
395     }
396     if ((rv = rdseek(seekp, br)) != BIR_OK)
397     return rv;
398     br->yscan = yseek - 1;
399     } else if (y < br->yscan) /* else we can't back up */
400     return BIR_SEEKERR;
401     /* read until we get there */
402     while (br->yscan < y)
403     if ((rv = BMPreadScanline(br)) != BIR_OK)
404     return rv;
405     return BIR_OK;
406     }
407    
408     /* get ith pixel from last scanline */
409     RGBquad
410 greg 2.3 BMPdecodePixel(int i, const BMPReader *br)
411 greg 2.1 {
412 greg 2.3 static const uint32 std16mask[3] = {0x7c00, 0x3e0, 0x1f};
413 greg 2.1 static const RGBquad black = {0, 0, 0, 0};
414 greg 2.3 const uint32 *mask;
415     const uint8 *pp;
416     uint32 pval, v;
417     RGBquad cval;
418 greg 2.1
419 schorsch 2.2 if (((br == NULL) | (i < 0)) || i >= br->hdr->width)
420 greg 2.1 return black;
421    
422 greg 2.3 cval.padding = 0;
423    
424 greg 2.1 switch (br->hdr->bpp) {
425 greg 2.3 case 24:
426     pp = br->scanline + 3*i;
427     cval.b = *pp++;
428     cval.g = *pp++;
429     cval.r = *pp;
430 greg 2.1 return cval;
431     case 32:
432 greg 2.3 if (br->hdr->compr == BI_UNCOMPR)
433     return ((RGBquad *)br->scanline)[i];
434     /* convert bit fields */
435     pp = br->scanline + 4*i;
436     pval = *pp++;
437     pval |= *pp++ << 8;
438     pval |= *pp++ << 16;
439     pval |= *pp << 24;
440     mask = BMPbitField(br->hdr);
441     v = pval & mask[0];
442     while (v & ~0xff) v >>= 8;
443     cval.r = v;
444     v = pval & mask[1];
445     while (v & ~0xff) v >>= 8;
446     cval.g = v;
447     v = pval & mask[2];
448     while (v & ~0xff) v >>= 8;
449     cval.b = v;
450     return cval;
451 greg 2.1 case 8:
452     return br->hdr->palette[br->scanline[i]];
453     case 1:
454 schorsch 2.4 return br->hdr->palette[br->scanline[i>>3]>>((7-i)&7) & 1];
455 greg 2.1 case 4:
456 greg 2.3 return br->hdr->palette[br->scanline[i>>1]>>(i&1?4:0) & 0xf];
457     case 16:
458     pp = br->scanline + 2*i;
459     pval = *pp++;
460     pval |= *pp++ << 8;
461     mask = std16mask;
462     if (br->hdr->compr == BI_BITFIELDS)
463     mask = BMPbitField(br->hdr);
464     cval.r = ((pval & mask[0]) << 8) / (mask[0] + 1);
465     cval.g = ((pval & mask[1]) << 8) / (mask[1] + 1);
466     cval.b = ((pval & mask[2]) << 8) / (mask[2] + 1);
467     return cval;
468 greg 2.1 }
469     return black; /* should never happen */
470     }
471    
472     /* free BMP reader resources */
473     void
474     BMPfreeReader(BMPReader *br)
475     {
476     if (br == NULL)
477     return;
478     free((void *)br->hdr);
479     free((void *)br->scanline);
480     free((void *)br);
481     }
482    
483     /* stdio getc() callback */
484     int
485     stdio_getc(void *p)
486     {
487     if (!p)
488     return EOF;
489     return getc((FILE *)p);
490     }
491    
492     /* stdio putc() callback */
493     void
494     stdio_putc(int c, void *p)
495     {
496     if (p)
497     putc(c, (FILE *)p);
498     }
499    
500     /* stdio fseek() callback */
501     int
502     stdio_fseek(uint32 pos, void *p)
503     {
504     if (!p)
505     return -1;
506     return fseek((FILE *)p, (long)pos, 0);
507     }
508    
509     /* allocate uncompressed (24-bit) RGB header */
510     BMPHeader *
511     BMPtruecolorHeader(int xr, int yr, int infolen)
512     {
513     BMPHeader *hdr;
514    
515     if (xr <= 0 || yr <= 0 || infolen < 0)
516     return NULL;
517     hdr = (BMPHeader *)malloc(sizeof(BMPHeader) - sizeof(hdr->palette) +
518     infolen);
519     if (hdr == NULL)
520     return NULL;
521     hdr->width = xr;
522     hdr->height = yr;
523     hdr->yIsDown = 0; /* default to upwards order */
524     hdr->bpp = 24;
525     hdr->compr = BI_UNCOMPR;
526     hdr->hRes = hdr->vRes = 2835; /* default to 72 ppi */
527     hdr->nColors = hdr->impColors = 0;
528     hdr->infoSiz = infolen;
529     return hdr;
530     }
531    
532 greg 2.5 /* allocate color-mapped header (defaults to minimal grayscale) */
533 greg 2.1 BMPHeader *
534     BMPmappedHeader(int xr, int yr, int infolen, int ncolors)
535     {
536     int n;
537     BMPHeader *hdr;
538    
539     if (xr <= 0 || yr <= 0 || infolen < 0 || ncolors < 2)
540     return NULL;
541     if (ncolors <= 2)
542     n = 1;
543     else if (ncolors <= 16)
544     n = 4;
545     else if (ncolors <= 256)
546     n = 8;
547     else
548     return NULL;
549     hdr = (BMPHeader *)malloc(sizeof(BMPHeader) +
550     sizeof(RGBquad)*(1<<n) -
551     sizeof(hdr->palette) +
552     infolen);
553     if (hdr == NULL)
554     return NULL;
555     hdr->width = xr;
556     hdr->height = yr;
557     hdr->yIsDown = 0; /* default to upwards order */
558     hdr->bpp = n;
559 greg 2.5 hdr->compr = BI_UNCOMPR; /* compression needs seek */
560 greg 2.1 hdr->hRes = hdr->vRes = 2835; /* default to 72 ppi */
561     hdr->nColors = ncolors;
562     hdr->impColors = 0; /* says all colors important */
563     hdr->infoSiz = infolen;
564     memset((void *)hdr->palette, 0, sizeof(RGBquad)*(1<<n) + infolen);
565     for (n = ncolors; n--; )
566     hdr->palette[n].r = hdr->palette[n].g =
567     hdr->palette[n].b = n*255/(ncolors-1);
568     return hdr;
569     }
570    
571     /* put byte to writer */
572     #define wrbyte(c,bw) ( (*(bw)->cput)(c,(bw)->c_data), \
573 greg 2.3 ++(bw)->fpos > (bw)->flen ? \
574 greg 2.1 ((bw)->flen = (bw)->fpos) : \
575     (bw)->fpos )
576    
577     /* write out a string of bytes */
578     static void
579     wrbytes(char *bp, uint32 n, BMPWriter *bw)
580     {
581     while (n--)
582     wrbyte(*bp++, bw);
583     }
584    
585     /* write 32-bit integer in littlendian order */
586     static void
587     wrint32(int32 i, BMPWriter *bw)
588     {
589     wrbyte(i& 0xff, bw);
590     wrbyte(i>>8 & 0xff, bw);
591     wrbyte(i>>16 & 0xff, bw);
592 greg 2.3 wrbyte(i>>24 & 0xff, bw);
593 greg 2.1 }
594    
595     /* write 16-bit unsigned integer in littlendian order */
596     static void
597     wruint16(uint16 ui, BMPWriter *bw)
598     {
599     wrbyte(ui & 0xff, bw);
600     wrbyte(ui>>8 & 0xff, bw);
601     }
602    
603     /* seek to the specified file position, returning 0 (BIR_OK) on success */
604     static int
605     wrseek(uint32 pos, BMPWriter *bw)
606     {
607     if (pos == bw->fpos)
608     return BIR_OK;
609 greg 2.8 if (bw->seek == NULL)
610     return BIR_SEEKERR;
611 greg 2.1 if ((*bw->seek)(pos, bw->c_data) != 0)
612     return BIR_SEEKERR;
613     bw->fpos = pos;
614     if (pos > bw->flen)
615     bw->flen = pos;
616     return BIR_OK;
617     }
618    
619     /* open BMP stream for writing */
620     BMPWriter *
621     BMPopenWriter(void (*cput)(int, void *), int (*seek)(uint32, void *),
622     void *c_data, BMPHeader *hdr)
623     {
624     BMPWriter *bw;
625     uint32 hdrSiz, palSiz, scanSiz, bmSiz;
626     /* check arguments */
627 greg 2.3 if (cput == NULL)
628 greg 2.1 return NULL;
629 greg 2.3 if (!BMPheaderOK(hdr))
630 greg 2.1 return NULL;
631 schorsch 2.4 if ((hdr->bpp == 16) | (hdr->compr == BI_RLE4))
632 greg 2.1 return NULL; /* unsupported */
633 greg 2.5 /* no seek means we may have the wrong file length, but most app's don't care
634 schorsch 2.4 if (seek == NULL && ((hdr->compr == BI_RLE8) | (hdr->compr == BI_RLE4)))
635 greg 2.1 return NULL;
636 greg 2.5 */
637 greg 2.1 /* compute sizes */
638     hdrSiz = 2 + 6*4 + 2*2 + 6*4;
639 greg 2.3 if (hdr->compr == BI_BITFIELDS)
640     hdrSiz += sizeof(uint32)*3;
641 greg 2.13 palSiz = sizeof(RGBquad)*hdr->nColors;
642 greg 2.1 scanSiz = getScanSiz(hdr);
643     bmSiz = hdr->height*scanSiz; /* wrong if compressed */
644     /* initialize writer */
645     bw = (BMPWriter *)malloc(sizeof(BMPWriter));
646     if (bw == NULL)
647     return NULL;
648     bw->hdr = hdr;
649     bw->yscan = 0;
650     bw->scanline = (uint8 *)calloc(scanSiz, sizeof(uint8));
651     if (bw->scanline == NULL) {
652     free((void *)bw);
653     return NULL;
654     }
655     bw->fbmp = hdrSiz + palSiz + hdr->infoSiz;
656     bw->fpos = bw->flen = 0;
657     bw->cput = cput;
658     bw->seek = seek;
659     bw->c_data = c_data;
660     /* write out header */
661     wrbyte('B', bw); wrbyte('M', bw); /* magic number */
662     wrint32(bw->fbmp + bmSiz, bw); /* file size */
663     wrint32(0, bw); /* reserved word */
664     wrint32(bw->fbmp, bw); /* offset to bitmap */
665     wrint32(hdrSiz - bw->fpos, bw); /* info header size */
666     wrint32(hdr->width, bw); /* bitmap width */
667     if (hdr->yIsDown) /* bitmap height */
668     wrint32(-hdr->height, bw);
669     else
670     wrint32(hdr->height, bw);
671     wruint16(1, bw); /* number of planes */
672     wruint16(hdr->bpp, bw); /* bits per pixel */
673     wrint32(hdr->compr, bw); /* compression mode */
674     wrint32(bmSiz, bw); /* bitmap size */
675     wrint32(hdr->hRes, bw); /* horizontal resolution */
676     wrint32(hdr->vRes, bw); /* vertical resolution */
677     wrint32(hdr->nColors, bw); /* # colors used */
678     wrint32(hdr->impColors, bw); /* # important colors */
679     /* write out color palette */
680     wrbytes((char *)hdr->palette, palSiz, bw);
681     /* write add'l information */
682     wrbytes(BMPinfo(hdr), hdr->infoSiz, bw);
683     #ifndef NDEBUG
684     if (bw->fpos != bw->fbmp) {
685     fputs("Coding error 1 in BMPopenWriter\n", stderr);
686     exit(1);
687     }
688     #endif
689     return bw;
690     }
691 greg 2.3
692     /* find position of next run of 5 or more identical bytes, or 255 if none */
693     static int
694     findNextRun(const int8 *bp, int len)
695     {
696     int pos, cnt;
697     /* look for run */
698 schorsch 2.4 for (pos = 0; (len > 0) & (pos < 255); pos++, bp++, len--) {
699 greg 2.3 if (len < 5) /* no hope left? */
700     continue;
701     cnt = 1; /* else let's try it */
702     while (bp[cnt] == bp[0])
703 greg 2.5 if (++cnt >= 5)
704     return pos; /* long enough */
705 greg 2.3 }
706     return pos; /* didn't find any */
707     }
708 greg 2.1
709     /* write the current scanline */
710     int
711     BMPwriteScanline(BMPWriter *bw)
712     {
713 greg 2.3 const int8 *sp;
714     int n;
715    
716 greg 2.1 if (bw->yscan >= bw->hdr->height)
717     return BIR_EOF;
718     /* writing uncompressed? */
719 greg 2.3 if (bw->hdr->compr == BI_UNCOMPR || bw->hdr->compr == BI_BITFIELDS) {
720 greg 2.1 uint32 scanSiz = getScanSiz(bw->hdr);
721     uint32 slpos = bw->fbmp + bw->yscan*scanSiz;
722     if (wrseek(slpos, bw) != BIR_OK)
723     return BIR_SEEKERR;
724     wrbytes((char *)bw->scanline, scanSiz, bw);
725     bw->yscan++;
726     return BIR_OK;
727     }
728 greg 2.3 /*
729     * RLE8 Encoding
730     *
731     * See the notes in BMPreadScanline() on this encoding. Needless
732     * to say, we avoid the nuttier aspects of this specification.
733     * We also assume that every scanline ends in a line break
734     * (0x0000) except for the last, which ends in a bitmap break
735     * (0x0001). We don't support RLE4 at all; it's too awkward.
736     */
737     sp = bw->scanline;
738     n = bw->hdr->width;
739     while (n > 0) {
740     int cnt, val;
741     cnt = findNextRun(sp, n); /* 0-255 < n */
742 greg 2.10 if (cnt >= 3) { /* output absolute */
743 greg 2.3 int skipOdd = cnt & 1;
744     wrbyte(0, bw);
745     wrbyte(cnt, bw);
746     n -= cnt;
747     while (cnt--)
748     wrbyte(*sp++, bw);
749     if (skipOdd)
750     wrbyte(0, bw);
751     }
752     if (n <= 0) /* was that it? */
753     break;
754     val = *sp; /* output run */
755 greg 2.10 for (cnt = 1; --n && cnt < 255; cnt++)
756     if (*++sp != val)
757 greg 2.5 break;
758 greg 2.3 wrbyte(cnt, bw);
759     wrbyte(val, bw);
760     }
761 greg 2.5 bw->yscan++; /* write line break or EOD */
762 greg 2.1 if (bw->yscan == bw->hdr->height) {
763 greg 2.3 wrbyte(0, bw); wrbyte(1, bw); /* end of bitmap marker */
764 greg 2.8 if (wrseek(2, bw) != BIR_OK)
765 greg 2.5 return BIR_OK; /* no one may care */
766 greg 2.8 wrint32(bw->flen, bw); /* correct file length */
767     if (wrseek(34, bw) != BIR_OK)
768     return BIR_OK;
769 greg 2.6 wrint32(bw->flen-bw->fbmp, bw); /* correct bitmap length */
770 greg 2.3 } else {
771     wrbyte(0, bw); wrbyte(0, bw); /* end of line marker */
772 greg 2.1 }
773     return BIR_OK;
774     }
775    
776     /* free BMP writer resources */
777     void
778     BMPfreeWriter(BMPWriter *bw)
779     {
780     if (bw == NULL)
781     return;
782     free((void *)bw->hdr);
783     free((void *)bw->scanline);
784     free((void *)bw);
785     }