/* 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 &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::BitStream32BEMSB &stream, Common::Array &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::BitStream32BEMSB &stream, Common::Array &data, uint bitHeight, uint bitWidth, uint rowBytes) { decodeHuffGraphic(PPIC1Huff, stream, data, bitHeight, bitWidth, rowBytes); } void ImageAsset::decodePPIC2(Common::BitStream32BEMSB &stream, Common::Array &data, uint bitHeight, uint bitWidth, uint rowBytes) { decodeHuffGraphic(PPIC2Huff, stream, data, bitHeight, bitWidth, rowBytes); } void ImageAsset::decodePPIC3(Common::BitStream32BEMSB &stream, Common::Array &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::BitStream32BEMSB &stream, Common::Array &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::BitStream32BEMSB &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 &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 &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 &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 &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