blob: 154cb48d0531d4a846bcef722be0c69c817be102 [file] [log] [blame]
#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;
}