plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

readbmp.c (13692B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <bio.h>
      4 #include <draw.h>
      5 #include "imagefile.h"
      6 #include "bmp.h"
      7 
      8 /*
      9  MS-BMP file reader
     10  (c) 2003, I.P.Keller
     11 
     12  aims to decode *all* valid bitmap formats, although some of the
     13  flavours couldn't be verified due to lack of suitable test-files.
     14  the following flavours are supported:
     15 
     16 	Bit/Pix	Orientation	Compression	Tested?
     17 	  1	top->bottom	n/a		yes
     18 	  1	bottom->top	n/a		yes
     19 	  4	top->bottom	no		yes
     20 	  4	bottom->top	no		yes
     21 	  4	top->bottom	RLE4		yes, but not with displacement
     22 	  8	top->bottom	no		yes
     23 	  8	bottom->top	no		yes
     24 	  8	top->bottom	RLE8		yes, but not with displacement
     25 	 16	top->bottom	no		no
     26 	 16	bottom->top	no		no
     27 	 16	top->bottom	BITMASK		no
     28 	 16	bottom->top	BITMASK		no
     29 	 24	top->bottom	n/a		yes
     30 	 24	bottom->top	n/a		yes
     31 	 32	top->bottom	no		no
     32 	 32	bottom->top	no		no
     33 	 32	top->bottom	BITMASK		no
     34 	 32	bottom->top	BITMASK		no
     35 
     36  OS/2 1.x bmp files are recognised as well, but testing was very limited.
     37 
     38  verifying was done with a number of test files, generated by
     39  different tools. nevertheless, the tests were in no way exhaustive
     40  enough to guarantee bug-free decoding. caveat emptor!
     41 */
     42 
     43 static short
     44 r16(Biobuf*b)
     45 {
     46 	short s;
     47 
     48 	s = Bgetc(b);
     49 	s |= ((short)Bgetc(b)) << 8;
     50 	return s;
     51 }
     52 
     53 
     54 static long
     55 r32(Biobuf*b)
     56 {
     57 	long l;
     58 
     59 	l = Bgetc(b);
     60 	l |= ((long)Bgetc(b)) << 8;
     61 	l |= ((long)Bgetc(b)) << 16;
     62 	l |= ((long)Bgetc(b)) << 24;
     63 	return l;
     64 }
     65 
     66 
     67 /* get highest bit set */
     68 static int
     69 msb(ulong x)
     70 {
     71 	int i;
     72 	for(i = 32; i; i--, x <<= 1)
     73 		if(x & 0x80000000L)
     74 			return i;
     75 	return 0;
     76 }
     77 
     78 /* Load a 1-Bit encoded BMP file (uncompressed) */
     79 static int
     80 load_1T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
     81 {
     82 	long ix, iy, i = 0, step_up = 0, padded_width = ((width + 31) / 32) * 32;
     83 	int val = 0, n;
     84 
     85 	if(height > 0) {	/* bottom-up */
     86 		i = (height - 1) * width;
     87 		step_up = -2 * width;
     88 	} else
     89 		height = -height;
     90 
     91 	for(iy = height; iy; iy--, i += step_up)
     92 		for(ix = 0, n = 0; ix < padded_width; ix++, n--) {
     93 			if(!n) {
     94 				val = Bgetc(b);
     95 				n = 8;
     96 			}
     97 			if(ix < width) {
     98 				buf[i] = clut[val & 0x80 ? 1 : 0];
     99 				i++;
    100 			}
    101 			val <<= 1;
    102 		}
    103 	return 0;
    104 }
    105 
    106 /* Load a 4-Bit encoded BMP file (uncompressed) */
    107 static int
    108 load_4T(Biobuf* b, long width, long height, Rgb* buf, Rgb* clut)
    109 {
    110 	long ix, iy, i = 0, step_up = 0, skip = (4 - (((width % 8) + 1) / 2)) & 3;
    111 	uint valH, valL;
    112 
    113 	if(height > 0) {	/* bottom-up */
    114 		i = (height - 1) * width;
    115 		step_up = -2 * width;
    116 	} else
    117 		height = -height;
    118 
    119 	for(iy = height; iy; iy--, i += step_up) {
    120 		for(ix = 0; ix < width; ) {
    121 			valH = valL = Bgetc(b) & 0xff;
    122 			valH >>= 4;
    123 
    124 			buf[i] = clut[valH];
    125 			i++; ix++;
    126 
    127 			if(ix < width) {
    128 				valL &= 0xf;
    129 				buf[i] = clut[valL];
    130 				i++; ix++;
    131 			}
    132 		}
    133 		Bseek(b, skip, 1);
    134 	}
    135 	return 0;
    136 }
    137 
    138 /* Load a 4-Bit encoded BMP file (RLE4-compressed) */
    139 static int
    140 load_4C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
    141 {
    142 	long ix, iy = height -1;
    143 	uint val, valS, skip;
    144 	Rgb* p;
    145 
    146 	while(iy >= 0) {
    147 		ix = 0;
    148 		while(ix < width) {
    149 			val = Bgetc(b);
    150 
    151 			if(0 != val) {
    152 				valS = (uint)Bgetc(b);
    153 				p = &buf[ix + iy * width];
    154 				while(val--) {
    155 					*p = clut[0xf & (valS >> 4)];
    156 					p++;
    157 					ix++;
    158 					if(0 < val) {
    159 						*p = clut[0xf & valS];
    160 						p++;
    161 						ix++;
    162 						val--;
    163 					}
    164 				}
    165 			} else {
    166 				/* Special modes... */
    167 				val = Bgetc(b);
    168 				switch(val) {
    169 					case 0:	/* End-Of-Line detected */
    170 						ix = width;
    171 						iy--;
    172 						break;
    173 					case 1:	/* End-Of-Picture detected -->> abort */
    174 						ix = width;
    175 						iy = -1;
    176 						break;
    177 					case 2:	/* Position change detected */
    178 						val = Bgetc(b);
    179 						ix += val;
    180 						val = Bgetc(b);
    181 						iy -= val;
    182 						break;
    183 
    184 					default:/* Transparent data sequence detected */
    185 						p = &buf[ix + iy * width];
    186 						if((1 == (val & 3)) || (2 == (val & 3)))
    187 							skip = 1;
    188 						else
    189 							skip = 0;
    190 
    191 						while(val--) {
    192 							valS = (uint)Bgetc(b);
    193 							*p = clut[0xf & (valS >> 4)];
    194 							p++;
    195 							ix++;
    196 							if(0 < val) {
    197 								*p = clut[0xf & valS];
    198 								p++;
    199 								ix++;
    200 								val--;
    201 							}
    202 						}
    203 						if(skip)
    204 							Bgetc(b);
    205 						break;
    206 				}
    207 			}
    208 		}
    209 	}
    210 	return 0;
    211 }
    212 
    213 /* Load a 8-Bit encoded BMP file (uncompressed) */
    214 static int
    215 load_8T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
    216 {
    217 	long ix, iy, i = 0, step_up = 0, skip = (4 - (width % 4)) & 3;
    218 
    219 	if(height > 0) {	/* bottom-up */
    220 		i = (height - 1) * width;
    221 		step_up = -2 * width;
    222 	} else
    223 		height = -height;
    224 
    225 	for(iy = height; iy; iy--, i += step_up) {
    226 		for(ix = 0; ix < width; ix++, i++)
    227 			buf[i] = clut[Bgetc(b) & 0xff];
    228 		Bseek(b, skip, 1);
    229 	}
    230 	return 0;
    231 }
    232 
    233 /* Load a 8-Bit encoded BMP file (RLE8-compressed) */
    234 static int
    235 load_8C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
    236 {
    237 	long ix, iy = height -1;
    238 	int val, valS, skip;
    239 	Rgb* p;
    240 
    241 	while(iy >= 0) {
    242 		ix = 0;
    243 		while(ix < width) {
    244 			val = Bgetc(b);
    245 
    246 			if(0 != val) {
    247 				valS = Bgetc(b);
    248 				p = &buf[ix + iy * width];
    249 				while(val--) {
    250 					*p = clut[valS];
    251 					p++;
    252 					ix++;
    253 				}
    254 			} else {
    255 				/* Special modes... */
    256 				val = Bgetc(b);
    257 				switch(val) {
    258 					case 0: /* End-Of-Line detected */
    259 						ix = width;
    260 						iy--;
    261 						break;
    262 					case 1: /* End-Of-Picture detected */
    263 						ix = width;
    264 						iy = -1;
    265 						break;
    266 					case 2: /* Position change detected */
    267 						val = Bgetc(b);
    268 						ix += val;
    269 						val = Bgetc(b);
    270 						iy -= val;
    271 						break;
    272 					default: /* Transparent (not compressed) sequence detected */
    273 						p = &buf[ix + iy * width];
    274 						if(val & 1)
    275 							skip = 1;
    276 						else
    277 							skip = 0;
    278 
    279 						while(val--) {
    280 							valS = Bgetc(b);
    281 							*p = clut[valS];
    282 							p++;
    283 							ix++;
    284 						}
    285 						if(skip)
    286 							/* Align data stream */
    287 							Bgetc(b);
    288 						break;
    289 				}
    290 			}
    291 		}
    292 	}
    293 	return 0;
    294 }
    295 
    296 /* Load a 16-Bit encoded BMP file (uncompressed) */
    297 static int
    298 load_16(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
    299 {
    300 	uchar c[2];
    301 	long ix, iy, i = 0, step_up = 0;
    302 
    303 	if(height > 0) {	/* bottom-up */
    304 		i = (height - 1) * width;
    305 		step_up = -2 * width;
    306 	} else
    307 		height = -height;
    308 
    309 	if(clut) {
    310 		unsigned mask_blue =  (unsigned)clut[0].blue +
    311 		                     ((unsigned)clut[0].green << 8);
    312 		unsigned mask_green =  (unsigned)clut[1].blue +
    313 		                      ((unsigned)clut[1].green << 8);
    314 		unsigned mask_red =  (unsigned)clut[2].blue +
    315 		                    ((unsigned)clut[2].green << 8);
    316 		int shft_blue = msb((ulong)mask_blue) - 8;
    317 		int shft_green = msb((ulong)mask_green) - 8;
    318 		int shft_red = msb((ulong)mask_red) - 8;
    319 
    320 		for(iy = height; iy; iy--, i += step_up)
    321 			for(ix = 0; ix < width; ix++, i++) {
    322 				unsigned val;
    323 				Bread(b, c, sizeof(c));
    324 				val = (unsigned)c[0] + ((unsigned)c[1] << 8);
    325 
    326 				buf[i].alpha = 0;
    327 				if(shft_blue >= 0)
    328 					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
    329 				else
    330 					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
    331 				if(shft_green >= 0)
    332 					buf[i].green = (uchar)((val & mask_green) >> shft_green);
    333 				else
    334 					buf[i].green = (uchar)((val & mask_green) << -shft_green);
    335 				if(shft_red >= 0)
    336 					buf[i].red = (uchar)((val & mask_red) >> shft_red);
    337 				else
    338 					buf[i].red = (uchar)((val & mask_red) << -shft_red);
    339 			}
    340 	} else
    341 		for(iy = height; iy; iy--, i += step_up)
    342 			for(ix = 0; ix < width; ix++, i++) {
    343 				Bread(b, c, sizeof(c));
    344 				buf[i].blue = (uchar)((c[0] << 3) & 0xf8);
    345 				buf[i].green = (uchar)(((((unsigned)c[1] << 6) +
    346 				                        (((unsigned)c[0]) >> 2))) & 0xf8);
    347 				buf[i].red = (uchar)((c[1] << 1) & 0xf8);
    348 			}
    349 	return 0;
    350 }
    351 
    352 /* Load a 24-Bit encoded BMP file (uncompressed) */
    353 static int
    354 load_24T(Biobuf* b, long width, long height, Rgb* buf)
    355 {
    356 	long ix, iy, i = 0, step_up = 0, skip = (4 - ((width * 3) % 4)) & 3;
    357 
    358 	if(height > 0) {	/* bottom-up */
    359 		i = (height - 1) * width;
    360 		step_up = -2 * width;
    361 	} else
    362 		height = -height;
    363 
    364 	for(iy = height; iy; iy--, i += step_up) {
    365 		for(ix = 0; ix < width; ix++, i++) {
    366 			buf[i].alpha = 0;
    367 			buf[i].blue = Bgetc(b);
    368 			buf[i].green = Bgetc(b);
    369 			buf[i].red = Bgetc(b);
    370 		}
    371 		Bseek(b, skip, 1);
    372 	}
    373 	return 0;
    374 }
    375 
    376 /* Load a 32-Bit encoded BMP file (uncompressed) */
    377 static int
    378 load_32(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
    379 {
    380 	uchar c[4];
    381 	long ix, iy, i = 0, step_up = 0;
    382 
    383 	if(height > 0) {	/* bottom-up */
    384 		i = (height - 1) * width;
    385 		step_up = -2 * width;
    386 	} else
    387 		height = -height;
    388 
    389 	if(clut) {
    390 		ulong mask_blue =  (ulong)clut[0].blue +
    391 		                          ((ulong)clut[0].green << 8) +
    392 		                          ((ulong)clut[0].red << 16) +
    393 		                          ((ulong)clut[0].alpha << 24);
    394 		ulong mask_green =  (ulong)clut[1].blue +
    395 		                           ((ulong)clut[1].green << 8) +
    396 		                           ((ulong)clut[1].red << 16) +
    397 		                           ((ulong)clut[1].alpha << 24);
    398 		ulong mask_red =  (ulong)clut[2].blue +
    399 		                         ((ulong)clut[2].green << 8) +
    400 		                         ((ulong)clut[2].red << 16) +
    401 		                         ((ulong)clut[2].alpha << 24);
    402 		int shft_blue = msb(mask_blue) - 8;
    403 		int shft_green = msb(mask_green) - 8;
    404 		int shft_red = msb(mask_red) - 8;
    405 
    406 		for(iy = height; iy; iy--, i += step_up)
    407 			for(ix = 0; ix < width; ix++, i++) {
    408 				ulong val;
    409 				Bread(b, c, sizeof(c));
    410 				val =  (ulong)c[0] + ((ulong)c[1] << 8) +
    411 				      ((ulong)c[2] << 16) + ((ulong)c[1] << 24);
    412 
    413 				buf[i].alpha = 0;
    414 				if(shft_blue >= 0)
    415 					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
    416 				else
    417 					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
    418 				if(shft_green >= 0)
    419 					buf[i].green = (uchar)((val & mask_green) >> shft_green);
    420 				else
    421 					buf[i].green = (uchar)((val & mask_green) << -shft_green);
    422 				if(shft_red >= 0)
    423 					buf[i].red = (uchar)((val & mask_red) >> shft_red);
    424 				else
    425 					buf[i].red = (uchar)((val & mask_red) << -shft_red);
    426 			}
    427 	} else
    428 		for(iy = height; iy; iy--, i += step_up)
    429 			for(ix = 0; ix < width; ix++, i++) {
    430 				Bread(b, c, nelem(c));
    431 				buf[i].blue = c[0];
    432 				buf[i].green = c[1];
    433 				buf[i].red = c[2];
    434 			}
    435 	return 0;
    436 }
    437 
    438 
    439 static Rgb*
    440 ReadBMP(Biobuf *b, int *width, int *height)
    441 {
    442 	int colours, num_coltab = 0;
    443 	Filehdr bmfh;
    444 	Infohdr bmih;
    445 	Rgb clut[256];
    446 	Rgb* buf;
    447 
    448 	bmfh.type = r16(b);
    449 	if(bmfh.type != 0x4d42) 	/* signature must be 'BM' */
    450 		sysfatal("bad magic number, not a BMP file");
    451 
    452 	bmfh.size = r32(b);
    453 	bmfh.reserved1 = r16(b);
    454 	bmfh.reserved2 = r16(b);
    455 	bmfh.offbits = r32(b);
    456 
    457 	memset(&bmih, 0, sizeof(bmih));
    458 	bmih.size = r32(b);
    459 
    460 	if(bmih.size == 0x0c) {			/* OS/2 1.x version */
    461 		bmih.width = r16(b);
    462 		bmih.height = r16(b);
    463 		bmih.planes = r16(b);
    464 		bmih.bpp = r16(b);
    465 		bmih.compression = BMP_RGB;
    466 	} else {				/* Windows */
    467 		bmih.width = r32(b);
    468 		bmih.height = r32(b);
    469 		bmih.planes = r16(b);
    470 		bmih.bpp = r16(b);
    471 		bmih.compression = r32(b);
    472 		bmih.imagesize = r32(b);
    473 		bmih.hres = r32(b);
    474 		bmih.vres = r32(b);
    475 		bmih.colours = r32(b);
    476 		bmih.impcolours = r32(b);
    477 	}
    478 
    479 	if(bmih.bpp < 16) {
    480 		/* load colour table */
    481 		if(bmih.impcolours)
    482 			num_coltab = (int)bmih.impcolours;
    483 		else
    484 			num_coltab = 1 << bmih.bpp;
    485 	} else if(bmih.compression == BMP_BITFIELDS &&
    486 	          (bmih.bpp == 16 || bmih.bpp == 32))
    487 		/* load bitmasks */
    488 		num_coltab = 3;
    489 
    490 	if(num_coltab) {
    491 		int i;
    492 		Bseek(b, bmih.size + sizeof(Infohdr), 0);
    493 
    494 		for(i = 0; i < num_coltab; i++) {
    495 			clut[i].blue  = (uchar)Bgetc(b);
    496 			clut[i].green = (uchar)Bgetc(b);
    497 			clut[i].red   = (uchar)Bgetc(b);
    498 			clut[i].alpha = (uchar)Bgetc(b);
    499 		}
    500 	}
    501 
    502 	*width = bmih.width;
    503 	*height = bmih.height;
    504 	colours = bmih.bpp;
    505 
    506 	Bseek(b, bmfh.offbits, 0);
    507 
    508 	if ((buf = calloc(sizeof(Rgb), *width * abs(*height))) == nil)
    509 		sysfatal("no memory");
    510 
    511 	switch(colours) {
    512 		case 1:
    513 			load_1T(b, *width, *height, buf, clut);
    514 			break;
    515 		case 4:
    516 			if(bmih.compression == BMP_RLE4)
    517 				load_4C(b, *width, *height, buf, clut);
    518 			else
    519 				load_4T(b, *width, *height, buf, clut);
    520 			break;
    521 		case 8:
    522 			if(bmih.compression == BMP_RLE8)
    523 				load_8C(b, *width, *height, buf, clut);
    524 			else
    525 				load_8T(b, *width, *height, buf, clut);
    526 			break;
    527 		case 16:
    528 			load_16(b, *width, *height, buf,
    529 			        bmih.compression == BMP_BITFIELDS ? clut : nil);
    530 			break;
    531 		case 24:
    532 			load_24T(b, *width, *height, buf);
    533 			break;
    534 		case 32:
    535 			load_32(b, *width, *height, buf,
    536 			        bmih.compression == BMP_BITFIELDS ? clut : nil);
    537 			break;
    538 	}
    539 	return buf;
    540 }
    541 
    542 Rawimage**
    543 Breadbmp(Biobuf *bp, int colourspace)
    544 {
    545 	Rawimage *a, **array;
    546 	int c, width, height;
    547 	uchar *r, *g, *b;
    548 	Rgb *s, *e;
    549 	Rgb *bmp;
    550 	char ebuf[128];
    551 
    552 	a = nil;
    553 	bmp = nil;
    554 	array = nil;
    555 	USED(a);
    556 	USED(bmp);
    557 	if (colourspace != CRGB) {
    558 		errstr(ebuf, sizeof ebuf);	/* throw it away */
    559 		werrstr("ReadRGB: unknown colour space %d", colourspace);
    560 		return nil;
    561 	}
    562 
    563 	if ((bmp = ReadBMP(bp, &width, &height)) == nil)
    564 		return nil;
    565 
    566 	if ((a = calloc(sizeof(Rawimage), 1)) == nil)
    567 		goto Error;
    568 
    569 	for (c = 0; c  < 3; c++)
    570 		if ((a->chans[c] = calloc(width, height)) == nil)
    571 			goto Error;
    572 
    573 	if ((array = calloc(sizeof(Rawimage *), 2)) == nil)
    574 		goto Error;
    575 	array[0] = a;
    576 	array[1] = nil;
    577 
    578 	a->nchans = 3;
    579 	a->chandesc = CRGB;
    580 	a->chanlen = width * height;
    581 	a->r = Rect(0, 0, width, height);
    582 
    583 	s = bmp;
    584 	e = s + width * height;
    585 	r = a->chans[0];
    586 	g = a->chans[1];
    587 	b = a->chans[2];
    588 
    589 	do {
    590 		*r++ = s->red;
    591 		*g++ = s->green;
    592 		*b++ = s->blue;
    593 	}while(++s < e);
    594 
    595 	free(bmp);
    596 	return array;
    597 
    598 Error:
    599 	if (a)
    600 		for (c = 0; c < 3; c++)
    601 			if (a->chans[c])
    602 				free(a->chans[c]);
    603 	if (a)
    604 		free(a);
    605 	if (array)
    606 		free(array);
    607 	if (bmp)
    608 		free(bmp);
    609 	return nil;
    610 
    611 }
    612 
    613 Rawimage**
    614 readbmp(int fd, int colorspace)
    615 {
    616 	Rawimage * *a;
    617 	Biobuf b;
    618 
    619 	if (Binit(&b, fd, OREAD) < 0)
    620 		return nil;
    621 	a = Breadbmp(&b, colorspace);
    622 	Bterm(&b);
    623 	return a;
    624 }