#include #include #include #include #include "imagefile.h" #include "bmp.h" /* MS-BMP file reader (c) 2003, I.P.Keller aims to decode *all* valid bitmap formats, although some of the flavours couldn't be verified due to lack of suitable test-files. the following flavours are supported: Bit/Pix Orientation Compression Tested? 1 top->bottom n/a yes 1 bottom->top n/a yes 4 top->bottom no yes 4 bottom->top no yes 4 top->bottom RLE4 yes, but not with displacement 8 top->bottom no yes 8 bottom->top no yes 8 top->bottom RLE8 yes, but not with displacement 16 top->bottom no no 16 bottom->top no no 16 top->bottom BITMASK no 16 bottom->top BITMASK no 24 top->bottom n/a yes 24 bottom->top n/a yes 32 top->bottom no no 32 bottom->top no no 32 top->bottom BITMASK no 32 bottom->top BITMASK no OS/2 1.x bmp files are recognised as well, but testing was very limited. verifying was done with a number of test files, generated by different tools. nevertheless, the tests were in no way exhaustive enough to guarantee bug-free decoding. caveat emptor! */ static short r16(Biobuf*b) { short s; s = Bgetc(b); s |= ((short)Bgetc(b)) << 8; return s; } static long r32(Biobuf*b) { long l; l = Bgetc(b); l |= ((long)Bgetc(b)) << 8; l |= ((long)Bgetc(b)) << 16; l |= ((long)Bgetc(b)) << 24; return l; } /* get highest bit set */ static int msb(ulong x) { int i; for(i = 32; i; i--, x <<= 1) if(x & 0x80000000L) return i; return 0; } /* Load a 1-Bit encoded BMP file (uncompressed) */ static int load_1T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { long ix, iy, i = 0, step_up = 0, padded_width = ((width + 31) / 32) * 32; int val = 0, n; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; for(iy = height; iy; iy--, i += step_up) for(ix = 0, n = 0; ix < padded_width; ix++, n--) { if(!n) { val = Bgetc(b); n = 8; } if(ix < width) { buf[i] = clut[val & 0x80 ? 1 : 0]; i++; } val <<= 1; } return 0; } /* Load a 4-Bit encoded BMP file (uncompressed) */ static int load_4T(Biobuf* b, long width, long height, Rgb* buf, Rgb* clut) { long ix, iy, i = 0, step_up = 0, skip = (4 - (((width % 8) + 1) / 2)) & 3; uint valH, valL; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; for(iy = height; iy; iy--, i += step_up) { for(ix = 0; ix < width; ) { valH = valL = Bgetc(b) & 0xff; valH >>= 4; buf[i] = clut[valH]; i++; ix++; if(ix < width) { valL &= 0xf; buf[i] = clut[valL]; i++; ix++; } } Bseek(b, skip, 1); } return 0; } /* Load a 4-Bit encoded BMP file (RLE4-compressed) */ static int load_4C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { long ix, iy = height -1; uint val, valS, skip; Rgb* p; while(iy >= 0) { ix = 0; while(ix < width) { val = (uint)Bgetc(b); if(0 != val) { valS = (uint)Bgetc(b); p = &buf[ix + iy * width]; while(val--) { *p = clut[0xf & (valS >> 4)]; p++; ix++; if(val != 0) { *p = clut[0xf & valS]; p++; ix++; val--; } } } else { /* Special modes... */ val = Bgetc(b); switch(val) { case 0: /* End-Of-Line detected */ ix = width; iy--; break; case 1: /* End-Of-Picture detected -->> abort */ ix = width; iy = -1; break; case 2: /* Position change detected */ val = (uint)Bgetc(b); ix += val; val = (uint)Bgetc(b); iy -= val; break; default:/* Transparent data sequence detected */ p = &buf[ix + iy * width]; if((1 == (val & 3)) || (2 == (val & 3))) skip = 1; else skip = 0; while(val--) { valS = (uint)Bgetc(b); *p = clut[0xf & (valS >> 4)]; p++; ix++; if(val != 0) { *p = clut[0xf & valS]; p++; ix++; val--; } } if(skip) Bgetc(b); break; } } } } return 0; } /* Load a 8-Bit encoded BMP file (uncompressed) */ static int load_8T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { long ix, iy, i = 0, step_up = 0, skip = (4 - (width % 4)) & 3; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; for(iy = height; iy; iy--, i += step_up) { for(ix = 0; ix < width; ix++, i++) buf[i] = clut[Bgetc(b) & 0xff]; Bseek(b, skip, 1); } return 0; } /* Load a 8-Bit encoded BMP file (RLE8-compressed) */ static int load_8C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { long ix, iy = height -1; int val, valS, skip; Rgb* p; while(iy >= 0) { ix = 0; while(ix < width) { val = Bgetc(b); if(0 != val) { valS = Bgetc(b); p = &buf[ix + iy * width]; while(val--) { *p = clut[valS]; p++; ix++; } } else { /* Special modes... */ val = Bgetc(b); switch(val) { case 0: /* End-Of-Line detected */ ix = width; iy--; break; case 1: /* End-Of-Picture detected */ ix = width; iy = -1; break; case 2: /* Position change detected */ val = Bgetc(b); ix += val; val = Bgetc(b); iy -= val; break; default: /* Transparent (not compressed) sequence detected */ p = &buf[ix + iy * width]; if(val & 1) skip = 1; else skip = 0; while(val--) { valS = Bgetc(b); *p = clut[valS]; p++; ix++; } if(skip) /* Align data stream */ Bgetc(b); break; } } } } return 0; } /* Load a 16-Bit encoded BMP file (uncompressed) */ static int load_16(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { uchar c[2]; long ix, iy, i = 0, step_up = 0; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; if(clut) { unsigned mask_blue = (unsigned)clut[0].blue + ((unsigned)clut[0].green << 8); unsigned mask_green = (unsigned)clut[1].blue + ((unsigned)clut[1].green << 8); unsigned mask_red = (unsigned)clut[2].blue + ((unsigned)clut[2].green << 8); int shft_blue = msb((ulong)mask_blue) - 8; int shft_green = msb((ulong)mask_green) - 8; int shft_red = msb((ulong)mask_red) - 8; for(iy = height; iy; iy--, i += step_up) for(ix = 0; ix < width; ix++, i++) { unsigned val; Bread(b, c, sizeof(c)); val = (unsigned)c[0] + ((unsigned)c[1] << 8); buf[i].alpha = 0; if(shft_blue >= 0) buf[i].blue = (uchar)((val & mask_blue) >> shft_blue); else buf[i].blue = (uchar)((val & mask_blue) << -shft_blue); if(shft_green >= 0) buf[i].green = (uchar)((val & mask_green) >> shft_green); else buf[i].green = (uchar)((val & mask_green) << -shft_green); if(shft_red >= 0) buf[i].red = (uchar)((val & mask_red) >> shft_red); else buf[i].red = (uchar)((val & mask_red) << -shft_red); } } else for(iy = height; iy; iy--, i += step_up) for(ix = 0; ix < width; ix++, i++) { Bread(b, c, sizeof(c)); buf[i].blue = (uchar)((c[0] << 3) & 0xf8); buf[i].green = (uchar)(((((unsigned)c[1] << 6) + (((unsigned)c[0]) >> 2))) & 0xf8); buf[i].red = (uchar)((c[1] << 1) & 0xf8); } return 0; } /* Load a 24-Bit encoded BMP file (uncompressed) */ static int load_24T(Biobuf* b, long width, long height, Rgb* buf) { long ix, iy, i = 0, step_up = 0, skip = (4 - ((width * 3) % 4)) & 3; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; for(iy = height; iy; iy--, i += step_up) { for(ix = 0; ix < width; ix++, i++) { buf[i].alpha = 0; buf[i].blue = Bgetc(b); buf[i].green = Bgetc(b); buf[i].red = Bgetc(b); } Bseek(b, skip, 1); } return 0; } /* Load a 32-Bit encoded BMP file (uncompressed) */ static int load_32(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut) { uchar c[4]; long ix, iy, i = 0, step_up = 0; if(height > 0) { /* bottom-up */ i = (height - 1) * width; step_up = -2 * width; } else height = -height; if(clut) { ulong mask_blue = (ulong)clut[0].blue + ((ulong)clut[0].green << 8) + ((ulong)clut[0].red << 16) + ((ulong)clut[0].alpha << 24); ulong mask_green = (ulong)clut[1].blue + ((ulong)clut[1].green << 8) + ((ulong)clut[1].red << 16) + ((ulong)clut[1].alpha << 24); ulong mask_red = (ulong)clut[2].blue + ((ulong)clut[2].green << 8) + ((ulong)clut[2].red << 16) + ((ulong)clut[2].alpha << 24); int shft_blue = msb(mask_blue) - 8; int shft_green = msb(mask_green) - 8; int shft_red = msb(mask_red) - 8; for(iy = height; iy; iy--, i += step_up) for(ix = 0; ix < width; ix++, i++) { ulong val; Bread(b, c, sizeof(c)); val = (ulong)c[0] + ((ulong)c[1] << 8) + ((ulong)c[2] << 16) + ((ulong)c[1] << 24); buf[i].alpha = 0; if(shft_blue >= 0) buf[i].blue = (uchar)((val & mask_blue) >> shft_blue); else buf[i].blue = (uchar)((val & mask_blue) << -shft_blue); if(shft_green >= 0) buf[i].green = (uchar)((val & mask_green) >> shft_green); else buf[i].green = (uchar)((val & mask_green) << -shft_green); if(shft_red >= 0) buf[i].red = (uchar)((val & mask_red) >> shft_red); else buf[i].red = (uchar)((val & mask_red) << -shft_red); } } else for(iy = height; iy; iy--, i += step_up) for(ix = 0; ix < width; ix++, i++) { Bread(b, c, nelem(c)); buf[i].blue = c[0]; buf[i].green = c[1]; buf[i].red = c[2]; } return 0; } static Rgb* ReadBMP(Biobuf *b, int *width, int *height) { int colours, num_coltab = 0; Filehdr bmfh; Infohdr bmih; Rgb clut[256]; Rgb* buf; bmfh.type = r16(b); if(bmfh.type != 0x4d42) /* signature must be 'BM' */ sysfatal("bad magic number, not a BMP file"); bmfh.size = r32(b); bmfh.reserved1 = r16(b); bmfh.reserved2 = r16(b); bmfh.offbits = r32(b); memset(&bmih, 0, sizeof(bmih)); bmih.size = r32(b); if(bmih.size == 0x0c) { /* OS/2 1.x version */ bmih.width = r16(b); bmih.height = r16(b); bmih.planes = r16(b); bmih.bpp = r16(b); bmih.compression = BMP_RGB; } else { /* Windows */ bmih.width = r32(b); bmih.height = r32(b); bmih.planes = r16(b); bmih.bpp = r16(b); bmih.compression = r32(b); bmih.imagesize = r32(b); bmih.hres = r32(b); bmih.vres = r32(b); bmih.colours = r32(b); bmih.impcolours = r32(b); } if(bmih.bpp < 16) { /* load colour table */ if(bmih.impcolours) num_coltab = (int)bmih.impcolours; else num_coltab = 1 << bmih.bpp; } else if(bmih.compression == BMP_BITFIELDS && (bmih.bpp == 16 || bmih.bpp == 32)) /* load bitmasks */ num_coltab = 3; if(num_coltab) { int i; Bseek(b, bmih.size + Filehdrsz, 0); for(i = 0; i < num_coltab; i++) { clut[i].blue = (uchar)Bgetc(b); clut[i].green = (uchar)Bgetc(b); clut[i].red = (uchar)Bgetc(b); clut[i].alpha = (uchar)Bgetc(b); } } *width = bmih.width; *height = bmih.height; colours = bmih.bpp; Bseek(b, bmfh.offbits, 0); if ((buf = calloc(sizeof(Rgb), *width * abs(*height))) == nil) sysfatal("no memory"); switch(colours) { case 1: load_1T(b, *width, *height, buf, clut); break; case 4: if(bmih.compression == BMP_RLE4) load_4C(b, *width, *height, buf, clut); else load_4T(b, *width, *height, buf, clut); break; case 8: if(bmih.compression == BMP_RLE8) load_8C(b, *width, *height, buf, clut); else load_8T(b, *width, *height, buf, clut); break; case 16: load_16(b, *width, *height, buf, bmih.compression == BMP_BITFIELDS ? clut : nil); break; case 24: load_24T(b, *width, *height, buf); break; case 32: load_32(b, *width, *height, buf, bmih.compression == BMP_BITFIELDS ? clut : nil); break; } return buf; } Rawimage** Breadbmp(Biobuf *bp, int colourspace) { Rawimage *a, **array; int c, width, height; uchar *r, *g, *b; Rgb *s, *e; Rgb *bmp; char ebuf[128]; a = nil; bmp = nil; array = nil; USED(a); USED(bmp); if (colourspace != CRGB) { errstr(ebuf, sizeof ebuf); /* throw it away */ werrstr("ReadRGB: unknown colour space %d", colourspace); return nil; } if ((bmp = ReadBMP(bp, &width, &height)) == nil) return nil; if ((a = calloc(sizeof(Rawimage), 1)) == nil) goto Error; for (c = 0; c < 3; c++) if ((a->chans[c] = calloc(width, height)) == nil) goto Error; if ((array = calloc(sizeof(Rawimage *), 2)) == nil) goto Error; array[0] = a; array[1] = nil; a->nchans = 3; a->chandesc = CRGB; a->chanlen = width * height; a->r = Rect(0, 0, width, height); s = bmp; e = s + width * height; r = a->chans[0]; g = a->chans[1]; b = a->chans[2]; do { *r++ = s->red; *g++ = s->green; *b++ = s->blue; }while(++s < e); free(bmp); return array; Error: if (a) for (c = 0; c < 3; c++) if (a->chans[c]) free(a->chans[c]); if (a) free(a); if (array) free(array); if (bmp) free(bmp); return nil; } Rawimage** readbmp(int fd, int colorspace) { Rawimage * *a; Biobuf b; if (Binit(&b, fd, OREAD) < 0) return nil; a = Breadbmp(&b, colorspace); Bterm(&b); return a; }