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

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bmpfile.c,v 2.12 2004/09/14 02:53:50 greg Exp $";
3 #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 #ifdef getc_unlocked /* avoid horrendous overhead of flockfile */
14 #undef getc
15 #undef putc
16 #define getc getc_unlocked
17 #define putc putc_unlocked
18 #endif
19
20 /* 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 case BIR_RLERROR:
34 return "BMP runlength encoding error";
35 case BIR_SEEKERR:
36 return "BMP seek error";
37 }
38 return "Unknown BMP error";
39 }
40
41 /* check than header is sensible */
42 static int
43 BMPheaderOK(const BMPHeader *hdr)
44 {
45 if (!hdr)
46 return 0;
47 if ((hdr->width <= 0) | (hdr->height <= 0))
48 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 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_BITFIELDS))
58 return 0;
59 break;
60 case 4:
61 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE4))
62 return 0;
63 break;
64 case 8:
65 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE8))
66 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 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 return 1;
84 }
85
86 /* compute uncompressed scan size */
87 #define getScanSiz(h) ( ((((h)->bpp*(h)->width+7) >>3) + 3) & ~03 )
88
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 uint32 bmPos, hdrSiz, palSiz;
150 int magic[2]; /* check magic number */
151
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 hdrSiz = 2 + 3*4 + rdint32(br); /* header size */
175 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 if (((br->hdr->width <= 0) | (br->hdr->height == 0)))
180 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 if (!BMPheaderOK(br->hdr))
195 goto err;
196 palSiz = sizeof(RGBquad)*br->hdr->nColors;
197 if (br->hdr->impColors <= 0)
198 br->hdr->impColors = br->hdr->nColors;
199 /* extend header */
200 if (bmPos < hdrSiz + palSiz)
201 goto err;
202 br->hdr->infoSiz = bmPos - (hdrSiz + palSiz);
203 if (br->hdr->nColors > 0 || br->hdr->infoSiz > 0) {
204 br->hdr = (BMPHeader *)realloc((void *)br->hdr,
205 sizeof(BMPHeader) +
206 palSiz + br->hdr->infoSiz);
207 if (br->hdr == NULL)
208 goto err;
209 }
210 /* 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 } else if (rdbytes((char *)br->hdr->palette, palSiz, br) != BIR_OK)
216 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 if (seek != NULL && ((br->hdr->compr == BI_RLE8) |
226 (br->hdr->compr == BI_RLE4))) {
227 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 if (BMPreadScanline(br) == BIR_OK)
239 return br;
240 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 for (rgbp = hdr->palette, n = hdr->nColors; n-- > 0; rgbp++)
261 if (((rgbp->r != rgbp->g) | (rgbp->g != rgbp->b)))
262 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 int n;
271 int8 *sp;
272
273 if (br->yscan + 1 >= br->hdr->height)
274 return BIR_EOF;
275 br->yscan++; /* prepare to read */
276 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 * RLE4/RLE8 Decoding
281 *
282 * Certain aspects of this scheme are completely insane, so
283 * we don't support them. Fortunately, they rarely appear.
284 * One is the mid-file EOD (0x0001) and another is the ill-conceived
285 * "delta" (0x0002), which is like a "goto" statement for bitmaps.
286 * 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 * 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 * painting every other line black. Also, I assume any skipped
297 * 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 n = br->hdr->width;
303 if (br->hdr->compr == BI_RLE4)
304 n = (n + 1) >> 1;
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 /* check for escape */
326 switch (rdbyte(len, br)) {
327 case EOF:
328 return BIR_TRUNCATED;
329 case 0: /* end of line */
330 while (n--)
331 *sp++ = 0;
332 /* leaves n == -1 as flag for test after loop */
333 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 if (!n && (rdbyte(n, br) != 0 || (rdbyte(n, br) != 0 &&
358 (n != 1 || br->yscan != br->hdr->height-1))))
359 return BIR_RLERROR;
360 if (br->seek != NULL) /* record next scanline position */
361 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 if (br->hdr->compr == BI_UNCOMPR ||
385 br->hdr->compr == BI_BITFIELDS) {
386 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 BMPdecodePixel(int i, const BMPReader *br)
411 {
412 static const uint32 std16mask[3] = {0x7c00, 0x3e0, 0x1f};
413 static const RGBquad black = {0, 0, 0, 0};
414 const uint32 *mask;
415 const uint8 *pp;
416 uint32 pval, v;
417 RGBquad cval;
418
419 if (((br == NULL) | (i < 0)) || i >= br->hdr->width)
420 return black;
421
422 cval.padding = 0;
423
424 switch (br->hdr->bpp) {
425 case 24:
426 pp = br->scanline + 3*i;
427 cval.b = *pp++;
428 cval.g = *pp++;
429 cval.r = *pp;
430 return cval;
431 case 32:
432 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 case 8:
452 return br->hdr->palette[br->scanline[i]];
453 case 1:
454 return br->hdr->palette[br->scanline[i>>3]>>((7-i)&7) & 1];
455 case 4:
456 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 }
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 /* allocate color-mapped header (defaults to minimal grayscale) */
533 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 hdr->compr = BI_UNCOMPR; /* compression needs seek */
560 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 ++(bw)->fpos > (bw)->flen ? \
574 ((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 wrbyte(i>>24 & 0xff, bw);
593 }
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 if (bw->seek == NULL)
610 return BIR_SEEKERR;
611 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 if (cput == NULL)
628 return NULL;
629 if (!BMPheaderOK(hdr))
630 return NULL;
631 if ((hdr->bpp == 16) | (hdr->compr == BI_RLE4))
632 return NULL; /* unsupported */
633 /* no seek means we may have the wrong file length, but most app's don't care
634 if (seek == NULL && ((hdr->compr == BI_RLE8) | (hdr->compr == BI_RLE4)))
635 return NULL;
636 */
637 /* compute sizes */
638 hdrSiz = 2 + 6*4 + 2*2 + 6*4;
639 if (hdr->compr == BI_BITFIELDS)
640 hdrSiz += sizeof(uint32)*3;
641 palSiz = sizeof(RGBquad)*hdr->nColors;
642 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
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 for (pos = 0; (len > 0) & (pos < 255); pos++, bp++, len--) {
699 if (len < 5) /* no hope left? */
700 continue;
701 cnt = 1; /* else let's try it */
702 while (bp[cnt] == bp[0])
703 if (++cnt >= 5)
704 return pos; /* long enough */
705 }
706 return pos; /* didn't find any */
707 }
708
709 /* write the current scanline */
710 int
711 BMPwriteScanline(BMPWriter *bw)
712 {
713 const int8 *sp;
714 int n;
715
716 if (bw->yscan >= bw->hdr->height)
717 return BIR_EOF;
718 /* writing uncompressed? */
719 if (bw->hdr->compr == BI_UNCOMPR || bw->hdr->compr == BI_BITFIELDS) {
720 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 /*
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 if (cnt >= 3) { /* output absolute */
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 for (cnt = 1; --n && cnt < 255; cnt++)
756 if (*++sp != val)
757 break;
758 wrbyte(cnt, bw);
759 wrbyte(val, bw);
760 }
761 bw->yscan++; /* write line break or EOD */
762 if (bw->yscan == bw->hdr->height) {
763 wrbyte(0, bw); wrbyte(1, bw); /* end of bitmap marker */
764 if (wrseek(2, bw) != BIR_OK)
765 return BIR_OK; /* no one may care */
766 wrint32(bw->flen, bw); /* correct file length */
767 if (wrseek(34, bw) != BIR_OK)
768 return BIR_OK;
769 wrint32(bw->flen-bw->fbmp, bw); /* correct bitmap length */
770 } else {
771 wrbyte(0, bw); wrbyte(0, bw); /* end of line marker */
772 }
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 }