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

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bmpfile.c,v 2.8 2004/04/10 02:54:44 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 #define getc getc_unlocked
15 #define putc putc_unlocked
16 #endif
17
18 /* 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 case BIR_RLERROR:
32 return "BMP runlength encoding error";
33 case BIR_SEEKERR:
34 return "BMP seek error";
35 }
36 return "Unknown BMP error";
37 }
38
39 /* check than header is sensible */
40 static int
41 BMPheaderOK(const BMPHeader *hdr)
42 {
43 if (!hdr)
44 return 0;
45 if ((hdr->width <= 0) | (hdr->height <= 0))
46 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 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_BITFIELDS))
56 return 0;
57 break;
58 case 4:
59 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE4))
60 return 0;
61 break;
62 case 8:
63 if ((hdr->compr != BI_UNCOMPR) & (hdr->compr != BI_RLE8))
64 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 if ((hdr->nColors < 0) | (hdr->impColors < 0))
73 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
81 /* compute uncompressed scan size */
82 #define getScanSiz(h) ( ((((h)->bpp*(h)->width+7) >>3) + 3) & ~03 )
83
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 hdrSiz = 2 + 3*4 + rdint32(br); /* header size */
171 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 if (((br->hdr->width <= 0) | (br->hdr->height == 0)))
176 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 if (!BMPheaderOK(br->hdr))
191 goto err;
192 palLen = BMPpalLen(br->hdr);
193 if (br->hdr->bpp <= 8) { /* normalize color counts */
194 if (br->hdr->nColors <= 0)
195 br->hdr->nColors = palLen;
196 if (br->hdr->impColors <= 0)
197 br->hdr->impColors = br->hdr->nColors;
198 }
199 /* 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 sizeof(RGBquad)*palLen +
207 br->hdr->infoSiz);
208 if (br->hdr == NULL)
209 goto err;
210 }
211 /* 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 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 if (seek != NULL && ((br->hdr->compr == BI_RLE8) |
228 (br->hdr->compr == BI_RLE4))) {
229 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 for (rgbp = hdr->palette, n = hdr->nColors; n-- > 0; rgbp++)
264 if (((rgbp->r != rgbp->g) | (rgbp->g != rgbp->b)))
265 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 int n;
274 int8 *sp;
275
276 if (br->yscan + 1 >= br->hdr->height)
277 return BIR_EOF;
278 br->yscan++; /* prepare to read */
279 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 * One is the mid-file EOD (0x0001) and another is the ill-conceived
288 * "delta" (0x0002), which is like a "goto" statement for bitmaps.
289 * Whoever thought this up should be shot, then told why
290 * it's impossible to support such a scheme in any reasonable way.
291 * 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 * painting every other line black. Also, I assume any skipped
300 * 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 /* leaves n == -1 as flag for test after loop */
332 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 if (!n && (rdbyte(n, br) != 0 || (rdbyte(n, br) != 0 &&
357 (n != 1 || br->yscan != br->hdr->height-1))))
358 return BIR_RLERROR;
359 if (br->seek != NULL) /* record next scanline position */
360 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 if (br->hdr->compr == BI_UNCOMPR ||
384 br->hdr->compr == BI_BITFIELDS) {
385 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 BMPdecodePixel(int i, const BMPReader *br)
410 {
411 static const uint32 std16mask[3] = {0x7c00, 0x3e0, 0x1f};
412 static const RGBquad black = {0, 0, 0, 0};
413 const uint32 *mask;
414 const uint8 *pp;
415 uint32 pval, v;
416 RGBquad cval;
417
418 if (((br == NULL) | (i < 0)) || i >= br->hdr->width)
419 return black;
420
421 cval.padding = 0;
422
423 switch (br->hdr->bpp) {
424 case 24:
425 pp = br->scanline + 3*i;
426 cval.b = *pp++;
427 cval.g = *pp++;
428 cval.r = *pp;
429 return cval;
430 case 32:
431 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 case 8:
451 return br->hdr->palette[br->scanline[i]];
452 case 1:
453 return br->hdr->palette[br->scanline[i>>3]>>((7-i)&7) & 1];
454 case 4:
455 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 }
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 /* allocate color-mapped header (defaults to minimal grayscale) */
532 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 hdr->compr = BI_UNCOMPR; /* compression needs seek */
559 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 ++(bw)->fpos > (bw)->flen ? \
573 ((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 wrbyte(i>>24 & 0xff, bw);
592 }
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 if (bw->seek == NULL)
609 return BIR_SEEKERR;
610 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 if (cput == NULL)
627 return NULL;
628 if (!BMPheaderOK(hdr))
629 return NULL;
630 if ((hdr->bpp == 16) | (hdr->compr == BI_RLE4))
631 return NULL; /* unsupported */
632 /* no seek means we may have the wrong file length, but most app's don't care
633 if (seek == NULL && ((hdr->compr == BI_RLE8) | (hdr->compr == BI_RLE4)))
634 return NULL;
635 */
636 /* compute sizes */
637 hdrSiz = 2 + 6*4 + 2*2 + 6*4;
638 if (hdr->compr == BI_BITFIELDS)
639 hdrSiz += sizeof(uint32)*3;
640 palSiz = sizeof(RGBquad)*BMPpalLen(hdr);
641 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
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 for (pos = 0; (len > 0) & (pos < 255); pos++, bp++, len--) {
698 if (len < 5) /* no hope left? */
699 continue;
700 cnt = 1; /* else let's try it */
701 while (bp[cnt] == bp[0])
702 if (++cnt >= 5)
703 return pos; /* long enough */
704 }
705 return pos; /* didn't find any */
706 }
707
708 /* write the current scanline */
709 int
710 BMPwriteScanline(BMPWriter *bw)
711 {
712 const int8 *sp;
713 int n;
714
715 if (bw->yscan >= bw->hdr->height)
716 return BIR_EOF;
717 /* writing uncompressed? */
718 if (bw->hdr->compr == BI_UNCOMPR || bw->hdr->compr == BI_BITFIELDS) {
719 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 /*
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 for (cnt = 1; cnt < 255; cnt++)
756 if ((!--n) | (*++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 }