diff options
Diffstat (limited to 'deps/g3dlite/source/GImage_bmp.cpp')
-rw-r--r-- | deps/g3dlite/source/GImage_bmp.cpp | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/deps/g3dlite/source/GImage_bmp.cpp b/deps/g3dlite/source/GImage_bmp.cpp new file mode 100644 index 0000000000..425a7e1a1d --- /dev/null +++ b/deps/g3dlite/source/GImage_bmp.cpp @@ -0,0 +1,717 @@ +/** + @file GImage_bmp.cpp + @author Morgan McGuire, http://graphics.cs.williams.edu + @created 2002-05-27 + @edited 2006-05-10 + */ +#include "G3D/platform.h" +#include "G3D/GImage.h" +#include "G3D/BinaryInput.h" +#include "G3D/BinaryOutput.h" +#include "G3D/Log.h" + +namespace G3D { + +#ifndef G3D_WIN32 +/** + This is used by the Windows bitmap I/O. + */ +static const int BI_RGB = 0; +#endif + +void GImage::encodeBMP( + BinaryOutput& out) const { + + debugAssert(m_channels == 1 || m_channels == 3); + out.setEndian(G3D_LITTLE_ENDIAN); + + uint8 red; + uint8 green; + uint8 blue; + int pixelBufferSize = m_width * m_height * 3; + int fileHeaderSize = 14; + int infoHeaderSize = 40; + int BMScanWidth; + int BMPadding; + + // First write the BITMAPFILEHEADER + // + // WORD bfType; + // DWORD bfSize; + // WORD bfReserved1; + // WORD bfReserved2; + // DWORD bfOffBits; + + // Type + out.writeUInt8('B'); + out.writeUInt8('M'); + + // File size + out.writeUInt32(fileHeaderSize + infoHeaderSize + pixelBufferSize); + + // Two reserved fields set to zero + out.writeUInt16(0); + out.writeUInt16(0); + + // The offset, in bytes, from the BITMAPFILEHEADER structure + // to the bitmap bits. + out.writeUInt32(infoHeaderSize + fileHeaderSize); + + // Now the BITMAPINFOHEADER + // + // DWORD biSize; + // LONG biWidth; + // LONG biHeight; + // WORD biPlanes; + // WORD biBitCount + // DWORD biCompression; + // DWORD biSizeImage; + // LONG biXPelsPerMeter; + // LONG biYPelsPerMeter; + // DWORD biClrUsed; + // DWORD biClrImportant; + + // Size of the info header + out.writeUInt32(infoHeaderSize); + + // Width and height of the image + out.writeUInt32(m_width); + out.writeUInt32(m_height); + + // Planes ("must be set to 1") + out.writeUInt16(1); + + // BitCount and CompressionType + out.writeUInt16(24); + out.writeUInt32(BI_RGB); + + // Image size ("may be zero for BI_RGB bitmaps") + out.writeUInt32(0); + + // biXPelsPerMeter + out.writeUInt32(0); + // biYPelsPerMeter + out.writeUInt32(0); + + // biClrUsed + out.writeUInt32(0); + + // biClrImportant + out.writeUInt32(0); + + BMScanWidth = m_width * 3; + + if (BMScanWidth & 3) { + BMPadding = 4 - (BMScanWidth & 3); + } else { + BMPadding = 0; + } + + int hStart = m_height - 1; + int hEnd = -1; + int hDir = -1; + int dest; + + // Write the pixel data + for (int h = hStart; h != hEnd; h += hDir) { + dest = m_channels * h * m_width; + for (int w = 0; w < m_width; ++w) { + + if (m_channels == 3) { + red = m_byte[dest]; + green = m_byte[dest + 1]; + blue = m_byte[dest + 2]; + } else { + red = m_byte[dest]; + green = m_byte[dest]; + blue = m_byte[dest]; + } + + out.writeUInt8(blue); + out.writeUInt8(green); + out.writeUInt8(red); + + dest += m_channels; + } + + if (BMPadding > 0) { + out.skip(BMPadding); + } + } +} + + +void GImage::decodeBMP( + BinaryInput& input) { + + // The BMP decoding uses these flags. + static const uint16 PICTURE_NONE = 0x0000; + static const uint16 PICTURE_BITMAP = 0x1000; + + // Compression Flags + static const uint16 PICTURE_UNCOMPRESSED = 0x0100; + static const uint16 PICTURE_MONOCHROME = 0x0001; + static const uint16 PICTURE_4BIT = 0x0002; + static const uint16 PICTURE_8BIT = 0x0004; + static const uint16 PICTURE_16BIT = 0x0008; + static const uint16 PICTURE_24BIT = 0x0010; + static const uint16 PICTURE_32BIT = 0x0020; + + (void)PICTURE_16BIT; + (void)PICTURE_32BIT; + + // This is a simple BMP loader that can handle uncompressed BMP files. + // Verify this is a BMP file by looking for the BM tag. + input.reset(); + std::string tag = input.readString(2); + if (tag != "BM") { + throw Error("Not a BMP file", input.getFilename()); + } + + m_channels = 3; + // Skip to the BITMAPINFOHEADER's width and height + input.skip(16); + + m_width = input.readUInt32(); + m_height = input.readUInt32(); + + // Skip to the bit count and compression type + input.skip(2); + + uint16 bitCount = input.readUInt16(); + uint32 compressionType = input.readUInt32(); + + uint8 red; + uint8 green; + uint8 blue; + uint8 blank; + + // Only uncompressed bitmaps are supported by this code + if ((int32)compressionType != BI_RGB) { + throw Error("BMP images must be uncompressed", input.getFilename()); + } + + uint8* palette = NULL; + + // Create the palette if needed + if (bitCount <= 8) { + + // Skip to the palette color count in the header + input.skip(12); + + int numColors = input.readUInt32(); + + palette = (uint8*)System::malloc(numColors * 3); + debugAssert(palette); + + // Skip past the end of the header to the palette info + input.skip(4); + + int c; + for(c = 0; c < numColors * 3; c += 3) { + // Palette information in bitmaps is stored in BGR_ format. + // That means it's blue-green-red-blank, for each entry. + blue = input.readUInt8(); + green = input.readUInt8(); + red = input.readUInt8(); + blank = input.readUInt8(); + + palette[c] = red; + palette[c + 1] = green; + palette[c + 2] = blue; + } + } + + int hStart = 0; + int hEnd = 0; + int hDir = 0; + + if (m_height < 0) { + m_height = -m_height; + hStart = 0; + hEnd = m_height; + hDir = 1; + } else { + //height = height; + hStart = m_height - 1; + hEnd = -1; + hDir = -1; + } + + m_byte = (uint8*)m_memMan->alloc(m_width * m_height * 3); + debugAssert(m_byte); + + int BMScanWidth; + int BMPadding; + uint8 BMGroup; + uint8 BMPixel8; + int currPixel; + int dest; + int flags = PICTURE_NONE; + + if (bitCount == 1) { + // Note that this file is not necessarily grayscale, since it's possible + // the palette is blue-and-white, or whatever. But of course most image + // programs only write 1-bit images if they're black-and-white. + flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_MONOCHROME; + + // For bitmaps, each scanline is dword-aligned. + BMScanWidth = (m_width + 7) >> 3; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + // Powers of 2 + int pow2[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + dest = 3 * h * m_width; + + for (int w = 0; w < BMScanWidth; ++w) { + + BMGroup = input.readUInt8(); + + // Now we read the pixels. Usually there are eight pixels per byte, + // since each pixel is represented by one bit, but if the width + // is not a multiple of eight, the last byte will have some bits + // set, with the others just being extra. Plus there's the + // dword-alignment padding. So we keep checking to see if we've + // already read "width" number of pixels. + for (int i = 7; i >= 0; --i) { + if (currPixel < m_width) { + int src = 3 * ((BMGroup & pow2[i]) >> i); + + m_byte[dest] = palette[src]; + m_byte[dest + 1] = palette[src + 1]; + m_byte[dest + 2] = palette[src + 2]; + + ++currPixel; + dest += 3; + } + } + } + } + + } else if (bitCount == 4) { + + flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_4BIT; + + // For bitmaps, each scanline is dword-aligned. + int BMScanWidth = (m_width + 1) >> 1; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + dest = 3 * h * m_width; + + for (int w = 0; w < BMScanWidth; w++) { + + BMGroup = input.readUInt8(); + int src[2]; + src[0] = 3 * ((BMGroup & 0xF0) >> 4); + src[1] = 3 * (BMGroup & 0x0F); + + // Now we read the pixels. Usually there are two pixels per byte, + // since each pixel is represented by four bits, but if the width + // is not a multiple of two, the last byte will have only four bits + // set, with the others just being extra. Plus there's the + // dword-alignment padding. So we keep checking to see if we've + // already read "Width" number of pixels. + + for (int i = 0; i < 2; ++i) { + if (currPixel < m_width) { + int tsrc = src[i]; + + m_byte[dest] = palette[tsrc]; + m_byte[dest + 1] = palette[tsrc + 1]; + m_byte[dest + 2] = palette[tsrc + 2]; + + ++currPixel; + dest += 3; + } + } + } + } + + } else if (bitCount == 8) { + + flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_8BIT; + + // For bitmaps, each scanline is dword-aligned. + BMScanWidth = m_width; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + + for (int w = 0; w < BMScanWidth; ++w) { + + BMPixel8 = input.readUInt8(); + + if (currPixel < m_width) { + dest = 3 * ((h * m_width) + currPixel); + int src = 3 * BMPixel8; + + m_byte[dest] = palette[src]; + m_byte[dest + 1] = palette[src + 1]; + m_byte[dest + 2] = palette[src + 2]; + + ++currPixel; + } + } + } + + } else if (bitCount == 16) { + + m_memMan->free(m_byte); + m_byte = NULL; + System::free(palette); + palette = NULL; + throw Error("16-bit bitmaps not supported", input.getFilename()); + + } else if (bitCount == 24) { + input.skip(20); + + flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_24BIT; + + // For bitmaps, each scanline is dword-aligned. + BMScanWidth = m_width * 3; + + if (BMScanWidth & 3) { + BMPadding = 4 - (BMScanWidth & 3); + } else { + BMPadding = 0; + } + + for (int h = hStart; h != hEnd; h += hDir) { + dest = 3 * h * m_width; + for (int w = 0; w < m_width; ++w) { + + blue = input.readUInt8(); + green = input.readUInt8(); + red = input.readUInt8(); + + m_byte[dest] = red; + m_byte[dest + 1] = green; + m_byte[dest + 2] = blue; + + dest += 3; + } + + if (BMPadding) { + input.skip(2); + } + } + + } else if (bitCount == 32) { + + m_memMan->free(m_byte); + m_byte = NULL; + System::free(palette); + palette = NULL; + throw Error("32 bit bitmaps not supported", input.getFilename()); + + } else { + // We support all possible bit depths, so if the + // code gets here, it's not even a real bitmap. + m_memMan->free(m_byte); + m_byte = NULL; + throw Error("Not a bitmap!", input.getFilename()); + } + + System::free(palette); + palette = NULL; +} + + +void GImage::decodeICO( + BinaryInput& input) { + + // Header + uint16 r = input.readUInt16(); + debugAssert(r == 0); + r = input.readUInt16(); + debugAssert(r == 1); + + // Read the number of icons, although we'll only load the + // first one. + int count = input.readUInt16(); + + m_channels = 4; + + debugAssert(count > 0); + + const uint8* headerBuffer = input.getCArray() + input.getPosition(); + int maxWidth = 0, maxHeight = 0; + int maxHeaderNum = 0; + for (int currentHeader = 0; currentHeader < count; ++currentHeader) { + + const uint8* curHeaderBuffer = headerBuffer + (currentHeader * 16); + int tmpWidth = curHeaderBuffer[0]; + int tmpHeight = curHeaderBuffer[1]; + // Just in case there is a non-square icon, checking area + if ((tmpWidth * tmpHeight) > (maxWidth * maxHeight)) { + maxWidth = tmpWidth; + maxHeight = tmpHeight; + maxHeaderNum = currentHeader; + } + } + + input.skip(maxHeaderNum * 16); + + m_width = input.readUInt8(); + m_height = input.readUInt8(); + int numColors = input.readUInt8(); + + m_byte = (uint8*)m_memMan->alloc(m_width * m_height * m_channels); + debugAssert(m_byte); + + // Bit mask for packed bits + int mask = 0; + + int bitsPerPixel = 8; + + switch (numColors) { + case 2: + mask = 0x01; + bitsPerPixel = 1; + break; + + case 16: + mask = 0x0F; + bitsPerPixel = 4; + break; + + case 0: + numColors = 256; + mask = 0xFF; + bitsPerPixel = 8; + break; + + default: + throw Error("Unsupported ICO color count.", input.getFilename()); + } + + input.skip(5); + // Skip 'size' unused + input.skip(4); + + int offset = input.readUInt32(); + + // Skip over any other icon descriptions + input.setPosition(offset); + + // Skip over bitmap header; it is redundant + input.skip(40); + + Array<Color4uint8> palette; + palette.resize(numColors, true); + for (int c = 0; c < numColors; ++c) { + palette[c].b = input.readUInt8(); + palette[c].g = input.readUInt8(); + palette[c].r = input.readUInt8(); + palette[c].a = input.readUInt8(); + } + + // The actual image and mask follow + + // The XOR Bitmap is stored as 1-bit, 4-bit or 8-bit uncompressed Bitmap + // using the same encoding as BMP files. The AND Bitmap is stored in as + // 1-bit uncompressed Bitmap. + // + // Pixels are stored bottom-up, left-to-right. Pixel lines are padded + // with zeros to end on a 32bit (4byte) boundary. Every line will have the + // same number of bytes. Color indices are zero based, meaning a pixel color + // of 0 represents the first color table entry, a pixel color of 255 (if there + // are that many) represents the 256th entry. +/* + int bitsPerRow = width * bitsPerPixel; + int bytesPerRow = iCeil((double)bitsPerRow / 8); + // Rows are padded to 32-bit boundaries + bytesPerRow += bytesPerRow % 4; + + // Read the XOR values into the color channel + for (int y = height - 1; y >= 0; --y) { + int x = 0; + // Read the row + for (int i = 0; i < bytesPerRow; ++i) { + uint8 byte = input.readUInt8(); + for (int j = 0; (j < 8) && (x < width); ++x, j += bitsPerPixel) { + int bit = ((byte << j) >> (8 - bitsPerPixel)) & mask; + pixel4(x, y) = colorTable[bit]; + } + } + } +*/ + int hStart = 0; + int hEnd = 0; + int hDir = 0; + + if (m_height < 0) { + m_height = -m_height; + hStart = 0; + hEnd = m_height; + hDir = 1; + } else { + //height = height; + hStart = m_height - 1; + hEnd = -1; + hDir = -1; + } + + int BMScanWidth; + uint8 BMGroup; + uint8 BMPixel8; + int currPixel; + int dest; + + if (bitsPerPixel == 1) { + // Note that this file is not necessarily grayscale, since it's possible + // the palette is blue-and-white, or whatever. But of course most image + // programs only write 1-bit images if they're black-and-white. + + // For bitmaps, each scanline is dword-aligned. + BMScanWidth = (m_width + 7) >> 3; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + // Powers of 2 + int pow2[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + dest = 3 * h * m_width; + + for (int w = 0; w < BMScanWidth; ++w) { + + BMGroup = input.readUInt8(); + + // Now we read the pixels. Usually there are eight pixels per byte, + // since each pixel is represented by one bit, but if the width + // is not a multiple of eight, the last byte will have some bits + // set, with the others just being extra. Plus there's the + // dword-alignment padding. So we keep checking to see if we've + // already read "width" number of pixels. + for (int i = 7; i >= 0; --i) { + if (currPixel < m_width) { + int src = ((BMGroup & pow2[i]) >> i); + + m_byte[dest] = palette[src].r; + m_byte[dest + 1] = palette[src].g; + m_byte[dest + 2] = palette[src].b; + + ++currPixel; + dest += 4; + } + } + } + } + + } else if (bitsPerPixel == 4) { + + // For bitmaps, each scanline is dword-aligned. + int BMScanWidth = (m_width + 1) >> 1; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + dest = 4 * h * m_width; + + for (int w = 0; w < BMScanWidth; w++) { + + BMGroup = input.readUInt8(); + int src[2]; + src[0] = ((BMGroup & 0xF0) >> 4); + src[1] = (BMGroup & 0x0F); + + // Now we read the pixels. Usually there are two pixels per byte, + // since each pixel is represented by four bits, but if the width + // is not a multiple of two, the last byte will have only four bits + // set, with the others just being extra. Plus there's the + // dword-alignment padding. So we keep checking to see if we've + // already read "Width" number of pixels. + + for (int i = 0; i < 2; ++i) { + if (currPixel < m_width) { + int tsrc = src[i]; + + m_byte[dest] = palette[tsrc].r; + m_byte[dest + 1] = palette[tsrc].g; + m_byte[dest + 2] = palette[tsrc].b; + + ++currPixel; + dest += 4; + } + } + } + } + + } else if (bitsPerPixel == 8) { + + // For bitmaps, each scanline is dword-aligned. + BMScanWidth = m_width; + if (BMScanWidth & 3) { + BMScanWidth += 4 - (BMScanWidth & 3); + } + + for (int h = hStart; h != hEnd; h += hDir) { + + currPixel = 0; + + for (int w = 0; w < BMScanWidth; ++w) { + + BMPixel8 = input.readUInt8(); + + if (currPixel < m_width) { + dest = 4 * ((h * m_width) + currPixel); + int src = BMPixel8; + + m_byte[dest] = palette[src].r; + m_byte[dest + 1] = palette[src].g; + m_byte[dest + 2] = palette[src].b; + + ++currPixel; + } + } + } + } + + // Read the mask into the alpha channel + int bitsPerRow = m_width; + int bytesPerRow = iCeil((double)bitsPerRow / 8); + + // For bitmaps, each scanline is dword-aligned. + //BMScanWidth = (width + 1) >> 1; + if (bytesPerRow & 3) { + bytesPerRow += 4 - (bytesPerRow & 3); + } + + for (int y = m_height - 1; y >= 0; --y) { + int x = 0; + // Read the row + for (int i = 0; i < bytesPerRow; ++i) { + uint8 byte = input.readUInt8(); + for (int j = 0; (j < 8) && (x < m_width); ++x, ++j) { + int bit = (byte >> (7 - j)) & 0x01; + pixel4(x, y).a = (1 - bit) * 0xFF; + } + } + } + +} + + +} |