server/dep/src/g3dlite/GImage_bmp.cpp
Lynx3d ae3ad10bcf [10097] Update G3D up to v8.0b4
+ Got rid of zip lib requirement in G3D...
  Still can re-enable code by defining _HAVE_ZIP...

+ Remove silly X11 lib dependency from G3D
  Code doesn't seem to do anything yet anyway, and even if, we don't want it :p

+ Fix another weird G3D build problem...

+ Remove some __asm usage in g3d, which is not available on Win64
  My editor also decided to remove a ton of trailing white spaces...tss...

+ Reapply G3D fixes for 64bit VC

+ not use SSE specific header when SSE not enabled in *nix

+ Updated project files

+ New vmap_assembler VC90/VC80 Project

+ vmap assembler binaries updates

NOTE: Old vmap fikes expected work (as tests show) with new library version.
      But better use new generated versions. Its different in small parts to bad or good...

(based on Lynx3d's repo commit 44798d3)

Signed-off-by: VladimirMangos <vladimir@getmangos.com>
2010-06-23 06:45:25 +04:00

717 lines
20 KiB
C++

/**
@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;
}
}
}
}
}