1257 lines
33 KiB
C++
1257 lines
33 KiB
C++
// Copyright 2003 Jeremy Sawicki
|
|
//
|
|
// This file is part of OxydLib.
|
|
//
|
|
// OxydLib is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// OxydLib is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with OxydLib; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
#include "Bitmap.h"
|
|
#include <stdio.h>
|
|
#include <set>
|
|
#include <map>
|
|
#include <algorithm>
|
|
|
|
namespace OxydLib {
|
|
|
|
using namespace std;
|
|
|
|
Palette::Palette()
|
|
{
|
|
}
|
|
|
|
Palette::~Palette()
|
|
{
|
|
}
|
|
|
|
int Palette::getNumColors() const
|
|
{
|
|
return m_colors.size() / 3;
|
|
}
|
|
|
|
void Palette::setNumColors(int numColors)
|
|
{
|
|
m_colors.resize(numColors * 3);
|
|
}
|
|
|
|
void Palette::getColor(int nColor,
|
|
unsigned int *pRed,
|
|
unsigned int *pGreen,
|
|
unsigned int *pBlue) const
|
|
{
|
|
unsigned int red = m_colors[3 * nColor + 0];
|
|
unsigned int green = m_colors[3 * nColor + 1];
|
|
unsigned int blue = m_colors[3 * nColor + 2];
|
|
|
|
if (pRed) { *pRed = red; }
|
|
if (pGreen) { *pGreen = green; }
|
|
if (pBlue) { *pBlue = blue; }
|
|
}
|
|
|
|
void Palette::setColor(int nColor,
|
|
unsigned int red,
|
|
unsigned int green,
|
|
unsigned int blue)
|
|
{
|
|
m_colors[3 * nColor + 0] = red;
|
|
m_colors[3 * nColor + 1] = green;
|
|
m_colors[3 * nColor + 2] = blue;
|
|
}
|
|
|
|
Bitmap::Bitmap()
|
|
{
|
|
m_pixels.resize(640*400);
|
|
clear();
|
|
}
|
|
|
|
Bitmap::~Bitmap()
|
|
{
|
|
}
|
|
|
|
void Bitmap::clear()
|
|
{
|
|
setBitDepth(0);
|
|
|
|
for (int y = 0; y < 400; y++) {
|
|
for (int x = 0; x < 640; x++) {
|
|
setPixel(x, y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned char Bitmap::getPixel(int x, int y) const
|
|
{
|
|
return m_pixels[(y * 640) + x];
|
|
}
|
|
|
|
void Bitmap::setPixel(int x, int y, unsigned char pixelVal)
|
|
{
|
|
m_pixels[(y * 640) + x] = pixelVal;
|
|
}
|
|
|
|
int Bitmap::getBit(int x, int y, int bitNum) const
|
|
{
|
|
return (getPixel(x, y) & (1 << bitNum)) >> bitNum;
|
|
}
|
|
|
|
void Bitmap::setBit(int x, int y, int bitNum, int bitVal)
|
|
{
|
|
setPixel(x, y,
|
|
(getPixel(x, y) & ~(1 << bitNum)) |
|
|
((bitVal ? 1 : 0) << bitNum));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const unsigned char bitPlaneTypeName[8] =
|
|
{'I', 'B', 'M', '1', '6', 'C', 'O', 'L'};
|
|
|
|
int getBitStreamBit(const ByteVec &in,
|
|
int bitStreamStart,
|
|
int offset)
|
|
{
|
|
return (in[bitStreamStart + (offset >> 3)] & (1 << (7 - (offset & 0x7))))
|
|
>> (7 - (offset & 0x7));
|
|
}
|
|
}
|
|
|
|
|
|
bool parseBitmap(const ByteVec &in,
|
|
Bitmap *pBitmap,
|
|
Palette *pPalette,
|
|
string *pMsg)
|
|
{
|
|
string msg;
|
|
if (!pMsg) {
|
|
pMsg = &msg;
|
|
}
|
|
|
|
pBitmap->clear();
|
|
|
|
int sz = in.size();
|
|
int cur = 0;
|
|
|
|
if (cur + 2 > sz) {
|
|
pMsg->assign("Not enough data reading bit depth.");
|
|
return false;
|
|
}
|
|
|
|
int bitDepth = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (bitDepth != 1 &&
|
|
bitDepth != 4) {
|
|
pMsg->assign("Expected a bit depth of 1 or 4.");
|
|
return false;
|
|
}
|
|
|
|
pBitmap->setBitDepth(bitDepth);
|
|
|
|
if (cur + (2 * bitDepth) > sz) {
|
|
pMsg->assign("Not enough data reading bit plane sizes.");
|
|
return false;
|
|
}
|
|
|
|
vector<int> bitPlaneSizes;
|
|
bitPlaneSizes.resize(bitDepth);
|
|
int bitPlaneSizeTotal = 0;
|
|
int nBitPlane;
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
bitPlaneSizes[nBitPlane] = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
bitPlaneSizeTotal += bitPlaneSizes[nBitPlane];
|
|
}
|
|
|
|
if (cur + 2 > sz) {
|
|
pMsg->assign("Not enough data reading block size.");
|
|
return false;
|
|
}
|
|
|
|
int blockSize = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (blockSize != 16) {
|
|
pMsg->assign("Expected a block size of 16.");
|
|
return false;
|
|
}
|
|
|
|
if (cur + 96 > sz) {
|
|
pMsg->assign("Not enough data reading palette.");
|
|
return false;
|
|
}
|
|
|
|
pPalette->setNumColors(16);
|
|
for (int nColor = 0; nColor < 16; nColor++) {
|
|
unsigned int red = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
unsigned int green = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
unsigned int blue = getInt2Reversed(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
pPalette->setColor(nColor, red, green, blue);
|
|
}
|
|
|
|
if (cur + bitPlaneSizeTotal > sz) {
|
|
pMsg->assign("Not enough data in chunk based on bit plane sizes.");
|
|
return false;
|
|
}
|
|
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
if (bitPlaneSizes[nBitPlane] < 40) {
|
|
pMsg->assign("Not enough data in bit plane for header.");
|
|
return false;
|
|
}
|
|
|
|
int bitPlaneEnd = cur + bitPlaneSizes[nBitPlane];
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
unsigned char ch = in[cur];
|
|
cur++;
|
|
|
|
if (ch != bitPlaneTypeName[i]) {
|
|
pMsg->assign("Unexpected character in bit plane type name.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int bitPlaneDataSize = getInt4(in.begin() + cur);
|
|
cur += 4;
|
|
|
|
if (bitPlaneDataSize + 8 > bitPlaneSizes[nBitPlane]) {
|
|
pMsg->assign("Bit plane data size is too large for bit plane size.");
|
|
return false;
|
|
}
|
|
|
|
int bitPlaneDataEnd = cur - 4 + bitPlaneDataSize;
|
|
|
|
int pixelWidth = getInt2(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (pixelWidth != 640) {
|
|
pMsg->assign("Pixel width of 640 expected.");
|
|
return false;
|
|
}
|
|
|
|
int pixelHeight = getInt2(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (pixelHeight != 400) {
|
|
pMsg->assign("Pixel height of 400 expected.");
|
|
return false;
|
|
}
|
|
|
|
int blockWidth = getInt2(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (blockWidth != 40) {
|
|
pMsg->assign("Block width of 40 expected.");
|
|
return false;
|
|
}
|
|
|
|
int blockHeight = getInt2(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
if (blockHeight != 25) {
|
|
pMsg->assign("Block height of 25 expected.");
|
|
return false;
|
|
}
|
|
|
|
int bitStreamSize = getInt4(in.begin() + cur);
|
|
cur += 4;
|
|
|
|
int byteStreamSize = getInt4(in.begin() + cur);
|
|
cur += 4;
|
|
|
|
if (bitStreamSize + byteStreamSize + 32 != bitPlaneDataSize) {
|
|
pMsg->assign("Bit stream and byte stream sizes don't add up.");
|
|
return false;
|
|
}
|
|
|
|
bool invertFlags[2];
|
|
for (int nInvertFlag = 0; nInvertFlag < 2; nInvertFlag++) {
|
|
unsigned char invertVal = in[cur];
|
|
cur++;
|
|
|
|
if (invertVal != (unsigned char)0x00 &&
|
|
invertVal != (unsigned char)0xff) {
|
|
pMsg->assign("Invalid invert value.");
|
|
return false;
|
|
}
|
|
|
|
invertFlags[nInvertFlag] = (invertVal == (unsigned char)0xff);
|
|
}
|
|
|
|
int blockCopyFlagVal = getInt2(in.begin() + cur);
|
|
cur += 2;
|
|
|
|
bool bHasBlockCopies = (blockCopyFlagVal == 0xdead);
|
|
|
|
// Unused bytes
|
|
cur += 8;
|
|
|
|
int bitStreamStart = cur;
|
|
int bitStreamEnd = bitStreamStart + bitStreamSize;
|
|
|
|
int byteStreamStart = bitStreamEnd;
|
|
int byteStreamEnd = byteStreamStart + byteStreamSize;
|
|
|
|
int curBit = 0;
|
|
int numBits = bitStreamSize * 8;
|
|
|
|
int curByte = byteStreamStart;
|
|
|
|
int st = bitStreamStart;
|
|
|
|
int curBlockY;
|
|
int curBlockX;
|
|
|
|
for (curBlockY = 0; curBlockY < blockHeight; curBlockY++) {
|
|
if (curBit + 1 > numBits) {
|
|
pMsg->assign("Not enough bits reading row skip bit.");
|
|
return false;
|
|
}
|
|
|
|
int rowSkip = getBitStreamBit(in, st, curBit);
|
|
curBit++;
|
|
|
|
if (rowSkip == 0) {
|
|
// Row is empty.
|
|
} else {
|
|
for (curBlockX = 0; curBlockX < blockWidth; curBlockX++) {
|
|
if (curBit + 1 > numBits) {
|
|
pMsg->assign("Not enough bits reading first bit of block info.");
|
|
return false;
|
|
}
|
|
|
|
int bit1 = getBitStreamBit(in, st, curBit);
|
|
curBit++;
|
|
|
|
if (bit1 == 0) {
|
|
// Block is empty.
|
|
} else {
|
|
if (curBit + 2 > numBits) {
|
|
pMsg->assign("Not enough bits reading block compression type.");
|
|
return false;
|
|
}
|
|
|
|
int compressionType = (getBitStreamBit(in, st, curBit) << 1) +
|
|
getBitStreamBit(in, st, curBit + 1);
|
|
curBit += 2;
|
|
|
|
if (compressionType == 3) {
|
|
if (curByte + 32 > byteStreamEnd) {
|
|
pMsg->assign("Not enough bytes reading uncompressed block.");
|
|
return false;
|
|
}
|
|
|
|
int nByte;
|
|
for (nByte = 0; nByte < 32; nByte++) {
|
|
int byteVal = in[curByte];
|
|
curByte++;
|
|
|
|
int byteBit;
|
|
for (byteBit = 7; byteBit >= 0; byteBit--) {
|
|
int bitVal = (byteVal & (1 << byteBit)) >> byteBit;
|
|
|
|
pBitmap->setBit((curBlockX * 16) + ((nByte % 2) * 8) +
|
|
(7 - byteBit),
|
|
(curBlockY * 16) + (nByte >> 1),
|
|
nBitPlane,
|
|
bitVal);
|
|
}
|
|
}
|
|
} else {
|
|
if (curBit + 4 > numBits) {
|
|
pMsg->assign("Not enough bits reading quadrant skip bits.");
|
|
return false;
|
|
}
|
|
|
|
int quadrantSkips[4];
|
|
int nQuadrant;
|
|
for (nQuadrant = 0; nQuadrant < 4; nQuadrant++) {
|
|
quadrantSkips[nQuadrant] = getBitStreamBit(in, st, curBit);
|
|
curBit++;
|
|
}
|
|
|
|
for (nQuadrant = 0; nQuadrant < 4; nQuadrant++) {
|
|
unsigned char quadrantHeader = 0;
|
|
if (quadrantSkips[nQuadrant]) {
|
|
if (curByte + 1 > byteStreamEnd) {
|
|
pMsg->assign("Not enough bytes reading quadrant header.");
|
|
return false;
|
|
}
|
|
|
|
quadrantHeader = in[curByte];
|
|
curByte++;
|
|
}
|
|
|
|
int nLine;
|
|
for (nLine = 0; nLine < 8; nLine++) {
|
|
unsigned char lineByte = 0;
|
|
|
|
if (quadrantHeader & (1 << (7 - nLine))) {
|
|
if (curByte + 1 > byteStreamEnd) {
|
|
pMsg->assign("Not enough bytes reading line byte.");
|
|
return false;
|
|
}
|
|
|
|
lineByte = in[curByte];
|
|
curByte++;
|
|
}
|
|
|
|
int pixXstart = (curBlockX * 16) + (nQuadrant & 0x1) * 8;
|
|
int pixY = (curBlockY * 16) + (nQuadrant & 0x2) * 4 + nLine;
|
|
|
|
int bXor = (compressionType > 0);
|
|
int xorDiff = compressionType;
|
|
|
|
int nBit;
|
|
for (nBit = 0; nBit < 8; nBit++) {
|
|
int lineBit = (lineByte & (1 << (7 - nBit))) >> (7 - nBit);
|
|
|
|
if (bXor) {
|
|
if (((nQuadrant & 0x2) * 4 + nLine) >= xorDiff) {
|
|
lineBit ^= pBitmap->getBit(pixXstart + nBit,
|
|
pixY - xorDiff,
|
|
nBitPlane);
|
|
}
|
|
}
|
|
|
|
pBitmap->setBit(pixXstart + nBit,
|
|
pixY,
|
|
nBitPlane,
|
|
lineBit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (curByte < byteStreamEnd) {
|
|
pMsg->assign("Extra bytes in byte stream.");
|
|
return false;
|
|
}
|
|
|
|
if (numBits - curBit >= 8) {
|
|
pMsg->assign("Too many spare bits in bit stream.");
|
|
return false;
|
|
}
|
|
|
|
while (curBit < numBits) {
|
|
//int bb = getBitStreamBit(in, st, curBit);
|
|
curBit++;
|
|
|
|
// Spare bits are usually 0, but not always.
|
|
/*
|
|
if (bb != 0) {
|
|
pMsg->assign("Spare bit is not 0.");
|
|
return false;
|
|
}
|
|
*/
|
|
}
|
|
|
|
cur = bitPlaneDataEnd;
|
|
|
|
// Process copied blocks.
|
|
if (bHasBlockCopies) {
|
|
bool foundLastSource = false;
|
|
do {
|
|
if (cur + 2 > bitPlaneEnd) {
|
|
pMsg->assign("Ran out of data reading block copy source.");
|
|
return false;
|
|
}
|
|
|
|
unsigned char sourceBlockX = in[cur];
|
|
cur++;
|
|
unsigned char sourceBlockY = in[cur];
|
|
cur++;
|
|
|
|
if (sourceBlockX == 0xff && sourceBlockY == 0xff) {
|
|
foundLastSource = true;
|
|
} else {
|
|
if (sourceBlockX >= blockWidth) {
|
|
pMsg->assign("Block copy source X coordinate too large.");
|
|
return false;
|
|
}
|
|
if (sourceBlockY >= blockHeight) {
|
|
pMsg->assign("Block copy source Y coordinate too large.");
|
|
return false;
|
|
}
|
|
|
|
vector<unsigned char> destBlockXVec;
|
|
vector<unsigned char> destBlockYVec;
|
|
bool foundLastDest = false;
|
|
do {
|
|
if (cur + 2 > bitPlaneEnd) {
|
|
pMsg->assign("Ran out of data reading block copy destination.");
|
|
return false;
|
|
}
|
|
|
|
unsigned char destBlockX = in[cur];
|
|
cur++;
|
|
unsigned char destBlockY = in[cur];
|
|
cur++;
|
|
|
|
if (destBlockX == 0xff && destBlockY == 0xff) {
|
|
foundLastDest = true;
|
|
} else {
|
|
if (destBlockX >= blockWidth) {
|
|
pMsg->assign("Block copy destination X coordinate too large.");
|
|
return false;
|
|
}
|
|
if (destBlockY >= blockHeight) {
|
|
pMsg->assign("Block copy destination Y coordinate too large.");
|
|
return false;
|
|
}
|
|
|
|
destBlockXVec.push_back(destBlockX);
|
|
destBlockYVec.push_back(destBlockY);
|
|
}
|
|
} while (!foundLastDest);
|
|
|
|
if (destBlockXVec.size() == 0) {
|
|
pMsg->assign("Block copy source has no destinations.");
|
|
return false;
|
|
}
|
|
|
|
for (int nDest = 0; nDest < (int)destBlockXVec.size(); nDest++) {
|
|
unsigned char destBlockX = destBlockXVec[nDest];
|
|
unsigned char destBlockY = destBlockYVec[nDest];
|
|
|
|
for (int pixelDeltaY = 0; pixelDeltaY < 16; pixelDeltaY++) {
|
|
int sourcePixelY = sourceBlockY * 16 + pixelDeltaY;
|
|
int destPixelY = destBlockY * 16 + pixelDeltaY;
|
|
for (int pixelDeltaX = 0; pixelDeltaX < 16; pixelDeltaX++) {
|
|
int sourcePixelX = sourceBlockX * 16 + pixelDeltaX;
|
|
int destPixelX = destBlockX * 16 + pixelDeltaX;
|
|
|
|
int bit = pBitmap->getBit(sourcePixelX,
|
|
sourcePixelY,
|
|
nBitPlane);
|
|
pBitmap->setBit(destPixelX,
|
|
destPixelY,
|
|
nBitPlane,
|
|
bit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (!foundLastSource);
|
|
}
|
|
|
|
if (cur < bitPlaneEnd) {
|
|
pMsg->assign("Unexpected data at the end of bit plane.");
|
|
return false;
|
|
}
|
|
|
|
// Invert the bit plane
|
|
if (invertFlags[0] || invertFlags[1]) {
|
|
for (int y = 0; y < pixelHeight; y++) {
|
|
if (invertFlags[y & 0x1]) {
|
|
for (int x = 0; x < pixelWidth; x++) {
|
|
int bit = pBitmap->getBit(x, y, nBitPlane);
|
|
pBitmap->setBit(x, y, nBitPlane, 1 - bit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cur < sz) {
|
|
pMsg->assign("Unexpected data at the end of bitmap.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void addBit(ByteVec *pBits, int *pBitCount, int bit)
|
|
{
|
|
int nByte = (*pBitCount) >> 3;
|
|
int nBit = 7 - ((*pBitCount) & 0x7);
|
|
if (nByte >= (int)pBits->size()) {
|
|
pBits->resize(nByte + 1);
|
|
}
|
|
unsigned char ch = (*pBits)[nByte];
|
|
ch = ch & ~(1<<nBit);
|
|
ch = ch | (bit<<nBit);
|
|
(*pBits)[nByte] = ch;
|
|
(*pBitCount)++;
|
|
}
|
|
|
|
void addBits(ByteVec *pBits, int *pBitCount,
|
|
const ByteVec &addedBits, int addedBitCount)
|
|
{
|
|
for (int i = 0; i < addedBitCount; i++) {
|
|
int bit = getBitStreamBit(addedBits, 0, i);
|
|
addBit(pBits, pBitCount, bit);
|
|
}
|
|
}
|
|
|
|
void addBytes(ByteVec *pBytes, const ByteVec &addedBytes)
|
|
{
|
|
int sz = pBytes->size();
|
|
pBytes->resize(sz + addedBytes.size());
|
|
copy(addedBytes.begin(), addedBytes.end(),
|
|
pBytes->begin() + sz);
|
|
}
|
|
|
|
int getBitInverted(const Bitmap &bitmap,
|
|
int x,
|
|
int y,
|
|
int bitNum,
|
|
bool bInvert)
|
|
{
|
|
int bit = bitmap.getBit(x, y, bitNum);
|
|
if (bInvert) {
|
|
bit = 1 - bit;
|
|
}
|
|
return bit;
|
|
}
|
|
|
|
bool blockIsEmpty(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
int blockX,
|
|
int blockY,
|
|
bool bInvert)
|
|
{
|
|
for (int deltaY = 0; deltaY < 16; deltaY++) {
|
|
for (int deltaX = 0; deltaX < 16; deltaX++) {
|
|
int bit = getBitInverted(bitmap,
|
|
blockX * 16 + deltaX,
|
|
blockY * 16 + deltaY,
|
|
nBitPlane,
|
|
bInvert);
|
|
if (bit == 1) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void compressBlock(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
int blockX,
|
|
int blockY,
|
|
bool bInvert,
|
|
int compressionType,
|
|
ByteVec *pBits,
|
|
int *pBitCount,
|
|
ByteVec *pBytes)
|
|
{
|
|
addBit(pBits, pBitCount, 1);
|
|
addBit(pBits, pBitCount, (compressionType & 0x2) >> 1);
|
|
addBit(pBits, pBitCount, (compressionType & 0x1));
|
|
|
|
if (compressionType == 3) {
|
|
for (int i = 0; i < 32; i++) {
|
|
int byteVal = 0x00;
|
|
for (int b = 0; b < 8; b++) {
|
|
int pixelY = (blockY * 16) + ((i & 0x1e) >> 1);
|
|
int pixelX = (blockX * 16) + ((i & 0x01) * 8) + b;
|
|
int bit = getBitInverted(bitmap,
|
|
pixelX,
|
|
pixelY,
|
|
nBitPlane,
|
|
bInvert);
|
|
byteVal |= (bit << (7 - b));
|
|
}
|
|
|
|
pBytes->push_back(byteVal);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int bXor = (compressionType > 0);
|
|
int xorDiff = compressionType;
|
|
|
|
for (int nQuadrant = 0; nQuadrant < 4; nQuadrant++) {
|
|
unsigned char lineBytes[8];
|
|
|
|
unsigned char quadrantHeader = 0x00;
|
|
|
|
int nLine;
|
|
for (nLine = 0; nLine < 8; nLine++) {
|
|
unsigned char lineByte = 0;
|
|
|
|
int pixXstart = (blockX * 16) + (nQuadrant & 0x1) * 8;
|
|
int pixY = (blockY * 16) + (nQuadrant & 0x2) * 4 + nLine;
|
|
|
|
int nBit;
|
|
for (nBit = 0; nBit < 8; nBit++) {
|
|
int lineBit = getBitInverted(bitmap,
|
|
pixXstart + nBit,
|
|
pixY,
|
|
nBitPlane,
|
|
bInvert);
|
|
|
|
if (bXor) {
|
|
if (((nQuadrant & 0x2) * 4 + nLine) >= xorDiff) {
|
|
lineBit ^= getBitInverted(bitmap,
|
|
pixXstart + nBit,
|
|
pixY - xorDiff,
|
|
nBitPlane,
|
|
bInvert);
|
|
}
|
|
}
|
|
|
|
lineByte |= lineBit << (7 - nBit);
|
|
}
|
|
|
|
lineBytes[nLine] = lineByte;
|
|
|
|
if (lineByte != 0x00) {
|
|
quadrantHeader |= 1 << (7 - nLine);
|
|
}
|
|
}
|
|
|
|
if (quadrantHeader == 0x00) {
|
|
addBit(pBits, pBitCount, 0);
|
|
} else {
|
|
addBit(pBits, pBitCount, 1);
|
|
pBytes->push_back(quadrantHeader);
|
|
for (nLine = 0; nLine < 8; nLine++) {
|
|
unsigned char lineByte = lineBytes[nLine];
|
|
if (lineByte != 0x00) {
|
|
pBytes->push_back(lineByte);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void compressBlock(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
int blockX,
|
|
int blockY,
|
|
bool bInvert,
|
|
ByteVec *pBits,
|
|
int *pBitCount,
|
|
ByteVec *pBytes)
|
|
{
|
|
ByteVec bits;
|
|
int bitCount = 0;
|
|
ByteVec bytes;
|
|
|
|
for (int compressionType = 0; compressionType <= 3; compressionType++) {
|
|
bool first = (compressionType == 0);
|
|
ByteVec curBits;
|
|
int curBitCount = 0;
|
|
ByteVec curBytes;
|
|
|
|
compressBlock(bitmap,
|
|
nBitPlane,
|
|
blockX,
|
|
blockY,
|
|
bInvert,
|
|
compressionType,
|
|
&curBits,
|
|
&curBitCount,
|
|
&curBytes);
|
|
|
|
if (first ||
|
|
(curBytes.size() * 8 + curBitCount <
|
|
bytes.size() * 8 + bitCount)) {
|
|
bits.swap(curBits);
|
|
bitCount = curBitCount;
|
|
bytes.swap(curBytes);
|
|
}
|
|
}
|
|
|
|
addBits(pBits, pBitCount, bits, bitCount);
|
|
addBytes(pBytes, bytes);
|
|
}
|
|
|
|
class BlockCoord
|
|
{
|
|
public:
|
|
BlockCoord(int x, int y);
|
|
|
|
bool operator < (const BlockCoord &other) const;
|
|
|
|
int m_x;
|
|
int m_y;
|
|
};
|
|
|
|
BlockCoord::BlockCoord(int x, int y)
|
|
: m_x(x)
|
|
, m_y(y)
|
|
{
|
|
}
|
|
|
|
bool BlockCoord::operator < (const BlockCoord &other) const
|
|
{
|
|
if (m_y != other.m_y) {
|
|
return m_y < other.m_y;
|
|
}
|
|
|
|
if (m_x != other.m_x) {
|
|
return m_x < other.m_x;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int blockCompare(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
const BlockCoord &coord1,
|
|
const BlockCoord &coord2)
|
|
{
|
|
for (int deltaY = 0; deltaY < 16; deltaY++) {
|
|
for (int deltaX = 0; deltaX < 16; deltaX++) {
|
|
int bit1 = bitmap.getBit(coord1.m_x * 16 + deltaX,
|
|
coord1.m_y * 16 + deltaY,
|
|
nBitPlane);
|
|
int bit2 = bitmap.getBit(coord2.m_x * 16 + deltaX,
|
|
coord2.m_y * 16 + deltaY,
|
|
nBitPlane);
|
|
if (bit1 < bit2) {
|
|
return -1;
|
|
}
|
|
if (bit1 > bit2) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
class BlockComp
|
|
{
|
|
public:
|
|
BlockComp(const Bitmap &bitmap, int nBitPlane);
|
|
|
|
bool operator () (const BlockCoord &coord1, const BlockCoord &coord2);
|
|
|
|
private:
|
|
const Bitmap &m_bitmap;
|
|
int m_nBitPlane;
|
|
};
|
|
|
|
BlockComp::BlockComp(const Bitmap &bitmap, int nBitPlane)
|
|
: m_bitmap(bitmap)
|
|
, m_nBitPlane(nBitPlane)
|
|
{
|
|
}
|
|
|
|
bool BlockComp::operator () (const BlockCoord &coord1,
|
|
const BlockCoord &coord2)
|
|
{
|
|
int blockCompResult = blockCompare(m_bitmap, m_nBitPlane, coord1, coord2);
|
|
if (blockCompResult == -1) {
|
|
return true;
|
|
}
|
|
if (blockCompResult == 1) {
|
|
return false;
|
|
}
|
|
|
|
return coord1 < coord2;
|
|
}
|
|
|
|
void computeBlockCopies(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
bool bInvert,
|
|
map<BlockCoord, set<BlockCoord> > *pBlockCopyMap,
|
|
set<BlockCoord> *pBlockCopyDests)
|
|
{
|
|
vector<BlockCoord> sortVec;
|
|
{
|
|
int x, y;
|
|
for (y = 0; y < 25; y++) {
|
|
for (x = 0; x < 40; x++) {
|
|
sortVec.push_back(BlockCoord(x, y));
|
|
}
|
|
}
|
|
}
|
|
|
|
sort(sortVec.begin(), sortVec.end(),
|
|
BlockComp(bitmap, nBitPlane));
|
|
|
|
int bitsSaved = 0;
|
|
|
|
int low = 0;
|
|
int high = 0;
|
|
int sz = sortVec.size();
|
|
while (low < sz) {
|
|
while (high + 1 < sz &&
|
|
blockCompare(bitmap, nBitPlane,
|
|
sortVec[low], sortVec[high + 1]) == 0) {
|
|
high++;
|
|
}
|
|
|
|
if (low < high) {
|
|
int destCount = high - low;
|
|
int x = sortVec[low].m_x;
|
|
int y = sortVec[low].m_y;
|
|
if (!blockIsEmpty(bitmap,
|
|
nBitPlane,
|
|
sortVec[low].m_x,
|
|
sortVec[low].m_y,
|
|
bInvert)) {
|
|
ByteVec bits;
|
|
int bitCount = 0;
|
|
ByteVec bytes;
|
|
compressBlock(bitmap,
|
|
nBitPlane,
|
|
x,
|
|
y,
|
|
bInvert,
|
|
&bits,
|
|
&bitCount,
|
|
&bytes);
|
|
|
|
int sizeWithoutCopy = (bytes.size() * 8 + bitCount) * destCount;
|
|
int sizeWithCopy = (2 + 2 * destCount + 2) * 8 + destCount;
|
|
if (sizeWithCopy < sizeWithoutCopy) {
|
|
bitsSaved += (sizeWithoutCopy - sizeWithCopy);
|
|
set<BlockCoord> &mapSet = (*pBlockCopyMap)[sortVec[low]];
|
|
for (int nDest = low + 1; nDest <= high; nDest++) {
|
|
mapSet.insert(sortVec[nDest]);
|
|
pBlockCopyDests->insert(sortVec[nDest]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
low = high + 1;
|
|
high = low;
|
|
}
|
|
|
|
if (bitsSaved <= 16) {
|
|
pBlockCopyMap->clear();
|
|
pBlockCopyDests->clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool unparseBitmap(const Bitmap &bitmap,
|
|
const Palette &palette,
|
|
ByteVec *pOut,
|
|
string *pMsg)
|
|
{
|
|
string msg;
|
|
if (!pMsg) {
|
|
pMsg = &msg;
|
|
}
|
|
|
|
int bitDepth = bitmap.getBitDepth();
|
|
|
|
vector<ByteVec> unparsedBitPlanes;
|
|
unparsedBitPlanes.resize(bitDepth);
|
|
|
|
int nBitPlane;
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
bool bInvert = false;
|
|
ByteVec bitStream;
|
|
ByteVec byteStream;
|
|
ByteVec blockCopies;
|
|
|
|
for (int invertInt = 0; invertInt <= 1; invertInt++) {
|
|
bool first = (invertInt == 0);
|
|
bool bCurInvert = (invertInt == 1);
|
|
ByteVec curBitStream;
|
|
ByteVec curByteStream;
|
|
ByteVec curBlockCopies;
|
|
|
|
int bitCount = 0;
|
|
|
|
map<BlockCoord, set<BlockCoord> > blockCopyMap;
|
|
set<BlockCoord> blockCopyDests;
|
|
|
|
// Don't use block copies for now because it isn't clear
|
|
// how to do it properly for Bolo.
|
|
if (false) {
|
|
computeBlockCopies(bitmap, nBitPlane, bCurInvert,
|
|
&blockCopyMap, &blockCopyDests);
|
|
}
|
|
|
|
int blockX, blockY;
|
|
for (blockY = 0; blockY < 25; blockY++) {
|
|
bool emptyRow = true;
|
|
for (blockX = 0; blockX < 40; blockX++) {
|
|
if (!blockIsEmpty(bitmap, nBitPlane, blockX, blockY, bCurInvert)) {
|
|
emptyRow = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (emptyRow) {
|
|
addBit(&curBitStream, &bitCount, 0);
|
|
} else {
|
|
addBit(&curBitStream, &bitCount, 1);
|
|
for (blockX = 0; blockX < 40; blockX++) {
|
|
if ((blockCopyDests.find(BlockCoord(blockX, blockY)) !=
|
|
blockCopyDests.end()) ||
|
|
(blockIsEmpty(bitmap,
|
|
nBitPlane,
|
|
blockX,
|
|
blockY,
|
|
bCurInvert))) {
|
|
addBit(&curBitStream, &bitCount, 0);
|
|
} else {
|
|
compressBlock(bitmap, nBitPlane, blockX, blockY, bCurInvert,
|
|
&curBitStream, &bitCount, &curByteStream);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!blockCopyMap.empty()) {
|
|
map<BlockCoord, set<BlockCoord> >::const_iterator sourceIter =
|
|
blockCopyMap.begin();
|
|
map<BlockCoord, set<BlockCoord> >::const_iterator sourceEnd =
|
|
blockCopyMap.end();
|
|
for (; sourceIter != sourceEnd; ++sourceIter) {
|
|
const BlockCoord &source = sourceIter->first;
|
|
const set<BlockCoord> &dests = sourceIter->second;
|
|
|
|
curBlockCopies.push_back((unsigned char)source.m_x);
|
|
curBlockCopies.push_back((unsigned char)source.m_y);
|
|
|
|
set<BlockCoord>::const_iterator destIter = dests.begin();
|
|
set<BlockCoord>::const_iterator destEnd = dests.end();
|
|
for (; destIter != destEnd; ++destIter) {
|
|
const BlockCoord &dest = *destIter;
|
|
|
|
curBlockCopies.push_back((unsigned char)dest.m_x);
|
|
curBlockCopies.push_back((unsigned char)dest.m_y);
|
|
}
|
|
|
|
curBlockCopies.push_back(0xff);
|
|
curBlockCopies.push_back(0xff);
|
|
}
|
|
|
|
curBlockCopies.push_back(0xff);
|
|
curBlockCopies.push_back(0xff);
|
|
}
|
|
|
|
if (first ||
|
|
(curBitStream.size() + curByteStream.size() + curBlockCopies.size() <
|
|
bitStream.size() + byteStream.size() + blockCopies.size())) {
|
|
bInvert = bCurInvert;
|
|
bitStream.swap(curBitStream);
|
|
byteStream.swap(curByteStream);
|
|
blockCopies.swap(curBlockCopies);
|
|
}
|
|
}
|
|
|
|
int bitPlaneSize = 8 + 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2 + 2 + 8 +
|
|
bitStream.size() + byteStream.size() + blockCopies.size();
|
|
|
|
ByteVec &bitPlane = unparsedBitPlanes[nBitPlane];
|
|
bitPlane.resize(bitPlaneSize);
|
|
|
|
int bitPlaneCur = 0;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
bitPlane[bitPlaneCur] = bitPlaneTypeName[i];
|
|
bitPlaneCur++;
|
|
}
|
|
|
|
putInt4(bitPlane.begin() + bitPlaneCur,
|
|
bitPlaneSize - 8 - blockCopies.size());
|
|
bitPlaneCur += 4;
|
|
|
|
putInt2(bitPlane.begin() + bitPlaneCur, 640);
|
|
bitPlaneCur += 2;
|
|
|
|
putInt2(bitPlane.begin() + bitPlaneCur, 400);
|
|
bitPlaneCur += 2;
|
|
|
|
putInt2(bitPlane.begin() + bitPlaneCur, 40);
|
|
bitPlaneCur += 2;
|
|
|
|
putInt2(bitPlane.begin() + bitPlaneCur, 25);
|
|
bitPlaneCur += 2;
|
|
|
|
putInt4(bitPlane.begin() + bitPlaneCur, bitStream.size());
|
|
bitPlaneCur += 4;
|
|
|
|
putInt4(bitPlane.begin() + bitPlaneCur, byteStream.size());
|
|
bitPlaneCur += 4;
|
|
|
|
putInt2(bitPlane.begin() + bitPlaneCur, bInvert ? 0xffff : 0x0000);
|
|
bitPlaneCur += 2;
|
|
|
|
bool bHasBlockCopies = !blockCopies.empty();
|
|
putInt2(bitPlane.begin() + bitPlaneCur, bHasBlockCopies ? 0xdead : 0x0000);
|
|
bitPlaneCur += 2;
|
|
|
|
for (int iUnusedByte = 0; iUnusedByte < 8; iUnusedByte++) {
|
|
bitPlane[bitPlaneCur] = 0x00;
|
|
bitPlaneCur++;
|
|
}
|
|
|
|
copy(bitStream.begin(), bitStream.end(),
|
|
bitPlane.begin() + bitPlaneCur);
|
|
bitPlaneCur += bitStream.size();
|
|
|
|
copy(byteStream.begin(), byteStream.end(),
|
|
bitPlane.begin() + bitPlaneCur);
|
|
bitPlaneCur += byteStream.size();
|
|
|
|
copy(blockCopies.begin(), blockCopies.end(),
|
|
bitPlane.begin() + bitPlaneCur);
|
|
bitPlaneCur += blockCopies.size();
|
|
|
|
if (bitPlaneCur != (int)bitPlane.size()) {
|
|
pMsg->assign("Error computing size of unparsed bit plane.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int totalSize = 2 + (2 * bitDepth) + 2 + 96;
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
totalSize += unparsedBitPlanes[nBitPlane].size();
|
|
}
|
|
|
|
ByteVec out;
|
|
out.resize(totalSize);
|
|
|
|
int cur = 0;
|
|
|
|
putInt2Reversed(out.begin() + cur, bitDepth);
|
|
cur += 2;
|
|
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
putInt2Reversed(out.begin() + cur, unparsedBitPlanes[nBitPlane].size());
|
|
cur += 2;
|
|
}
|
|
|
|
putInt2Reversed(out.begin() + cur, 0x0010);
|
|
cur += 2;
|
|
|
|
if (palette.getNumColors() != 16) {
|
|
pMsg->assign("Palette must have 16 colors.");
|
|
return false;
|
|
}
|
|
|
|
for (int nColor = 0; nColor < 16; nColor++) {
|
|
unsigned int red, green, blue;
|
|
palette.getColor(nColor, &red, &green, &blue);
|
|
|
|
putInt2Reversed(out.begin() + cur, red);
|
|
cur += 2;
|
|
|
|
putInt2Reversed(out.begin() + cur, green);
|
|
cur += 2;
|
|
|
|
putInt2Reversed(out.begin() + cur, blue);
|
|
cur += 2;
|
|
}
|
|
|
|
for (nBitPlane = 0; nBitPlane < bitDepth; nBitPlane++) {
|
|
copy(unparsedBitPlanes[nBitPlane].begin(),
|
|
unparsedBitPlanes[nBitPlane].end(),
|
|
out.begin() + cur);
|
|
cur += unparsedBitPlanes[nBitPlane].size();
|
|
}
|
|
|
|
if (cur != (int)out.size()) {
|
|
pMsg->assign("Error computing size of unparsed bitmap.");
|
|
return false;
|
|
}
|
|
|
|
pOut->clear();
|
|
pOut->resize(out.size());
|
|
copy(out.begin(), out.end(), pOut->begin());
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void toPPM(const Bitmap &bitmap,
|
|
const Palette &palette,
|
|
int nBitPlane,
|
|
ByteVec *pOut)
|
|
{
|
|
pOut->clear();
|
|
|
|
pOut->resize(15 + (640 * 400 * 3));
|
|
|
|
ByteVec &out = *pOut;
|
|
|
|
int cur = 0;
|
|
out[cur++] = 'P';
|
|
out[cur++] = '6';
|
|
out[cur++] = '\n';
|
|
out[cur++] = '6';
|
|
out[cur++] = '4';
|
|
out[cur++] = '0';
|
|
out[cur++] = ' ';
|
|
out[cur++] = '4';
|
|
out[cur++] = '0';
|
|
out[cur++] = '0';
|
|
out[cur++] = '\n';
|
|
out[cur++] = '2';
|
|
out[cur++] = '5';
|
|
out[cur++] = '5';
|
|
out[cur++] = '\n';
|
|
|
|
int x, y;
|
|
for (y = 0; y < 400; y++) {
|
|
for (x = 0; x < 640; x++) {
|
|
int nColor;
|
|
|
|
if (nBitPlane == -1) {
|
|
nColor = bitmap.getPixel(x, y);
|
|
} else {
|
|
nColor = bitmap.getBit(x, y, nBitPlane);
|
|
}
|
|
|
|
unsigned int red, green, blue;
|
|
palette.getColor(nColor, &red, &green, &blue);
|
|
|
|
out[cur++] = (red & 0xff00) >> 8;
|
|
out[cur++] = (green & 0xff00) >> 8;
|
|
out[cur++] = (blue & 0xff00) >> 8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void bitmapToPPM(const Bitmap &bitmap,
|
|
const Palette &palette,
|
|
ByteVec *pOut)
|
|
{
|
|
toPPM(bitmap, palette, -1, pOut);
|
|
}
|
|
|
|
void bitPlaneToPPM(const Bitmap &bitmap,
|
|
int nBitPlane,
|
|
ByteVec *pOut)
|
|
{
|
|
Palette palette;
|
|
palette.setNumColors(2);
|
|
palette.setColor(0, 0x0000, 0x0000, 0x0000);
|
|
palette.setColor(1, 0xffff, 0xffff, 0xffff);
|
|
|
|
toPPM(bitmap, palette, nBitPlane, pOut);
|
|
}
|
|
|
|
}
|