| 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 |
}
|