scummvm/engines/cine/gfx.cpp
2008-10-22 20:45:25 +00:00

1758 lines
43 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.
*
* $URL$
* $Id$
*
*/
#include "cine/cine.h"
#include "cine/bg.h"
#include "cine/bg_list.h"
#include "cine/various.h"
#include "cine/pal.h"
#include "common/endian.h"
#include "common/system.h"
#include "common/events.h"
#include "graphics/cursorman.h"
namespace Cine {
byte *collisionPage;
FWRenderer *renderer = NULL;
static const byte mouseCursorNormal[] = {
0x00, 0x00, 0x40, 0x00, 0x60, 0x00, 0x70, 0x00,
0x78, 0x00, 0x7C, 0x00, 0x7E, 0x00, 0x7F, 0x00,
0x7F, 0x80, 0x7C, 0x00, 0x6C, 0x00, 0x46, 0x00,
0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
0xC0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00,
0xFC, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x80,
0xFF, 0xC0, 0xFF, 0xC0, 0xFE, 0x00, 0xFF, 0x00,
0xCF, 0x00, 0x07, 0x80, 0x07, 0x80, 0x03, 0x80
};
static const byte mouseCursorDisk[] = {
0x7F, 0xFC, 0x9F, 0x12, 0x9F, 0x12, 0x9F, 0x12,
0x9F, 0x12, 0x9F, 0xE2, 0x80, 0x02, 0x9F, 0xF2,
0xA0, 0x0A, 0xA0, 0x0A, 0xA0, 0x0A, 0xA0, 0x0A,
0xA0, 0x0A, 0xA0, 0x0A, 0x7F, 0xFC, 0x00, 0x00,
0x7F, 0xFC, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0x7F, 0xFC, 0x00, 0x00
};
static const byte mouseCursorCross[] = {
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7C, 0x7C,
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
0x03, 0x80, 0x03, 0x80, 0xFF, 0xFE, 0xFE, 0xFE,
0xFF, 0xFE, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00
};
static const struct MouseCursor {
int hotspotX;
int hotspotY;
const byte *bitmap;
} mouseCursors[] = {
{ 1, 1, mouseCursorNormal },
{ 0, 0, mouseCursorDisk },
{ 7, 7, mouseCursorCross }
};
static const byte cursorPalette[] = {
0, 0, 0, 0xff,
0xff, 0xff, 0xff, 0xff
};
/*! \brief Initialize renderer
*/
FWRenderer::FWRenderer() : _background(NULL), _palette(NULL), _cmd(""),
_cmdY(0), _messageBg(0), _backBuffer(new byte[_screenSize]),
_activeLowPal(NULL), _changePal(0), _showCollisionPage(false) {
assert(_backBuffer);
memset(_backBuffer, 0, _screenSize);
memset(_bgName, 0, sizeof (_bgName));
}
/* \brief Destroy renderer
*/
FWRenderer::~FWRenderer() {
delete[] _background;
delete[] _palette;
delete[] _backBuffer;
delete[] _activeLowPal;
}
/* \brief Reset renderer state
*/
void FWRenderer::clear() {
delete[] _background;
delete[] _palette;
delete[] _activeLowPal;
_background = NULL;
_palette = NULL;
_activeLowPal = NULL;
memset(_backBuffer, 0, _screenSize);
_cmd = "";
_cmdY = 0;
_messageBg = 0;
_changePal = 0;
_showCollisionPage = false;
}
/*! \brief Draw 1bpp sprite using selected color
* \param obj Object info
* \param fillColor Sprite color
*/
void FWRenderer::fillSprite(const objectStruct &obj, uint8 color) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height;
x = obj.x;
y = obj.y;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
gfxFillSprite(data, width, height, _backBuffer, x, y, color);
}
/*! \brief Draw 1bpp sprite using selected color on background
* \param obj Object info
* \param fillColor Sprite color
*/
void FWRenderer::incrustMask(const objectStruct &obj, uint8 color) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height;
x = obj.x;
y = obj.y;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
gfxFillSprite(data, width, height, _background, x, y, color);
}
/*! \brief Draw color sprite using with external mask
* \param obj Object info
* \param mask External mask
*/
void FWRenderer::drawMaskedSprite(const objectStruct &obj, const byte *mask) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height;
x = obj.x;
y = obj.y;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
assert(mask);
drawSpriteRaw(data, mask, width, height, _backBuffer, x, y);
}
/*! \brief Draw color sprite
* \param obj Object info
*/
void FWRenderer::drawSprite(const objectStruct &obj) {
const byte *mask = animDataTable[obj.frame].mask();
drawMaskedSprite(obj, mask);
}
/*! \brief Draw color sprite on background
* \param obj Object info
*/
void FWRenderer::incrustSprite(const objectStruct &obj) {
const byte *data = animDataTable[obj.frame].data();
const byte *mask = animDataTable[obj.frame].mask();
int x, y, width, height;
x = obj.x;
y = obj.y;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
// There was an assert(mask) here before but it made savegame loading
// in Future Wars sometimes fail the assertion (e.g. see bug #2055912).
// Not drawing sprites that have no mask seems to work, but not sure
// if this is really a correct way to fix this.
if (mask) {
drawSpriteRaw(data, mask, width, height, _background, x, y);
} else { // mask == NULL
warning("FWRenderer::incrustSprite: Skipping maskless sprite (frame=%d)", obj.frame);
}
}
/*! \brief Draw command box on screen
*/
void FWRenderer::drawCommand() {
unsigned int i;
int x = 10, y = _cmdY;
drawPlainBox(x, y, 301, 11, 0);
drawBorder(x - 1, y - 1, 302, 12, 2);
x += 2;
y += 2;
for (i = 0; i < _cmd.size(); i++) {
x = drawChar(_cmd[i], x, y);
}
}
/*! \brief Draw message in a box
* \param str Message to draw
* \param x Top left message box corner coordinate
* \param y Top left message box corner coordinate
* \param width Message box width
* \param color Message box background color (Or if negative draws only the text)
* \note Negative colors are used in Operation Stealth's timed cutscenes
* (e.g. when first meeting The Movement for the Liberation of Santa Paragua).
*/
void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color) {
int i, tx, ty, tw;
int line = 0, words = 0, cw = 0;
int space = 0, extraSpace = 0;
if (color >= 0) {
drawPlainBox(x, y, width, 4, color);
}
tx = x + 4;
ty = str[0] ? y - 5 : y + 4;
tw = width - 8;
for (i = 0; str[i]; i++, line--) {
// Fit line of text into textbox
if (!line) {
while (str[i] == ' ') i++;
line = fitLine(str + i, tw, words, cw);
if ( str[i + line] != '\0' && str[i + line] != 0x7C && words) {
space = (tw - cw) / words;
extraSpace = (tw - cw) % words;
} else {
space = 5;
extraSpace = 0;
}
ty += 9;
if (color >= 0) {
drawPlainBox(x, ty, width, 9, color);
}
tx = x + 4;
}
// draw characters
if (str[i] == ' ') {
tx += space + extraSpace;
if (extraSpace) {
extraSpace = 0;
}
} else {
tx = drawChar(str[i], tx, ty);
}
}
ty += 9;
if (color >= 0) {
drawPlainBox(x, ty, width, 4, color);
drawDoubleBorder(x, y, width, ty - y + 4, 2);
}
}
/*! \brief Draw rectangle on screen
* \param x Top left corner coordinate
* \param y Top left corner coordinate
* \param width Rectangle width (Negative values draw the box horizontally flipped)
* \param height Rectangle height (Negative values draw the box vertically flipped)
* \param color Fill color
* \note An on-screen rectangle's drawn width is always at least one.
* \note An on-screen rectangle's drawn height is always at least one.
*/
void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
// Make width's and height's absolute values at least one
// which forces this function to always draw something if the
// drawing position is inside screen bounds. This fixes at least
// the showing of the oxygen gauge meter in Operation Stealth's
// first arcade sequence where this function is called with a
// height of zero.
if (width == 0) {
width = 1;
}
if (height == 0) {
height = 1;
}
// Handle horizontally flipped boxes
if (width < 0) {
width = ABS(width);
x -= width;
}
// Handle vertically flipped boxes
if (height < 0) {
height = ABS(height);
y -= height;
}
// Clip the rectangle to screen dimensions
Common::Rect boxRect(x, y, x + width, y + height);
Common::Rect screenRect(320, 200);
boxRect.clip(screenRect);
// Draw the filled rectangle
byte *dest = _backBuffer + boxRect.top * 320 + boxRect.left;
for (int i = 0; i < boxRect.height(); i++) {
memset(dest + i * 320, color, boxRect.width());
}
}
/*! \brief Draw empty rectangle
* \param x Top left corner coordinate
* \param y Top left corner coordinate
* \param width Rectangle width
* \param height Rectangle height
* \param color Line color
*/
void FWRenderer::drawBorder(int x, int y, int width, int height, byte color) {
drawLine(x, y, width, 1, color);
drawLine(x, y + height, width, 1, color);
drawLine(x, y, 1, height, color);
drawLine(x + width, y, 1, height + 1, color);
}
/*! \brief Draw empty 2 color rectangle (inner line color is black)
* \param x Top left corner coordinate
* \param y Top left corner coordinate
* \param width Rectangle width
* \param height Rectangle height
* \param color Outter line color
*/
void FWRenderer::drawDoubleBorder(int x, int y, int width, int height, byte color) {
drawBorder(x + 1, y + 1, width - 2, height - 2, 0);
drawBorder(x, y, width, height, color);
}
/*! \brief Draw text character on screen
* \param character Character to draw
* \param x Character coordinate
* \param y Character coordinate
*/
int FWRenderer::drawChar(char character, int x, int y) {
int width, idx;
if (character == ' ') {
x += 5;
} else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
drawSpriteRaw(g_cine->_textHandler.textTable[idx][FONT_DATA], g_cine->_textHandler.textTable[idx][FONT_MASK], FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
x += width + 1;
}
return x;
}
/*! \brief Draw Line
* \param x Line end coordinate
* \param y Line end coordinate
* \param width Horizontal line length
* \param height Vertical line length
* \param color Line color
* \note Either width or height must be equal to 1
*/
void FWRenderer::drawLine(int x, int y, int width, int height, byte color) {
// this line is a special case of rectangle ;-)
drawPlainBox(x, y, width, height, color);
}
/*! \brief Hide invisible parts of the sprite
* \param[in,out] mask Mask to be updated
* \param it Overlay info from overlayList
*/
void FWRenderer::remaskSprite(byte *mask, Common::List<overlay>::iterator it) {
AnimData &sprite = animDataTable[objectTable[it->objIdx].frame];
int x, y, width, height, idx;
int mx, my, mw, mh;
x = objectTable[it->objIdx].x;
y = objectTable[it->objIdx].y;
width = sprite._realWidth;
height = sprite._height;
for (++it; it != overlayList.end(); ++it) {
if (it->type != 5) {
continue;
}
idx = ABS(objectTable[it->objIdx].frame);
mx = objectTable[it->objIdx].x;
my = objectTable[it->objIdx].y;
mw = animDataTable[idx]._realWidth;
mh = animDataTable[idx]._height;
gfxUpdateSpriteMask(mask, x, y, width, height, animDataTable[idx].data(), mx, my, mw, mh);
}
}
/*! \brief Draw background to backbuffer
*/
void FWRenderer::drawBackground() {
assert(_background);
memcpy(_backBuffer, _background, _screenSize);
}
/*! \brief Draw one overlay
* \param it Overlay info
*/
void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
int idx, len, width;
objectStruct *obj;
AnimData *sprite;
byte *mask;
switch (it->type) {
// color sprite
case 0:
if (objectTable[it->objIdx].frame < 0) {
return;
}
sprite = &animDataTable[objectTable[it->objIdx].frame];
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
memcpy(mask, sprite->mask(), len);
remaskSprite(mask, it);
drawMaskedSprite(objectTable[it->objIdx], mask);
delete[] mask;
break;
// game message
case 2:
if (it->objIdx >= messageTable.size()) {
return;
}
_messageLen += messageTable[it->objIdx].size();
drawMessage(messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
waitForPlayerClick = 1;
break;
// action failure message
case 3:
idx = it->objIdx * 4 + g_cine->_rnd.getRandomNumber(3);
len = strlen(failureMessages[idx]);
_messageLen += len;
width = 6 * len + 20;
width = width > 300 ? 300 : width;
drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, 4);
waitForPlayerClick = 1;
break;
// bitmap
case 4:
assert(it->objIdx < NUM_MAX_OBJECT);
obj = &objectTable[it->objIdx];
if (obj->frame < 0) {
return;
}
if (!animDataTable[obj->frame].data()) {
return;
}
fillSprite(*obj);
break;
}
}
/*! \brief Draw overlays
*/
void FWRenderer::drawOverlays() {
Common::List<overlay>::iterator it;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
renderOverlay(it);
}
}
/*! \brief Draw another frame
*/
void FWRenderer::drawFrame() {
drawBackground();
drawOverlays();
if (!_cmd.empty()) {
drawCommand();
}
if (_changePal) {
refreshPalette();
}
blit();
}
/*!
* \brief Turn on or off the showing of the collision page.
* If turned on the blitting routine shows the collision page instead of the back buffer.
* \note Useful for debugging collision page related problems.
*/
void FWRenderer::showCollisionPage(bool state) {
_showCollisionPage = state;
}
/*! \brief Update screen
*/
void FWRenderer::blit() {
// Show the back buffer or the collision page. Normally the back
// buffer but showing the collision page is useful for debugging.
byte *source = (_showCollisionPage ? collisionPage : _backBuffer);
g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
}
/*! \brief Set player command string
* \param cmd New command string
*/
void FWRenderer::setCommand(Common::String cmd) {
_cmd = cmd;
}
/*! \brief Refresh current palette
*/
void FWRenderer::refreshPalette() {
int i;
byte pal[16*4];
assert(_activeLowPal);
for (i = 0; i < 16; i++) {
// This seems to match the output from DOSbox.
pal[i * 4 + 2] = ((_activeLowPal[i] & 0x00f) >> 0) * 32;
pal[i * 4 + 1] = ((_activeLowPal[i] & 0x0f0) >> 4) * 32;
pal[i * 4 + 0] = ((_activeLowPal[i] & 0xf00) >> 8) * 32;
pal[i * 4 + 3] = 0;
}
g_system->setPalette(pal, 0, 16);
_changePal = 0;
}
/*! \brief Load palette of current background
*/
void FWRenderer::reloadPalette() {
assert(_palette);
if (!_activeLowPal) {
_activeLowPal = new uint16[_lowPalSize];
}
assert(_activeLowPal);
memcpy(_activeLowPal, _palette, _lowPalSize * sizeof (uint16));
_changePal = 1;
}
/*! \brief Load background into renderer
* \param bg Raw background data
*/
void FWRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
assert(idx == 0);
int i;
if (!_background) {
_background = new byte[_screenSize];
}
if (!_palette) {
_palette = new uint16[_lowPalSize];
}
assert(_background && _palette);
strcpy(_bgName, name);
for (i = 0; i < _lowPalSize; i++, bg += 2) {
_palette[i] = READ_BE_UINT16(bg);
}
gfxConvertSpriteToRaw(_background, bg, 160, 200);
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::loadCt16(const byte *ct, const char *name) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::loadCt256(const byte *ct, const char *name) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::selectBg(unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::selectScrollBg(unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::setScroll(unsigned int shift) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero.
*/
uint FWRenderer::getScroll() const {
return 0;
}
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::removeBg(unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
fHandle.write(_bgName, 13);
}
const char *FWRenderer::getBgName(uint idx) const {
assert(idx == 0);
return _bgName;
}
/*! \brief Restore active and backup palette from save
* \param fHandle Savefile open for reading
*/
void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
int i;
if (!_palette) {
_palette = new uint16[_lowPalSize];
}
if (!_activeLowPal) {
_activeLowPal = new uint16[_lowPalSize];
}
assert(_palette && _activeLowPal);
for (i = 0; i < _lowPalSize; i++) {
_activeLowPal[i] = fHandle.readUint16BE();
}
for (i = 0; i < _lowPalSize; i++) {
_palette[i] = fHandle.readUint16BE();
}
_changePal = 1;
}
/*! \brief Write active and backup palette to save
* \param fHandle Savefile open for writing
*/
void FWRenderer::savePalette(Common::OutSaveFile &fHandle) {
int i;
assert(_palette && _activeLowPal);
for (i = 0; i < _lowPalSize; i++) {
fHandle.writeUint16BE(_activeLowPal[i]);
}
for (i = 0; i < _lowPalSize; i++) {
fHandle.writeUint16BE(_palette[i]);
}
}
/*! \brief Write active and backup palette to save
* \param fHandle Savefile open for writing
*/
void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
int i;
assert(_activeHiPal);
// Write the active 256 color palette.
for (i = 0; i < _hiPalSize; i++) {
fHandle.writeByte(_activeHiPal[i]);
}
// Write the active 256 color palette a second time.
// FIXME: The backup 256 color palette should be saved here instead of the active one.
for (i = 0; i < _hiPalSize; i++) {
fHandle.writeByte(_activeHiPal[i]);
}
}
/*! \brief Restore active and backup palette from save
* \param fHandle Savefile open for reading
*/
void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
int i;
if (!_activeHiPal) {
_activeHiPal = new byte[_hiPalSize];
}
assert(_activeHiPal);
for (i = 0; i < _hiPalSize; i++) {
_activeHiPal[i] = fHandle.readByte();
}
// Jump over the backup 256 color palette.
// FIXME: Load the backup 256 color palette and use it properly.
fHandle.seek(_hiPalSize, SEEK_CUR);
_changePal = 1;
}
/*! \brief Rotate active palette
* \param a First color to rotate
* \param b Last color to rotate
* \param c Possibly rotation step, must be equal to 1 at the moment
*/
void FWRenderer::rotatePalette(int a, int b, int c) {
palRotate(_activeLowPal, a, b, c);
refreshPalette();
}
/*! \brief Copy part of backup palette to active palette and transform
* \param first First color to transform
* \param last Last color to transform
* \param r Red channel transformation
* \param g Green channel transformation
* \param b Blue channel transformation
*/
void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
if (!_activeLowPal) {
_activeLowPal = new uint16[_lowPalSize];
memset(_activeLowPal, 0, _lowPalSize * sizeof (uint16));
}
transformPaletteRange(_activeLowPal, _palette, first, last, r, g, b);
refreshPalette();
}
/*! \brief Draw menu box, one item per line with possible highlight
* \param items Menu items
* \param height Item count
* \param x Top left menu corner coordinate
* \param y Top left menu corner coordinate
* \param width Menu box width
* \param selected Index of highlighted item (no highlight if less than 0)
*/
void FWRenderer::drawMenu(const CommandeType *items, unsigned int height, int x, int y, int width, int selected) {
int tx, ty, th = height * 9 + 10;
unsigned int i, j;
if (x + width > 319) {
x = 319 - width;
}
if (y + th > 199) {
y = 199 - th;
}
drawPlainBox(x, y, width, 4, _messageBg);
ty = y + 4;
for (i = 0; i < height; i++, ty += 9) {
drawPlainBox(x, ty, width, 9, (int)i == selected ? 0 : _messageBg);
tx = x + 4;
for (j = 0; items[i][j]; j++) {
tx = drawChar(items[i][j], tx, ty);
}
}
drawPlainBox(x, ty, width, 4, _messageBg);
drawDoubleBorder(x, y, width, ty - y + 4, 2);
}
/*! \brief Draw text input box
* \param info Input box message
* \param input Text entered in the input area
* \param cursor Cursor position in the input area
* \param x Top left input box corner coordinate
* \param y Top left input box corner coordinate
* \param width Input box width
*/
void FWRenderer::drawInputBox(const char *info, const char *input, int cursor, int x, int y, int width) {
int i, tx, ty, tw;
int line = 0, words = 0, cw = 0;
int space = 0, extraSpace = 0;
drawPlainBox(x, y, width, 4, _messageBg);
tx = x + 4;
ty = info[0] ? y - 5 : y + 4;
tw = width - 8;
// input box info message
for (i = 0; info[i]; i++, line--) {
// fit line of text
if (!line) {
line = fitLine(info + i, tw, words, cw);
if ( info[i + line] != '\0' && words) {
space = (tw - cw) / words;
extraSpace = (tw - cw) % words;
} else {
space = 5;
extraSpace = 0;
}
ty += 9;
drawPlainBox(x, ty, width, 9, _messageBg);
tx = x + 4;
}
// draw characters
if (info[i] == ' ') {
tx += space + extraSpace;
if (extraSpace) {
extraSpace = 0;
}
} else {
tx = drawChar(info[i], tx, ty);
}
}
// input area background
ty += 9;
drawPlainBox(x, ty, width, 9, _messageBg);
drawPlainBox(x + 16, ty - 1, width - 32, 9, 0);
tx = x + 20;
// text in input area
for (i = 0; input[i]; i++) {
tx = drawChar(input[i], tx, ty);
if (cursor == i + 2) {
drawLine(tx, ty - 1, 1, 9, 2);
}
}
if (!input[0] || cursor == 1) {
drawLine(x + 20, ty - 1, 1, 9, 2);
}
ty += 9;
drawPlainBox(x, ty, width, 4, _messageBg);
drawDoubleBorder(x, y, width, ty - y + 4, 2);
}
/*! \brief Fade to black
*/
void FWRenderer::fadeToBlack() {
// FIXME: _activeLowPal is invalid when starting Operation Stealth
// Adding this sanity check fixes a crash when the game
// starts, but I'm not sure if this is the best place to check it
if (!_activeLowPal) {
warning("_activeLowPal is invalid");
return;
}
assert(_activeLowPal);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 16; j++) {
_activeLowPal[j] = transformColor(_activeLowPal[j], -1, -1, -1);
}
refreshPalette();
g_system->updateScreen();
g_system->delayMillis(50);
}
}
/*! \brief Initialize Operation Stealth renderer
*/
OSRenderer::OSRenderer() : _activeHiPal(NULL), _currentBg(0), _scrollBg(0),
_bgShift(0) {
int i;
for (i = 0; i < 9; i++) {
_bgTable[i].bg = NULL;
_bgTable[i].lowPal = NULL;
_bgTable[i].hiPal = NULL;
memset(_bgTable[i].name, 0, sizeof (_bgTable[i].name));
}
}
/*! \brief Destroy Operation Stealth renderer
*/
OSRenderer::~OSRenderer() {
delete[] _activeHiPal;
for (int i = 0; i < 9; i++) {
delete[] _bgTable[i].bg;
delete[] _bgTable[i].lowPal;
delete[] _bgTable[i].hiPal;
}
}
/*! \brief Reset Operation Stealth renderer state
*/
void OSRenderer::clear() {
delete[] _activeHiPal;
_activeHiPal = NULL;
for (int i = 0; i < 9; i++) {
delete[] _bgTable[i].bg;
delete[] _bgTable[i].lowPal;
delete[] _bgTable[i].hiPal;
_bgTable[i].bg = NULL;
_bgTable[i].lowPal = NULL;
_bgTable[i].hiPal = NULL;
memset(_bgTable[i].name, 0, sizeof (_bgTable[i].name));
}
_currentBg = 0;
_scrollBg = 0;
_bgShift = 0;
FWRenderer::clear();
}
/*! \brief Draw 1bpp sprite using selected color on backgrounds
* \param obj Object info
* \param fillColor Sprite color
*/
void OSRenderer::incrustMask(const objectStruct &obj, uint8 color) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height;
x = obj.x;
y = obj.y;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
if (_bgTable[_currentBg].bg) {
gfxFillSprite(data, width, height, _bgTable[_currentBg].bg, x, y, color);
}
}
/*! \brief Draw color sprite
* \param obj Object info
*/
void OSRenderer::drawSprite(const objectStruct &obj) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height, transColor;
x = obj.x;
y = obj.y;
transColor = obj.part;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
drawSpriteRaw2(data, transColor, width, height, _backBuffer, x, y);
}
/*! \brief Draw color sprite
* \param obj Object info
*/
void OSRenderer::incrustSprite(const objectStruct &obj) {
const byte *data = animDataTable[obj.frame].data();
int x, y, width, height, transColor;
x = obj.x;
y = obj.y;
transColor = obj.part;
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
if (_bgTable[_currentBg].bg) {
drawSpriteRaw2(data, transColor, width, height, _bgTable[_currentBg].bg, x, y);
}
}
/*! \brief Draw text character on screen
* \param character Character to draw
* \param x Character coordinate
* \param y Character coordinate
*/
int OSRenderer::drawChar(char character, int x, int y) {
int width, idx;
if (character == ' ') {
x += 5;
} else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
drawSpriteRaw2(g_cine->_textHandler.textTable[idx][FONT_DATA], 0, FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
x += width + 1;
}
return x;
}
/*! \brief Draw background to backbuffer
*/
void OSRenderer::drawBackground() {
byte *main;
main = _bgTable[_currentBg].bg;
assert(main);
if (!_bgShift) {
memcpy(_backBuffer, main, _screenSize);
} else {
byte *scroll = _bgTable[_scrollBg].bg;
int mainShift = _bgShift * _screenWidth;
int mainSize = _screenSize - mainShift;
assert(scroll);
if (mainSize > 0) { // Just a precaution
memcpy(_backBuffer, main + mainShift, mainSize);
}
if (mainShift > 0) { // Just a precaution
memcpy(_backBuffer + mainSize, scroll, mainShift);
}
}
}
/*! \brief Draw one overlay
* \param it Overlay info
* \todo Add handling of type 22 overlays
*/
void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
int len, idx, width, height;
objectStruct *obj;
AnimData *sprite;
byte *mask;
byte color;
switch (it->type) {
// color sprite
case 0:
if (objectTable[it->objIdx].frame < 0) {
break;
}
sprite = &animDataTable[objectTable[it->objIdx].frame];
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
generateMask(sprite->data(), mask, len, objectTable[it->objIdx].part);
remaskSprite(mask, it);
drawMaskedSprite(objectTable[it->objIdx], mask);
delete[] mask;
break;
// game message
case 2:
if (it->objIdx >= messageTable.size()) {
return;
}
_messageLen += messageTable[it->objIdx].size();
drawMessage(messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
if (it->color >= 0) { // This test isn't in Future Wars's implementation
waitForPlayerClick = 1;
}
break;
// action failure message
case 3:
idx = it->objIdx * 4 + g_cine->_rnd.getRandomNumber(3);
len = strlen(failureMessages[idx]);
_messageLen += len;
width = 6 * len + 20;
width = width > 300 ? 300 : width;
// The used color here differs from Future Wars
drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, _messageBg);
waitForPlayerClick = 1;
break;
// bitmap
case 4:
if (objectTable[it->objIdx].frame >= 0) {
FWRenderer::renderOverlay(it);
}
break;
// masked background
case 20:
assert(it->objIdx < NUM_MAX_OBJECT);
var5 = it->x; // A global variable updated here!
obj = &objectTable[it->objIdx];
sprite = &animDataTable[obj->frame];
if (obj->frame < 0 || it->x < 0 || it->x > 8 || !_bgTable[it->x].bg || sprite->_bpp != 1) {
break;
}
maskBgOverlay(_bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
break;
// FIXME: Implement correct drawing of type 21 overlays.
// Type 21 overlays aren't just filled rectangles, I found their drawing routine
// from Operation Stealth's drawSprite routine. So they're likely some kind of sprites
// and it's just a coincidence that the oxygen meter during the first arcade sequence
// works even somehow currently. I tried the original under DOSBox and the oxygen gauge
// is a long red bar that gets shorter as the air runs out.
case 21:
// A filled rectangle:
case 22:
// TODO: Check it this implementation really works correctly (Some things might be wrong, needs testing).
assert(it->objIdx < NUM_MAX_OBJECT);
obj = &objectTable[it->objIdx];
color = obj->part & 0x0F;
width = obj->frame;
height = obj->costume;
drawPlainBox(obj->x, obj->y, width, height, color);
debug(5, "renderOverlay: type=%d, x=%d, y=%d, width=%d, height=%d, color=%d",
it->type, obj->x, obj->y, width, height, color);
break;
// something else
default:
FWRenderer::renderOverlay(it);
break;
}
}
/*! \brief Refresh current palette
*/
void OSRenderer::refreshPalette() {
if (!_activeHiPal) {
FWRenderer::refreshPalette();
return;
}
int i;
byte pal[256*4];
for (i = 0; i < 256; i++) {
pal[i * 4 + 0] = _activeHiPal[i * 3 + 0];
pal[i * 4 + 1] = _activeHiPal[i * 3 + 1];
pal[i * 4 + 2] = _activeHiPal[i * 3 + 2];
pal[i * 4 + 3] = 0;
}
g_system->setPalette(pal, 0, 256);
_changePal = 0;
}
/*! \brief Load palette of current background
*/
void OSRenderer::reloadPalette() {
// selected background in plane takeoff scene has swapped colors 12
// and 14, shift background has it right
palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
assert(bg->lowPal || bg->hiPal);
if (bg->lowPal) {
if (!_activeLowPal) {
_activeLowPal = new uint16[_lowPalSize];
}
assert(_activeLowPal);
delete[] _activeHiPal;
_activeHiPal = NULL;
memcpy(_activeLowPal, bg->lowPal, _lowPalSize * sizeof (uint16));
} else {
if (!_activeHiPal) {
_activeHiPal = new byte[_hiPalSize];
}
assert(_activeHiPal);
delete[] _activeLowPal;
_activeLowPal = NULL;
memcpy(_activeHiPal, bg->hiPal, _hiPalSize);
}
_changePal = 1;
}
/*! \brief Rotate active palette
* \param a First color to rotate
* \param b Last color to rotate
* \param c Possibly rotation step, must be equal to 1 at the moment
*/
void OSRenderer::rotatePalette(int a, int b, int c) {
if (_activeLowPal) {
FWRenderer::rotatePalette(a, b, c);
return;
}
palRotate(_activeHiPal, a, b, c);
refreshPalette();
}
/*! \brief Copy part of backup palette to active palette and transform
* \param first First color to transform
* \param last Last color to transform
* \param r Red channel transformation
* \param g Green channel transformation
* \param b Blue channel transformation
*/
void OSRenderer::transformPalette(int first, int last, int r, int g, int b) {
palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
if (!bg->lowPal) {
if (!_activeHiPal) {
_activeHiPal = new byte[_hiPalSize];
memset(_activeHiPal, 0, _hiPalSize);
}
delete[] _activeLowPal;
_activeLowPal = NULL;
transformPaletteRange(_activeHiPal, bg->hiPal, first, last, r, g, b);
} else {
if (!_activeLowPal) {
_activeLowPal = new uint16[_lowPalSize];
memset(_activeLowPal, 0, _lowPalSize * sizeof (uint16));
}
delete[] _activeHiPal;
_activeHiPal = NULL;
transformPaletteRange(_activeLowPal, bg->lowPal, first, last, r, g, b);
}
refreshPalette();
}
/*! \brief Load 16 color background into renderer
* \param bg Raw background data
* \param name Background filename
* \param pos Background index
*/
void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
int i;
assert(idx < 9);
if (!_bgTable[idx].bg) {
_bgTable[idx].bg = new byte[_screenSize];
}
if (!_bgTable[idx].lowPal) {
_bgTable[idx].lowPal = new uint16[_lowPalSize];
}
assert(_bgTable[idx].bg && _bgTable[idx].lowPal);
delete[] _bgTable[idx].hiPal;
_bgTable[idx].hiPal = NULL;
strcpy(_bgTable[idx].name, name);
for (i = 0; i < _lowPalSize; i++, bg += 2) {
_bgTable[idx].lowPal[i] = READ_BE_UINT16(bg);
}
gfxConvertSpriteToRaw(_bgTable[idx].bg, bg, 160, 200);
}
/*! \brief Load 16 color CT data as background into renderer
* \param ct Raw CT data
* \param name Background filename
*/
void OSRenderer::loadCt16(const byte *ct, const char *name) {
loadBg16(ct, name, 8);
}
/*! \brief Load 256 color background into renderer
* \param bg Raw background data
* \param name Background filename
* \param pos Background index
*/
void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
assert(idx < 9);
if (!_bgTable[idx].bg) {
_bgTable[idx].bg = new byte[_screenSize];
}
if (!_bgTable[idx].hiPal) {
_bgTable[idx].hiPal = new byte[_hiPalSize];
}
assert(_bgTable[idx].bg && _bgTable[idx].hiPal);
delete[] _bgTable[idx].lowPal;
_bgTable[idx].lowPal = NULL;
strcpy(_bgTable[idx].name, name);
memcpy(_bgTable[idx].hiPal, bg, _hiPalSize);
memcpy(_bgTable[idx].bg, bg + _hiPalSize, _screenSize);
}
/*! \brief Load 256 color CT data as background into renderer
* \param ct Raw CT data
* \param name Background filename
*/
void OSRenderer::loadCt256(const byte *ct, const char *name) {
loadBg256(ct, name, 8);
}
/*! \brief Select active background and load its palette
* \param idx Background index
*/
void OSRenderer::selectBg(unsigned int idx) {
assert(idx < 9 && _bgTable[idx].bg);
assert(_bgTable[idx].lowPal || _bgTable[idx].hiPal);
_currentBg = idx;
reloadPalette();
}
/*! \brief Select scroll background
* \param idx Scroll background index
*/
void OSRenderer::selectScrollBg(unsigned int idx) {
assert(idx < 9);
if (_bgTable[idx].bg) {
_scrollBg = idx;
}
reloadPalette();
}
/*! \brief Set background scroll
* \param shift Background scroll in pixels
*/
void OSRenderer::setScroll(unsigned int shift) {
assert(shift <= 200);
_bgShift = shift;
}
/*! \brief Get background scroll
* \return Background scroll in pixels
*/
uint OSRenderer::getScroll() const {
return _bgShift;
}
/*! \brief Unload background from renderer
* \param idx Background to unload
*/
void OSRenderer::removeBg(unsigned int idx) {
assert(idx > 0 && idx < 9);
if (_currentBg == idx) {
_currentBg = 0;
}
if (_scrollBg == idx) {
_scrollBg = 0;
}
delete[] _bgTable[idx].bg;
delete[] _bgTable[idx].lowPal;
delete[] _bgTable[idx].hiPal;
_bgTable[idx].bg = NULL;
_bgTable[idx].lowPal = NULL;
_bgTable[idx].hiPal = NULL;
memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name));
}
void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
for (int i = 0; i < 8; i++) {
fHandle.write(_bgTable[i].name, 13);
}
}
const char *OSRenderer::getBgName(uint idx) const {
assert(idx < 9);
return _bgTable[idx].name;
}
/*! \brief Fade to black
* \bug Operation Stealth sometimes seems to fade to black using
* transformPalette resulting in double fadeout
*/
void OSRenderer::fadeToBlack() {
if (!_activeHiPal) {
FWRenderer::fadeToBlack();
return;
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < _hiPalSize; j++) {
_activeHiPal[j] = CLIP(_activeHiPal[j] - 32, 0, 255);
}
refreshPalette();
g_system->updateScreen();
g_system->delayMillis(50);
}
}
void setMouseCursor(int cursor) {
static int currentMouseCursor = -1;
assert(cursor >= 0 && cursor < 3);
if (currentMouseCursor != cursor) {
byte mouseCursor[16 * 16];
const MouseCursor *mc = &mouseCursors[cursor];
const byte *src = mc->bitmap;
for (int i = 0; i < 32; ++i) {
int offs = i * 8;
for (byte mask = 0x80; mask != 0; mask >>= 1) {
if (src[0] & mask) {
mouseCursor[offs] = 1;
} else if (src[32] & mask) {
mouseCursor[offs] = 0;
} else {
mouseCursor[offs] = 0xFF;
}
++offs;
}
++src;
}
CursorMan.replaceCursor(mouseCursor, 16, 16, mc->hotspotX, mc->hotspotY);
CursorMan.replaceCursorPalette(cursorPalette, 0, 2);
currentMouseCursor = cursor;
}
}
void gfxFillSprite(const byte *spritePtr, uint16 width, uint16 height, byte *page, int16 x, int16 y, uint8 fillColor) {
int16 i, j;
for (i = 0; i < height; i++) {
byte *destPtr = page + x + y * 320;
destPtr += i * 320;
for (j = 0; j < width; j++) {
if (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200 && !*spritePtr) {
*destPtr = fillColor;
}
destPtr++;
spritePtr++;
}
}
}
void gfxDrawMaskedSprite(const byte *spritePtr, const byte *maskPtr, uint16 width, uint16 height, byte *page, int16 x, int16 y) {
int16 i, j;
for (i = 0; i < height; i++) {
byte *destPtr = page + x + y * 320;
destPtr += i * 320;
for (j = 0; j < width; j++) {
if (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200 && *maskPtr == 0) {
*destPtr = *spritePtr;
}
++destPtr;
++spritePtr;
++maskPtr;
}
}
}
void gfxUpdateSpriteMask(byte *destMask, int16 x, int16 y, int16 width, int16 height, const byte *srcMask, int16 xm, int16 ym, int16 maskWidth, int16 maskHeight) {
int16 i, j, d, spritePitch, maskPitch;
spritePitch = width;
maskPitch = maskWidth;
// crop update area to overlapping parts of masks
if (y > ym) {
d = y - ym;
srcMask += d * maskPitch;
maskHeight -= d;
} else if (y < ym) {
d = ym - y;
destMask += d * spritePitch;
height -= d;
}
if (x > xm) {
d = x - xm;
srcMask += d;
maskWidth -= d;
} else if (x < xm) {
d = xm - x;
destMask += d;
width -= d;
}
// update mask
for (j = 0; j < MIN(maskHeight, height); ++j) {
for (i = 0; i < MIN(maskWidth, width); ++i) {
destMask[i] |= srcMask[i] ^ 1;
}
destMask += spritePitch;
srcMask += maskPitch;
}
}
void gfxUpdateIncrustMask(byte *destMask, int16 x, int16 y, int16 width, int16 height, const byte *srcMask, int16 xm, int16 ym, int16 maskWidth, int16 maskHeight) {
int16 i, j, d, spritePitch, maskPitch;
spritePitch = width;
maskPitch = maskWidth;
// crop update area to overlapping parts of masks
if (y > ym) {
d = y - ym;
srcMask += d * maskPitch;
maskHeight -= d;
} else if (y < ym) {
d = ym - y > height ? height : ym - y;
memset(destMask, 1, d * spritePitch);
destMask += d * spritePitch;
height -= d;
}
if (x > xm) {
d = x - xm;
xm = x;
srcMask += d;
maskWidth -= d;
}
d = xm - x;
maskWidth += d;
// update mask
for (j = 0; j < MIN(maskHeight, height); ++j) {
for (i = 0; i < width; ++i) {
destMask[i] |= i < d || i >= maskWidth ? 1 : srcMask[i - d];
}
destMask += spritePitch;
srcMask += maskPitch;
}
if (j < height) {
memset(destMask, 1, (height - j) * spritePitch);
}
}
void gfxDrawLine(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page) {
if (x1 == x2) {
if (y1 > y2) {
SWAP(y1, y2);
}
while (y1 <= y2) {
*(page + (y1 * 320 + x1)) = color;
y1++;
}
} else {
if (x1 > x2) {
SWAP(x1, x2);
}
while (x1 <= x2) {
*(page + (y1 * 320 + x1)) = color;
x1++;
}
}
}
void gfxDrawPlainBoxRaw(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page) {
int16 t;
if (x1 > x2) {
SWAP(x1, x2);
}
if (y1 > y2) {
SWAP(y1, y2);
}
t = x1;
while (y1 <= y2) {
x1 = t;
while (x1 <= x2) {
*(page + y1 * 320 + x1) = color;
x1++;
}
y1++;
}
}
int16 gfxGetBit(int16 x, int16 y, const byte *ptr, int16 width) {
const byte *ptrToData = (ptr) + y * width + x;
if (x > width) {
return 0;
}
if (*ptrToData) {
return 0;
}
return 1;
}
void gfxResetRawPage(byte *pageRaw) {
memset(pageRaw, 0, 320 * 200);
}
void gfxConvertSpriteToRaw(byte *dst, const byte *src, uint16 w, uint16 h) {
// Output is 4 bits per pixel.
// Pixels are in 16 pixel chunks (8 bytes of source per 16 pixels of output).
// The source data is interleaved so that
// 1st big-endian 16-bit value contains all bit position 0 values for 16 pixels,
// 2nd big-endian 16-bit value contains all bit position 1 values for 16 pixels,
// 3rd big-endian 16-bit value contains all bit position 2 values for 16 pixels,
// 4th big-endian 16-bit value contains all bit position 3 values for 16 pixels.
// 1st pixel's bits are in the 16th bits,
// 2nd pixel's bits are in the 15th bits,
// 3rd pixel's bits are in the 14th bits etc.
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w / 8; ++x) {
for (int bit = 0; bit < 16; ++bit) {
uint8 color = 0;
for (int p = 0; p < 4; ++p) {
if (READ_BE_UINT16(src + p * 2) & (1 << (15 - bit))) {
color |= 1 << p;
}
}
*dst++ = color;
}
src += 8;
}
}
}
void drawSpriteRaw(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y) {
int16 i, j;
// FIXME: Is it a bug if maskPtr == NULL?
if (!maskPtr)
warning("drawSpriteRaw: maskPtr == NULL");
for (i = 0; i < height; i++) {
byte *destPtr = page + x + y * 320;
destPtr += i * 320;
for (j = 0; j < width; j++) {
if ((!maskPtr || !(*maskPtr)) && x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200) {
*(destPtr++) = *(spritePtr++);
} else {
destPtr++;
spritePtr++;
}
if (maskPtr)
maskPtr++;
}
}
}
void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 height, byte *page, int16 x, int16 y) {
int16 i, j;
for (i = 0; i < height; i++) {
byte *destPtr = page + x + y * 320;
destPtr += i * 320;
for (j = 0; j < width; j++) {
if ((*spritePtr != transColor) && (x + j >= 0 && x + j < 320 && i + y >= 0 && i + y < 200)) {
*destPtr = *spritePtr;
}
destPtr++;
spritePtr++;
}
}
}
void maskBgOverlay(const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
byte *page, int16 x, int16 y) {
int16 i, j, tmpWidth, tmpHeight;
Common::List<BGIncrust>::iterator it;
byte *mask;
const byte *backup = maskPtr;
// background pass
for (i = 0; i < height; i++) {
byte *destPtr = page + x + y * 320;
const byte *srcPtr = bgPtr + x + y * 320;
destPtr += i * 320;
srcPtr += i * 320;
for (j = 0; j < width; j++) {
if ((!maskPtr || !(*maskPtr)) && (x + j >= 0
&& x + j < 320 && i + y >= 0 && i + y < 200)) {
*destPtr = *srcPtr;
}
destPtr++;
srcPtr++;
maskPtr++;
}
}
maskPtr = backup;
// incrust pass
for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) {
tmpWidth = animDataTable[it->frame]._realWidth;
tmpHeight = animDataTable[it->frame]._height;
mask = (byte*)malloc(tmpWidth * tmpHeight);
if (it->param == 0) {
generateMask(animDataTable[it->frame].data(), mask, tmpWidth * tmpHeight, it->part);
gfxUpdateIncrustMask(mask, it->x, it->y, tmpWidth, tmpHeight, maskPtr, x, y, width, height);
gfxDrawMaskedSprite(animDataTable[it->frame].data(), mask, tmpWidth, tmpHeight, page, it->x, it->y);
} else {
memcpy(mask, animDataTable[it->frame].data(), tmpWidth * tmpHeight);
gfxUpdateIncrustMask(mask, it->x, it->y, tmpWidth, tmpHeight, maskPtr, x, y, width, height);
gfxFillSprite(mask, tmpWidth, tmpHeight, page, it->x, it->y);
}
free(mask);
}
}
} // End of namespace Cine