ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bmpfile.c
Revision: 2.12
Committed: Tue Sep 14 02:53:50 2004 UTC (19 years, 7 months ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: rad3R6, rad3R6P1
Changes since 2.11: +3 -1 lines
Log Message:
Added #undef for getc/putc where they were being replaced

File Contents

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