ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bmpfile.c
Revision: 2.9
Committed: Thu Apr 29 14:36:49 2004 UTC (20 years ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.8: +6 -1 lines
Log Message:
Added macros to avoid flockfile(3) overhead

File Contents

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