mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-03 23:52:41 +00:00
562 lines
15 KiB
C++
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
|