scummvm/engines/macventure/image.cpp

562 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* Based on
* WebVenture (c) 2010, Sean Kasun
* https://github.com/mrkite/webventure, http://seancode.com/webventure/
*
* Used with explicit permission from the author
*/
#include "macventure/image.h"
namespace MacVenture {
static const PPICHuff PPIC1Huff = {
// Masks
{ 0x0000,0x2000,0x4000,0x5000,0x6000,0x7000,0x8000,0x9000,0xa000,
0xb000,0xc000,0xd000,0xd800,0xe000,0xe800,0xf000,0xf800 },
// Lens
{ 3,3,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5 },
// Symbols
{ 0x00,0x0f,0x03,0x05,0x06,0x07,0x08,0x09,0x0a,0x0c,0xff,0x01,
0x02,0x04,0x0b,0x0d,0xe }
};
static const PPICHuff PPIC2Huff = {
// Masks
{ 0x0000,0x4000,0x8000,0xc000,0xc800,0xd000,0xd800,0xe000,0xe800,
0xf000,0xf400,0xf600,0xf800,0xfa00,0xfc00,0xfe00,0xff00 },
// Lens
{ 2,2,2,5,5,5,5,5,5,6,7,7,7,7,7,8,8 },
// Symbols
{ 0xff,0x00,0x0f,0x01,0x03,0x07,0x0e,0x0c,0x08,0x06,0x02,0x04,
0x09,0x0d,0x0b,0x0a,0x05 }
};
// Used to load the huffman table in PPIC3 decoding
static const byte loadBits[] = {
0x08, 0x0f, 0x02, 0xff, 0x00,
0x04, 0xff, 0x01,
0x07, 0x09, 0x08, 0xff, 0x03,
0x04, 0xff, 0x04,
0x0a, 0x07, 0x0a, 0x0b, 0x06, 0xff, 0x05,
0x06, 0x06, 0x0b, 0xff, 0x07,
0x03, 0xff, 0x09,
0x04, 0x03, 0x0e, 0xff, 0x0c,
0x02, 0xff, 0x0d,
0x01, 0xff, 0x0f,
0xff
};
ImageAsset::ImageAsset(ObjID original, Container *container) {
_id = (original * 2);
_mask = (original * 2) + 1;
uint imgRowBytes = 0;
uint imgBitWidth = 0;
uint imgBitHeight = 0;
uint maskRowBytes = 0;
uint maskBitWidth = 0;
uint maskBitHeight = 0;
_container = container;
decodePPIC(_id, _imgData, imgBitHeight, imgBitWidth, imgRowBytes);
_imgRowBytes = imgRowBytes;
_imgBitWidth = imgBitWidth;
_imgBitHeight = imgBitHeight;
if (_container->getItemByteSize(_mask)) {
decodePPIC(_mask, _maskData, maskBitHeight, maskBitWidth, maskRowBytes);
}
_maskRowBytes = maskRowBytes;
_maskBitWidth = maskBitWidth;
_maskBitHeight = maskBitHeight;
}
ImageAsset::~ImageAsset() {
debugC(3, kMVDebugImage, "~ImageAsset(%d)", _id / 2);
}
void ImageAsset::decodePPIC(ObjID id, Common::Array<byte> &data, uint &bitHeight, uint &bitWidth, uint &rowBytes) {
ObjID realID = id;
uint32 size = _container->getItemByteSize(id);
if (size < 2) {
rowBytes = 0;
bitHeight = 0;
bitWidth = 0;
return;
}
if (size == 2) {
Common::SeekableReadStream *newItemStream = _container->getItem(id);
realID = newItemStream->readUint16BE();
delete newItemStream;
}
Common::SeekableReadStream *baseStream = _container->getItem(realID);
Common::BitStream32BEMSB stream(baseStream);
uint8 mode = stream.getBits(3);
int w, h;
if (stream.getBit()) {
h = stream.getBits(10);
} else {
h = stream.getBits(6);
}
if (stream.getBit()) {
w = stream.getBits(10);
} else {
w = stream.getBits(6);
}
rowBytes = ((w + 0xF) >> 3) & 0xFFFE;
bitWidth = w;
bitHeight = h;
for (uint i = 0; i < rowBytes * h; i++) {
data.push_back(0);
}
switch (mode) {
case MacVenture::kPPIC0:
decodePPIC0(stream, data, bitHeight, bitWidth, rowBytes);
break;
case MacVenture::kPPIC1:
decodePPIC1(stream, data, bitHeight, bitWidth, rowBytes);
break;
case MacVenture::kPPIC2:
decodePPIC2(stream, data, bitHeight, bitWidth, rowBytes);
break;
case MacVenture::kPPIC3:
decodePPIC3(stream, data, bitHeight, bitWidth, rowBytes);
break;
}
delete baseStream;
}
void ImageAsset::decodePPIC0(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
uint words = bitWidth >> 4;
uint bytes = bitWidth & 0xF;
uint v = 0;
uint p = 0;
for (uint y = 0; y < bitHeight; y++) {
for (uint x = 0; x < words; x++) {
v = stream.peekBits(32);
stream.skip(16);
v >>= 16 - (stream.pos() % 8);
data[p] = (v >> 8) & 0xff; p++;
data[p] = v & 0xff; p++;
}
if (bytes) {
v = stream.getBits(bytes);
v <<= 16 - bytes;
data[p] = (v >> 8) & 0xff; p++;
data[p] = v & 0xff; p++;
}
}
}
void ImageAsset::decodePPIC1(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
decodeHuffGraphic(PPIC1Huff, stream, data, bitHeight, bitWidth, rowBytes);
}
void ImageAsset::decodePPIC2(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
decodeHuffGraphic(PPIC2Huff, stream, data, bitHeight, bitWidth, rowBytes);
}
void ImageAsset::decodePPIC3(Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
// We need to load the huffman from the PPIC itself
PPICHuff huff;
uint16 v, bits;
uint16 load = 0;
while ((bits = loadBits[load++]) != 0xFF) {
v = stream.getBits(bits);
while ((bits = loadBits[load++]) != 0xFF) {
huff.symbols[loadBits[load++]] = v % bits;
v = (bits != 0) ? (v / bits) : 0;
}
huff.symbols[loadBits[load++]] = v;
}
huff.symbols[0x10] = 0;
for (uint i = 0x10; i > 0; i--) {
for (uint j = i; j <= 0x10; j++) {
if (huff.symbols[j] >= huff.symbols[i - 1]) {
huff.symbols[j]++;
}
}
}
for (int i = 0x10; i >= 0; i--) {
if (huff.symbols[i] == 0x10) {
huff.symbols[i] = 0xff;
break;
}
}
bits = stream.getBits(2) + 1;
uint16 mask = 0;
for (uint i = 0; i < 0xf; i++) {
if (i) {
while (!stream.getBit()) {
bits++;
}
}
huff.lens[i] = bits;
huff.masks[i] = mask;
mask += 1 << (16 - bits);
}
huff.masks[0xf] = mask;
while (mask&(1 << (16 - bits))) {
bits++;
}
huff.masks[0x10] = mask | (1 << (16 - bits));
huff.lens[0xf] = bits;
huff.lens[0x10] = bits;
decodeHuffGraphic(huff, stream, data, bitHeight, bitWidth, rowBytes);
}
void ImageAsset::decodeHuffGraphic(const PPICHuff &huff, Common::BitStream &stream, Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
byte flags = 0;
_walkRepeat = 0;
_walkLast = 0;
if (bitWidth & 3) {
flags = stream.getBits(5);
} else {
flags = stream.getBits(4) << 1;
}
byte odd = 0;
byte blank = bitWidth & 0xf;
if (blank) {
blank >>= 2;
odd = blank & 1;
blank = 2 - (blank >> 1);
}
uint16 pos = 0;
for (uint y = 0; y < bitHeight; y++) {
uint16 x = 0;
for (; x < bitWidth >> 3; x++) {
byte hi = walkHuff(huff, stream) << 4;
data[pos++] = walkHuff(huff, stream) | hi;
}
if (odd) {
data[pos] = walkHuff(huff, stream) << 4;
}
pos += blank;
}
uint16 edge = bitWidth & 3;
if (edge) {
pos = rowBytes - blank;
uint16 bits = 0;
uint16 val = 0;
uint16 v;
for (uint y = 0; y < bitHeight; y++) {
if (flags & 1) {
if (bits < edge) {
v = walkHuff(huff, stream) << 4;
val |= v >> bits;
bits += 4;
}
bits -= edge;
v = val;
val <<= edge;
val &= 0xFF;
} else {
v = stream.getBits(edge);
v <<= 8 - edge;
}
if (odd)
v >>= 4;
data[pos] |= v & 0xff;
pos += rowBytes;
}
}
if (flags & 8) {
pos = 0;
for (uint y = 0; y < bitHeight; y++) {
uint16 v = 0;
if (flags & 2) {
for (uint x = 0; x < rowBytes; x++) {
data[pos] ^= v;
v = data[pos];
pos++;
}
} else {
for (uint x = 0; x < rowBytes; x++) {
uint16 val = data[pos] ^ v;
val ^= (val >> 4) & 0xf;
data[pos] = val;
pos++;
v = (val << 4) & 0xff;
}
}
}
}
if (flags & 4) {
uint16 delta = rowBytes * 4;
if (flags & 2) {
delta *= 2;
}
pos = 0;
uint q = delta;
for (uint i = 0; i < bitHeight * rowBytes - delta; i++) {
data[q] ^= data[pos];
q++;
pos++;
}
}
}
byte ImageAsset::walkHuff(const PPICHuff &huff, Common::BitStream &stream) {
if (_walkRepeat) {
_walkRepeat--;
_walkLast = ((_walkLast << 8) & 0xFF00) | (_walkLast >> 8);
return _walkLast & 0xFF;
}
uint16 dw = stream.peekBits(16);
uint16 i = 0;
for (;i < 16; i++) {
if (huff.masks[i + 1] > dw) {
break;
}
}
stream.skip(huff.lens[i]);
uint8 val = huff.symbols[i];
if (val == 0xFF) {
if (!stream.getBit()) {
_walkLast &= 0xFF;
_walkLast |= _walkLast << 8;
}
_walkRepeat = stream.getBits(3);
if (_walkRepeat < 3) {
_walkRepeat <<= 4;
_walkRepeat |= stream.getBits(4);
if (_walkRepeat < 8) {
_walkRepeat <<= 8;
_walkRepeat |= stream.getBits(8);
}
}
_walkRepeat -= 2;
_walkLast = ((_walkLast << 8) & 0xFF00) | (_walkLast >> 8);
return _walkLast & 0xFF;
} else {
_walkLast <<= 8;
_walkLast |= val;
_walkLast &= 0xFFFF;
}
return val;
}
void ImageAsset::blitInto(Graphics::ManagedSurface *target, int x, int y, BlitMode mode) {
if (mode == kBlitDirect) {
blitDirect(target, x, y, _imgData, _imgBitHeight, _imgBitWidth, _imgRowBytes);
} else if (mode < kBlitXOR) {
if (_container->getItemByteSize(_mask)) { // Has mask
switch (mode) {
case MacVenture::kBlitBIC:
blitBIC(target, x, y, _maskData, _maskBitHeight, _maskBitWidth, _maskRowBytes);
break;
case MacVenture::kBlitOR:
blitOR(target, x, y, _maskData, _maskBitHeight, _maskBitWidth, _maskRowBytes);
break;
default:
break;
}
} else if (_container->getItemByteSize(_id)) {
switch (mode) {
case MacVenture::kBlitBIC:
target->fillRect(Common::Rect(x, y, x + _imgBitWidth, y + _imgBitHeight), kColorWhite);
break;
case MacVenture::kBlitOR:
target->fillRect(Common::Rect(x, y, x + _imgBitWidth, y + _imgBitHeight), kColorBlack);
break;
default:
break;
}
}
if (_container->getItemByteSize(_id) && mode > 0) {
blitXOR(target, x, y, _imgData, _imgBitHeight, _imgBitWidth, _imgRowBytes);
}
}
}
bool ImageAsset::isPointInside(Common::Point point) {
if (point.x >= _maskBitWidth || point.y >= _maskBitHeight) {
return false;
}
if (_maskData.empty()) {
return false;
}
// We see if the point lands on the mask.
uint pix = _maskData[(point.y * _maskRowBytes) + (point.x >> 3)] & (1 << (7 - (point.x & 7)));
return pix != 0;
}
bool ImageAsset::isRectInside(Common::Rect rect) {
if (_maskData.empty()) {
return (rect.width() > 0 && rect.height() > 0);
}
for (int y = rect.top; y < rect.top + rect.height(); y++) {
uint bmpofs = y * _maskRowBytes;
byte pix;
for (int x = rect.left; x < rect.left + rect.width(); x++) {
pix = _maskData[bmpofs + (x >> 3)] & (1 << (7 - (x & 7)));
if (pix) {
return true;
}
}
}
return false;
}
int ImageAsset::getWidth() {
if (_imgData.size() == 0) {
return 0;
}
return MAX(0, (int)_imgBitWidth);
}
int ImageAsset::getHeight() {
if (_imgData.size() == 0) {
return 0;
}
return MAX(0, (int)_imgBitHeight);
}
void ImageAsset::blitDirect(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
uint sx, sy, w, h;
calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h);
for (uint y = 0; y < h; y++) {
uint bmpofs = (y + sy) * rowBytes;
byte pix = 0;
for (uint x = 0; x < w; x++) {
assert(ox + x <= target->w);
assert(oy + y <= target->h);
pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7)));
pix = pix ? kColorBlack : kColorWhite;
*((byte *)target->getBasePtr(ox + x, oy + y)) = pix;
}
}
}
void ImageAsset::blitBIC(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
uint sx, sy, w, h;
calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h);
for (uint y = 0; y < h; y++) {
uint bmpofs = (y + sy) * rowBytes;
byte pix = 0;
for (uint x = 0; x < w; x++) {
assert(ox + x <= target->w);
assert(oy + y <= target->h);
pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7)));
if (pix) {
*((byte *)target->getBasePtr(ox + x, oy + y)) = kColorWhite;
}
}
}
}
void ImageAsset::blitOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
uint sx, sy, w, h;
calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h);
for (uint y = 0; y < h; y++) {
uint bmpofs = (y + sy) * rowBytes;
byte pix = 0;
for (uint x = 0; x < w; x++) {
assert(ox + x <= target->w);
assert(oy + y <= target->h);
pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7)));
if (pix) {
*((byte *)target->getBasePtr(ox + x, oy + y)) = kColorBlack;
}
}
}
}
void ImageAsset::blitXOR(Graphics::ManagedSurface *target, int ox, int oy, const Common::Array<byte> &data, uint bitHeight, uint bitWidth, uint rowBytes) {
uint sx, sy, w, h;
calculateSectionToDraw(target, ox, oy, bitWidth, bitHeight, sx, sy, w, h);
for (uint y = 0; y < h; y++) {
uint bmpofs = (y + sy) * rowBytes;
byte pix = 0;
for (uint x = 0; x < w; x++) {
pix = data[bmpofs + ((x + sx) >> 3)] & (1 << (7 - ((x + sx) & 7)));
if (pix) { // We need to xor
assert(ox + x <= target->w);
assert(oy + y <= target->h);
byte p = *((byte *)target->getBasePtr(ox + x, oy + y));
*((byte *)target->getBasePtr(ox + x, oy + y)) =
(p == kColorWhite) ? kColorBlack : kColorWhite;
}
}
}
}
void ImageAsset::calculateSectionToDraw(Graphics::ManagedSurface *target, int &ox, int &oy, uint bitWidth, uint bitHeight, uint &sx, uint &sy, uint &w, uint &h) {
calculateSectionInDirection(target->w, bitWidth, ox, sx, w);
calculateSectionInDirection(target->h, bitHeight, oy, sy, h);
assert(w <= target->w);
assert((int)w >= 0);
assert(w <= bitWidth);
assert(h <= target->h);
assert((int)h >= 0);
assert(h <= bitHeight);
}
void ImageAsset::calculateSectionInDirection(uint targetWhole, uint originWhole, int &originPosition, uint &startPosition, uint &blittedWhole) {
startPosition = 0;
blittedWhole = originWhole;
if (originPosition < 0) {
if (ABS(originPosition) > (int)blittedWhole) {
blittedWhole = 0;
} else {
blittedWhole -= -originPosition;
}
startPosition = -originPosition;
originPosition = 0;
}
if (originPosition + blittedWhole > targetWhole) {
if (originPosition > (int)targetWhole) {
blittedWhole = 0;
} else {
blittedWhole = targetWhole - originPosition;
}
}
}
} // End of namespace MacVenture