ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/Development/ray/src/common/bmpfile.c
Revision: 2.22
Committed: Fri Sep 12 19:31:15 2025 UTC (12 days, 9 hours ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 2.21: +17 -16 lines
Log Message:
feat: Eliminated unneeded casts and made sure info field is nuls, initially

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bmpfile.c,v 2.21 2021/10/04 23:04:59 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 that 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 if (cget == &stdio_getc && c_data == NULL)
155 return NULL; /* stdio error condition */
156 magic[0] = (*cget)(c_data);
157 if (magic[0] != 'B')
158 return NULL;
159 magic[1] = (*cget)(c_data);
160 if (magic[1] != 'M' && magic[1] != 'A')
161 return NULL;
162 br = (BMPReader *)calloc(1, sizeof(BMPReader));
163 if (br == NULL)
164 return NULL;
165 br->cget = cget;
166 br->seek = seek;
167 br->c_data = c_data;
168 br->hdr = (BMPHeader *)malloc(sizeof(BMPHeader));
169 if (br->hdr == NULL)
170 goto err;
171 br->fpos = 2;
172 /* read & verify file header */
173 (void)rdint32(br); /* file size */
174 (void)rdint32(br); /* reserved word */
175 bmPos = rdint32(br); /* offset to bitmap */
176 hdrSiz = 2 + 3*4 + rdint32(br); /* header size */
177 if (hdrSiz < 2 + 6*4 + 2*2 + 6*4)
178 goto err;
179 br->hdr->width = rdint32(br); /* bitmap width */
180 br->hdr->height = rdint32(br); /* bitmap height */
181 if (((br->hdr->width <= 0) | (br->hdr->height == 0)))
182 goto err;
183 if ((br->hdr->yIsDown = br->hdr->height < 0))
184 br->hdr->height = -br->hdr->height;
185 if (rduint16(br) != 1) /* number of planes */
186 goto err;
187 br->hdr->bpp = rduint16(br); /* bits per pixel */
188 br->hdr->compr = rdint32(br); /* compression mode */
189 (void)rdint32(br); /* bitmap size */
190 br->hdr->hRes = rdint32(br); /* horizontal resolution */
191 br->hdr->vRes = rdint32(br); /* vertical resolution */
192 br->hdr->nColors = rdint32(br); /* # colors used */
193 if (!br->hdr->nColors && br->hdr->bpp <= 8)
194 br->hdr->nColors = 1<<br->hdr->bpp;
195 br->hdr->impColors = rdint32(br); /* # important colors */
196 if (br->hdr->impColors < 0)
197 goto err; /* catch premature EOF */
198 if (!BMPheaderOK(br->hdr))
199 goto err;
200 palSiz = sizeof(RGBquad)*br->hdr->nColors;
201 if (br->hdr->impColors <= 0)
202 br->hdr->impColors = br->hdr->nColors;
203 /* extend header */
204 if (bmPos < hdrSiz + palSiz)
205 goto err;
206 br->hdr->infoSiz = bmPos - (hdrSiz + palSiz);
207 if (br->hdr->nColors > 0 || br->hdr->infoSiz > 0) {
208 br->hdr = (BMPHeader *)realloc(br->hdr,
209 sizeof(BMPHeader) +
210 palSiz + br->hdr->infoSiz);
211 if (br->hdr == NULL)
212 goto err;
213 }
214 /* read colors or fields */
215 if (br->hdr->compr == BI_BITFIELDS) {
216 BMPbitField(br->hdr)[0] = (uint32)rdint32(br);
217 BMPbitField(br->hdr)[1] = (uint32)rdint32(br);
218 BMPbitField(br->hdr)[2] = (uint32)rdint32(br);
219 } else if (rdbytes((char *)br->hdr->palette, palSiz, 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 if (seek != NULL && ((br->hdr->compr == BI_RLE8) |
230 (br->hdr->compr == BI_RLE4))) {
231 BMPReader *newbr = (BMPReader *)realloc(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(br->scanpos+1, 0,
239 sizeof(br->scanpos[0])*br->hdr->height);
240 }
241 br->scanpos[0] = br->fpos;
242 if (BMPreadScanline(br) == BIR_OK)
243 return br;
244 err:
245 if (br->hdr != NULL)
246 free(br->hdr);
247 if (br->scanline != NULL)
248 free(br->scanline);
249 free(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 for (rgbp = hdr->palette, n = hdr->impColors; n-- > 0; rgbp++)
265 if ((rgbp->r != rgbp->g) | (rgbp->g != rgbp->b))
266 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 int n;
275 int8 *sp;
276
277 if (br->yscan + 1 >= br->hdr->height)
278 return BIR_EOF;
279 br->yscan++; /* prepare to read */
280 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 * RLE4/RLE8 Decoding
285 *
286 * Certain aspects of this scheme are completely insane, so
287 * we don't support them. Fortunately, they rarely appear.
288 * One is the mid-file EOD (0x0001) and another is the ill-conceived
289 * "delta" (0x0002), which is like a "goto" statement for bitmaps.
290 * 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 * 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 * painting every other line black. Also, I assume any skipped
301 * 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 = (int8 *)br->scanline;
306 n = br->hdr->width;
307 if (br->hdr->compr == BI_RLE4)
308 n = (n + 1) >> 1;
309 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 /* check for escape */
330 switch (rdbyte(len, br)) {
331 case EOF:
332 return BIR_TRUNCATED;
333 case 0: /* end of line */
334 while (n--)
335 *sp++ = 0;
336 /* leaves n == -1 as flag for test after loop */
337 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 if (!n && (rdbyte(n, br) != 0 || (rdbyte(n, br) != 0 &&
362 (n != 1 || br->yscan != br->hdr->height-1))))
363 return BIR_RLERROR;
364 if (br->seek != NULL) /* record next scanline position */
365 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 if (br->hdr->compr == BI_UNCOMPR ||
389 br->hdr->compr == BI_BITFIELDS) {
390 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 BMPdecodePixel(int i, const BMPReader *br)
415 {
416 static const uint32 std16mask[3] = {0x7c00, 0x3e0, 0x1f};
417 static const RGBquad black = {0, 0, 0, 0};
418 const uint32 *mask;
419 const uint8 *pp;
420 uint32 pval, v;
421 RGBquad cval;
422
423 if (((br == NULL) | (i < 0)) || i >= br->hdr->width)
424 return black;
425
426 cval.padding = 0;
427
428 switch (br->hdr->bpp) {
429 case 24:
430 pp = br->scanline + 3*i;
431 cval.b = *pp++;
432 cval.g = *pp++;
433 cval.r = *pp;
434 return cval;
435 case 32:
436 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 case 8:
456 return br->hdr->palette[br->scanline[i]];
457 case 1:
458 return br->hdr->palette[br->scanline[i>>3]>>(7-(i&7)) & 1];
459 case 4:
460 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 }
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(br->hdr);
483 free(br->scanline);
484 free(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 if ((hdr->infoSiz = infolen) > 0)
533 memset(BMPinfo(hdr), 0, hdr->infoSiz);
534 return hdr;
535 }
536
537 /* Allocate color-mapped header (defaults to minimal grayscale) */
538 BMPHeader *
539 BMPmappedHeader(int xr, int yr, int infolen, int ncolors)
540 {
541 int n;
542 BMPHeader *hdr;
543
544 if (xr <= 0 || yr <= 0 || infolen < 0 || ncolors < 2)
545 return NULL;
546 if (ncolors <= 2)
547 n = 1;
548 else if (ncolors <= 16)
549 n = 4;
550 else if (ncolors <= 256)
551 n = 8;
552 else
553 return NULL;
554 hdr = (BMPHeader *)malloc(sizeof(BMPHeader) +
555 sizeof(RGBquad)*((size_t)1<<n) -
556 sizeof(hdr->palette) +
557 infolen);
558 if (hdr == NULL)
559 return NULL;
560 hdr->width = xr;
561 hdr->height = yr;
562 hdr->yIsDown = 0; /* default to upwards order */
563 hdr->bpp = n;
564 hdr->compr = BI_UNCOMPR; /* compression needs seek */
565 hdr->hRes = hdr->vRes = 2835; /* default to 72 ppi */
566 hdr->nColors = ncolors;
567 hdr->impColors = 0; /* says all colors important */
568 hdr->infoSiz = infolen;
569 memset(hdr->palette, 0, sizeof(RGBquad)*((size_t)1<<n) + infolen);
570 for (n = ncolors; n--; )
571 hdr->palette[n].r = hdr->palette[n].g =
572 hdr->palette[n].b = n*255/(ncolors-1);
573 return hdr;
574 }
575
576 /* put byte to writer */
577 #define wrbyte(c,bw) ( (*(bw)->cput)(c,(bw)->c_data), \
578 ++(bw)->fpos > (bw)->flen ? \
579 ((bw)->flen = (bw)->fpos) : \
580 (bw)->fpos )
581
582 /* Write out a string of bytes */
583 static void
584 wrbytes(char *bp, uint32 n, BMPWriter *bw)
585 {
586 while (n--)
587 wrbyte(*bp++, bw);
588 }
589
590 /* Write 32-bit integer in littlendian order */
591 static void
592 wrint32(int32 i, BMPWriter *bw)
593 {
594 wrbyte(i& 0xff, bw);
595 wrbyte(i>>8 & 0xff, bw);
596 wrbyte(i>>16 & 0xff, bw);
597 wrbyte(i>>24 & 0xff, bw);
598 }
599
600 /* Write 16-bit unsigned integer in littlendian order */
601 static void
602 wruint16(uint16 ui, BMPWriter *bw)
603 {
604 wrbyte(ui & 0xff, bw);
605 wrbyte(ui>>8 & 0xff, bw);
606 }
607
608 /* Seek to the specified file position, returning 0 (BIR_OK) on success */
609 static int
610 wrseek(uint32 pos, BMPWriter *bw)
611 {
612 if (pos == bw->fpos)
613 return BIR_OK;
614 if (bw->seek == NULL)
615 return BIR_SEEKERR;
616 if ((*bw->seek)(pos, bw->c_data) != 0)
617 return BIR_SEEKERR;
618 bw->fpos = pos;
619 if (pos > bw->flen)
620 bw->flen = pos;
621 return BIR_OK;
622 }
623
624 /* Open BMP stream for writing */
625 BMPWriter *
626 BMPopenWriter(void (*cput)(int, void *), int (*seek)(uint32, void *),
627 void *c_data, BMPHeader *hdr)
628 {
629 BMPWriter *bw;
630 uint32 hdrSiz, palSiz, scanSiz, bmSiz;
631 /* check arguments */
632 if (cput == NULL)
633 return NULL;
634 if (cput == &stdio_putc && c_data == NULL)
635 return NULL; /* stdio error condition */
636 if (!BMPheaderOK(hdr))
637 return NULL;
638 if ((hdr->bpp == 16) | (hdr->compr == BI_RLE4))
639 return NULL; /* unsupported */
640 /* no seek means we may have the wrong file length, but most app's don't care
641 if (seek == NULL && ((hdr->compr == BI_RLE8) | (hdr->compr == BI_RLE4)))
642 return NULL;
643 */
644 /* compute sizes */
645 hdrSiz = 2 + 6*4 + 2*2 + 6*4;
646 if (hdr->compr == BI_BITFIELDS)
647 hdrSiz += sizeof(uint32)*3;
648 palSiz = sizeof(RGBquad)*hdr->nColors;
649 scanSiz = getScanSiz(hdr);
650 bmSiz = hdr->height*scanSiz; /* wrong if compressed */
651 /* initialize writer */
652 bw = (BMPWriter *)malloc(sizeof(BMPWriter));
653 if (bw == NULL)
654 return NULL;
655 bw->hdr = hdr;
656 bw->yscan = 0;
657 bw->scanline = (uint8 *)calloc(scanSiz, sizeof(uint8));
658 if (bw->scanline == NULL) {
659 free(bw);
660 return NULL;
661 }
662 bw->fbmp = hdrSiz + palSiz + hdr->infoSiz;
663 bw->fpos = bw->flen = 0;
664 bw->cput = cput;
665 bw->seek = seek;
666 bw->c_data = c_data;
667 /* write out header */
668 wrbyte('B', bw); wrbyte('M', bw); /* magic number */
669 wrint32(bw->fbmp + bmSiz, bw); /* file size */
670 wrint32(0, bw); /* reserved word */
671 wrint32(bw->fbmp, bw); /* offset to bitmap */
672 wrint32(hdrSiz - bw->fpos, bw); /* info header size */
673 wrint32(hdr->width, bw); /* bitmap width */
674 if (hdr->yIsDown) /* bitmap height */
675 wrint32(-hdr->height, bw);
676 else
677 wrint32(hdr->height, bw);
678 wruint16(1, bw); /* number of planes */
679 wruint16(hdr->bpp, bw); /* bits per pixel */
680 wrint32(hdr->compr, bw); /* compression mode */
681 wrint32(bmSiz, bw); /* bitmap size */
682 wrint32(hdr->hRes, bw); /* horizontal resolution */
683 wrint32(hdr->vRes, bw); /* vertical resolution */
684 wrint32(hdr->nColors, bw); /* # colors used */
685 wrint32(hdr->impColors, bw); /* # important colors */
686 /* write out color palette */
687 wrbytes((char *)hdr->palette, palSiz, bw);
688 /* write add'l information */
689 wrbytes(BMPinfo(hdr), hdr->infoSiz, bw);
690 #ifndef NDEBUG
691 if (bw->fpos != bw->fbmp) {
692 fputs("Coding error 1 in BMPopenWriter\n", stderr);
693 exit(1);
694 }
695 #endif
696 return bw;
697 }
698
699 /* Find position of next run of 5 or more identical bytes, or 255 if none */
700 static int
701 findNextRun(const int8 *bp, int len)
702 {
703 int pos, cnt;
704 /* look for run */
705 for (pos = 0; (len > 0) & (pos < 255); pos++, bp++, len--) {
706 if (len < 5) /* no hope left? */
707 continue;
708 cnt = 1; /* else let's try it */
709 while (bp[cnt] == bp[0])
710 if (++cnt >= 5)
711 return pos; /* long enough */
712 }
713 return pos; /* didn't find any */
714 }
715
716 /* Write the current scanline */
717 int
718 BMPwriteScanline(BMPWriter *bw)
719 {
720 const int8 *sp;
721 int n;
722
723 if (bw->yscan >= bw->hdr->height)
724 return BIR_EOF;
725 /* writing uncompressed? */
726 if (bw->hdr->compr == BI_UNCOMPR || bw->hdr->compr == BI_BITFIELDS) {
727 uint32 scanSiz = getScanSiz(bw->hdr);
728 uint32 slpos = bw->fbmp + bw->yscan*scanSiz;
729 if (wrseek(slpos, bw) != BIR_OK)
730 return BIR_SEEKERR;
731 wrbytes((char *)bw->scanline, scanSiz, bw);
732 bw->yscan++;
733 return BIR_OK;
734 }
735 /*
736 * RLE8 Encoding
737 *
738 * See the notes in BMPreadScanline() on this encoding. Needless
739 * to say, we avoid the nuttier aspects of this specification.
740 * We also assume that every scanline ends in a line break
741 * (0x0000) except for the last, which ends in a bitmap break
742 * (0x0001). We don't support RLE4 at all; it's too awkward.
743 */
744 sp = (const int8 *)bw->scanline;
745 n = bw->hdr->width;
746 while (n > 0) {
747 int cnt, val;
748 cnt = findNextRun(sp, n); /* 0-255 < n */
749 if (cnt >= 3) { /* output absolute */
750 int skipOdd = cnt & 1;
751 wrbyte(0, bw);
752 wrbyte(cnt, bw);
753 n -= cnt;
754 while (cnt--)
755 wrbyte(*sp++, bw);
756 if (skipOdd)
757 wrbyte(0, bw);
758 }
759 if (n <= 0) /* was that it? */
760 break;
761 val = *sp++; /* output run */
762 for (cnt = 1; --n && cnt < 255; cnt++, sp++)
763 if (*sp != val)
764 break;
765 wrbyte(cnt, bw);
766 wrbyte(val, bw);
767 }
768 bw->yscan++; /* write line break or EOD */
769 if (bw->yscan == bw->hdr->height) {
770 wrbyte(0, bw); wrbyte(1, bw); /* end of bitmap marker */
771 if (wrseek(2, bw) != BIR_OK)
772 return BIR_OK; /* no one may care */
773 wrint32(bw->flen, bw); /* correct file length */
774 if (wrseek(34, bw) != BIR_OK)
775 return BIR_OK;
776 wrint32(bw->flen-bw->fbmp, bw); /* correct bitmap length */
777 } else {
778 wrbyte(0, bw); wrbyte(0, bw); /* end of line marker */
779 }
780 return BIR_OK;
781 }
782
783 /* Free BMP writer resources */
784 void
785 BMPfreeWriter(BMPWriter *bw)
786 {
787 if (bw == NULL)
788 return;
789 free(bw->hdr);
790 free(bw->scanline);
791 free(bw);
792 }