| #include <u.h> |
| #include <libc.h> |
| #include <bio.h> |
| #include <draw.h> |
| #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 = Bgetc(b); |
| |
| if(0 != val) { |
| valS = (uint)Bgetc(b); |
| p = &buf[ix + iy * width]; |
| while(val--) { |
| *p = clut[0xf & (valS >> 4)]; |
| p++; |
| ix++; |
| if(0 < val) { |
| *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 = Bgetc(b); |
| ix += val; |
| val = 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(0 < val) { |
| *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 + sizeof(Infohdr), 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; |
| } |
| |
| |