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
r16(Biobuf * b)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
r32(Biobuf * b)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
msb(ulong x)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
load_1T(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_4T(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_4C(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_8T(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_8C(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_16(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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
load_24T(Biobuf * b,long width,long height,Rgb * buf)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
load_32(Biobuf * b,long width,long height,Rgb * buf,Rgb * clut)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*
ReadBMP(Biobuf * b,int * width,int * height)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**
Breadbmp(Biobuf * bp,int colourspace)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**
readbmp(int fd,int colorspace)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 }
625