aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/gfx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/scumm/gfx.cpp')
-rw-r--r--engines/scumm/gfx.cpp3307
1 files changed, 3307 insertions, 0 deletions
diff --git a/engines/scumm/gfx.cpp b/engines/scumm/gfx.cpp
new file mode 100644
index 0000000000..dc476a8f0b
--- /dev/null
+++ b/engines/scumm/gfx.cpp
@@ -0,0 +1,3307 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001 Ludvig Strigeus
+ * Copyright (C) 2001-2006 The ScummVM project
+ *
+ * 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 "common/stdafx.h"
+#include "common/system.h"
+#include "scumm/scumm.h"
+#include "scumm/actor.h"
+#include "scumm/charset.h"
+#include "scumm/intern.h"
+#ifndef DISABLE_HE
+#include "scumm/intern_he.h"
+#endif
+#include "scumm/resource.h"
+#include "scumm/usage_bits.h"
+#include "scumm/wiz_he.h"
+
+namespace Scumm {
+
+static void blit(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h);
+static void fill(byte *dst, int dstPitch, byte color, int w, int h);
+
+static void copy8Col(byte *dst, int dstPitch, const byte *src, int height);
+static void clear8Col(byte *dst, int dstPitch, int height);
+
+
+struct StripTable {
+ int offsets[160];
+ int run[160];
+ int color[160];
+ int zoffsets[120]; // FIXME: Why only 120 here?
+ int zrun[120]; // FIXME: Why only 120 here?
+};
+
+enum {
+ kScrolltime = 500, // ms scrolling is supposed to take
+ kPictureDelay = 20
+};
+
+#define NUM_SHAKE_POSITIONS 8
+static const int8 shake_positions[NUM_SHAKE_POSITIONS] = {
+ 0, 1 * 2, 2 * 2, 1 * 2, 0 * 2, 2 * 2, 3 * 2, 1 * 2
+};
+
+/**
+ * The following structs define four basic fades/transitions used by
+ * transitionEffect(), each looking differently to the user.
+ * Note that the stripTables contain strip numbers, and they assume
+ * that the screen has 40 vertical strips (i.e. 320 pixel), and 25 horizontal
+ * strips (i.e. 200 pixel). There is a hack in transitionEffect that
+ * makes it work correctly in games which have a different screen height
+ * (for example, 240 pixel), but nothing is done regarding the width, so this
+ * code won't work correctly in COMI. Also, the number of iteration depends
+ * on min(vertStrips, horizStrips}. So the 13 is derived from 25/2, rounded up.
+ * And the 25 = min(25,40). Hence for Zak256 instead of 13 and 25, the values
+ * 15 and 30 should be used, and for COMI probably 30 and 60.
+ */
+struct TransitionEffect {
+ byte numOfIterations;
+ int8 deltaTable[16]; // four times l / t / r / b
+ byte stripTable[16]; // ditto
+};
+
+#ifdef PALMOS_68K
+static const TransitionEffect *transitionEffects;
+#else
+static const TransitionEffect transitionEffects[6] = {
+ // Iris effect (looks like an opening/closing camera iris)
+ {
+ 13, // Number of iterations
+ {
+ 1, 1, -1, 1,
+ -1, 1, -1, -1,
+ 1, -1, -1, -1,
+ 1, 1, 1, -1
+ },
+ {
+ 0, 0, 39, 0,
+ 39, 0, 39, 24,
+ 0, 24, 39, 24,
+ 0, 0, 0, 24
+ }
+ },
+
+ // Box wipe (a box expands from the upper-left corner to the lower-right corner)
+ {
+ 25, // Number of iterations
+ {
+ 0, 1, 2, 1,
+ 2, 0, 2, 1,
+ 2, 0, 2, 1,
+ 0, 0, 0, 0
+ },
+ {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 255, 0, 0, 0
+ }
+ },
+
+ // Box wipe (a box expands from the lower-right corner to the upper-left corner)
+ {
+ 25, // Number of iterations
+ {
+ -2, -1, 0, -1,
+ -2, -1, -2, 0,
+ -2, -1, -2, 0,
+ 0, 0, 0, 0
+ },
+ {
+ 39, 24, 39, 24,
+ 39, 24, 39, 24,
+ 38, 24, 38, 24,
+ 255, 0, 0, 0
+ }
+ },
+
+ // Inverse box wipe
+ {
+ 25, // Number of iterations
+ {
+ 0, -1, -2, -1,
+ -2, 0, -2, -1,
+ -2, 0, -2, -1,
+ 0, 0, 0, 0
+ },
+ {
+ 0, 24, 39, 24,
+ 39, 0, 39, 24,
+ 38, 0, 38, 24,
+ 255, 0, 0, 0
+ }
+ },
+
+ // Inverse iris effect, specially tailored for V1/V2 games
+ {
+ 9, // Number of iterations
+ {
+ -1, -1, 1, -1,
+ -1, 1, 1, 1,
+ -1, -1, -1, 1,
+ 1, -1, 1, 1
+ },
+ {
+ 7, 7, 32, 7,
+ 7, 8, 32, 8,
+ 7, 8, 7, 8,
+ 32, 7, 32, 8
+ }
+ },
+
+ // Horizontal wipe (a box expands from left to right side). For MM NES
+ {
+ 16, // Number of iterations
+ {
+ 2, 0, 2, 0,
+ 2, 0, 2, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0
+ },
+ {
+ 0, 0, 0, 15,
+ 1, 0, 1, 15,
+ 255, 0, 0, 0,
+ 255, 0, 0, 0
+ }
+ }
+
+};
+#endif
+
+
+Gdi::Gdi(ScummEngine *vm) {
+ memset(this, 0, sizeof(*this));
+ _vm = vm;
+ _paletteMod = 0;
+ _roomPalette = vm->_roomPalette;
+ _roomStrips = 0;
+}
+
+Gdi::~Gdi() {
+ free(_roomStrips);
+}
+
+void Gdi::init() {
+ _numStrips = _vm->_screenWidth / 8;
+
+ // Increase the number of screen strips by one; needed for smooth scrolling
+ if (_vm->_version >= 7) {
+ // We now have mostly working smooth scrolling code in place for V7+ games
+ // (i.e. The Dig, Full Throttle and COMI). It seems to work very well so far.
+ // One area which still may need some work are the AKOS codecs (except for
+ // codec 1, which I already updated): their masking code may need adjustments,
+ // similar to the treatment codec 1 received.
+ //
+ // To understand how we achieve smooth scrolling, first note that with it, the
+ // virtual screen strips don't match the display screen strips anymore. To
+ // overcome that problem, we simply use a screen pitch that is 8 pixel wider
+ // than the actual screen width, and always draw one strip more than needed to
+ // the backbuf (of course we have to treat the right border seperately). This
+ _numStrips += 1;
+ }
+}
+
+void Gdi::roomChanged(byte *roomptr, uint32 IM00_offs, byte transparentColor) {
+ if (_vm->_version == 1) {
+ if (_vm->_platform == Common::kPlatformNES) {
+ decodeNESGfx(roomptr);
+ } else {
+ for (int i = 0; i < 4; i++){
+ _C64.colors[i] = roomptr[6 + i];
+ }
+ decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 10), _C64.charMap, 2048);
+ decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 12), _C64.picMap, roomptr[4] * roomptr[5]);
+ decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 14), _C64.colorMap, roomptr[4] * roomptr[5]);
+ decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 16), _C64.maskMap, roomptr[4] * roomptr[5]);
+ decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 18) + 2, _C64.maskChar, READ_LE_UINT16(roomptr + READ_LE_UINT16(roomptr + 18)));
+ _objectMode = true;
+ }
+ } else if (_vm->_version == 2) {
+ _roomStrips = generateStripTable(roomptr + IM00_offs, _vm->_roomWidth, _vm->_roomHeight, _roomStrips);
+ }
+
+ _transparentColor = transparentColor;
+}
+
+
+#pragma mark -
+#pragma mark --- Virtual Screens ---
+#pragma mark -
+
+
+void ScummEngine::initScreens(int b, int h) {
+ int i;
+ int adj = 0;
+
+ for (i = 0; i < 3; i++) {
+ res.nukeResource(rtBuffer, i + 1);
+ res.nukeResource(rtBuffer, i + 5);
+ }
+
+ if (!getResourceAddress(rtBuffer, 4)) {
+ // Since the size of screen 3 is fixed, there is no need to reallocate
+ // it if its size changed.
+ // Not sure what it is good for, though. I think it may have been used
+ // in pre-V7 for the games messages (like 'Pause', Yes/No dialogs,
+ // version display, etc.). I don't know about V7, maybe the same is the
+ // case there. If so, we could probably just remove it completely.
+ if (_version >= 7) {
+ initVirtScreen(kUnkVirtScreen, (_screenHeight / 2) - 10, _screenWidth, 13, false, false);
+ } else {
+ initVirtScreen(kUnkVirtScreen, 80, _screenWidth, 13, false, false);
+ }
+ }
+
+ if ((_platform == Common::kPlatformNES) && (h != _screenHeight)) {
+ // This is a hack to shift the whole screen downwards to match the original.
+ // Otherwise we would have to do lots of coordinate adjustments all over
+ // the code.
+ adj = 16;
+ initVirtScreen(kUnkVirtScreen, 0, _screenWidth, adj, false, false);
+ }
+
+ initVirtScreen(kMainVirtScreen, b + adj, _screenWidth, h - b, true, true);
+ initVirtScreen(kTextVirtScreen, adj, _screenWidth, b, false, false);
+ initVirtScreen(kVerbVirtScreen, h + adj, _screenWidth, _screenHeight - h - adj, false, false);
+ _screenB = b;
+ _screenH = h;
+
+ gdi.init();
+}
+
+void ScummEngine::initVirtScreen(VirtScreenNumber slot, int top, int width, int height, bool twobufs,
+ bool scrollable) {
+ VirtScreen *vs = &virtscr[slot];
+ int size;
+
+ assert(height >= 0);
+ assert(slot >= 0 && slot < 4);
+
+ if (_version >= 7) {
+ if (slot == kMainVirtScreen && (_roomHeight != 0))
+ height = _roomHeight;
+ }
+
+ vs->number = slot;
+ vs->w = width;
+ vs->topline = top;
+ vs->h = height;
+ vs->hasTwoBuffers = twobufs;
+ vs->xstart = 0;
+ vs->backBuf = NULL;
+ vs->bytesPerPixel = 1;
+ vs->pitch = width;
+
+ if (_version >= 7) {
+ // Increase the pitch by one; needed to accomodate the extra
+ // screen strip which we use to implement smooth scrolling.
+ // See Gdi::init()
+ vs->pitch += 8;
+ }
+
+ size = vs->pitch * vs->h;
+ if (scrollable) {
+ // Allow enough spaces so that rooms can be up to 4 resp. 8 screens
+ // wide. To achieve (horizontal!) scrolling, we use a neat trick:
+ // only the offset into the screen buffer (xstart) is changed. That way
+ // very little of the screen has to be redrawn, and we have a very low
+ // memory overhead (namely for every pixel we want to scroll, we need
+ // one additional byte in the buffer).
+ if (_version >= 7) {
+ size += vs->pitch * 8;
+ } else {
+ size += vs->pitch * 4;
+ }
+ }
+
+ res.createResource(rtBuffer, slot + 1, size);
+ vs->pixels = getResourceAddress(rtBuffer, slot + 1);
+ memset(vs->pixels, 0, size); // reset background
+
+ if (twobufs) {
+ vs->backBuf = res.createResource(rtBuffer, slot + 5, size);
+ }
+
+ if (slot != 3) {
+ vs->setDirtyRange(0, height);
+ }
+}
+
+VirtScreen *ScummEngine::findVirtScreen(int y) {
+ VirtScreen *vs = virtscr;
+ int i;
+
+ for (i = 0; i < 3; i++, vs++) {
+ if (y >= vs->topline && y < vs->topline + vs->h) {
+ return vs;
+ }
+ }
+ return NULL;
+}
+
+void ScummEngine::markRectAsDirty(VirtScreenNumber virt, int left, int right, int top, int bottom, int dirtybit) {
+ VirtScreen *vs = &virtscr[virt];
+ int lp, rp;
+
+ if (left > right || top > bottom)
+ return;
+ if (top > vs->h || bottom < 0)
+ return;
+
+ if (top < 0)
+ top = 0;
+ if (bottom > vs->h)
+ bottom = vs->h;
+
+ if (virt == kMainVirtScreen && dirtybit) {
+
+ lp = left / 8 + _screenStartStrip;
+ if (lp < 0)
+ lp = 0;
+
+ rp = (right + vs->xstart) / 8;
+ if (_version >= 7) {
+ if (rp > 409)
+ rp = 409;
+ } else {
+ if (rp >= 200)
+ rp = 200;
+ }
+ for (; lp <= rp; lp++)
+ setGfxUsageBit(lp, dirtybit);
+ }
+
+ // The following code used to be in the separate method setVirtscreenDirty
+ lp = left / 8;
+ rp = right / 8;
+
+ if ((lp >= gdi._numStrips) || (rp < 0))
+ return;
+ if (lp < 0)
+ lp = 0;
+ if (rp >= gdi._numStrips)
+ rp = gdi._numStrips - 1;
+
+ while (lp <= rp) {
+ if (top < vs->tdirty[lp])
+ vs->tdirty[lp] = top;
+ if (bottom > vs->bdirty[lp])
+ vs->bdirty[lp] = bottom;
+ lp++;
+ }
+}
+
+/**
+ * Update all dirty screen areas. This method blits all of the internal engine
+ * graphics to the actual display, as needed. In addition, the 'shaking'
+ * code in the backend is controlled from here.
+ */
+void ScummEngine::drawDirtyScreenParts() {
+ // Update verbs
+ updateDirtyScreen(kVerbVirtScreen);
+
+ // Update the conversation area (at the top of the screen)
+ updateDirtyScreen(kTextVirtScreen);
+
+ // Update game area ("stage")
+ if (camera._last.x != camera._cur.x || (_features & GF_NEW_CAMERA && (camera._cur.y != camera._last.y))) {
+ // Camera moved: redraw everything
+ VirtScreen *vs = &virtscr[kMainVirtScreen];
+ drawStripToScreen(vs, 0, vs->w, 0, vs->h);
+ vs->setDirtyRange(vs->h, 0);
+ } else {
+ updateDirtyScreen(kMainVirtScreen);
+ }
+
+ // Handle shaking
+ if (_shakeEnabled) {
+ _shakeFrame = (_shakeFrame + 1) % NUM_SHAKE_POSITIONS;
+ _system->setShakePos(shake_positions[_shakeFrame]);
+ } else if (!_shakeEnabled &&_shakeFrame != 0) {
+ _shakeFrame = 0;
+ _system->setShakePos(0);
+ }
+}
+
+void ScummEngine_v6::drawDirtyScreenParts() {
+ // For the Full Throttle credits to work properly, the blast
+ // texts have to be drawn before the blast objects. Unless
+ // someone can think of a better way to achieve this effect.
+
+ if (_version >= 7 && VAR(VAR_BLAST_ABOVE_TEXT) == 1) {
+ drawBlastTexts();
+ drawBlastObjects();
+ } else {
+ drawBlastObjects();
+ drawBlastTexts();
+ }
+ if (_version == 8)
+ processUpperActors();
+
+ // Call the original method.
+ ScummEngine::drawDirtyScreenParts();
+
+ // Remove all blasted objects/text again.
+ removeBlastTexts();
+ removeBlastObjects();
+}
+
+/**
+ * Blit the dirty data from the given VirtScreen to the display. If the camera moved,
+ * a full blit is done, otherwise only the visible dirty areas are updated.
+ */
+void ScummEngine::updateDirtyScreen(VirtScreenNumber slot) {
+ VirtScreen *vs = &virtscr[slot];
+
+ // Do nothing for unused virtual screens
+ if (vs->h == 0)
+ return;
+
+ int i;
+ int w = 8;
+ int start = 0;
+
+ for (i = 0; i < gdi._numStrips; i++) {
+ if (vs->bdirty[i]) {
+ const int top = vs->tdirty[i];
+ const int bottom = vs->bdirty[i];
+ vs->tdirty[i] = vs->h;
+ vs->bdirty[i] = 0;
+ if (i != (gdi._numStrips - 1) && vs->bdirty[i + 1] == bottom && vs->tdirty[i + 1] == top) {
+ // Simple optimizations: if two or more neighbouring strips
+ // form one bigger rectangle, coalesce them.
+ w += 8;
+ continue;
+ }
+ drawStripToScreen(vs, start * 8, w, top, bottom);
+ w = 8;
+ }
+ start = i + 1;
+ }
+}
+
+/**
+ * Blit the specified rectangle from the given virtual screen to the display.
+ * Note: t and b are in *virtual screen* coordinates, while x is relative to
+ * the *real screen*. This is due to the way tdirty/vdirty work: they are
+ * arrays which map 'strips' (sections of the real screen) to dirty areas as
+ * specified by top/bottom coordinate in the virtual screen.
+ */
+void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, int bottom) {
+
+ if (bottom <= top)
+ return;
+
+ if (top >= vs->h)
+ return;
+
+ assert(top >= 0 && bottom <= vs->h); // Paranoia checks
+ assert(x >= 0 && width <= vs->pitch);
+ assert(_charset->_textSurface.pixels);
+ assert(_compositeBuf);
+
+ if (width > vs->w - x)
+ width = vs->w - x;
+
+ // Clip to the visible part of the scene
+ if (top < _screenTop)
+ top = _screenTop;
+ if (bottom > _screenTop + _screenHeight)
+ bottom = _screenTop + _screenHeight;
+
+ // Convert the vertical coordinates to real screen coords
+ int y = vs->topline + top - _screenTop;
+ int height = bottom - top;
+
+ if (height <= 0 || width <= 0)
+ return;
+
+ // Compute screen etc. buffer pointers
+ const byte *src = vs->getPixels(x, top);
+ byte *dst = _compositeBuf + x + y * _screenWidth;
+
+ if (_version < 7) {
+ // Handle the text mask in older games; newer (V7/V8) games do not use it anymore.
+ const byte *text = (byte *)_charset->_textSurface.pixels + x + y * _charset->_textSurface.pitch;
+
+ // Compose the text over the game graphics
+ for (int h = 0; h < height; ++h) {
+ for (int w = 0; w < width; ++w) {
+ if (text[w] == CHARSET_MASK_TRANSPARENCY)
+ dst[w] = src[w];
+ else
+ dst[w] = text[w];
+ }
+ src += vs->pitch;
+ dst += _screenWidth;
+ text += _charset->_textSurface.pitch;
+ }
+ } else {
+ // Just do a simple blit in V7/V8 games.
+ blit(dst, _screenWidth, src, vs->pitch, width, height);
+ }
+
+ if (_renderMode == Common::kRenderCGA)
+ ditherCGA(_compositeBuf + x + y * _screenWidth, _screenWidth, x, y, width, height);
+
+ if (_renderMode == Common::kRenderHercA || _renderMode == Common::kRenderHercG) {
+ ditherHerc(_compositeBuf + x + y * _screenWidth, _herculesBuf, _screenWidth, &x, &y, &width, &height);
+ // center image on the screen
+ _system->copyRectToScreen(_herculesBuf + x + y * Common::kHercW,
+ Common::kHercW, x + (Common::kHercW - _screenWidth * 2) / 2, y, width, height);
+ } else {
+ // Finally blit the whole thing to the screen
+ int x1 = x;
+
+ // HACK: This is dirty hack which renders narrow NES rooms centered
+ // NES can address negative number strips and that poses problem for
+ // our code. So instead adding zillions of fixes and potentially break
+ // other games we shift it right on rendering stage
+ if ((_platform == Common::kPlatformNES) && (((_NESStartStrip > 0) && (vs->number == kMainVirtScreen)) || (vs->number == kTextVirtScreen))) {
+ x += 16;
+ while (x + width >= _screenWidth)
+ width -= 16;
+ if (width < 0)
+ return;
+ }
+
+ _system->copyRectToScreen(_compositeBuf + x1 + y * _screenWidth, _screenWidth, x, y, width, height);
+ }
+}
+
+// CGA
+// indy3 loom maniac monkey1 zak
+//
+// Herc (720x350)
+// maniac monkey1 zak
+//
+// EGA
+// monkey2 loom maniac monkey1 atlantis indy3 zak loomcd
+
+// CGA dithers 4x4 square with direct substitutes
+// Odd lines have colors swapped, so there will be checkered patterns.
+// But apparently there is a mistake for 10th color.
+void ScummEngine::ditherCGA(byte *dst, int dstPitch, int x, int y, int width, int height) const {
+ byte *ptr;
+ int idx1, idx2;
+ static const byte cgaDither[2][2][16] = {
+ {{0, 1, 0, 1, 2, 2, 0, 0, 3, 1, 3, 1, 3, 2, 1, 3},
+ {0, 0, 1, 1, 0, 2, 2, 3, 0, 3, 1, 1, 3, 3, 1, 3}},
+ {{0, 0, 1, 1, 0, 2, 2, 3, 0, 3, 1, 1, 3, 3, 1, 3},
+ {0, 1, 0, 1, 2, 2, 0, 0, 3, 1, 1, 1, 3, 2, 1, 3}}};
+
+ for (int y1 = 0; y1 < height; y1++) {
+ ptr = dst + y1 * dstPitch;
+
+ idx1 = (y + y1) % 2;
+
+ if (_version == 2)
+ idx1 = 0;
+
+ for (int x1 = 0; x1 < width; x1++) {
+ idx2 = (x + x1) % 2;
+ *ptr = cgaDither[idx1][idx2][*ptr & 0xF];
+ ptr++;
+ }
+ }
+}
+
+// Hercules dithering. It uses same dithering tables but output is 1bpp and
+// it stretches in this way:
+// aaaa0
+// aa aaaa1
+// bb bbbb0 Here 0 and 1 mean dithering table row number
+// cc --> bbbb1
+// dd cccc0
+// cccc1
+// dddd0
+void ScummEngine::ditherHerc(byte *src, byte *hercbuf, int srcPitch, int *x, int *y, int *width, int *height) const {
+ byte *srcptr, *dstptr;
+ int xo = *x, yo = *y, widtho = *width, heighto = *height;
+ int idx1, idx2, dsty = 0, y1;
+ static const byte cgaDither[2][2][16] = {
+ {{0, 1, 0, 1, 2, 2, 0, 0, 3, 1, 3, 1, 3, 2, 1, 3},
+ {0, 0, 1, 1, 0, 2, 2, 3, 0, 3, 1, 1, 3, 3, 1, 3}},
+ {{0, 0, 1, 1, 0, 2, 2, 3, 0, 3, 1, 1, 3, 3, 1, 3},
+ {0, 1, 0, 1, 2, 2, 0, 0, 3, 1, 1, 1, 3, 2, 1, 3}}};
+
+ // calculate dsty
+ for (y1 = 0; y1 < yo; y1++) {
+ dsty += 2;
+ if (y1 % 4 == 3)
+ dsty--;
+ }
+ *y = dsty;
+ *x *= 2;
+ *width *= 2;
+ *height = 0;
+
+ for (y1 = 0; y1 < heighto;) {
+ srcptr = src + y1 * srcPitch;
+ dstptr = hercbuf + dsty * Common::kHercW + xo * 2;
+
+ assert(dstptr < hercbuf + Common::kHercW * Common::kHercH + widtho * 2);
+
+ idx1 = (dsty % 7) % 2;
+ for (int x1 = 0; x1 < widtho; x1++) {
+ idx2 = (xo + x1) % 2;
+ *dstptr++ = cgaDither[idx1][idx2][*srcptr & 0xF] >> 1;
+ *dstptr++ = cgaDither[idx1][idx2][*srcptr & 0xF] & 0x1;
+ srcptr++;
+ }
+ if (idx1 || dsty % 7 == 6)
+ y1++;
+ dsty++;
+ (*height)++;
+ }
+}
+
+
+#pragma mark -
+#pragma mark --- Background buffers & charset mask ---
+#pragma mark -
+
+
+void ScummEngine::initBGBuffers(int height) {
+ const byte *ptr;
+ int size, itemsize, i;
+ byte *room;
+
+ if (_version >= 7) {
+ // Resize main virtual screen in V7 games. This is necessary
+ // because in V7, rooms may be higher than one screen, so we have
+ // to accomodate for that.
+ initVirtScreen(kMainVirtScreen, virtscr[0].topline, _screenWidth, height, true, true);
+ }
+
+ if (_heversion >= 70)
+ room = getResourceAddress(rtRoomImage, _roomResource);
+ else
+ room = getResourceAddress(rtRoom, _roomResource);
+
+ if (_version <= 3) {
+ gdi._numZBuffer = 2;
+ } else if (_features & GF_SMALL_HEADER) {
+ int off;
+ ptr = findResourceData(MKID('SMAP'), room);
+ gdi._numZBuffer = 0;
+
+ if (_features & GF_16COLOR)
+ off = READ_LE_UINT16(ptr);
+ else
+ off = READ_LE_UINT32(ptr);
+
+ while (off && gdi._numZBuffer < 4) {
+ gdi._numZBuffer++;
+ ptr += off;
+ off = READ_LE_UINT16(ptr);
+ }
+ } else if (_version == 8) {
+ // in V8 there is no RMIH and num z buffers is in RMHD
+ ptr = findResource(MKID('RMHD'), room);
+ gdi._numZBuffer = READ_LE_UINT32(ptr + 24) + 1;
+ } else if (_heversion >= 70) {
+ ptr = findResource(MKID('RMIH'), room);
+ gdi._numZBuffer = READ_LE_UINT16(ptr + 8) + 1;
+ } else {
+ ptr = findResource(MKID('RMIH'), findResource(MKID('RMIM'), room));
+ gdi._numZBuffer = READ_LE_UINT16(ptr + 8) + 1;
+ }
+ assert(gdi._numZBuffer >= 1 && gdi._numZBuffer <= 8);
+
+ if (_version >= 7)
+ itemsize = (_roomHeight + 10) * gdi._numStrips;
+ else
+ itemsize = (_roomHeight + 4) * gdi._numStrips;
+
+
+ size = itemsize * gdi._numZBuffer;
+ memset(res.createResource(rtBuffer, 9, size), 0, size);
+
+ for (i = 0; i < (int)ARRAYSIZE(gdi._imgBufOffs); i++) {
+ if (i < gdi._numZBuffer)
+ gdi._imgBufOffs[i] = i * itemsize;
+ else
+ gdi._imgBufOffs[i] = (gdi._numZBuffer - 1) * itemsize;
+ }
+}
+
+/**
+ * Redraw background as needed, i.e. the left/right sides if scrolling took place etc.
+ * Note that this only updated the virtual screen, not the actual display.
+ */
+void ScummEngine::redrawBGAreas() {
+ int i;
+ int diff;
+ int val = 0;
+
+ if (!(_features & GF_NEW_CAMERA))
+ if (camera._cur.x != camera._last.x && _charset->_hasMask && (_version > 3 && _gameId != GID_PASS))
+ stopTalk();
+
+ // Redraw parts of the background which are marked as dirty.
+ if (!_fullRedraw && _bgNeedsRedraw) {
+ for (i = 0; i != gdi._numStrips; i++) {
+ if (testGfxUsageBit(_screenStartStrip + i, USAGE_BIT_DIRTY)) {
+ redrawBGStrip(i, 1);
+ }
+ }
+ }
+
+ if (_features & GF_NEW_CAMERA) {
+ diff = camera._cur.x / 8 - camera._last.x / 8;
+ if (_fullRedraw || ABS(diff) >= gdi._numStrips) {
+ _bgNeedsRedraw = false;
+ redrawBGStrip(0, gdi._numStrips);
+ } else if (diff > 0) {
+ val = -diff;
+ redrawBGStrip(gdi._numStrips - diff, diff);
+ } else if (diff < 0) {
+ val = -diff;
+ redrawBGStrip(0, -diff);
+ }
+ } else {
+ diff = camera._cur.x - camera._last.x;
+ if (!_fullRedraw && diff == 8) {
+ val = -1;
+ redrawBGStrip(gdi._numStrips - 1, 1);
+ } else if (!_fullRedraw && diff == -8) {
+ val = +1;
+ redrawBGStrip(0, 1);
+ } else if (_fullRedraw || diff != 0) {
+ _bgNeedsRedraw = false;
+ _flashlight.isDrawn = false;
+ redrawBGStrip(0, gdi._numStrips);
+ }
+ }
+
+ drawRoomObjects(val);
+ _bgNeedsRedraw = false;
+}
+
+#ifndef DISABLE_HE
+void ScummEngine_v71he::redrawBGAreas() {
+ if (camera._cur.x != camera._last.x && _charset->_hasMask)
+ stopTalk();
+
+ byte *room = getResourceAddress(rtRoomImage, _roomResource) + _IM00_offs;
+ if (_fullRedraw) {
+ _bgNeedsRedraw = false;
+ gdi.drawBMAPBg(room, &virtscr[0]);
+ }
+
+ drawRoomObjects(0);
+ _bgNeedsRedraw = false;
+}
+
+void ScummEngine_v72he::redrawBGAreas() {
+ ScummEngine_v71he::redrawBGAreas();
+ _wiz->flushWizBuffer();
+}
+#endif
+
+void ScummEngine::redrawBGStrip(int start, int num) {
+ byte *room;
+
+ int s = _screenStartStrip + start;
+
+ for (int i = 0; i < num; i++)
+ setGfxUsageBit(s + i, USAGE_BIT_DIRTY);
+
+ if (_heversion >= 70)
+ room = getResourceAddress(rtRoomImage, _roomResource);
+ else
+ room = getResourceAddress(rtRoom, _roomResource);
+
+ gdi.drawBitmap(room + _IM00_offs, &virtscr[0], s, 0, _roomWidth, virtscr[0].h, s, num, 0);
+}
+
+void ScummEngine::restoreBG(Common::Rect rect, byte backColor) {
+ VirtScreen *vs;
+ byte *screenBuf;
+
+ if (rect.top < 0)
+ rect.top = 0;
+ if (rect.left >= rect.right || rect.top >= rect.bottom)
+ return;
+
+ if ((vs = findVirtScreen(rect.top)) == NULL)
+ return;
+
+ if (rect.left > vs->w)
+ return;
+
+ // Convert 'rect' to local (virtual screen) coordinates
+ rect.top -= vs->topline;
+ rect.bottom -= vs->topline;
+
+ rect.clip(vs->w, vs->h);
+
+ markRectAsDirty(vs->number, rect, USAGE_BIT_RESTORED);
+
+ screenBuf = vs->getPixels(rect.left, rect.top);
+
+ const int height = rect.height();
+ const int width = rect.width();
+
+ if (!height)
+ return;
+
+ if (vs->hasTwoBuffers && _currentRoom != 0 && isLightOn()) {
+ blit(screenBuf, vs->pitch, vs->getBackPixels(rect.left, rect.top), vs->pitch, width, height);
+ if (vs->number == kMainVirtScreen && _charset->_hasMask) {
+ byte *mask = (byte *)_charset->_textSurface.pixels + _charset->_textSurface.pitch * (rect.top - _screenTop) + rect.left;
+ fill(mask, _charset->_textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width, height);
+ }
+ } else {
+ fill(screenBuf, vs->pitch, backColor, width, height);
+ }
+}
+
+void CharsetRenderer::restoreCharsetBg() {
+ _nextLeft = _vm->_string[0].xpos;
+ _nextTop = _vm->_string[0].ypos + _vm->_screenTop;
+
+ if (_hasMask) {
+ _hasMask = false;
+ _str.left = -1;
+ _left = -1;
+
+ // Restore background on the whole text area. This code is based on
+ // restoreBG(), but was changed to only restore those parts which are
+ // currently covered by the charset mask.
+
+ VirtScreen *vs = &_vm->virtscr[_textScreenID];
+ if (!vs->h)
+ return;
+
+ _vm->markRectAsDirty(vs->number, Common::Rect(vs->w, vs->h), USAGE_BIT_RESTORED);
+
+ byte *screenBuf = vs->getPixels(0, 0);
+
+ if (vs->hasTwoBuffers && _vm->_currentRoom != 0 && _vm->isLightOn()) {
+ if (vs->number != kMainVirtScreen) {
+ // Restore from back buffer
+ const byte *backBuf = vs->getBackPixels(0, 0);
+ blit(screenBuf, vs->pitch, backBuf, vs->pitch, vs->w, vs->h);
+ }
+ } else {
+ // Clear area
+ memset(screenBuf, 0, vs->h * vs->pitch);
+ }
+
+ if (vs->hasTwoBuffers) {
+ // Clean out the charset mask
+ clearTextSurface();
+ }
+ }
+}
+
+void CharsetRenderer::clearCharsetMask() {
+ memset(_vm->getResourceAddress(rtBuffer, 9), 0, _vm->gdi._imgBufOffs[1]);
+}
+
+void CharsetRenderer::clearTextSurface() {
+ memset(_textSurface.pixels, CHARSET_MASK_TRANSPARENCY, _textSurface.pitch * _textSurface.h);
+}
+
+byte *ScummEngine::getMaskBuffer(int x, int y, int z) {
+ return gdi.getMaskBuffer((x + virtscr[0].xstart) / 8, y, z);
+}
+
+byte *Gdi::getMaskBuffer(int x, int y, int z) {
+ return _vm->getResourceAddress(rtBuffer, 9)
+ + x + y * _numStrips + _imgBufOffs[z];
+}
+
+
+#pragma mark -
+#pragma mark --- Misc ---
+#pragma mark -
+
+static void blit(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h) {
+ assert(w > 0);
+ assert(h > 0);
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ if (w == srcPitch && w == dstPitch) {
+ memcpy(dst, src, w*h);
+ } else {
+ do {
+ memcpy(dst, src, w);
+ dst += dstPitch;
+ src += srcPitch;
+ } while (--h);
+ }
+}
+
+static void fill(byte *dst, int dstPitch, byte color, int w, int h) {
+ assert(h > 0);
+ assert(dst != NULL);
+
+ if (w == dstPitch) {
+ memset(dst, color, w*h);
+ } else {
+ do {
+ memset(dst, color, w);
+ dst += dstPitch;
+ } while (--h);
+ }
+}
+
+static void copy8Col(byte *dst, int dstPitch, const byte *src, int height) {
+ do {
+#if defined(SCUMM_NEED_ALIGNMENT)
+ memcpy(dst, src, 8);
+#else
+ ((uint32 *)dst)[0] = ((const uint32 *)src)[0];
+ ((uint32 *)dst)[1] = ((const uint32 *)src)[1];
+#endif
+ dst += dstPitch;
+ src += dstPitch;
+ } while (--height);
+}
+
+static void clear8Col(byte *dst, int dstPitch, int height) {
+ do {
+#if defined(SCUMM_NEED_ALIGNMENT)
+ memset(dst, 0, 8);
+#else
+ ((uint32 *)dst)[0] = 0;
+ ((uint32 *)dst)[1] = 0;
+#endif
+ dst += dstPitch;
+ } while (--height);
+}
+
+void ScummEngine::drawBox(int x, int y, int x2, int y2, int color) {
+ int width, height;
+ VirtScreen *vs;
+ byte *backbuff, *bgbuff;
+
+ if ((vs = findVirtScreen(y)) == NULL)
+ return;
+
+ if (x > x2)
+ SWAP(x, x2);
+
+ if (y > y2)
+ SWAP(y, y2);
+
+ x2++;
+ y2++;
+
+ // Adjust for the topline of the VirtScreen
+ y -= vs->topline;
+ y2 -= vs->topline;
+
+ // Clip the coordinates
+ if (x < 0)
+ x = 0;
+ else if (x >= vs->w)
+ return;
+
+ if (x2 < 0)
+ return;
+ else if (x2 > vs->w)
+ x2 = vs->w;
+
+ if (y < 0)
+ y = 0;
+ else if (y > vs->h)
+ return;
+
+ if (y2 < 0)
+ return;
+ else if (y2 > vs->h)
+ y2 = vs->h;
+
+ width = x2 - x;
+ height = y2 - y;
+
+ // This will happen in the Sam & Max intro - see bug #1039162 - where
+ // it would trigger an assertion in blit().
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ markRectAsDirty(vs->number, x, x2, y, y2);
+
+ backbuff = vs->getPixels(x, y);
+ bgbuff = vs->getBackPixels(x, y);
+
+ if (color == -1) {
+ if (vs->number != kMainVirtScreen)
+ error("can only copy bg to main window");
+ blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height);
+ if (_charset->_hasMask) {
+ byte *mask = (byte *)_charset->_textSurface.pixels + _charset->_textSurface.pitch * (y - _screenTop) + x;
+ fill(mask, _charset->_textSurface.pitch, CHARSET_MASK_TRANSPARENCY, width, height);
+ }
+ } else if (_heversion == 100) {
+ // Flags are used for different methods in HE games
+ int32 flags = color;
+ if (flags & 0x4000000) {
+ blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height);
+ } else if (flags & 0x2000000) {
+ blit(bgbuff, vs->pitch, backbuff, vs->pitch, width, height);
+ } else if (flags & 0x1000000) {
+ flags &= 0xFFFFFF;
+ fill(backbuff, vs->pitch, flags, width, height);
+ fill(bgbuff, vs->pitch, flags, width, height);
+ } else {
+ fill(backbuff, vs->pitch, flags, width, height);
+ }
+ } else {
+ // Flags are used for different methods in HE games
+ int16 flags = color;
+ if (flags & 0x2000) {
+ blit(backbuff, vs->pitch, bgbuff, vs->pitch, width, height);
+ } else if (flags & 0x4000) {
+ blit(bgbuff, vs->pitch, backbuff, vs->pitch, width, height);
+ } else if (flags & 0x8000) {
+ flags &= 0x7FFF;
+ fill(backbuff, vs->pitch, flags, width, height);
+ fill(bgbuff, vs->pitch, flags, width, height);
+ } else {
+ fill(backbuff, vs->pitch, flags, width, height);
+ }
+ }
+}
+
+void ScummEngine::drawFlashlight() {
+ int i, j, x, y;
+ VirtScreen *vs = &virtscr[kMainVirtScreen];
+
+ // Remove the flash light first if it was previously drawn
+ if (_flashlight.isDrawn) {
+ markRectAsDirty(kMainVirtScreen, _flashlight.x, _flashlight.x + _flashlight.w,
+ _flashlight.y, _flashlight.y + _flashlight.h, USAGE_BIT_DIRTY);
+
+ if (_flashlight.buffer) {
+ fill(_flashlight.buffer, vs->pitch, 0, _flashlight.w, _flashlight.h);
+ }
+ _flashlight.isDrawn = false;
+ }
+
+ if (_flashlight.xStrips == 0 || _flashlight.yStrips == 0)
+ return;
+
+ // Calculate the area of the flashlight
+ if (_gameId == GID_ZAK || _gameId == GID_MANIAC) {
+ x = _mouse.x + vs->xstart;
+ y = _mouse.y - vs->topline;
+ } else {
+ Actor *a = derefActor(VAR(VAR_EGO), "drawFlashlight");
+ x = a->_pos.x;
+ y = a->_pos.y;
+ }
+ _flashlight.w = _flashlight.xStrips * 8;
+ _flashlight.h = _flashlight.yStrips * 8;
+ _flashlight.x = x - _flashlight.w / 2 - _screenStartStrip * 8;
+ _flashlight.y = y - _flashlight.h / 2;
+
+ if (_gameId == GID_LOOM)
+ _flashlight.y -= 12;
+
+ // Clip the flashlight at the borders
+ if (_flashlight.x < 0)
+ _flashlight.x = 0;
+ else if (_flashlight.x + _flashlight.w > gdi._numStrips * 8)
+ _flashlight.x = gdi._numStrips * 8 - _flashlight.w;
+ if (_flashlight.y < 0)
+ _flashlight.y = 0;
+ else if (_flashlight.y + _flashlight.h> vs->h)
+ _flashlight.y = vs->h - _flashlight.h;
+
+ // Redraw any actors "under" the flashlight
+ for (i = _flashlight.x / 8; i < (_flashlight.x + _flashlight.w) / 8; i++) {
+ assert(0 <= i && i < gdi._numStrips);
+ setGfxUsageBit(_screenStartStrip + i, USAGE_BIT_DIRTY);
+ vs->tdirty[i] = 0;
+ vs->bdirty[i] = vs->h;
+ }
+
+ byte *bgbak;
+ _flashlight.buffer = vs->getPixels(_flashlight.x, _flashlight.y);
+ bgbak = vs->getBackPixels(_flashlight.x, _flashlight.y);
+
+ blit(_flashlight.buffer, vs->pitch, bgbak, vs->pitch, _flashlight.w, _flashlight.h);
+
+ // Round the corners. To do so, we simply hard-code a set of nicely
+ // rounded corners.
+ static const int corner_data[] = { 8, 6, 4, 3, 2, 2, 1, 1 };
+ int minrow = 0;
+ int maxcol = _flashlight.w - 1;
+ int maxrow = (_flashlight.h - 1) * vs->pitch;
+
+ for (i = 0; i < 8; i++, minrow += vs->pitch, maxrow -= vs->pitch) {
+ int d = corner_data[i];
+
+ for (j = 0; j < d; j++) {
+ _flashlight.buffer[minrow + j] = 0;
+ _flashlight.buffer[minrow + maxcol - j] = 0;
+ _flashlight.buffer[maxrow + j] = 0;
+ _flashlight.buffer[maxrow + maxcol - j] = 0;
+ }
+ }
+
+ _flashlight.isDrawn = true;
+}
+
+bool ScummEngine::isLightOn() const {
+ return (VAR_CURRENT_LIGHTS == 0xFF) || (VAR(VAR_CURRENT_LIGHTS) & LIGHTMODE_screen);
+}
+
+void ScummEngine::setShake(int mode) {
+ if (_shakeEnabled != (mode != 0))
+ _fullRedraw = true;
+
+ _shakeEnabled = mode != 0;
+ _shakeFrame = 0;
+ _system->setShakePos(0);
+}
+
+#pragma mark -
+#pragma mark --- Image drawing ---
+#pragma mark -
+
+
+void Gdi::drawBitmapV2Helper(const byte *ptr, VirtScreen *vs, int x, int y, const int width, const int height, int stripnr, int numstrip) {
+ StripTable *table = (_objectMode ? 0 : _roomStrips);
+ const int left = (stripnr * 8);
+ const int right = left + (numstrip * 8);
+ byte *dst;
+ byte *mask_ptr;
+ const byte *src;
+ byte color, data = 0;
+ int run;
+ bool dither = false;
+ byte dither_table[128];
+ byte *ptr_dither_table;
+ int theX, theY, maxX;
+
+ memset(dither_table, 0, sizeof(dither_table));
+
+ if (vs->hasTwoBuffers)
+ dst = vs->backBuf + y * vs->pitch + x * 8;
+ else
+ dst = (byte *)vs->pixels + y * vs->pitch + x * 8;
+
+ mask_ptr = getMaskBuffer(x, y, 1);
+
+
+ if (table) {
+ run = table->run[stripnr];
+ color = table->color[stripnr];
+ src = ptr + table->offsets[stripnr];
+ theX = left;
+ maxX = right;
+ } else {
+ run = 1;
+ color = 0;
+ src = ptr;
+ theX = 0;
+ maxX = width;
+ }
+
+ // Decode and draw the image data.
+ assert(height <= 128);
+ for (; theX < maxX; theX++) {
+ ptr_dither_table = dither_table;
+ for (theY = 0; theY < height; theY++) {
+ if (--run == 0) {
+ data = *src++;
+ if (data & 0x80) {
+ run = data & 0x7f;
+ dither = true;
+ } else {
+ run = data >> 4;
+ dither = false;
+ }
+ color = _roomPalette[data & 0x0f];
+ if (run == 0) {
+ run = *src++;
+ }
+ }
+ if (!dither) {
+ *ptr_dither_table = color;
+ }
+ if (left <= theX && theX < right) {
+ *dst = *ptr_dither_table++;
+ dst += vs->pitch;
+ }
+ }
+ if (left <= theX && theX < right) {
+ dst -= _vertStripNextInc;
+ }
+ }
+
+
+ // Draw mask (zplane) data
+ theY = 0;
+
+ if (table) {
+ src = ptr + table->zoffsets[stripnr];
+ run = table->zrun[stripnr];
+ theX = left;
+ } else {
+ run = *src++;
+ theX = 0;
+ }
+ while (theX < right) {
+ const byte runFlag = run & 0x80;
+ if (runFlag) {
+ run &= 0x7f;
+ data = *src++;
+ }
+ do {
+ if (!runFlag)
+ data = *src++;
+
+ if (left <= theX) {
+ *mask_ptr = data;
+ mask_ptr += _numStrips;
+ }
+ theY++;
+ if (theY >= height) {
+ if (left <= theX) {
+ mask_ptr -= _numStrips * height - 1;
+ }
+ theY = 0;
+ theX += 8;
+ if (theX >= right)
+ break;
+ }
+ } while (--run);
+ run = *src++;
+ }
+}
+
+int Gdi::getZPlanes(const byte *ptr, const byte *zplane_list[9], bool bmapImage) const {
+ int numzbuf;
+ int i;
+
+ if ((_vm->_features & GF_SMALL_HEADER) || _vm->_version == 8)
+ zplane_list[0] = ptr;
+ else if (bmapImage)
+ zplane_list[0] = _vm->findResource(MKID('BMAP'), ptr);
+ else
+ zplane_list[0] = _vm->findResource(MKID('SMAP'), ptr);
+
+ if (_zbufferDisabled)
+ numzbuf = 0;
+ else if (_numZBuffer <= 1 || (_vm->_version <= 2))
+ numzbuf = _numZBuffer;
+ else {
+ numzbuf = _numZBuffer;
+ assert(numzbuf <= 9);
+
+ if (_vm->_features & GF_SMALL_HEADER) {
+ if (_vm->_features & GF_16COLOR)
+ zplane_list[1] = ptr + READ_LE_UINT16(ptr);
+ else {
+ zplane_list[1] = ptr + READ_LE_UINT32(ptr);
+ if (_vm->_features & GF_OLD256) {
+ if (0 == READ_LE_UINT32(zplane_list[1]))
+ zplane_list[1] = 0;
+ }
+ }
+ for (i = 2; i < numzbuf; i++) {
+ zplane_list[i] = zplane_list[i-1] + READ_LE_UINT16(zplane_list[i-1]);
+ }
+ } else if (_vm->_version == 8) {
+ // Find the OFFS chunk of the ZPLN chunk
+ const byte *zplnOffsChunkStart = ptr + 24 + READ_BE_UINT32(ptr + 12);
+
+ // Each ZPLN contains a WRAP chunk, which has (as always) an OFFS subchunk pointing
+ // at ZSTR chunks. These once more contain a WRAP chunk which contains nothing but
+ // an OFFS chunk. The content of this OFFS chunk contains the offsets to the
+ // Z-planes.
+ // We do not directly make use of this, but rather hard code offsets (like we do
+ // for all other Scumm-versions, too). Clearly this is a bit hackish, but works
+ // well enough, and there is no reason to assume that there are any cases where it
+ // might fail. Still, doing this properly would have the advantage of catching
+ // invalid/damaged data files, and allow us to exit gracefully instead of segfaulting.
+ for (i = 1; i < numzbuf; i++) {
+ zplane_list[i] = zplnOffsChunkStart + READ_LE_UINT32(zplnOffsChunkStart + 4 + i*4) + 16;
+ }
+ } else {
+ const uint32 zplane_tags[] = {
+ MKID('ZP00'),
+ MKID('ZP01'),
+ MKID('ZP02'),
+ MKID('ZP03'),
+ MKID('ZP04')
+ };
+
+ for (i = 1; i < numzbuf; i++) {
+ zplane_list[i] = _vm->findResource(zplane_tags[i], ptr);
+ }
+ }
+ }
+
+ return numzbuf;
+}
+
+/**
+ * Draw a bitmap onto a virtual screen. This is main drawing method for room backgrounds
+ * and objects, used throughout all SCUMM versions.
+ */
+void Gdi::drawBitmap(const byte *ptr, VirtScreen *vs, int x, int y, const int width, const int height,
+ int stripnr, int numstrip, byte flag) {
+ assert(ptr);
+ assert(height > 0);
+ byte *dstPtr;
+ const byte *smap_ptr;
+ const byte *z_plane_ptr;
+ byte *mask_ptr;
+
+ int i;
+ const byte *zplane_list[9];
+
+ int bottom;
+ int numzbuf;
+ int sx;
+ bool transpStrip = false;
+
+ // Check whether lights are turned on or not
+ const bool lightsOn = _vm->isLightOn();
+
+ _objectMode = (flag & dbObjectMode) == dbObjectMode;
+
+ if (_objectMode && _vm->_version == 1) {
+ if (_vm->_platform == Common::kPlatformNES) {
+ decodeNESObject(ptr, x, y, width, height);
+ } else {
+ decodeC64Gfx(ptr, _C64.objectMap, (width / 8) * (height / 8) * 3);
+ }
+ }
+
+ CHECK_HEAP;
+ if (_vm->_features & GF_SMALL_HEADER) {
+ smap_ptr = ptr;
+ } else if (_vm->_version == 8) {
+ // Skip to the BSTR->WRAP->OFFS chunk
+ smap_ptr = ptr + 24;
+ } else
+ smap_ptr = _vm->findResource(MKID('SMAP'), ptr);
+
+ assert(smap_ptr);
+
+ numzbuf = getZPlanes(ptr, zplane_list, false);
+
+ const byte *tmsk_ptr = NULL;
+ if (_vm->_heversion >= 72) {
+ tmsk_ptr = _vm->findResource(MKID('TMSK'), ptr);
+ }
+
+ bottom = y + height;
+ if (bottom > vs->h) {
+ warning("Gdi::drawBitmap, strip drawn to %d below window bottom %d", bottom, vs->h);
+ }
+
+ _vertStripNextInc = height * vs->pitch - 1;
+
+ //
+ // Since V3, all graphics data was encoded in strips, which is very efficient
+ // for redrawing only parts of the screen. However, V2 is different: here
+ // the whole graphics are encoded as one big chunk. That makes it rather
+ // dificult to draw only parts of a room/object. We handle the V2 graphics
+ // differently from all other (newer) graphic formats for this reason.
+ //
+ if (_vm->_version == 2)
+ drawBitmapV2Helper(ptr, vs, x, y, width, height, stripnr, numstrip);
+
+ sx = x - vs->xstart / 8;
+ if (sx < 0) {
+ numstrip -= -sx;
+ x += -sx;
+ stripnr += -sx;
+ sx = 0;
+ }
+
+ //if (_vm->_NESStartStrip > 0)
+ // stripnr -= _vm->_NESStartStrip;
+
+ // Compute the number of strips we have to iterate over.
+ // TODO/FIXME: The computation of its initial value looks very fishy.
+ // It was added as a kind of hack to fix some corner cases, but it compares
+ // the room width to the virtual screen width; but the former should always
+ // be bigger than the latter (except for MM NES, maybe)... strange
+ int limit = MAX(_vm->_roomWidth, (int) vs->w) / 8 - x;
+ if (limit > numstrip)
+ limit = numstrip;
+ if (limit > _numStrips - sx)
+ limit = _numStrips - sx;
+ for (int k = 0; k < limit; ++k, ++stripnr) {
+ CHECK_HEAP;
+
+ if (y < vs->tdirty[sx + k])
+ vs->tdirty[sx + k] = y;
+
+ if (bottom > vs->bdirty[sx + k])
+ vs->bdirty[sx + k] = bottom;
+
+ // In the case of a double buffered virtual screen, we draw to
+ // the backbuffer, otherwise to the primary surface memory.
+ if (vs->hasTwoBuffers)
+ dstPtr = vs->backBuf + y * vs->pitch + (x + k) * 8;
+ else
+ dstPtr = (byte *)vs->pixels + y * vs->pitch + (x + k) * 8;
+
+ if (_vm->_version == 1) {
+ if (_vm->_platform == Common::kPlatformNES) {
+ mask_ptr = getMaskBuffer(x + k, y, 1);
+ drawStripNES(dstPtr, mask_ptr, vs->pitch, stripnr, y, height);
+ }
+ else if (_objectMode)
+ drawStripC64Object(dstPtr, vs->pitch, stripnr, width, height);
+ else
+ drawStripC64Background(dstPtr, vs->pitch, stripnr, height);
+ } else if (_vm->_version == 2) {
+ // Do nothing here for V2 games - drawing was already handled.
+ } else {
+ // Do some input verification and make sure the strip/strip offset
+ // are actually valid. Normally, this should never be a problem,
+ // but if e.g. a savegame gets corrupted, we can easily get into
+ // trouble here. See also bug #795214.
+ int offset = -1, smapLen;
+ if (_vm->_features & GF_16COLOR) {
+ smapLen = READ_LE_UINT16(smap_ptr);
+ if (stripnr * 2 + 2 < smapLen)
+ offset = READ_LE_UINT16(smap_ptr + stripnr * 2 + 2);
+ } else if (_vm->_features & GF_SMALL_HEADER) {
+ smapLen = READ_LE_UINT32(smap_ptr);
+ if (stripnr * 4 + 4 < smapLen)
+ offset = READ_LE_UINT32(smap_ptr + stripnr * 4 + 4);
+ } else {
+ smapLen = READ_BE_UINT32(smap_ptr);
+ if (stripnr * 4 + 8 < smapLen)
+ offset = READ_LE_UINT32(smap_ptr + stripnr * 4 + 8);
+ }
+ if (offset < 0 || offset >= smapLen) {
+ error("drawBitmap: Trying to draw a non-existant strip");
+ return;
+ }
+ transpStrip = decompressBitmap(dstPtr, vs->pitch, smap_ptr + offset, height);
+ }
+
+ CHECK_HEAP;
+ if (vs->hasTwoBuffers) {
+ byte *frontBuf = (byte *)vs->pixels + y * vs->pitch + (x + k) * 8;
+ if (lightsOn)
+ copy8Col(frontBuf, vs->pitch, dstPtr, height);
+ else
+ clear8Col(frontBuf, vs->pitch, height);
+ }
+ CHECK_HEAP;
+
+ // COMI and HE games only uses flag value
+ if (_vm->_version == 8 || _vm->_heversion >= 60)
+ transpStrip = true;
+
+ if (_vm->_version == 1) {
+ mask_ptr = getMaskBuffer(x + k, y, 1);
+ if (_vm->_platform == Common::kPlatformNES) {
+ drawStripNESMask(mask_ptr, stripnr, y, height);
+ } else {
+ drawStripC64Mask(mask_ptr, stripnr, width, height);
+ }
+ } else if (_vm->_version == 2) {
+ // Do nothing here for V2 games - zplane was already handled.
+ } else if (flag & dbDrawMaskOnAll) {
+ // Sam & Max uses dbDrawMaskOnAll for things like the inventory
+ // box and the speech icons. While these objects only have one
+ // mask, it should be applied to all the Z-planes in the room,
+ // i.e. they should mask every actor.
+ //
+ // This flag used to be called dbDrawMaskOnBoth, and all it
+ // would do was to mask Z-plane 0. (Z-plane 1 would also be
+ // masked, because what is now the else-clause used to be run
+ // always.) While this seems to be the only way there is to
+ // mask Z-plane 0, this wasn't good enough since actors in
+ // Z-planes >= 2 would not be masked.
+ //
+ // The flag is also used by The Dig and Full Throttle, but I
+ // don't know what for. At the time of writing, these games
+ // are still too unstable for me to investigate.
+
+ if (_vm->_version == 8)
+ z_plane_ptr = zplane_list[1] + READ_LE_UINT32(zplane_list[1] + stripnr * 4 + 8);
+ else
+ z_plane_ptr = zplane_list[1] + READ_LE_UINT16(zplane_list[1] + stripnr * 2 + 8);
+ for (i = 0; i < numzbuf; i++) {
+ mask_ptr = getMaskBuffer(x + k, y, i);
+ if (transpStrip && (flag & dbAllowMaskOr))
+ decompressMaskImgOr(mask_ptr, z_plane_ptr, height);
+ else
+ decompressMaskImg(mask_ptr, z_plane_ptr, height);
+ }
+ } else {
+ for (i = 1; i < numzbuf; i++) {
+ uint32 offs;
+
+ if (!zplane_list[i])
+ continue;
+
+ if (_vm->_features & GF_OLD_BUNDLE)
+ offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2);
+ else if (_vm->_features & GF_OLD256)
+ offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 4);
+ else if (_vm->_features & GF_SMALL_HEADER)
+ offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 2);
+ else if (_vm->_version == 8)
+ offs = READ_LE_UINT32(zplane_list[i] + stripnr * 4 + 8);
+ else
+ offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 8);
+
+ mask_ptr = getMaskBuffer(x + k, y, i);
+
+ if (offs) {
+ z_plane_ptr = zplane_list[i] + offs;
+
+ if (tmsk_ptr) {
+ const byte *tmsk = tmsk_ptr + READ_LE_UINT16(tmsk_ptr + 8);
+ decompressTMSK(mask_ptr, tmsk, z_plane_ptr, height);
+ } else if (transpStrip && (flag & dbAllowMaskOr)) {
+ decompressMaskImgOr(mask_ptr, z_plane_ptr, height);
+ } else {
+ decompressMaskImg(mask_ptr, z_plane_ptr, height);
+ }
+
+ } else {
+ if (!(transpStrip && (flag & dbAllowMaskOr)))
+ for (int h = 0; h < height; h++)
+ mask_ptr[h * _numStrips] = 0;
+ // FIXME: needs better abstraction
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Draw a bitmap onto a virtual screen. This is main drawing method for room backgrounds
+ * used throughout in 7.2+ HE versions.
+ *
+ * @note This function essentially is a stripped down & special cased version of
+ * the generic Gdi::drawBitmap() method.
+ */
+void Gdi::drawBMAPBg(const byte *ptr, VirtScreen *vs) {
+ const byte *z_plane_ptr;
+ byte *mask_ptr;
+ const byte *zplane_list[9];
+
+ const byte *bmap_ptr = _vm->findResourceData(MKID('BMAP'), ptr);
+ assert(bmap_ptr);
+
+ byte code = *bmap_ptr++;
+ int scrX = _vm->_screenStartStrip * 8;
+ byte *dst = (byte *)_vm->virtscr[0].backBuf + scrX;
+
+ // The following few lines more or less duplicate decompressBitmap(), only
+ // for an area spanning multiple strips. In particular, the codecs 13 & 14
+ // in decompressBitmap call drawStripHE()
+ _decomp_shr = code % 10;
+ _decomp_mask = 0xFF >> (8 - _decomp_shr);
+
+ switch (code) {
+ case 134:
+ case 135:
+ case 136:
+ case 137:
+ case 138:
+ drawStripHE(dst, vs->pitch, bmap_ptr, vs->w, vs->h, false);
+ break;
+ case 144:
+ case 145:
+ case 146:
+ case 147:
+ case 148:
+ drawStripHE(dst, vs->pitch, bmap_ptr, vs->w, vs->h, true);
+ break;
+ case 150:
+ fill(dst, vs->pitch, *bmap_ptr, vs->w, vs->h);
+ break;
+ default:
+ // Alternative russian freddi3 uses badly formatted bitmaps
+ debug(0, "Gdi::drawBMAPBg: default case %d", code);
+ }
+
+ copyVirtScreenBuffers(Common::Rect(vs->w, vs->h));
+
+ int numzbuf = getZPlanes(ptr, zplane_list, true);
+ if (numzbuf <= 1)
+ return;
+
+ uint32 offs;
+ for (int stripnr = 0; stripnr < _numStrips; stripnr++)
+ for (int i = 1; i < numzbuf; i++) {
+ if (!zplane_list[i])
+ continue;
+
+ offs = READ_LE_UINT16(zplane_list[i] + stripnr * 2 + 8);
+ mask_ptr = getMaskBuffer(stripnr, 0, i);
+
+ if (offs) {
+ z_plane_ptr = zplane_list[i] + offs;
+ decompressMaskImg(mask_ptr, z_plane_ptr, vs->h);
+ }
+ }
+}
+
+void Gdi::drawBMAPObject(const byte *ptr, VirtScreen *vs, int obj, int x, int y, int w, int h) {
+#ifndef DISABLE_HE
+ const byte *bmap_ptr = _vm->findResourceData(MKID('BMAP'), ptr);
+ assert(bmap_ptr);
+
+ byte code = *bmap_ptr++;
+ int scrX = _vm->_screenStartStrip * 8;
+
+ if (code == 8 || code == 9) {
+ Common::Rect rScreen(0, 0, vs->w, vs->h);
+ byte *dst = (byte *)_vm->virtscr[0].backBuf + scrX;
+ Wiz::copyWizImage(dst, bmap_ptr, vs->w, vs->h, x - scrX, y, w, h, &rScreen);
+ }
+
+ Common::Rect rect1(x, y, x + w, y + h);
+ Common::Rect rect2(scrX, 0, vs->w + scrX, vs->h);
+
+ if (rect1.intersects(rect2)) {
+ rect1.clip(rect2);
+ rect1.left -= rect2.left;
+ rect1.right -= rect2.left;
+ rect1.top -= rect2.top;
+ rect1.bottom -= rect2.top;
+
+ copyVirtScreenBuffers(rect1);
+ }
+#endif
+}
+
+void Gdi::copyVirtScreenBuffers(Common::Rect rect, int dirtybit) {
+ byte *src, *dst;
+ VirtScreen *vs = &_vm->virtscr[0];
+
+ debug(1,"copyVirtScreenBuffers: Left %d Right %d Top %d Bottom %d", rect.left, rect.right, rect.top, rect.bottom);
+
+ if (rect.top > vs->h || rect.bottom < 0)
+ return;
+
+ if (rect.left > vs->w || rect.right < 0)
+ return;
+
+ rect.left = MAX(0, (int)rect.left);
+ rect.left = MIN((int)rect.left, (int)vs->w - 1);
+
+ rect.right = MAX(0, (int)rect.right);
+ rect.right = MIN((int)rect.right, (int)vs->w);
+
+ rect.top = MAX(0, (int)rect.top);
+ rect.top = MIN((int)rect.top, (int)vs->h - 1);
+
+ rect.bottom = MAX(0, (int)rect.bottom);
+ rect.bottom = MIN((int)rect.bottom, (int)vs->h);
+
+ const int rw = rect.width();
+ const int rh = rect.height();
+
+ if (rw == 0 || rh == 0)
+ return;
+
+ src = _vm->virtscr[0].getBackPixels(rect.left, rect.top);
+ dst = _vm->virtscr[0].getPixels(rect.left, rect.top);
+
+ assert(rw <= _vm->_screenWidth && rw > 0);
+ assert(rh <= _vm->_screenHeight && rh > 0);
+ blit(dst, _vm->virtscr[0].pitch, src, _vm->virtscr[0].pitch, rw, rh);
+ _vm->markRectAsDirty(kMainVirtScreen, rect, dirtybit);
+}
+
+/**
+ * Reset the background behind an actor or blast object.
+ */
+void Gdi::resetBackground(int top, int bottom, int strip) {
+ VirtScreen *vs = &_vm->virtscr[0];
+ byte *backbuff_ptr, *bgbak_ptr;
+ int numLinesToProcess;
+
+ if (top < 0)
+ top = 0;
+
+ if (bottom > vs->h)
+ bottom = vs->h;
+
+ if (top >= bottom)
+ return;
+
+ assert(0 <= strip && strip < _numStrips);
+
+ if (top < vs->tdirty[strip])
+ vs->tdirty[strip] = top;
+
+ if (bottom > vs->bdirty[strip])
+ vs->bdirty[strip] = bottom;
+
+ bgbak_ptr = (byte *)vs->backBuf + top * vs->pitch + (strip + vs->xstart/8) * 8;
+ backbuff_ptr = (byte *)vs->pixels + top * vs->pitch + (strip + vs->xstart/8) * 8;
+
+ numLinesToProcess = bottom - top;
+ if (numLinesToProcess) {
+ if (_vm->isLightOn()) {
+ copy8Col(backbuff_ptr, vs->pitch, bgbak_ptr, numLinesToProcess);
+ } else {
+ clear8Col(backbuff_ptr, vs->pitch, numLinesToProcess);
+ }
+ }
+}
+
+bool Gdi::decompressBitmap(byte *dst, int dstPitch, const byte *src, int numLinesToProcess) {
+ assert(numLinesToProcess);
+
+ if (_vm->_features & GF_16COLOR) {
+ drawStripEGA(dst, dstPitch, src, numLinesToProcess);
+ return false;
+ }
+
+ if ((_vm->_platform == Common::kPlatformAmiga) && (_vm->_version >= 4))
+ _paletteMod = 16;
+ else
+ _paletteMod = 0;
+
+ byte code = *src++;
+ bool transpStrip = false;
+ _decomp_shr = code % 10;
+ _decomp_mask = 0xFF >> (8 - _decomp_shr);
+
+ switch (code) {
+ case 1:
+ drawStripRaw(dst, dstPitch, src, numLinesToProcess, false);
+ break;
+
+ case 2:
+ unkDecode8(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
+ break;
+
+ case 3:
+ unkDecode9(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
+ break;
+
+ case 4:
+ unkDecode10(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
+ break;
+
+ case 7:
+ unkDecode11(dst, dstPitch, src, numLinesToProcess); /* Ender - Zak256/Indy256 */
+ break;
+
+ case 8:
+ // Used in 3DO versions of HE games
+ transpStrip = true;
+ drawStrip3DO(dst, dstPitch, src, numLinesToProcess, true);
+ break;
+
+ case 9:
+ drawStrip3DO(dst, dstPitch, src, numLinesToProcess, false);
+ break;
+
+ case 10:
+ // Used in Amiga version of Monkey Island 1
+ drawStripEGA(dst, dstPitch, src, numLinesToProcess);
+ break;
+
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ drawStripBasicV(dst, dstPitch, src, numLinesToProcess, false);
+ break;
+
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ drawStripBasicH(dst, dstPitch, src, numLinesToProcess, false);
+ break;
+
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 38:
+ transpStrip = true;
+ drawStripBasicV(dst, dstPitch, src, numLinesToProcess, true);
+ break;
+
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ case 48:
+ transpStrip = true;
+ drawStripBasicH(dst, dstPitch, src, numLinesToProcess, true);
+ break;
+
+ case 64:
+ case 65:
+ case 66:
+ case 67:
+ case 68:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ case 108:
+ drawStripComplex(dst, dstPitch, src, numLinesToProcess, false);
+ break;
+
+ case 84:
+ case 85:
+ case 86:
+ case 87:
+ case 88:
+ case 124:
+ case 125:
+ case 126:
+ case 127:
+ case 128:
+ transpStrip = true;
+ drawStripComplex(dst, dstPitch, src, numLinesToProcess, true);
+ break;
+
+ case 134:
+ case 135:
+ case 136:
+ case 137:
+ case 138:
+ drawStripHE(dst, dstPitch, src, 8, numLinesToProcess, false);
+ break;
+
+ case 143: // Triggered by Russian water
+ case 144:
+ case 145:
+ case 146:
+ case 147:
+ case 148:
+ transpStrip = true;
+ drawStripHE(dst, dstPitch, src, 8, numLinesToProcess, true);
+ break;
+
+ case 149:
+ drawStripRaw(dst, dstPitch, src, numLinesToProcess, true);
+ break;
+
+ default:
+ error("Gdi::decompressBitmap: default case %d", code);
+ }
+
+ return transpStrip;
+}
+
+void Gdi::decompressMaskImg(byte *dst, const byte *src, int height) const {
+ byte b, c;
+
+ while (height) {
+ b = *src++;
+
+ if (b & 0x80) {
+ b &= 0x7F;
+ c = *src++;
+
+ do {
+ *dst = c;
+ dst += _numStrips;
+ --height;
+ } while (--b && height);
+ } else {
+ do {
+ *dst = *src++;
+ dst += _numStrips;
+ --height;
+ } while (--b && height);
+ }
+ }
+}
+
+void Gdi::decompressTMSK(byte *dst, const byte *tmsk, const byte *src, int height) const {
+ byte srcbits = 0;
+ byte srcFlag = 0;
+ byte maskFlag = 0;
+
+ byte srcCount = 0;
+ byte maskCount = 0;
+ byte maskbits = 0;
+
+ while (height) {
+ if (srcCount == 0) {
+ srcCount = *src++;
+ srcFlag = srcCount & 0x80;
+ if (srcFlag) {
+ srcCount &= 0x7F;
+ srcbits = *src++;
+ }
+ }
+
+ if (srcFlag == 0) {
+ srcbits = *src++;
+ }
+
+ srcCount--;
+
+ if (maskCount == 0) {
+ maskCount = *tmsk++;
+ maskFlag = maskCount & 0x80;
+ if (maskFlag) {
+ maskCount &= 0x7F;
+ maskbits = *tmsk++;
+ }
+ }
+
+ if (maskFlag == 0) {
+ maskbits = *tmsk++;
+ }
+
+ maskCount--;
+
+ *dst |= srcbits;
+ *dst &= ~maskbits;
+
+ dst += _numStrips;
+ height--;
+ }
+}
+
+void Gdi::decompressMaskImgOr(byte *dst, const byte *src, int height) const {
+ byte b, c;
+
+ while (height) {
+ b = *src++;
+
+ if (b & 0x80) {
+ b &= 0x7F;
+ c = *src++;
+
+ do {
+ *dst |= c;
+ dst += _numStrips;
+ --height;
+ } while (--b && height);
+ } else {
+ do {
+ *dst |= *src++;
+ dst += _numStrips;
+ --height;
+ } while (--b && height);
+ }
+ }
+}
+
+void decodeNESTileData(const byte *src, byte *dest) {
+ int len = READ_LE_UINT16(src); src += 2;
+ const byte *end = src + len;
+ src++; // skip number-of-tiles byte, assume it is correct
+ while (src < end) {
+ byte data = *src++;
+ for (int j = 0; j < (data & 0x7F); j++)
+ *dest++ = (data & 0x80) ? (*src++) : (*src);
+ if (!(data & 0x80))
+ src++;
+ }
+}
+
+void ScummEngine::decodeNESBaseTiles() {
+ byte *basetiles = getResourceAddress(rtCostume, 37);
+ _NESBaseTiles = basetiles[2];
+ decodeNESTileData(basetiles, _NESPatTable[1]);
+}
+
+static const int v1MMNEScostTables[2][6] = {
+ /* desc lens offs data gfx pal */
+ { 25, 27, 29, 31, 33, 35},
+ { 26, 28, 30, 32, 34, 36}
+};
+void ScummEngine::NES_loadCostumeSet(int n) {
+ int i;
+ _NESCostumeSet = n;
+
+ _NEScostdesc = getResourceAddress(rtCostume, v1MMNEScostTables[n][0]) + 2;
+ _NEScostlens = getResourceAddress(rtCostume, v1MMNEScostTables[n][1]) + 2;
+ _NEScostoffs = getResourceAddress(rtCostume, v1MMNEScostTables[n][2]) + 2;
+ _NEScostdata = getResourceAddress(rtCostume, v1MMNEScostTables[n][3]) + 2;
+ decodeNESTileData(getResourceAddress(rtCostume, v1MMNEScostTables[n][4]), _NESPatTable[0]);
+ byte *palette = getResourceAddress(rtCostume, v1MMNEScostTables[n][5]) + 2;
+ for (i = 0; i < 16; i++) {
+ byte c = *palette++;
+ if (c == 0x1D) // HACK - switch around colors 0x00 and 0x1D
+ c = 0; // so we don't need a zillion extra checks
+ else if (c == 0)// for determining the proper background color
+ c = 0x1D;
+ _NESPalette[1][i] = c;
+ }
+
+}
+
+void Gdi::decodeNESGfx(const byte *room) {
+ const byte *gdata = room + READ_LE_UINT16(room + 0x0A);
+ int tileset = *gdata++;
+ int width = READ_LE_UINT16(room + 0x04);
+ // int height = READ_LE_UINT16(room + 0x06);
+ int i, j, n;
+
+ // We have narrow room. so expand it
+ if (width < 32) {
+ _vm->_NESStartStrip = (32 - width) >> 1;
+ } else {
+ _vm->_NESStartStrip = 0;
+ }
+
+ decodeNESTileData(_vm->getResourceAddress(rtCostume, 37 + tileset), _vm->_NESPatTable[1] + _vm->_NESBaseTiles * 16);
+ for (i = 0; i < 16; i++) {
+ byte c = *gdata++;
+ if (c == 0x0D)
+ c = 0x1D;
+
+ if (c == 0x1D) // HACK - switch around colors 0x00 and 0x1D
+ c = 0; // so we don't need a zillion extra checks
+ else if (c == 0) // for determining the proper background color
+ c = 0x1D;
+
+ _vm->_NESPalette[0][i] = c;
+ }
+ for (i = 0; i < 16; i++) {
+ _NES.nametable[i][0] = _NES.nametable[i][1] = 0;
+ n = 0;
+ while (n < width) {
+ byte data = *gdata++;
+ for (j = 0; j < (data & 0x7F); j++)
+ _NES.nametable[i][2 + n++] = (data & 0x80) ? (*gdata++) : (*gdata);
+ if (!(data & 0x80))
+ gdata++;
+ }
+ _NES.nametable[i][width+2] = _NES.nametable[i][width+3] = 0;
+ }
+ memcpy(_NES.nametableObj,_NES.nametable, 16*64);
+
+ const byte *adata = room + READ_LE_UINT16(room + 0x0C);
+ for (n = 0; n < 64;) {
+ byte data = *adata++;
+ for (j = 0; j < (data & 0x7F); j++)
+ _NES.attributes[n++] = (data & 0x80) ? (*adata++) : (*adata);
+ if (!(n & 7) && (width == 0x1C))
+ n += 8;
+ if (!(data & 0x80))
+ adata++;
+ }
+ memcpy(_NES.attributesObj, _NES.attributes, 64);
+
+ const byte *mdata = room + READ_LE_UINT16(room + 0x0E);
+ int mask = *mdata++;
+ if (mask == 0) {
+ _NES.hasmask = false;
+ return;
+ }
+ _NES.hasmask = true;
+ if (mask != 1)
+ debug(0,"NES room %i has irregular mask count %i!",_vm->_currentRoom,mask);
+ int mwidth = *mdata++;
+ for (i = 0; i < 16; i++) {
+ n = 0;
+ while (n < mwidth) {
+ byte data = *mdata++;
+ for (j = 0; j < (data & 0x7F); j++)
+ _NES.masktable[i][n++] = (data & 0x80) ? (*mdata++) : (*mdata);
+ if (!(data & 0x80))
+ mdata++;
+ }
+ }
+ memcpy(_NES.masktableObj, _NES.masktable, 16*8);
+}
+
+void Gdi::decodeNESObject(const byte *ptr, int xpos, int ypos, int width, int height) {
+ int x, y;
+
+ _NES.objX = xpos;
+
+ // decode tile update data
+ width /= 8;
+ ypos /= 8;
+ height /= 8;
+
+ for (y = ypos; y < ypos + height; y++) {
+ x = xpos;
+ while (x < xpos + width) {
+ byte len = *ptr++;
+ for (int i = 0; i < (len & 0x7F); i++)
+ _NES.nametableObj[y][2 + x++] = (len & 0x80) ? (*ptr++) : (*ptr);
+ if (!(len & 0x80))
+ ptr++;
+ }
+ }
+
+ int ax, ay;
+ // decode attribute update data
+ y = height / 2;
+ ay = ypos;
+ while (y) {
+ ax = xpos + 2;
+ x = 0;
+ int adata = 0;
+ while (x < (width >> 1)) {
+ if (!(x & 3))
+ adata = *ptr++;
+ byte *dest = &_NES.attributesObj[((ay << 2) & 0x30) | ((ax >> 2) & 0xF)];
+
+ int aand = 3;
+ int aor = adata & 3;
+ if (ay & 0x02) {
+ aand <<= 4;
+ aor <<= 4;
+ }
+ if (ax & 0x02) {
+ aand <<= 2;
+ aor <<= 2;
+ }
+ *dest = ((~aand) & *dest) | aor;
+
+ adata >>= 2;
+ ax += 2;
+ x++;
+ }
+ ay += 2;
+ y--;
+ }
+
+ // decode mask update data
+ if (!_NES.hasmask)
+ return;
+ int mx, mwidth;
+ int lmask, rmask;
+ mx = *ptr++;
+ mwidth = *ptr++;
+ lmask = *ptr++;
+ rmask = *ptr++;
+
+ y = 0;
+ do {
+ byte *dest = &_NES.masktableObj[y + ypos][mx];
+ *dest = (*dest & lmask) | *ptr++;
+ dest++;
+ for (x = 1; x < mwidth; x++) {
+ if (x + 1 == mwidth)
+ *dest = (*dest & rmask) | *ptr++;
+ else
+ *dest = *ptr++;
+ dest++;
+ }
+ y++;
+ } while (y < height);
+}
+
+void Gdi::drawStripNES(byte *dst, byte *mask, int dstPitch, int stripnr, int top, int height) {
+ top /= 8;
+ height /= 8;
+ int x = stripnr + 2; // NES version has a 2 tile gap on each edge
+
+ if (_objectMode)
+ x += _NES.objX; // for objects, need to start at the left edge of the object, not the screen
+ if (x > 63) {
+ debug(0,"NES tried to render invalid strip %i",stripnr);
+ return;
+ }
+ for (int y = top; y < top + height; y++) {
+ int palette = ((_objectMode ? _NES.attributesObj : _NES.attributes)[((y << 2) & 0x30) | ((x >> 2) & 0xF)] >> (((y & 2) << 1) | (x & 2))) & 0x3;
+ int tile = (_objectMode ? _NES.nametableObj : _NES.nametable)[y][x];
+
+ for (int i = 0; i < 8; i++) {
+ byte c0 = _vm->_NESPatTable[1][tile * 16 + i];
+ byte c1 = _vm->_NESPatTable[1][tile * 16 + i + 8];
+ for (int j = 0; j < 8; j++)
+ dst[j] = _vm->_NESPalette[0][((c0 >> (7 - j)) & 1) | (((c1 >> (7 - j)) & 1) << 1) | (palette << 2)];
+ dst += dstPitch;
+ *mask = c0 | c1;
+ mask += _numStrips;
+ }
+ }
+}
+
+void Gdi::drawStripNESMask(byte *dst, int stripnr, int top, int height) const {
+ top /= 8;
+ height /= 8;
+ int x = stripnr; // masks, unlike room graphics, should NOT be adjusted
+
+ if (_objectMode)
+ x += _NES.objX; // for objects, need to start at the left edge of the object, not the screen
+ if (x > 63) {
+ debug(0,"NES tried to mask invalid strip %i",stripnr);
+ return;
+ }
+ for (int y = top; y < top + height; y++) {
+ byte c;
+ if (_NES.hasmask)
+ c = (((_objectMode ? _NES.masktableObj : _NES.masktable)[y][x >> 3] >> (x & 7)) & 1) ? 0xFF : 0x00;
+ else
+ c = 0;
+
+ for (int i = 0; i < 8; i++) {
+ *dst &= c;
+ dst += _numStrips;
+ }
+ }
+}
+
+void Gdi::drawStripC64Background(byte *dst, int dstPitch, int stripnr, int height) {
+ int charIdx;
+ height /= 8;
+ for (int y = 0; y < height; y++) {
+ _C64.colors[3] = (_C64.colorMap[y + stripnr * height] & 7);
+ // Check for room color change in V1 zak
+ if (_roomPalette[0] == 255) {
+ _C64.colors[2] = _roomPalette[2];
+ _C64.colors[1] = _roomPalette[1];
+ }
+
+ charIdx = _C64.picMap[y + stripnr * height] * 8;
+ for (int i = 0; i < 8; i++) {
+ byte c = _C64.charMap[charIdx + i];
+ dst[0] = dst[1] = _C64.colors[(c >> 6) & 3];
+ dst[2] = dst[3] = _C64.colors[(c >> 4) & 3];
+ dst[4] = dst[5] = _C64.colors[(c >> 2) & 3];
+ dst[6] = dst[7] = _C64.colors[(c >> 0) & 3];
+ dst += dstPitch;
+ }
+ }
+}
+
+void Gdi::drawStripC64Object(byte *dst, int dstPitch, int stripnr, int width, int height) {
+ int charIdx;
+ height /= 8;
+ width /= 8;
+ for (int y = 0; y < height; y++) {
+ _C64.colors[3] = (_C64.objectMap[(y + height) * width + stripnr] & 7);
+ charIdx = _C64.objectMap[y * width + stripnr] * 8;
+ for (int i = 0; i < 8; i++) {
+ byte c = _C64.charMap[charIdx + i];
+ dst[0] = dst[1] = _C64.colors[(c >> 6) & 3];
+ dst[2] = dst[3] = _C64.colors[(c >> 4) & 3];
+ dst[4] = dst[5] = _C64.colors[(c >> 2) & 3];
+ dst[6] = dst[7] = _C64.colors[(c >> 0) & 3];
+ dst += dstPitch;
+ }
+ }
+}
+
+void Gdi::drawStripC64Mask(byte *dst, int stripnr, int width, int height) const {
+ int maskIdx;
+ height /= 8;
+ width /= 8;
+ for (int y = 0; y < height; y++) {
+ if (_objectMode)
+ maskIdx = _C64.objectMap[(y + 2 * height) * width + stripnr] * 8;
+ else
+ maskIdx = _C64.maskMap[y + stripnr * height] * 8;
+ for (int i = 0; i < 8; i++) {
+ byte c = _C64.maskChar[maskIdx + i];
+
+ // V1/C64 masks are inverted compared to what ScummVM expects
+ *dst = c ^ 0xFF;
+ dst += _numStrips;
+ }
+ }
+}
+
+void Gdi::decodeC64Gfx(const byte *src, byte *dst, int size) const {
+ int x, z;
+ byte color, run, common[4];
+
+ for (z = 0; z < 4; z++) {
+ common[z] = *src++;
+ }
+
+ x = 0;
+ while (x < size) {
+ run = *src++;
+ if (run & 0x80) {
+ color = common[(run >> 5) & 3];
+ run &= 0x1F;
+ for (z = 0; z <= run; z++) {
+ dst[x++] = color;
+ }
+ } else if (run & 0x40) {
+ run &= 0x3F;
+ color = *src++;
+ for (z = 0; z <= run; z++) {
+ dst[x++] = color;
+ }
+ } else {
+ for (z = 0; z <= run; z++) {
+ dst[x++] = *src++;
+ }
+ }
+ }
+}
+
+/**
+ * Create and fill a table with offsets to the graphic and mask strips in the
+ * given V2 EGA bitmap.
+ * @param src the V2 EGA bitmap
+ * @param width the width of the bitmap
+ * @param height the height of the bitmap
+ * @param table the strip table to fill
+ * @return filled strip table
+ */
+StripTable *Gdi::generateStripTable(const byte *src, int width, int height, StripTable *table) const {
+
+ // If no strip table was given to use, allocate a new one
+ if (table == 0)
+ table = (StripTable *)calloc(1, sizeof(StripTable));
+
+ const byte *bitmapStart = src;
+ byte color = 0, data = 0;
+ int x, y, length = 0;
+ byte run = 1;
+
+ // Decode the graphics strips, and memorize the run/color values
+ // as well as the byte offset.
+ for (x = 0 ; x < width; x++) {
+
+ if ((x % 8) == 0) {
+ assert(x / 8 < 160);
+ table->run[x / 8] = run;
+ table->color[x / 8] = color;
+ table->offsets[x / 8] = src - bitmapStart;
+ }
+
+ for (y = 0; y < height; y++) {
+ if (--run == 0) {
+ data = *src++;
+ if (data & 0x80) {
+ run = data & 0x7f;
+ } else {
+ run = data >> 4;
+ }
+ if (run == 0) {
+ run = *src++;
+ }
+ color = data & 0x0f;
+ }
+ }
+ }
+
+ // The mask data follows immediately after the graphics.
+ x = 0;
+ y = height;
+ width /= 8;
+
+ for (;;) {
+ length = *src++;
+ const byte runFlag = length & 0x80;
+ if (runFlag) {
+ length &= 0x7f;
+ data = *src++;
+ }
+ do {
+ if (!runFlag)
+ data = *src++;
+ if (y == height) {
+ assert(x < 120);
+ table->zoffsets[x] = src - bitmapStart - 1;
+ table->zrun[x] = length | runFlag;
+ }
+ if (--y == 0) {
+ if (--width == 0)
+ return table;
+ x++;
+ y = height;
+ }
+ } while (--length);
+ }
+
+ return table;
+}
+
+void Gdi::drawStripEGA(byte *dst, int dstPitch, const byte *src, int height) const {
+ byte color = 0;
+ int run = 0, x = 0, y = 0, z;
+
+ while (x < 8) {
+ color = *src++;
+
+ if (color & 0x80) {
+ run = color & 0x3f;
+
+ if (color & 0x40) {
+ color = *src++;
+
+ if (run == 0) {
+ run = *src++;
+ }
+ for (z = 0; z < run; z++) {
+ *(dst + y * dstPitch + x) = (z & 1) ? _roomPalette[color & 0xf] + _paletteMod : _roomPalette[color >> 4] + _paletteMod;
+
+ y++;
+ if (y >= height) {
+ y = 0;
+ x++;
+ }
+ }
+ } else {
+ if (run == 0) {
+ run = *src++;
+ }
+
+ for (z = 0; z < run; z++) {
+ *(dst + y * dstPitch + x) = *(dst + y * dstPitch + x - 1);
+
+ y++;
+ if (y >= height) {
+ y = 0;
+ x++;
+ }
+ }
+ }
+ } else {
+ run = color >> 4;
+ if (run == 0) {
+ run = *src++;
+ }
+
+ for (z = 0; z < run; z++) {
+ *(dst + y * dstPitch + x) = _roomPalette[color & 0xf] + _paletteMod;
+
+ y++;
+ if (y >= height) {
+ y = 0;
+ x++;
+ }
+ }
+ }
+ }
+}
+
+#define READ_BIT (shift--, dataBit = data & 1, data >>= 1, dataBit)
+#define FILL_BITS(n) do { \
+ if (shift < n) { \
+ data |= *src++ << shift; \
+ shift += 8; \
+ } \
+ } while (0)
+
+// NOTE: drawStripHE is actually very similar to drawStripComplex
+void Gdi::drawStripHE(byte *dst, int dstPitch, const byte *src, int width, int height, const bool transpCheck) const {
+ static const int delta_color[] = { -4, -3, -2, -1, 1, 2, 3, 4 };
+ uint32 dataBit, data;
+ byte color;
+ int shift;
+
+ color = *src++;
+ data = READ_LE_UINT24(src);
+ src += 3;
+ shift = 24;
+
+ int x = width;
+ while (1) {
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color];
+ dst++;
+ --x;
+ if (x == 0) {
+ x = width;
+ dst += dstPitch - width;
+ --height;
+ if (height == 0)
+ return;
+ }
+ FILL_BITS(1);
+ if (READ_BIT) {
+ FILL_BITS(1);
+ if (READ_BIT) {
+ FILL_BITS(3);
+ color += delta_color[data & 7];
+ shift -= 3;
+ data >>= 3;
+ } else {
+ FILL_BITS(_decomp_shr);
+ color = data & _decomp_mask;
+ shift -= _decomp_shr;
+ data >>= _decomp_shr;
+ }
+ }
+ }
+}
+
+#undef READ_BIT
+#undef FILL_BITS
+
+
+void Gdi::drawStrip3DO(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
+ if (height == 0)
+ return;
+
+ int decSize = height * 8;
+ int curSize = 0;
+
+ do {
+ uint8 data = *src++;
+ uint8 rle = data & 1;
+ int len = (data >> 1) + 1;
+
+ len = MIN(decSize, len);
+ decSize -= len;
+
+ if (!rle) {
+ for (; len > 0; len--, src++, dst++) {
+ if (!transpCheck || *src != _transparentColor)
+ *dst = _roomPalette[*src];
+ curSize++;
+ if (!(curSize & 7))
+ dst += dstPitch - 8; // Next row
+ }
+ } else {
+ byte color = *src++;
+ for (; len > 0; len--, dst++) {
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color];
+ curSize++;
+ if (!(curSize & 7))
+ dst += dstPitch - 8; // Next row
+ }
+ }
+ } while (decSize > 0);
+}
+
+
+#define READ_BIT (cl--, bit = bits & 1, bits >>= 1, bit)
+#define FILL_BITS do { \
+ if (cl <= 8) { \
+ bits |= (*src++ << cl); \
+ cl += 8; \
+ } \
+ } while (0)
+
+void Gdi::drawStripComplex(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
+ byte color = *src++;
+ uint bits = *src++;
+ byte cl = 8;
+ byte bit;
+ byte incm, reps;
+
+ do {
+ int x = 8;
+ do {
+ FILL_BITS;
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color] + _paletteMod;
+ dst++;
+
+ againPos:
+ if (!READ_BIT) {
+ } else if (!READ_BIT) {
+ FILL_BITS;
+ color = bits & _decomp_mask;
+ bits >>= _decomp_shr;
+ cl -= _decomp_shr;
+ } else {
+ incm = (bits & 7) - 4;
+ cl -= 3;
+ bits >>= 3;
+ if (incm) {
+ color += incm;
+ } else {
+ FILL_BITS;
+ reps = bits & 0xFF;
+ do {
+ if (!--x) {
+ x = 8;
+ dst += dstPitch - 8;
+ if (!--height)
+ return;
+ }
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color] + _paletteMod;
+ dst++;
+ } while (--reps);
+ bits >>= 8;
+ bits |= (*src++) << (cl - 8);
+ goto againPos;
+ }
+ }
+ } while (--x);
+ dst += dstPitch - 8;
+ } while (--height);
+}
+
+void Gdi::drawStripBasicH(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
+ byte color = *src++;
+ uint bits = *src++;
+ byte cl = 8;
+ byte bit;
+ int8 inc = -1;
+
+ do {
+ int x = 8;
+ do {
+ FILL_BITS;
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color] + _paletteMod;
+ dst++;
+ if (!READ_BIT) {
+ } else if (!READ_BIT) {
+ FILL_BITS;
+ color = bits & _decomp_mask;
+ bits >>= _decomp_shr;
+ cl -= _decomp_shr;
+ inc = -1;
+ } else if (!READ_BIT) {
+ color += inc;
+ } else {
+ inc = -inc;
+ color += inc;
+ }
+ } while (--x);
+ dst += dstPitch - 8;
+ } while (--height);
+}
+
+void Gdi::drawStripBasicV(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
+ byte color = *src++;
+ uint bits = *src++;
+ byte cl = 8;
+ byte bit;
+ int8 inc = -1;
+
+ int x = 8;
+ do {
+ int h = height;
+ do {
+ FILL_BITS;
+ if (!transpCheck || color != _transparentColor)
+ *dst = _roomPalette[color] + _paletteMod;
+ dst += dstPitch;
+ if (!READ_BIT) {
+ } else if (!READ_BIT) {
+ FILL_BITS;
+ color = bits & _decomp_mask;
+ bits >>= _decomp_shr;
+ cl -= _decomp_shr;
+ inc = -1;
+ } else if (!READ_BIT) {
+ color += inc;
+ } else {
+ inc = -inc;
+ color += inc;
+ }
+ } while (--h);
+ dst -= _vertStripNextInc;
+ } while (--x);
+}
+
+#undef READ_BIT
+#undef FILL_BITS
+
+/* Ender - Zak256/Indy256 decoders */
+#define READ_BIT_256 \
+ do { \
+ if ((mask <<= 1) == 256) { \
+ buffer = *src++; \
+ mask = 1; \
+ } \
+ bits = ((buffer & mask) != 0); \
+ } while (0)
+
+#define READ_N_BITS(n, c) \
+ do { \
+ c = 0; \
+ for (int b = 0; b < n; b++) { \
+ READ_BIT_256; \
+ c += (bits << b); \
+ } \
+ } while (0)
+
+#define NEXT_ROW \
+ do { \
+ dst += dstPitch; \
+ if (--h == 0) { \
+ if (!--x) \
+ return; \
+ dst -= _vertStripNextInc; \
+ h = height; \
+ } \
+ } while (0)
+
+void Gdi::drawStripRaw(byte *dst, int dstPitch, const byte *src, int height, const bool transpCheck) const {
+ int x;
+
+ if (_vm->_features & GF_OLD256) {
+ uint h = height;
+ x = 8;
+ for (;;) {
+ *dst = *src++;
+ NEXT_ROW;
+ }
+ } else {
+ do {
+ for (x = 0; x < 8; x ++) {
+ byte color = *src++;
+ if (!transpCheck || color != _transparentColor)
+ dst[x] = _roomPalette[color] + _paletteMod;
+ }
+ dst += dstPitch;
+ } while (--height);
+ }
+}
+
+void Gdi::unkDecode8(byte *dst, int dstPitch, const byte *src, int height) const {
+ uint h = height;
+
+ int x = 8;
+ for (;;) {
+ uint run = (*src++) + 1;
+ byte color = *src++;
+
+ do {
+ *dst = _roomPalette[color];
+ NEXT_ROW;
+ } while (--run);
+ }
+}
+
+void Gdi::unkDecode9(byte *dst, int dstPitch, const byte *src, int height) const {
+ byte c, bits, color, run;
+ int i;
+ uint buffer = 0, mask = 128;
+ int h = height;
+ i = run = 0;
+
+ int x = 8;
+ for (;;) {
+ READ_N_BITS(4, c);
+
+ switch (c >> 2) {
+ case 0:
+ READ_N_BITS(4, color);
+ for (i = 0; i < ((c & 3) + 2); i++) {
+ *dst = _roomPalette[run * 16 + color];
+ NEXT_ROW;
+ }
+ break;
+
+ case 1:
+ for (i = 0; i < ((c & 3) + 1); i++) {
+ READ_N_BITS(4, color);
+ *dst = _roomPalette[run * 16 + color];
+ NEXT_ROW;
+ }
+ break;
+
+ case 2:
+ READ_N_BITS(4, run);
+ break;
+ }
+ }
+}
+
+void Gdi::unkDecode10(byte *dst, int dstPitch, const byte *src, int height) const {
+ int i;
+ byte local_palette[256], numcolors = *src++;
+ uint h = height;
+
+ for (i = 0; i < numcolors; i++)
+ local_palette[i] = *src++;
+
+ int x = 8;
+
+ for (;;) {
+ byte color = *src++;
+ if (color < numcolors) {
+ *dst = _roomPalette[local_palette[color]];
+ NEXT_ROW;
+ } else {
+ uint run = color - numcolors + 1;
+ color = *src++;
+ do {
+ *dst = _roomPalette[color];
+ NEXT_ROW;
+ } while (--run);
+ }
+ }
+}
+
+
+void Gdi::unkDecode11(byte *dst, int dstPitch, const byte *src, int height) const {
+ int bits, i;
+ uint buffer = 0, mask = 128;
+ byte inc = 1, color = *src++;
+
+ int x = 8;
+ do {
+ int h = height;
+ do {
+ *dst = _roomPalette[color];
+ dst += dstPitch;
+ for (i = 0; i < 3; i++) {
+ READ_BIT_256;
+ if (!bits)
+ break;
+ }
+ switch (i) {
+ case 1:
+ inc = -inc;
+ color -= inc;
+ break;
+
+ case 2:
+ color -= inc;
+ break;
+
+ case 3:
+ inc = 1;
+ READ_N_BITS(8, color);
+ break;
+ }
+ } while (--h);
+ dst -= _vertStripNextInc;
+ } while (--x);
+}
+
+#undef NEXT_ROW
+#undef READ_BIT_256
+
+
+#pragma mark -
+#pragma mark --- Transition effects ---
+#pragma mark -
+
+void ScummEngine::fadeIn(int effect) {
+ updatePalette();
+
+ switch (effect) {
+ case 0:
+ // seems to do nothing
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ // Some of the transition effects won't work properly unless
+ // the screen is marked as clean first. At first I thought I
+ // could safely do this every time fadeIn() was called, but
+ // that broke the FOA intro. Probably other things as well.
+ //
+ // Hopefully it's safe to do it at this point, at least.
+ virtscr[0].setDirtyRange(0, 0);
+ transitionEffect(effect - 1);
+ break;
+ case 128:
+ unkScreenEffect6();
+ break;
+ case 129:
+ break;
+ case 130:
+ case 131:
+ case 132:
+ case 133:
+ scrollEffect(133 - effect);
+ break;
+ case 134:
+ dissolveEffect(1, 1);
+ break;
+ case 135:
+ dissolveEffect(1, virtscr[0].h);
+ break;
+ default:
+ error("Unknown screen effect, %d", effect);
+ }
+ _screenEffectFlag = true;
+}
+
+void ScummEngine::fadeOut(int effect) {
+ VirtScreen *vs = &virtscr[0];
+
+ vs->setDirtyRange(0, 0);
+ if (!(_features & GF_NEW_CAMERA))
+ camera._last.x = camera._cur.x;
+
+ if (_switchRoomEffect >= 130 && _switchRoomEffect <= 133) {
+ // We're going to use scrollEffect(), so we'll need a copy of
+ // the current VirtScreen zero.
+
+ free(_scrollBuffer);
+ _scrollBuffer = (byte *) malloc(vs->h * vs->pitch);
+ memcpy(_scrollBuffer, vs->getPixels(0, 0), vs->h * vs->pitch);
+ }
+
+
+ if (_screenEffectFlag && effect != 0) {
+
+ // Fill screen 0 with black
+ memset(vs->getPixels(0, 0), 0, vs->pitch * vs->h);
+
+ // Fade to black with the specified effect, if any.
+ switch (effect) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ transitionEffect(effect - 1);
+ break;
+ case 128:
+ unkScreenEffect6();
+ break;
+ case 129:
+ // Just blit screen 0 to the display (i.e. display will be black)
+ vs->setDirtyRange(0, vs->h);
+ updateDirtyScreen(kMainVirtScreen);
+ break;
+ case 134:
+ dissolveEffect(1, 1);
+ break;
+ case 135:
+ dissolveEffect(1, virtscr[0].h);
+ break;
+ default:
+ error("fadeOut: default case %d", effect);
+ }
+ }
+
+ // Update the palette at the end (once we faded to black) to avoid
+ // some nasty effects when the palette is changed
+ updatePalette();
+
+ _screenEffectFlag = false;
+}
+
+/**
+ * Perform a transition effect. There are four different effects possible:
+ * 0: Iris effect
+ * 1: Box wipe (a black box expands from the upper-left corner to the lower-right corner)
+ * 2: Box wipe (a black box expands from the lower-right corner to the upper-left corner)
+ * 3: Inverse box wipe
+ * All effects operate on 8x8 blocks of the screen. These blocks are updated
+ * in a certain order; the exact order determines how the effect appears to the user.
+ * @param a the transition effect to perform
+ */
+void ScummEngine::transitionEffect(int a) {
+ int delta[16]; // Offset applied during each iteration
+ int tab_2[16];
+ int i, j;
+ int bottom;
+ int l, t, r, b;
+ const int height = MIN((int)virtscr[0].h, _screenHeight);
+
+ for (i = 0; i < 16; i++) {
+ delta[i] = transitionEffects[a].deltaTable[i];
+ j = transitionEffects[a].stripTable[i];
+ if (j == 24)
+ j = height / 8 - 1;
+ tab_2[i] = j;
+ }
+
+ bottom = height / 8;
+ for (j = 0; j < transitionEffects[a].numOfIterations; j++) {
+ for (i = 0; i < 4; i++) {
+ l = tab_2[i * 4];
+ t = tab_2[i * 4 + 1];
+ r = tab_2[i * 4 + 2];
+ b = tab_2[i * 4 + 3];
+
+ if (t == b) {
+ while (l <= r) {
+ if (l >= 0 && l < gdi._numStrips && t < bottom) {
+ virtscr[0].tdirty[l] = _screenTop + t * 8;
+ virtscr[0].bdirty[l] = _screenTop + (b + 1) * 8;
+ }
+ l++;
+ }
+ } else {
+ if (l < 0 || l >= gdi._numStrips || b <= t)
+ continue;
+ if (b > bottom)
+ b = bottom;
+ if (t < 0)
+ t = 0;
+ virtscr[0].tdirty[l] = _screenTop + t * 8;
+ virtscr[0].bdirty[l] = _screenTop + (b + 1) * 8;
+ }
+ updateDirtyScreen(kMainVirtScreen);
+ }
+
+ for (i = 0; i < 16; i++)
+ tab_2[i] += delta[i];
+
+ // Draw the current state to the screen and wait half a sec so the user
+ // can watch the effect taking place.
+ _system->updateScreen();
+ waitForTimer(30);
+ }
+}
+
+/**
+ * Update width*height areas of the screen, in random order, until the whole
+ * screen has been updated. For instance:
+ *
+ * dissolveEffect(1, 1) produces a pixel-by-pixel dissolve
+ * dissolveEffect(8, 8) produces a square-by-square dissolve
+ * dissolveEffect(virtsrc[0].width, 1) produces a line-by-line dissolve
+ */
+void ScummEngine::dissolveEffect(int width, int height) {
+#ifdef PALMOS_68K
+ // Remove this dissolve effect for now on PalmOS since it is a bit
+ // too slow using 68k emulation
+ if (width == 1 && height == 1) {
+ waitForTimer(30);
+ return;
+ }
+#endif
+
+ VirtScreen *vs = &virtscr[0];
+ int *offsets;
+ int blits_before_refresh, blits;
+ int x, y;
+ int w, h;
+ int i;
+
+ // There's probably some less memory-hungry way of doing this. But
+ // since we're only dealing with relatively small images, it shouldn't
+ // be too bad.
+
+ w = vs->w / width;
+ h = vs->h / height;
+
+ // When used correctly, vs->width % width and vs->height % height
+ // should both be zero, but just to be safe...
+
+ if (vs->w % width)
+ w++;
+
+ if (vs->h % height)
+ h++;
+
+ offsets = (int *) malloc(w * h * sizeof(int));
+ if (offsets == NULL)
+ error("dissolveEffect: out of memory");
+
+ // Create a permutation of offsets into the frame buffer
+
+ if (width == 1 && height == 1) {
+ // Optimized case for pixel-by-pixel dissolve
+
+ for (i = 0; i < vs->w * vs->h; i++)
+ offsets[i] = i;
+
+ for (i = 1; i < w * h; i++) {
+ int j;
+
+ j = _rnd.getRandomNumber(i - 1);
+ offsets[i] = offsets[j];
+ offsets[j] = i;
+ }
+ } else {
+ int *offsets2;
+
+ for (i = 0, x = 0; x < vs->w; x += width)
+ for (y = 0; y < vs->h; y += height)
+ offsets[i++] = y * vs->pitch + x;
+
+ offsets2 = (int *) malloc(w * h * sizeof(int));
+ if (offsets2 == NULL)
+ error("dissolveEffect: out of memory");
+
+ memcpy(offsets2, offsets, w * h * sizeof(int));
+
+ for (i = 1; i < w * h; i++) {
+ int j;
+
+ j = _rnd.getRandomNumber(i - 1);
+ offsets[i] = offsets[j];
+ offsets[j] = offsets2[i];
+ }
+
+ free(offsets2);
+ }
+
+ // Blit the image piece by piece to the screen. The idea here is that
+ // the whole update should take about a quarter of a second, assuming
+ // most of the time is spent in waitForTimer(). It looks good to me,
+ // but might still need some tuning.
+
+ blits = 0;
+ blits_before_refresh = (3 * w * h) / 25;
+
+ // Speed up the effect for CD Loom since it uses it so often. I don't
+ // think the original had any delay at all, so on modern hardware it
+ // wasn't even noticeable.
+ if (_gameId == GID_LOOM && (_version == 4))
+ blits_before_refresh *= 2;
+
+ for (i = 0; i < w * h; i++) {
+ x = offsets[i] % vs->pitch;
+ y = offsets[i] / vs->pitch;
+ _system->copyRectToScreen(vs->getPixels(x, y), vs->pitch, x, y + vs->topline, width, height);
+
+ if (++blits >= blits_before_refresh) {
+ blits = 0;
+ _system->updateScreen();
+ waitForTimer(30);
+ }
+ }
+
+ free(offsets);
+
+ if (blits != 0) {
+ _system->updateScreen();
+ waitForTimer(30);
+ }
+}
+
+void ScummEngine::scrollEffect(int dir) {
+ // It is at least technically possible that this function will be
+ // called without _scrollBuffer having been set up, but will it ever
+ // happen? I don't know.
+ if (!_scrollBuffer)
+ warning("scrollEffect: No scroll buffer. This may look bad");
+
+ VirtScreen *vs = &virtscr[0];
+
+ int x, y;
+ int step;
+
+ if ((dir == 0) || (dir == 1))
+ step = vs->h;
+ else
+ step = vs->w;
+
+ step = (step * kPictureDelay) / kScrolltime;
+
+ switch (dir) {
+ case 0:
+ //up
+ y = step;
+ while (y < vs->h) {
+ _system->copyRectToScreen(vs->getPixels(0, 0),
+ vs->pitch,
+ 0, vs->h - y,
+ vs->w, y);
+ if (_scrollBuffer)
+ _system->copyRectToScreen(_scrollBuffer + y * vs->w,
+ vs->pitch,
+ 0, 0,
+ vs->w, vs->h - y);
+ _system->updateScreen();
+ waitForTimer(kPictureDelay);
+
+ y += step;
+ }
+ break;
+ case 1:
+ // down
+ y = step;
+ while (y < vs->h) {
+ _system->copyRectToScreen(vs->getPixels(0, vs->h - y),
+ vs->pitch,
+ 0, 0,
+ vs->w, y);
+ if (_scrollBuffer)
+ _system->copyRectToScreen(_scrollBuffer,
+ vs->pitch,
+ 0, y,
+ vs->w, vs->h - y);
+ _system->updateScreen();
+ waitForTimer(kPictureDelay);
+
+ y += step;
+ }
+ break;
+ case 2:
+ // left
+ x = step;
+ while (x < vs->w) {
+ _system->copyRectToScreen(vs->getPixels(0, 0),
+ vs->pitch,
+ vs->w - x, 0,
+ x, vs->h);
+ if (_scrollBuffer)
+ _system->copyRectToScreen(_scrollBuffer + x,
+ vs->pitch,
+ 0, 0,
+ vs->w - x, vs->h);
+ _system->updateScreen();
+ waitForTimer(kPictureDelay);
+
+ x += step;
+ }
+ break;
+ case 3:
+ // right
+ x = step;
+ while (x < vs->w) {
+ _system->copyRectToScreen(vs->getPixels(vs->w - x, 0),
+ vs->pitch,
+ 0, 0,
+ x, vs->h);
+ if (_scrollBuffer)
+ _system->copyRectToScreen(_scrollBuffer,
+ vs->pitch,
+ x, 0,
+ vs->w - x, vs->h);
+ _system->updateScreen();
+ waitForTimer(kPictureDelay);
+
+ x += step;
+ }
+ break;
+ }
+
+ free(_scrollBuffer);
+ _scrollBuffer = NULL;
+}
+
+void ScummEngine::unkScreenEffect6() {
+ // CD Loom (but not EGA Loom!) uses a more fine-grained dissolve
+ if (_gameId == GID_LOOM && (_version == 4))
+ dissolveEffect(1, 1);
+ else
+ dissolveEffect(8, 4);
+}
+
+} // End of namespace Scumm
+
+#ifdef PALMOS_68K
+#include "scumm_globals.h"
+
+_GINIT(Gfx)
+_GSETPTR(Scumm::transitionEffects, GBVARS_TRANSITIONEFFECTS_INDEX, Scumm::TransitionEffect, GBVARS_SCUMM)
+_GEND
+
+_GRELEASE(Gfx)
+_GRELEASEPTR(GBVARS_TRANSITIONEFFECTS_INDEX, GBVARS_SCUMM)
+_GEND
+
+#endif