[Scummvm-git-logs] scummvm master -> 713f9f2624f048d725c2389d6aeae9747c2ad032

npjg nathanael.gentrydb8 at gmail.com
Tue Jun 30 16:39:03 UTC 2020


This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
d04934a54f DIRECTOR: Move rendering to a Stage class
30bd4de3a7 Revert "GRAPHICS: MACGUI: Add QuickDraw class"
1ddc4bf6bd GRAPHICS: MACGUI: MacButton: Properly mark dirty button states
93a5c39381 DIRECTOR: Cache widgets for all loaded cast
487dcec139 DIRECTOR: Remove shape cast hack
713f9f2624 DIRECTOR: Unify sprite and channel internal rendering methods


Commit: d04934a54fdc05c92f4bb71ba9a6d2ba1a60da53
    https://github.com/scummvm/scummvm/commit/d04934a54fdc05c92f4bb71ba9a6d2ba1a60da53
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
DIRECTOR: Move rendering to a Stage class

This commit introduces several changes to the rendering pipeline.

 - Rather than having a window for each score that is played, there is a Stage
   in g_director that is only cleared when the upcoming stage has a different
   size. This permits transitions still clean transitions between movies.

 - Rather than relying upon general surface blitting functions, there is
   progress toward a custom blitter than makes working with inks much easier. A
	 pixelwise function, inkDrawPixel, has been introduced.

 - The mask-based approach to incremental rendering has been abandoned, as it
   caused performance issues and strange rendering bugs. Now the renderer
	 operates on a traditional list of dirty rects that must be redrawn.

 - QuickDraw shapes are more closely integrated with the renderer, and they do
    not require a temporary surface to draw.

Changed paths:
  A engines/director/stage.cpp
  A engines/director/stage.h
  R engines/director/ink.cpp
    engines/director/cast.cpp
    engines/director/director.cpp
    engines/director/director.h
    engines/director/graphics.cpp
    engines/director/lingo/lingo-builtins.cpp
    engines/director/lingo/lingo-the.cpp
    engines/director/module.mk
    engines/director/score-loading.cpp
    engines/director/score.cpp
    engines/director/score.h
    engines/director/sprite.cpp
    engines/director/transitions.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 74b0627f4c..21f8fcf035 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -138,7 +138,7 @@ void BitmapCast::createWidget() {
 		return;
 	}
 
-	_widget = new Graphics::MacWidget(g_director->getCurrentScore()->_window, 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, false);
+	_widget = new Graphics::MacWidget(g_director->getStage(), 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, false);
 	_widget->getSurface()->blitFrom(*_img->getSurface());
 }
 
@@ -394,13 +394,13 @@ void TextCast::createWidget() {
 
 	switch (_type) {
 	case kCastText:
-		_widget = new Graphics::MacText(g_director->getCurrentScore()->_window, 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), 1, _borderSize, _gutterSize, _boxShadow, _textShadow);
+		_widget = new Graphics::MacText(g_director->getStage(), 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), 1, _borderSize, _gutterSize, _boxShadow, _textShadow);
 
 		((Graphics::MacText *)_widget)->draw();
 		break;
 
 	case kCastButton:
-		_widget = new Graphics::MacButton(Graphics::MacButtonType(_buttonType), getAlignment(), g_director->getCurrentScore()->_window, 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), 0xff);
+		_widget = new Graphics::MacButton(Graphics::MacButtonType(_buttonType), getAlignment(), g_director->getStage(), 0, 0, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), 0xff);
 		((Graphics::MacButton *)_widget)->draw();
 		_widget->_focusable = true;
 
diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 169ec2d9e8..6014ececc5 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -32,6 +32,7 @@
 #include "graphics/macgui/macwindowmanager.h"
 
 #include "director/director.h"
+#include "director/stage.h"
 #include "director/archive.h"
 #include "director/score.h"
 #include "director/sound.h"
@@ -120,6 +121,8 @@ DirectorEngine::~DirectorEngine() {
 	delete _sharedScore;
 	delete _currentScore;
 
+	_wm->removeWindow(_currentStage);
+
 	if (_macBinary) {
 		delete _macBinary;
 		_macBinary = nullptr;
@@ -145,6 +148,11 @@ Common::Error DirectorEngine::run() {
 	_wm = new Graphics::MacWindowManager(wmMode, &_director3QuickDrawPatterns);
 	_wm->setEngine(this);
 
+	_currentStage = new Stage(_wm->getNextId(), false, false, false, _wm);
+	_currentStage->disableBorder();
+	_wm->addWindowInitialized(_currentStage);
+	_wm->setScreen(_currentStage->getSurface());
+
 	_lingo = new Lingo(this);
 	_soundManager = new DirectorSound(this);
 
@@ -274,13 +282,7 @@ Common::Error DirectorEngine::run() {
 
 		// If a loop was requested, do it
 		if (!_nextMovie.movie.empty()) {
-			// Persist screen between the movies
-			// TODO: this is a workaround until the rendering pipeline is reworked
-			if (_currentScore && _currentScore->_surface) {
-				_backSurface.copyFrom(*_currentScore->_surface);
-
-				_newMovieStarted = true;
-			}
+			_newMovieStarted = true;
 
 			delete _currentScore;
 			_currentScore = nullptr;
diff --git a/engines/director/director.h b/engines/director/director.h
index dabfbd498f..e0d41d28d0 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -41,6 +41,7 @@ class SeekableSubReadStreamEndian;
 
 namespace Graphics {
 class MacWindowManager;
+struct MacPlotData;
 typedef Common::Array<byte *> MacPatterns;
 }
 
@@ -56,6 +57,7 @@ class Archive;
 struct DirectorGameDescription;
 class DirectorSound;
 class Lingo;
+class Stage;
 class Score;
 class Cast;
 class Stxt;
@@ -92,6 +94,28 @@ struct PaletteV4 {
 	int length;
 };
 
+// An extension of MacPlotData for interfacing with inks and patterns without
+// needing extra surfaces.
+struct DirectorPlotData {
+	Graphics::ManagedSurface *src;
+	Graphics::ManagedSurface *dst;
+	Graphics::MacPlotData *macPlot;
+	Common::Rect destRect;
+	Common::Point srcPoint;
+
+	InkType ink;
+	int numColors;
+	uint backColor;
+
+	Graphics::MacWindowManager *_wm;
+
+	DirectorPlotData(Graphics::MacWindowManager *wm, Graphics::ManagedSurface *s, Graphics::ManagedSurface *d, InkType i, uint b, uint n) :
+		src(s), dst(d), ink(i), backColor(b), macPlot(nullptr), numColors(n), _wm(wm) {
+	}
+};
+
+void inkDrawPixel(int x, int y, int color, void *data);
+
 class DirectorEngine : public ::Engine {
 public:
 	DirectorEngine(OSystem *syst, const DirectorGameDescription *gameDesc);
@@ -109,6 +133,7 @@ public:
 	Graphics::MacWindowManager *getMacWindowManager() const { return _wm; }
 	Archive *getMainArchive() const { return _mainArchive; }
 	Lingo *getLingo() const { return _lingo; }
+	Stage *getStage() const { return _currentStage; }
 	Score *getCurrentScore() const { return _currentScore; }
 	Score *getSharedScore() const { return _sharedScore; }
 	Common::String getCurrentPath() const { return _currentPath; }
@@ -126,6 +151,7 @@ public:
 	uint32 transformColor(uint32 color);
 	Graphics::MacPatterns &getPatterns();
 	void setCursor(int type); // graphics.cpp
+
 	void loadKeyCodes();
 
 	void loadInitialMovie(const Common::String movie);
@@ -157,7 +183,6 @@ public:
 	MovieReference _nextMovie;
 	Common::List<MovieReference> _movieStack;
 
-	Graphics::ManagedSurface _backSurface;
 	bool _newMovieStarted;
 
 protected:
@@ -184,6 +209,7 @@ private:
 	uint16 _currentPaletteLength;
 	Lingo *_lingo;
 
+	Stage *_currentStage;
 	Score *_currentScore;
 	Common::String _currentPath;
 
@@ -201,7 +227,7 @@ private:
 	Common::StringArray _movieQueue;
 
 
-	// tests.cpp
+// tests.cpp
 private:
 	void testFontScaling();
 	void testFonts();
diff --git a/engines/director/graphics.cpp b/engines/director/graphics.cpp
index 5f0a193d7e..b85c69adc4 100644
--- a/engines/director/graphics.cpp
+++ b/engines/director/graphics.cpp
@@ -793,4 +793,115 @@ void DirectorEngine::setCursor(int type) {
 	}
 }
 
+void inkDrawPixel(int x, int y, int color, void *data) {
+	DirectorPlotData *p = (DirectorPlotData *)data;
+
+	if (!p->destRect.contains(x, y))
+		return;
+
+	byte rSrc, gSrc, bSrc;
+	byte rDst, gDst, bDst;
+
+	const byte *src;
+	byte *dst;
+
+	byte tmpSrc, tmpDst;
+
+	dst = (byte *)p->dst->getBasePtr(x, y);
+
+	if (p->macPlot) {
+		// Get the pixel that macDrawPixel will give us, but store it to apply the
+		// ink later.
+		tmpDst = *dst;
+		Graphics::macDrawPixel(x, y, color, p->macPlot);
+		tmpSrc = *dst;
+
+		*dst = tmpDst;
+		src = &tmpSrc;
+	} else {
+		if (!p->src)
+			error("Director::inkDrawPixel(): No source surface");
+
+		src = (const byte *)p->src->getBasePtr(p->srcPoint.x, p->srcPoint.y);
+	}
+
+	g_director->_wm->decomposeColor(*src, rSrc, gSrc, bSrc);
+	g_director->_wm->decomposeColor(*dst, rDst, gDst, bDst);
+
+	switch (p->ink) {
+	case kInkTypeBackgndTrans:
+		if (*src == p->backColor)
+			break;
+		// fall through
+	case kInkTypeCopy:
+		*dst = *src;
+	case kInkTypeMask:
+		// TODO: Migrate from Stage to here
+		break;
+	case kInkTypeTransparent:
+		// FIXME: Is colour to ignore always white (last entry in pallette)?
+		if (*src != p->numColors - 1)
+			*dst &= *src;
+		break;
+	case kInkTypeReverse:
+		// TODO: Migrate from Stage to here
+		*dst ^= *src;
+		break;
+	case kInkTypeGhost:
+		if (*src != p->numColors - 1)
+			*dst = *dst | ~(*src);
+		break;
+	case kInkTypeNotCopy:
+		if (*src != p->numColors - 1)
+			*dst = ~(*src);
+		break;
+	case kInkTypeNotTrans:
+		if (*src != p->numColors - 1)
+			*dst = *dst & ~(*src);
+		break;
+	case kInkTypeNotReverse:
+		if (*src != p->numColors - 1)
+			*dst = *dst ^ *src;
+		break;
+	case kInkTypeNotGhost:
+		if (*src != p->numColors - 1)
+			*dst = *dst | *src;
+		break;
+	case kInkTypeMatte:
+		// TODO: Migrate from Stage to here.
+		break;
+		// Arithmetic ink types
+	case kInkTypeBlend:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor((rSrc + rDst) / 2, (gSrc + gDst) / 2, (bSrc + bDst) / 2);
+		break;
+	case kInkTypeAddPin:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(MIN((rSrc + rDst), p->numColors - 1), MIN((gSrc + gDst), p->numColors - 1), MIN((bSrc + bDst), p->numColors - 1));
+		break;
+	case kInkTypeAdd:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(abs(rSrc + rDst) % p->numColors, abs(gSrc + gDst) % p->numColors, abs(bSrc + bDst) % p->numColors);
+		break;
+	case kInkTypeSubPin:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(MAX(rSrc - rDst, 0), MAX(gSrc - gDst, 0), MAX(bSrc - bDst, 0));
+		break;
+	case kInkTypeLight:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(MAX(rSrc, rDst), MAX(gSrc, gDst), MAX(bSrc, bDst));
+		break;
+	case kInkTypeSub:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(abs(rSrc - rDst) % p->numColors, abs(gSrc - gDst) % p->numColors, abs(bSrc - bDst) % p->numColors);
+		break;
+	case kInkTypeDark:
+		if (*src != p->numColors - 1)
+			*dst = p->_wm->findBestColor(MIN(rSrc, rDst), MIN(gSrc, gDst), MIN(bSrc, bDst));
+		break;
+	default:
+		warning("Stage::inkBlitFrom: Drawing not implemented yet");
+	}
+}
+
 }
diff --git a/engines/director/ink.cpp b/engines/director/ink.cpp
deleted file mode 100644
index fe47037589..0000000000
--- a/engines/director/ink.cpp
+++ /dev/null
@@ -1,278 +0,0 @@
-/* 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.
- *
- */
-
-#include "director/director.h"
-#include "director/cast.h"
-#include "director/score.h"
-#include "director/sprite.h"
-
-namespace Director {
-
-void Score::inkBasedBlit(Graphics::ManagedSurface *destSurface, Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId) {
-
-	byte rSrc, gSrc, bSrc;
-	byte rDst, gDst, bDst;
-	int numColors = _vm->getPaletteColorCount();
-
-	Common::Rect t = Common::Rect(spriteSurface.w, spriteSurface.h);
-	t.moveTo(drawRect.left, drawRect.top);
-
-	Common::Point maskOrigin(MAX(0, -drawRect.left), MAX(0, -drawRect.top));
-	drawRect.clip(Common::Rect(_maskSurface->w, _maskSurface->h));
-
-	Graphics::ManagedSurface *castMask = nullptr;
-	if (ink == kInkTypeMask) {
-		Cast *member = g_director->getCastMember(_channels[spriteId]->_sprite->_castId + 1);
-
-		if (!member->_widget)
-			member->createWidget();
-
-		if (member->_initialRect == _channels[spriteId]->_sprite->_cast->_initialRect)
-			castMask = member->_widget->getSurface();
-	}
-
-	// HACK: A custom blitter is needed for the logical AND necessary here;
-	// surface class doesn't provide it.
-	for (int ii = 0; ii < drawRect.height(); ii++) {
-		const byte *msk = castMask ? (const byte *)castMask->getBasePtr(maskOrigin.x, maskOrigin.y + ii) : nullptr;
-		const byte *src = (const byte *)maskSurface->getBasePtr(maskOrigin.x, maskOrigin.y + ii);
-		byte *dst = (byte *)_maskSurface->getBasePtr(t.left + maskOrigin.x, t.top + maskOrigin.y + ii);
-
-		for (int j = 0; j < drawRect.width(); j++, src++, dst++) {
-			*dst &= *src;
-
-			if (msk)
-				*dst = (*(msk++) ? 0 : *dst);
-		}
-	}
-
-	// TODO: Merge these two into the switch logic that is below
-	if (ink == kInkTypeMatte) {
-		Common::Rect spriteRect(spriteSurface.w, spriteSurface.h);
-		drawMatteSprite(destSurface, spriteSurface, t);
-		return;
-	} else if (ink == kInkTypeReverse) {
-		drawReverseSprite(destSurface, spriteSurface, t, spriteId);
-		return;
-	}
-
-	for (int ii = 0; ii < drawRect.height(); ii++) {
-		const byte *msk = (const byte *)_maskSurface->getBasePtr(t.left + maskOrigin.x, t.top + maskOrigin.y + ii);
-		const byte *src = (const byte *)spriteSurface.getBasePtr(maskOrigin.x, ii + maskOrigin.y);
-		byte *dst = (byte *)destSurface->getBasePtr(t.left + maskOrigin.x, t.top + maskOrigin.y + ii);
-
-		for (int j = 0; j < drawRect.width(); j++, msk++, src++, dst++) {
-			if (*msk) {
-				_vm->_wm->decomposeColor(*src, rSrc, gSrc, bSrc);
-				_vm->_wm->decomposeColor(*dst, rDst, gDst, bDst);
-
-				switch (ink) {
-				case kInkTypeBackgndTrans:
-					if (*src == _channels[spriteId]->_sprite->_backColor)
-						break;
-					// fall through
-				case kInkTypeCopy:
-				case kInkTypeMask:
-					*dst = *src;
-					break;
-				case kInkTypeTransparent:
-					// FIXME: Is colour to ignore always white (last entry in pallette)?
-					if (*src != numColors - 1)
-						*dst &= *src;
-					break;
-				case kInkTypeReverse:
-					if (*src != numColors - 1)
-						*dst ^= *src;
-					break;
-				case kInkTypeGhost:
-					if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && *src != numColors - 1)
-						*dst = *dst | ~(*src);
-					break;
-				case kInkTypeNotCopy:
-					if (*src != numColors - 1)
-						*dst = ~(*src);
-					break;
-				case kInkTypeNotTrans:
-					if (*src != numColors - 1)
-						*dst = *dst & ~(*src);
-					break;
-				case kInkTypeNotReverse:
-					if (*src != numColors - 1)
-						*dst = *dst ^ *src;
-					break;
-				case kInkTypeNotGhost:
-					if (*src != numColors - 1)
-						*dst = *dst | *src;
-					break;
-				case kInkTypeMatte:
-					break;
-					// Arithmetic ink types
-				case kInkTypeBlend:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor((rSrc + rDst) / 2, (gSrc + gDst) / 2, (bSrc + bDst) / 2);
-					break;
-				case kInkTypeAddPin:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(MIN((rSrc + rDst), numColors - 1), MIN((gSrc + gDst), numColors - 1), MIN((bSrc + bDst), numColors - 1));
-					break;
-				case kInkTypeAdd:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(abs(rSrc + rDst) % numColors, abs(gSrc + gDst) % numColors, abs(bSrc + bDst) % numColors);
-					break;
-				case kInkTypeSubPin:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(MAX(rSrc - rDst, 0), MAX(gSrc - gDst, 0), MAX(bSrc - bDst, 0));
-					break;
-				case kInkTypeLight:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(MAX(rSrc, rDst), MAX(gSrc, gDst), MAX(bSrc, bDst));
-					break;
-				case kInkTypeSub:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(abs(rSrc - rDst) % numColors, abs(gSrc - gDst) % numColors, abs(bSrc - bDst) % numColors);
-					break;
-				case kInkTypeDark:
-					if (*src != numColors - 1)
-						*dst = _vm->_wm->findBestColor(MIN(rSrc, rDst), MIN(gSrc, gDst), MIN(bSrc, bDst));
-					break;
-				}
-			}
-		}
-	}
-}
-
-void Score::drawReverseSprite(Graphics::ManagedSurface *destSurface, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId) {
-	Common::Rect srcRect(sprite.w, sprite.h);
-
-	if (!_surface->clip(srcRect, drawRect))
-		return; // Out of screen
-
-	uint8 skipColor = _vm->getPaletteColorCount() - 1;
-	for (int ii = 0; ii < drawRect.height(); ii++) {
-		const byte *msk = (const byte *)_maskSurface->getBasePtr(drawRect.left, drawRect.top + ii);
-		const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
-		byte *dst = (byte *)destSurface->getBasePtr(drawRect.left, drawRect.top + ii);
-		byte srcColor = *src;
-
-		for (int j = 0; j < drawRect.width(); j++, msk++, src++, dst++) {
-			if (*msk != 0) {
-				if (!_channels[spriteId]->_sprite->_cast || _channels[spriteId]->_sprite->_cast->_type == kCastShape)
-					srcColor = 0x0;
-				else
-					srcColor = *src;
-				uint16 targetSprite = getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii));
-				if ((targetSprite != 0)) {
-					// TODO: This entire reverse colour attempt needs a lot more testing on
-					// a lot more colour depths.
-					if (srcColor != skipColor) {
-						if (!_channels[targetSprite]->_sprite->_cast || _channels[targetSprite]->_sprite->_cast->_type != kCastBitmap) {
-							if (*dst == 0 || *dst == 255) {
-								*dst = _vm->transformColor(*dst);
-							} else if (srcColor == 255 || srcColor == 0) {
-								*dst = _vm->transformColor(*dst - 40);
-							} else {
-								*dst = _vm->transformColor(*src - 40);
-							}
-						} else {
-							if (*dst == 0 && _vm->getVersion() == 3 &&
-									_channels[spriteId]->_sprite->_cast->_type == kCastBitmap &&
-									((BitmapCast*)_channels[spriteId]->_sprite->_cast)->_bitsPerPixel > 1) {
-								*dst = _vm->transformColor(*src - 40);
-							} else {
-								*dst ^= _vm->transformColor(srcColor);
-							}
-						}
-					}
-				} else if (srcColor != skipColor) {
-					*dst = _vm->transformColor(srcColor);
-				}
-			}
-		}
-	}
-}
-
-	void Score::drawMatteSprite(Graphics::ManagedSurface *destSurface, const Graphics::Surface &sprite, Common::Rect &drawRect) {
-	// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
-	Graphics::Surface tmp;
-	tmp.copyFrom(sprite);
-	Common::Rect srcRect(sprite.w, sprite.h);
-
-	if (!_surface->clip(srcRect, drawRect))
-		return; // Out of screen
-
-	// Searching white color in the corners
-	int whiteColor = -1;
-
-	for (int y = 0; y < tmp.h; y++) {
-		for (int x = 0; x < tmp.w; x++) {
-			byte color = *(byte *)tmp.getBasePtr(x, y);
-
-			if (_vm->getPalette()[color * 3 + 0] == 0xff &&
-				_vm->getPalette()[color * 3 + 1] == 0xff &&
-				_vm->getPalette()[color * 3 + 2] == 0xff) {
-				whiteColor = color;
-				break;
-			}
-		}
-	}
-
-	if (whiteColor == -1) {
-		debugC(1, kDebugImages, "Score::drawMatteSprite(): No white color for Matte image");
-
-		for (int yy = 0; yy < drawRect.height(); yy++) {
-			const byte *msk = (const byte *)_maskSurface->getBasePtr(drawRect.left, drawRect.top + yy);
-			const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
-			byte *dst = (byte *)destSurface->getBasePtr(drawRect.left, drawRect.top + yy);
-
-			for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, msk++)
-				if (*msk != 0)
-					*dst = *src;
-		}
-	} else {
-		Graphics::FloodFill ff(&tmp, whiteColor, 0, true);
-
-		for (int yy = 0; yy < tmp.h; yy++) {
-			ff.addSeed(0, yy);
-			ff.addSeed(tmp.w - 1, yy);
-		}
-
-		for (int xx = 0; xx < tmp.w; xx++) {
-			ff.addSeed(xx, 0);
-			ff.addSeed(xx, tmp.h - 1);
-		}
-		ff.fillMask();
-
-		for (int yy = 0; yy < drawRect.height(); yy++) {
-			const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
-			const byte *mask = (const byte *)ff.getMask()->getBasePtr(srcRect.left, srcRect.top + yy);
-			byte *dst = (byte *)destSurface->getBasePtr(drawRect.left, drawRect.top + yy);
-
-			for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, mask++)
-				if (*mask == 0)
-					*dst = *src;
-		}
-	}
-
-	tmp.free();
-}
-
-} // End of namespace Director
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 2222d22102..ed9e282bf4 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1744,6 +1744,7 @@ void LB::b_puppetTempo(int nargs) {
 void LB::b_puppetTransition(int nargs) {
 	// puppetTransition whichTransition [, time] [, chunkSize] [, changeArea]
 	Score *score = g_director->getCurrentScore();
+	Stage *stage = g_director->getStage();
 	uint16 duration = 250, area = 1, chunkSize = 1, type = 0;
 	if (nargs == 4) {
 		area = g_lingo->pop().asInt();
@@ -1766,7 +1767,7 @@ void LB::b_puppetTransition(int nargs) {
 		ARGNUMCHECK(1);
 	}
 
-	score->playTransition(duration, area, chunkSize, ((TransitionType)type));
+	stage->playTransition(duration, area, chunkSize, ((TransitionType)type), score->getCurrentFrame());
 }
 
 void LB::b_ramNeeded(int nargs) {
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 430f049397..6f3d25f0b8 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -649,16 +649,8 @@ void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) {
 			}
 		}
 		break;
-	case kTheStageColor: {
-		Score *score = _vm->getCurrentScore();
-		score->_stageColor = _vm->transformColor(d.asInt());
-		if (score->_surface) {
-			score->_surface->clear(score->_stageColor);
-			score->renderFrame(score->getCurrentFrame(), kRenderForceUpdate);
-		} else {
-			warning("setStageColor: score has no surface, skipping");
-		}
-	}
+	case kTheStageColor:
+		g_director->getStage()->setStageColor(d.asInt());
 		break;
 	case kTheSprite:
 		setTheSprite(id, field, d);
@@ -866,7 +858,7 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 		break;
 	case kTheHeight:
 		if (sprite->_puppet && sprite->_stretch) {
-			score->markDirtyRect(channel->getBbox());
+			g_director->getStage()->addDirtyRect(channel->getBbox());
 			sprite->_height = d.asInt();
 		}
 		break;
@@ -928,7 +920,7 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 		break;
 	case kTheWidth:
 		if (sprite->_puppet && sprite->_stretch) {
-			score->markDirtyRect(channel->getBbox());
+			g_director->getStage()->addDirtyRect(channel->getBbox());
 			sprite->_width = d.asInt();
 		}
 		break;
diff --git a/engines/director/module.mk b/engines/director/module.mk
index ad04ef32e7..196a030656 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -9,9 +9,9 @@ MODULE_OBJS = \
 	frame.o \
 	graphics.o \
 	images.o \
-	ink.o \
 	movie.o \
 	resource.o \
+	stage.o \
 	score-loading.o \
 	score.o \
 	sound.o \
diff --git a/engines/director/score-loading.cpp b/engines/director/score-loading.cpp
index d108cb63c8..175ccf982a 100644
--- a/engines/director/score-loading.cpp
+++ b/engines/director/score-loading.cpp
@@ -126,9 +126,15 @@ bool Score::loadArchive(bool isSharedCast) {
 	}
 
 	if (!isSharedCast) {
-		_window = _vm->_wm->addWindow(false, false, false);
-		_window->disableBorder();
-		_window->resize(_movieRect.width(), _movieRect.height());
+		Stage *stage = g_director->getStage();
+
+		// If the stage dimensions are different, delete it and start again.
+		// Otherwise, do not clear it so there can be a nice transition.
+		if (stage->getSurface()->w != _movieRect.width() || stage->getSurface()->h != _movieRect.height()) {
+			stage->resize(_movieRect.width(), _movieRect.height());
+		}
+
+		stage->setStageColor(_stageColor);
 	}
 
 	// Cast Information Array
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index dba19a86bc..2fd6e41301 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -59,6 +59,14 @@ Channel::Channel(Sprite *sp) {
 	}
 }
 
+Graphics::ManagedSurface *Channel::getSurface() {
+	if (_sprite->_cast && _sprite->_cast->_widget) {
+		return  _sprite->_cast->_widget->getSurface();
+	} else {
+		return nullptr;
+	}
+}
+
 Common::Rect Channel::getBbox() {
 	Common::Rect bbox = _sprite->getDims();
 	bbox.moveTo(getPosition());
@@ -111,10 +119,65 @@ void Channel::resetPosition() {
 	_delta = _sprite->_startPoint;
 }
 
+MacShape *Channel::getShape() {
+	MacShape *shape = new MacShape();
+
+	if (_sprite->_castType != kCastShape)
+		return nullptr;
+
+	shape->ink = _sprite->_ink;
+	shape->spriteType = _sprite->_spriteType;
+	shape->foreColor = _sprite->_foreColor;
+	shape->backColor = _sprite->_backColor;
+	shape->lineSize = _sprite->_thickness & 0x3;
+
+	if (g_director->getVersion() >= 3 && shape->spriteType == kCastMemberSprite) {
+		if (!_sprite->_cast) {
+			warning("Channel::getShape(): kCastMemberSprite has no cast defined");
+			return nullptr;
+		}
+		switch (_sprite->_cast->_type) {
+		case kCastShape:
+			{
+				ShapeCast *sc = (ShapeCast *)_sprite->_cast;
+				switch (sc->_shapeType) {
+				case kShapeRectangle:
+					shape->spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite;
+					break;
+				case kShapeRoundRect:
+					shape->spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite;
+					break;
+				case kShapeOval:
+					shape->spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite;
+					break;
+				case kShapeLine:
+					shape->spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite;
+					break;
+				default:
+					break;
+				}
+				if (g_director->getVersion() > 3) {
+					shape->foreColor = sc->_fgCol;
+					shape->backColor = sc->_bgCol;
+					shape->lineSize = sc->_lineThickness;
+					shape->ink = sc->_ink;
+				}
+			}
+			break;
+		default:
+			warning("Channel::getShape(): Unhandled cast type: %d", _sprite->_cast->_type);
+			break;
+		}
+	}
+
+	// for outlined shapes, line thickness of 1 means invisible.
+	shape->lineSize -= 1;
+
+	return shape;
+}
+
 Score::Score(DirectorEngine *vm) {
 	_vm = vm;
-	_surface = nullptr;
-	_maskSurface = nullptr;
 	_lingo = _vm->getLingo();
 	_lingoArchive = kArchMain;
 	_soundManager = _vm->getSoundManager();
@@ -155,21 +218,9 @@ Score::Score(DirectorEngine *vm) {
 	_numChannelsDisplayed = 0;
 
 	_framesRan = 0; // used by kDebugFewFramesOnly and kDebugScreenshot
-
-	_window = nullptr;
-
-	_stageColor = 0;
 }
 
 Score::~Score() {
-	if (_maskSurface && _maskSurface->w)
-		_maskSurface->free();
-
-	delete _maskSurface;
-
-	if (_window)
-		_vm->_wm->removeWindow(_window);
-
 	for (uint i = 0; i < _frames.size(); i++)
 		delete _frames[i];
 
@@ -363,14 +414,6 @@ void Score::startLoop() {
 
 	initGraphics(_movieRect.width(), _movieRect.height());
 
-	_surface = _window->getWindowSurface();
-	_vm->_wm->setScreen(_surface);
-
-	_maskSurface = new Graphics::ManagedSurface;
-	_maskSurface->create(_movieRect.width(), _movieRect.height());
-
-	_surface->clear(_stageColor);
-
 	_currentFrame = 0;
 	_stopPlay = false;
 	_nextFrameTime = 0;
@@ -388,7 +431,6 @@ void Score::startLoop() {
 	if (_vm->getVersion() >= 3)
 		_lingo->processEvent(kEventStartMovie);
 
-	_maskSurface->clear(1);
 	while (!_stopPlay) {
 		if (_currentFrame >= _frames.size()) {
 			if (debugChannelSet(-1, kDebugNoLoop))
@@ -398,7 +440,6 @@ void Score::startLoop() {
 		}
 
 		update();
-		_maskSurface->clear(0);
 
 		if (_currentFrame < _frames.size())
 			_vm->processEvents();
@@ -482,7 +523,6 @@ void Score::update() {
 	}
 
 	debugC(1, kDebugImages, "******************************  Current frame: %d", _currentFrame);
-	_vm->_newMovieStarted = false;
 
 	_lingo->executeImmediateScripts(_frames[_currentFrame]);
 
@@ -493,8 +533,9 @@ void Score::update() {
 		// TODO: Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame
 	}
 
-	renderFrame(_currentFrame);
 	// Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100)
+	renderFrame(_currentFrame);
+	_vm->_newMovieStarted = false;
 
 	// Enter and exit from previous frame
 	if (!_vm->_playbackPaused) {
@@ -547,37 +588,31 @@ void Score::update() {
 void Score::renderFrame(uint16 frameId, RenderMode mode) {
 	Frame *currentFrame = _frames[frameId];
 
-	// When a transition is played, the next frame is rendered as a part of it.
 	if (currentFrame->_transType != 0 && mode != kRenderUpdateStageOnly) {
 		// TODO Handle changing area case
-		playTransition(currentFrame->_transDuration, currentFrame->_transArea, currentFrame->_transChunkSize, currentFrame->_transType);
+		g_director->getStage()->playTransition(currentFrame->_transDuration, currentFrame->_transArea, currentFrame->_transChunkSize, currentFrame->_transType, frameId);
 	} else {
-		renderSprites(frameId, _surface, mode);
+		renderSprites(frameId, mode);
 	}
 
-		_vm->_wm->renderZoomBox();
-		_vm->_wm->draw();
-
-		if (currentFrame->_sound1 != 0 || currentFrame->_sound2 != 0)
-			playSoundChannel(frameId);
+	_vm->_wm->renderZoomBox();
+	g_director->getStage()->render();
+	_vm->_wm->draw();
 
-		g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->getBounds().width(), _surface->getBounds().height());
+	if (currentFrame->_sound1 != 0 || currentFrame->_sound2 != 0)
+		playSoundChannel(frameId);
 }
 
-void Score::renderSprites(uint16 frameId, Graphics::ManagedSurface *surface, RenderMode mode) {
-	// HACK: Determine a batter way to do this.
-	if (mode == kRenderNoUnrender)
-		_maskSurface->clear(1);
+void Score::renderSprites(uint16 frameId, RenderMode mode) {
+	if (_vm->_newMovieStarted) {
+		// g_director->getStage()->reset();
+		mode = kRenderForceUpdate;
+	}
 
 	for (uint16 i = 0; i < _channels.size(); i++) {
 		Channel *channel = _channels[i];
 		Sprite *currentSprite = channel->_sprite;
-		Sprite *nextSprite;
-
-		if (currentSprite->_puppet)
-			nextSprite = currentSprite;
-		else
-			nextSprite = _frames[frameId]->_sprites[i];
+		Sprite *nextSprite = _frames[frameId]->_sprites[i];
 
 		// A sprite needs to be updated if one of the following happens:
 		// - The dimensions/bounding box of the sprite has changed (_dirty flag set)
@@ -588,63 +623,40 @@ void Score::renderSprites(uint16 frameId, Graphics::ManagedSurface *surface, Ren
 			currentSprite->_castId != nextSprite->_castId ||
 			channel->_delta != Common::Point(0, 0) ||
 			currentSprite->getDims() != nextSprite->getDims() ||
-			channel->_currentPoint != nextSprite->_startPoint;
-
-		if (mode != kRenderNoUnrender &&
-				(needsUpdate || mode == kRenderForceUpdate) &&
-				!currentSprite->_trails)
-			markDirtyRect(channel->getBbox());
+			currentSprite->_ink != nextSprite->_ink ||
+			(channel->_currentPoint != nextSprite->_startPoint &&
+			 !currentSprite->_puppet && !currentSprite->_moveable);
 
-		channel->_sprite = nextSprite;
-		channel->_sprite->updateCast();
+		if ((needsUpdate || mode == kRenderForceUpdate) && !currentSprite->_trails)
+			g_director->getStage()->addDirtyRect(channel->getBbox());
 
-		// Sprites marked moveable are constrained to the same bounding box until
-		// the moveable is disabled
-		if (!nextSprite->_puppet && !nextSprite->_moveable)
-			channel->_currentPoint = nextSprite->_startPoint;
+		currentSprite->setClean();
 
-		channel->updateLocation();
-
-		// TODO: Understand why conditioning this causes so many problems
-		if (mode != kRenderNoUnrender)
-			markDirtyRect(channel->getBbox());
-	}
+		if (!currentSprite->_puppet) {
+			channel->_sprite = nextSprite;
+			channel->_sprite->updateCast();
 
-	for (uint id = 0; id < _channels.size(); id++) {
-		Channel *channel = _channels[id];
-		Sprite *sprite = channel->_sprite;
-
-		if (!sprite || !sprite->_enabled || !sprite->_castId)
-			continue;
-
-		Common::Rect currentBbox = channel->getBbox();
+			// Sprites marked moveable are constrained to the same bounding box until
+			// the moveable is disabled
+			if (!channel->_sprite->_moveable)
+				channel->_currentPoint = channel->_sprite->_startPoint;
+		}
 
-		debugC(1, kDebugImages, "Score::renderFrame(): channel: %d,  castType: %d,  castId: %d", id, sprite->_castType, sprite->_castId);
+		channel->updateLocation();
 
-		if (sprite->_castType == kCastShape) {
-			renderShape(id, surface);
-		} else {
-			Cast *cast = sprite->_cast;
-			if (cast && cast->_widget) {
-				cast->_widget->_priority = id;
-				cast->_widget->draw();
-				inkBasedBlit(surface, cast->_widget->getMask(), cast->_widget->getSurface()->rawSurface(), channel->_sprite->_ink, currentBbox, id);
-			} else {
-				warning("Score::renderFrame(): No widget for channel ID %d", id);
-			}
+		if (channel->_sprite->_cast && channel->_sprite->_cast->_widget) {
+			channel->_sprite->_cast->_widget->_priority = i;
+			channel->_sprite->_cast->_widget->draw();
+			channel->_sprite->_cast->_widget->_contentIsDirty = false;
 		}
 
-		sprite->setClean();
+		if (needsUpdate || mode == kRenderForceUpdate)
+			g_director->getStage()->addDirtyRect(channel->getBbox());
 	}
 }
 
-void Score::markDirtyRect(Common::Rect dirty) {
-	_maskSurface->fillRect(dirty, 1);
-	_surface->fillRect(dirty, _stageColor);
-}
-
 void Score::screenShot() {
-	Graphics::Surface rawSurface = _surface->rawSurface();
+	Graphics::Surface rawSurface = g_director->getStage()->getSurface()->rawSurface();
 	const Graphics::PixelFormat requiredFormat_4byte(4, 8, 8, 8, 8, 0, 8, 16, 24);
 	Graphics::Surface *newSurface = rawSurface.convertTo(requiredFormat_4byte, _vm->getPalette());
 	Common::String currentPath = _vm->getCurrentPath().c_str();
@@ -664,118 +676,6 @@ void Score::screenShot() {
 	newSurface->free();
 }
 
-void Score::renderShape(uint16 spriteId, Graphics::ManagedSurface *surface) {
-	Sprite *sp = _channels[spriteId]->_sprite;
-
-	InkType ink = sp->_ink;
-	byte spriteType = sp->_spriteType;
-	byte foreColor = sp->_foreColor;
-	byte backColor = sp->_backColor;
-	int lineSize = sp->_thickness & 0x3;
-
-	if (_vm->getVersion() >= 3 && spriteType == kCastMemberSprite) {
-		if (!sp->_cast) {
-			warning("Score::renderShape(): kCastMemberSprite has no cast defined");
-			return;
-		}
-		switch (sp->_cast->_type) {
-		case kCastShape:
-			{
-				ShapeCast *sc = (ShapeCast *)sp->_cast;
-				switch (sc->_shapeType) {
-				case kShapeRectangle:
-					spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite;
-					break;
-				case kShapeRoundRect:
-					spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite;
-					break;
-				case kShapeOval:
-					spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite;
-					break;
-				case kShapeLine:
-					spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite;
-					break;
-				default:
-					break;
-				}
-				if (_vm->getVersion() > 3) {
-					foreColor = sc->_fgCol;
-					backColor = sc->_bgCol;
-					lineSize = sc->_lineThickness;
-					ink = sc->_ink;
-				}
-			}
-			break;
-		default:
-			warning("Score::renderShape(): Unhandled cast type: %d", sp->_cast->_type);
-			break;
-		}
-	}
-
-	// for outlined shapes, line thickness of 1 means invisible.
-	lineSize -= 1;
-
-	Common::Rect shapeRect = _channels[spriteId]->getBbox();
-
-	Graphics::ManagedSurface tmpSurface, maskSurface;
-	tmpSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
-	tmpSurface.clear(backColor);
-
-	maskSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
-	maskSurface.clear(0);
-
-	// Draw fill
-	Common::Rect fillRect((int)shapeRect.width(), (int)shapeRect.height());
-	Graphics::MacPlotData plotFill(&tmpSurface, &maskSurface, &_vm->getPatterns(), sp->getPattern(), -shapeRect.left, -shapeRect.top, 1, backColor);
-	switch (spriteType) {
-	case kRectangleSprite:
-		Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
-		break;
-	case kRoundedRectangleSprite:
-		Graphics::drawRoundRect(fillRect, 12, foreColor, true, Graphics::macDrawPixel, &plotFill);
-		break;
-	case kOvalSprite:
-		Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, foreColor, true, Graphics::macDrawPixel, &plotFill);
-		break;
-	case kCastMemberSprite: 		// Face kit D3
-		Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
-		break;
-	default:
-		break;
-	}
-
-	// Draw stroke
-	Common::Rect strokeRect(MAX((int)shapeRect.width() - lineSize, 0), MAX((int)shapeRect.height() - lineSize, 0));
-	Graphics::MacPlotData plotStroke(&tmpSurface, &maskSurface, &_vm->getPatterns(), 1, -shapeRect.left, -shapeRect.top, lineSize, backColor);
-	switch (spriteType) {
-	case kLineTopBottomSprite:
-		Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, Graphics::macDrawPixel, &plotStroke);
-		break;
-	case kLineBottomTopSprite:
-		Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, foreColor, Graphics::macDrawPixel, &plotStroke);
-		break;
-	case kRectangleSprite:
-		// fall through
-	case kOutlinedRectangleSprite:	// this is actually a mouse-over shape? I don't think it's a real button.
-		Graphics::drawRect(strokeRect, foreColor, Graphics::macDrawPixel, &plotStroke);
-		break;
-	case kRoundedRectangleSprite:
-		// fall through
-	case kOutlinedRoundedRectangleSprite:
-		Graphics::drawRoundRect(strokeRect, 12, foreColor, false, Graphics::macDrawPixel, &plotStroke);
-		break;
-	case kOvalSprite:
-		// fall through
-	case kOutlinedOvalSprite:
-		Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, false, Graphics::macDrawPixel, &plotStroke);
-		break;
-	default:
-		break;
-	}
-
-	inkBasedBlit(surface, &maskSurface, tmpSurface, ink, shapeRect, spriteId);
-}
-
 Cast *Score::getCastMember(int castId) {
 	Cast *result = nullptr;
 
@@ -809,6 +709,16 @@ bool Score::checkSpriteIntersection(uint16 spriteId, Common::Point pos) {
 	return false;
 }
 
+Common::List<Channel *> Score::getSpriteIntersections(const Common::Rect &r) {
+	Common::List<Channel *>intersections;
+
+	for (uint i = 0; i < _channels.size(); i++)
+		if (!r.findIntersectingRect(_channels[i]->getBbox()).isEmpty())
+			intersections.push_back(_channels[i]);
+
+	return intersections;
+}
+
 Sprite *Score::getSpriteById(uint16 id) {
 	Channel *channel = getChannelById(id);
 
diff --git a/engines/director/score.h b/engines/director/score.h
index 2b50328bbe..6d3143fec1 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -25,6 +25,7 @@
 
 #include "common/hash-str.h"
 #include "graphics/macgui/macwindowmanager.h"
+#include "director/stage.h"
 
 namespace Graphics {
 	class ManagedSurface;
@@ -40,6 +41,7 @@ namespace Common {
 
 namespace Director {
 
+class Stage;
 class Archive;
 struct CastInfo;
 class DirectorEngine;
@@ -51,6 +53,7 @@ struct Resource;
 struct Channel;
 class Sprite;
 class Stxt;
+class Cast;
 class BitmapCast;
 class ScriptCast;
 class ShapeCast;
@@ -63,34 +66,12 @@ enum RenderMode {
 	kRenderNoUnrender
 };
 
-struct TransParams {
-	TransitionType type;
-	uint duration;
-	uint chunkSize;
-	uint area;
-
-	int steps;
-	int stepDuration;
-
-	int xStepSize;
-	int yStepSize;
-
-	int xpos, ypos;
-
-	int stripSize;
-
-	TransParams() {
-		type = kTransNone;
-		duration = 250;
-		chunkSize = 1;
-		area = 0;
-		steps = 0;
-		stepDuration = 0;
-		stripSize = 0;
-
-		xStepSize = yStepSize = 0;
-		xpos = ypos = 0;
-	}
+struct MacShape {
+	InkType ink;
+	byte spriteType;
+	byte foreColor;
+	byte backColor;
+	int lineSize;
 };
 
 struct Channel {
@@ -104,6 +85,10 @@ struct Channel {
 	Channel(Sprite *sp);
 	Common::Rect getBbox();
 	Common::Point getPosition();
+	MacShape *getShape();
+	Graphics::ManagedSurface *getSurface();
+	Graphics::ManagedSurface *getMask();
+
 	void updateLocation();
 	void addDelta(Common::Point pos);
 	void resetPosition();
@@ -139,7 +124,6 @@ public:
 	void loadSpriteImages(bool isSharedCast);
 	void loadSpriteSounds(bool isSharedCast);
 	void copyCastStxts();
-	Graphics::ManagedSurface *getSurface() { return _surface; }
 
 	Common::Rect getCastMemberInitialRect(int castId);
 	void setCastMemberModified(int castId);
@@ -150,33 +134,16 @@ public:
 
 	uint16 getSpriteIDFromPos(Common::Point pos, bool onlyActive = false);
 	bool checkSpriteIntersection(uint16 spriteId, Common::Point pos);
+	Common::List<Channel *> getSpriteIntersections(const Common::Rect &r);
 
 	Cast *getCastMember(int castId);
 	const Stxt *getStxt(int castId);
 	void renderFrame(uint16 frameId, RenderMode mode = kRenderModeNormal);
-	void renderSprites(uint16 frameId, Graphics::ManagedSurface *surface, RenderMode mode = kRenderModeNormal);
-	void markDirtyRect(Common::Rect dirty);
-
-	// transition.cpp
-	void playTransition(uint16 transDuration, uint8 transArea, uint8 transChunkSize, TransitionType transType);
+	void renderSprites(uint16 frameId, RenderMode mode = kRenderModeNormal);
 
 private:
 	void update();
-	void renderShape(uint16 spriteId, Graphics::ManagedSurface *surface);
-
-	// ink.cpp
-	void inkBasedBlit(Graphics::ManagedSurface *destSurface, Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId);
-	void drawMatteSprite(Graphics::ManagedSurface *destSurface, const Graphics::Surface &sprite, Common::Rect &drawRect);
-	void drawReverseSprite(Graphics::ManagedSurface *destSurface, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId);
 
-	// transitions.cpp
-	void initTransParams(TransParams &t, Common::Rect &clipRect);
-	void dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
-	void dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
-	void transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
-	void transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
-
-	// score.cpp
 	void playSoundChannel(uint16 frameId);
 
 	void readVersion(uint32 rid);
@@ -203,10 +170,6 @@ public:
 	Common::HashMap<uint16, Common::String> _actions;
 	Common::HashMap<uint16, bool> _immediateActions;
 	Common::HashMap<uint16, Common::String> _fontMap;
-	Graphics::ManagedSurface *_surface;
-	Graphics::ManagedSurface *_maskSurface;
-	Graphics::ManagedSurface *_backSurface;
-	Graphics::ManagedSurface *_backSurface2;
 	Graphics::Font *_font;
 	Archive *_movieArchive;
 	Common::Rect _movieRect;
@@ -231,8 +194,6 @@ public:
 
 	int _numChannelsDisplayed;
 
-	Graphics::MacWindow *_window;
-
 	uint16 _framesRan; // used by kDebugFewFramesOnly
 
 	int _lingoArchive;
diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index da120cff5d..9e3a16ca35 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -93,13 +93,13 @@ void Sprite::updateCast() {
 }
 
 bool Sprite::isDirty() {
-	return _castType != kCastTypeNull && (_dirty || (_cast && _cast->isModified()));
+	return _castType != kCastTypeNull && (_cast && _cast->isModified());
 }
 
 bool Sprite::isActive() {
 	if (_moveable || _puppet || _scriptId)
 		return true;
-	
+
 	if (g_lingo->getScriptContext(kArchMain, kCastScript, _castId)
 			|| g_lingo->getScriptContext(kArchShared, kCastScript, _castId))
 		return true;
diff --git a/engines/director/stage.cpp b/engines/director/stage.cpp
new file mode 100644
index 0000000000..30f442caff
--- /dev/null
+++ b/engines/director/stage.cpp
@@ -0,0 +1,309 @@
+/* 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.
+ *
+ */
+
+#include "graphics/primitives.h"
+
+#include "director/director.h"
+#include "director/stage.h"
+#include "director/score.h"
+#include "director/cast.h"
+#include "director/sprite.h"
+
+#include "common/file.h"
+
+namespace Director {
+
+Stage::Stage(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm)
+	: MacWindow(id, scrollable, resizable, editable, wm) {
+	_stageColor = 0;
+}
+
+bool Stage::render(bool forceRedraw, Graphics::ManagedSurface *blitTo) {
+	if (!blitTo)
+		blitTo = &_surface;
+
+	if (forceRedraw) {
+		blitTo->clear(_stageColor);
+		_dirtyRects.clear();
+		_dirtyRects.push_back(Common::Rect(_surface.w, _surface.h));
+	} else {
+		if (_dirtyRects.size() == 0)
+			return true;
+
+		mergeDirtyRects();
+	}
+
+	for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); i++) {
+		const Common::Rect &r = *i;
+		blitTo->fillRect(r, _stageColor);
+
+		_dirtyChannels = g_director->getCurrentScore()->getSpriteIntersections(r);
+		for (Common::List<Channel *>::iterator j = _dirtyChannels.begin(); j != _dirtyChannels.end(); j++)
+			inkBlitFrom(*j, r, blitTo);
+	}
+
+	_dirtyRects.clear();
+	_contentIsDirty = true;
+
+	return true;
+}
+
+void Stage::setStageColor(uint stageColor) {
+	if (stageColor != _stageColor) {
+		_stageColor = stageColor;
+		reset();
+	}
+}
+
+void Stage::reset() {
+	_surface.clear(_stageColor);
+	_contentIsDirty = true;
+}
+
+void Stage::addDirtyRect(const Common::Rect &r) {
+	Common::Rect bounds = r;
+	bounds.clip(_dims);
+
+	if (bounds.width() > 0 && bounds.height() > 0)
+		_dirtyRects.push_back(bounds);
+}
+
+void Stage::mergeDirtyRects() {
+	Common::List<Common::Rect>::iterator rOuter, rInner;
+
+	// Process the dirty rect list to find any rects to merge
+	for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) {
+		rInner = rOuter;
+		while (++rInner != _dirtyRects.end()) {
+
+			if ((*rOuter).intersects(*rInner)) {
+				// These two rectangles overlap, so merge them
+				unionRectangle(*rOuter, *rOuter, *rInner);
+
+				// remove the inner rect from the list
+				_dirtyRects.erase(rInner);
+
+				// move back to beginning of list
+				rInner = rOuter;
+			}
+		}
+	}
+}
+
+bool Stage::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) {
+	destRect = src1;
+	destRect.extend(src2);
+
+	return !destRect.isEmpty();
+}
+
+void Stage::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo) {
+	Common::Rect srcRect = channel->getBbox();
+	destRect.clip(srcRect);
+
+	Sprite *sprite = channel->_sprite;
+	MacShape *ms = channel->getShape();
+	DirectorPlotData pd(_wm, channel->getSurface(), blitTo, sprite->_ink, sprite->_backColor, g_director->getPaletteColorCount());
+	pd.destRect = destRect;
+
+	// Shapes do not have surfaces of their own, so draw the shape directly upon
+	// stage surface.
+	if (ms) {
+		if (ms->foreColor == ms->backColor)
+			return;
+
+		Common::Rect fillRect((int)srcRect.width(), (int)srcRect.height());
+		fillRect.moveTo(srcRect.left, srcRect.top);
+		Graphics::MacPlotData plotFill(blitTo, nullptr, &g_director->getPatterns(), sprite->getPattern(), srcRect.left, srcRect.top, 1, ms->backColor);
+
+		Common::Rect strokeRect(MAX((int)srcRect.width() - ms->lineSize, 0), MAX((int)srcRect.height() - ms->lineSize, 0));
+		strokeRect.moveTo(srcRect.left, srcRect.top);
+		Graphics::MacPlotData plotStroke(blitTo, nullptr, &g_director->getPatterns(), 1, strokeRect.left, strokeRect.top, ms->lineSize, ms->backColor);
+
+		switch (ms->spriteType) {
+		case kRectangleSprite:
+			pd.macPlot = &plotFill;
+			Graphics::drawFilledRect(fillRect, ms->foreColor, inkDrawPixel, &pd);
+			// fall through
+		case kOutlinedRectangleSprite:
+			pd.macPlot = &plotStroke;
+			Graphics::drawRect(strokeRect, ms->foreColor, inkDrawPixel, &pd);
+			break;
+		case kRoundedRectangleSprite:
+			pd.macPlot = &plotFill;
+			Graphics::drawRoundRect(fillRect, 12, ms->foreColor, true, inkDrawPixel, &pd);
+			// fall through
+		case kOutlinedRoundedRectangleSprite:
+			pd.macPlot = &plotStroke;
+			Graphics::drawRoundRect(strokeRect, 12, ms->foreColor, false, inkDrawPixel, &pd);
+			break;
+		case kOvalSprite:
+			pd.macPlot = &plotFill;
+			Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, ms->foreColor, true, inkDrawPixel, &plotFill);
+			// fall through
+		case kOutlinedOvalSprite:
+			pd.macPlot = &plotStroke;
+			Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, ms->foreColor, false, inkDrawPixel, &plotStroke);
+			break;
+		case kLineTopBottomSprite:
+			pd.macPlot = &plotStroke;
+			Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, ms->foreColor, inkDrawPixel, &plotStroke);
+			break;
+		case kLineBottomTopSprite:
+			pd.macPlot = &plotStroke;
+			Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, ms->foreColor, inkDrawPixel, &plotStroke);
+			break;
+		default:
+			warning("Stage::inkBlitFrom: Expected shape type but got type %d", ms->spriteType);
+		}
+
+		delete ms;
+		return;
+	}
+
+	// Otherwise, we are drawing a cast type that does have a built-in surface, so
+	// blit from that.
+	// TODO: Work these ink types into inkDrawPixel.
+	if (sprite->_ink == kInkTypeMatte) {
+		drawMatteSprite(channel, srcRect, destRect, blitTo);
+		return;
+	} else if (sprite->_ink == kInkTypeReverse) {
+		drawReverseSprite(channel, srcRect, destRect, blitTo);
+		return;
+	}
+	// Otherwise, fall through to inkDrawPixel
+
+	pd.srcPoint.y = MAX(abs(srcRect.top - destRect.top), 0);
+	for (int i = 0; i < destRect.height(); i++, pd.srcPoint.y++) {
+		pd.srcPoint.x = MAX(abs(srcRect.left - destRect.left), 0);
+
+		for (int j = 0; j < destRect.width(); j++, pd.srcPoint.x++)
+			inkDrawPixel(destRect.left + j, destRect.top + i, 0, &pd);
+	}
+}
+
+void Stage::drawMatteSprite(Channel *channel, Common::Rect &srcRect, Common::Rect &destRect, Graphics::ManagedSurface *blitTo) {
+	// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
+	Graphics::Surface tmp;
+	tmp.create(destRect.width(), destRect.height(), Graphics::PixelFormat::createFormatCLUT8());
+	tmp.copyFrom(channel->getSurface()->rawSurface());
+
+	if (!blitTo->clip(srcRect, destRect))
+		return; // Out of screen
+
+	// Searching white color in the corners
+	int whiteColor = -1;
+
+	for (int y = 0; y < tmp.h; y++) {
+		for (int x = 0; x < tmp.w; x++) {
+			byte color = *(byte *)tmp.getBasePtr(x, y);
+
+			if (g_director->getPalette()[color * 3 + 0] == 0xff &&
+				g_director->getPalette()[color * 3 + 1] == 0xff &&
+				g_director->getPalette()[color * 3 + 2] == 0xff) {
+				whiteColor = color;
+				break;
+			}
+		}
+	}
+
+	if (whiteColor == -1) {
+		debugC(1, kDebugImages, "Score::drawMatteSprite(): No white color for Matte image");
+
+		for (int yy = 0; yy < destRect.height(); yy++) {
+		const byte *src = (const byte *)channel->getSurface()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top + yy), 0));
+		byte *dst = (byte *)blitTo->getBasePtr(destRect.left, destRect.top + yy);
+
+			for (int xx = 0; xx < destRect.width(); xx++, src++, dst++)
+				*dst = *src;
+		}
+	} else {
+		Graphics::FloodFill ff(&tmp, whiteColor, 0, true);
+
+		for (int yy = 0; yy < tmp.h; yy++) {
+			ff.addSeed(0, yy);
+			ff.addSeed(tmp.w - 1, yy);
+		}
+
+		for (int xx = 0; xx < tmp.w; xx++) {
+			ff.addSeed(xx, 0);
+			ff.addSeed(xx, tmp.h - 1);
+		}
+		ff.fillMask();
+
+		for (int yy = 0; yy < destRect.height(); yy++) {
+			const byte *mask = (const byte *)ff.getMask()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top - yy), 0));
+			const byte *src = (const byte *)channel->getSurface()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top - yy), 0));
+			byte *dst = (byte *)blitTo->getBasePtr(destRect.left, destRect.top + yy);
+
+			for (int xx = 0; xx < destRect.width(); xx++, src++, dst++, mask++)
+				if (*mask == 0)
+					*dst = *src;
+		}
+	}
+
+	tmp.free();
+}
+
+void Stage::drawReverseSprite(Channel *channel, Common::Rect &srcRect, Common::Rect &destRect, Graphics::ManagedSurface *blitTo) {
+	uint8 skipColor = g_director->getPaletteColorCount() - 1;
+	for (int ii = 0; ii < destRect.height(); ii++) {
+		const byte *src = (const byte *)channel->getSurface()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top - ii), 0));
+		byte *dst = (byte *)blitTo->getBasePtr(destRect.left, destRect.top + ii);
+		byte srcColor = *src;
+
+		for (int j = 0; j < destRect.width(); j++, src++, dst++) {
+			if (!channel->_sprite->_cast || channel->_sprite->_cast->_type == kCastShape)
+				srcColor = 0x0;
+			else
+				srcColor = *src;
+			uint16 targetSprite = g_director->getCurrentScore()->getSpriteIDFromPos(Common::Point(destRect.left + j, destRect.top + ii));
+			if ((targetSprite != 0)) {
+				// TODO: This entire reverse colour attempt needs a lot more testing on
+				// a lot more colour depths.
+				if (srcColor != skipColor) {
+					if (!g_director->getCurrentScore()->_channels[targetSprite]->_sprite->_cast ||  g_director->getCurrentScore()->_channels[targetSprite]->_sprite->_cast->_type != kCastBitmap) {
+						if (*dst == 0 || *dst == 255) {
+							*dst = g_director->transformColor(*dst);
+						} else if (srcColor == 255 || srcColor == 0) {
+							*dst = g_director->transformColor(*dst - 40);
+						} else {
+							*dst = g_director->transformColor(*src - 40);
+						}
+					} else {
+						if (*dst == 0 && g_director->getVersion() == 3 &&
+								g_director->getCurrentScore()->_channels[targetSprite]->_sprite->_cast->_type == kCastBitmap &&
+								((BitmapCast*)g_director->getCurrentScore()->_channels[targetSprite]->_sprite->_cast)->_bitsPerPixel > 1) {
+							*dst = g_director->transformColor(*src - 40);
+						} else {
+							*dst ^= g_director->transformColor(srcColor);
+						}
+					}
+				}
+			} else if (srcColor != skipColor) {
+				*dst = g_director->transformColor(srcColor);
+			}
+		}
+	}
+}
+
+} // end of namespace Director
diff --git a/engines/director/stage.h b/engines/director/stage.h
new file mode 100644
index 0000000000..9495afbabc
--- /dev/null
+++ b/engines/director/stage.h
@@ -0,0 +1,107 @@
+/* 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.
+ *
+ */
+
+#ifndef DIRECTOR_STAGE_H
+#define DIRECTOR_STAGE_H
+
+#include "graphics/macgui/macwindowmanager.h"
+#include "graphics/macgui/macwindow.h"
+
+#include "director/types.h"
+
+namespace Graphics {
+class ManagedSurface;
+class MacWindow;
+class MacWindowManager;
+}
+
+namespace Director {
+
+struct Channel;
+
+struct TransParams {
+	TransitionType type;
+	uint frame;
+	uint duration;
+	uint chunkSize;
+	uint area;
+
+	int steps;
+	int stepDuration;
+
+	int xStepSize;
+	int yStepSize;
+
+	int xpos, ypos;
+
+	int stripSize;
+
+	TransParams() {
+		type = kTransNone;
+		frame = 0;
+		duration = 250;
+		chunkSize = 1;
+		area = 0;
+		steps = 0;
+		stepDuration = 0;
+		stripSize = 0;
+
+		xStepSize = yStepSize = 0;
+		xpos = ypos = 0;
+	}
+};
+
+class Stage : public Graphics::MacWindow {
+ public:
+	Stage(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm);
+	// ~Stage();
+
+	bool render(bool forceRedraw = false, Graphics::ManagedSurface *blitTo = nullptr);
+
+	bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2);
+	void setStageColor(uint stageColor);
+	void addDirtyRect(const Common::Rect &r);
+	void mergeDirtyRects();
+	void reset();
+
+	// transitions.cpp
+	void playTransition(uint16 transDuration, uint8 transArea, uint8 transChunkSize, TransitionType transType, uint frame);
+	void initTransParams(TransParams &t, Common::Rect &clipRect);
+	void dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
+	void dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
+	void transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
+	void transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
+
+public:
+	Common::List<Common::Rect> _dirtyRects;
+	Common::List<Channel *> _dirtyChannels;
+
+private:
+	uint _stageColor;
+	void inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo = nullptr);
+	void drawReverseSprite(Channel *channel, Common::Rect &srcRect, Common::Rect &destRect, Graphics::ManagedSurface *blitTo);
+	void drawMatteSprite(Channel *channel, Common::Rect &srcRect, Common::Rect &destRect, Graphics::ManagedSurface *blitTo);
+};
+
+} // end of namespace Director
+
+#endif
diff --git a/engines/director/transitions.cpp b/engines/director/transitions.cpp
index 0f9a417db7..ea20975d40 100644
--- a/engines/director/transitions.cpp
+++ b/engines/director/transitions.cpp
@@ -131,16 +131,13 @@ struct {
 	TRANS(kTransDissolveBits,			kTransAlgoDissolve,	kTransDirNone)
 };
 
-void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChunkSize, TransitionType transType) {
+void Stage::playTransition(uint16 transDuration, uint8 transArea, uint8 transChunkSize, TransitionType transType, uint frame) {
 	// Play a transition and return the number of subframes rendered
-
-	if (!_surface)
-		return;
-
 	TransParams t;
 
 	t.type = transType;
 	t.duration = MAX<uint16>(250, transDuration); // When duration is < 1/4s, make it 1/4
+	t.frame = frame;
 	t.chunkSize = MAX<uint>(1, transChunkSize);
 	t.area = MAX<uint>(0, transArea);
 
@@ -149,18 +146,18 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 		t.duration = 250;
 
 	// Cache a copy of the frame before the transition.
-	Graphics::ManagedSurface *currentFrame = new Graphics::ManagedSurface(_movieRect.width(), _movieRect.height());
-	currentFrame->copyFrom(*_surface);
+	Graphics::ManagedSurface *currentFrame = new Graphics::ManagedSurface(_surface.w, _surface.h);
+	currentFrame->copyFrom(_surface);
 
 	// If a transition is being played, render the frame after the transition.
-	Graphics::ManagedSurface *nextFrame = new Graphics::ManagedSurface(_movieRect.width(), _movieRect.height());
-	nextFrame->clear(_stageColor);
-	renderSprites(_currentFrame, nextFrame, kRenderNoUnrender);
+	Graphics::ManagedSurface *nextFrame = new Graphics::ManagedSurface(_surface.w, _surface.h);
+	g_director->getCurrentScore()->renderSprites(t.frame, kRenderForceUpdate);
+	render(true, nextFrame);
 
 	if (t.area)
 		warning("STUB: Transition over changed area transition");
 
-	Common::Rect clipRect(_movieRect);
+	Common::Rect clipRect(_dims);
 	clipRect.moveTo(0, 0);
 
 	Common::Rect rfrom, rto;
@@ -216,7 +213,7 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 
 		if (transProps[t.type].algo == kTransAlgoReveal ||
 				transProps[t.type].algo == kTransAlgoEdgesIn) {
-			_surface->copyFrom(*nextFrame);
+			_surface.copyFrom(*nextFrame);
 		}
 
 		switch (t.type) {
@@ -286,7 +283,7 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 
 		case kTransPushLeft:								// 11
 			rto.moveTo(w - t.xStepSize * i, 0);
-			_surface->blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			_surface.blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rfrom.moveTo(t.xStepSize * i, 0);
 			rfrom.setWidth(w - t.xStepSize * i);
@@ -296,7 +293,7 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 		case kTransPushRight:								// 12
 			rfrom.moveTo(w - t.xStepSize * i, 0);
 			rfrom.setWidth(t.xStepSize * i);
-			_surface->blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			_surface.blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rto.setWidth(w - t.xStepSize * i);
 			rto.moveTo(t.xStepSize * i, 0);
@@ -307,7 +304,7 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 		case kTransPushDown:								// 13
 			rfrom.moveTo(0, h - t.yStepSize * i);
 			rfrom.setHeight(t.yStepSize * i);
-			_surface->blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			_surface.blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rto.setHeight(h - t.yStepSize * i);
 			rto.moveTo(0, t.yStepSize * i);
@@ -317,7 +314,7 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 
 		case kTransPushUp:									// 14
 			rto.moveTo(0, h - t.yStepSize * i);
-			_backSurface->blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
+			_surface.blitFrom(*nextFrame, rfrom, Common::Point(rto.left, rto.top));
 
 			rfrom.moveTo(0, t.yStepSize * i);
 			rfrom.setHeight(h - t.yStepSize * i);
@@ -435,25 +432,24 @@ void Score::playTransition(uint16 transDuration, uint8 transArea, uint8 transChu
 		if (stop)
 			break;
 
-		_surface->blitFrom(*blitFrom, rfrom, Common::Point(rto.left, rto.top));
+		_surface.blitFrom(*blitFrom, rfrom, Common::Point(rto.left, rto.top));
 
 		g_system->delayMillis(t.stepDuration);
 		if (processQuitEvent(true))
 			break;
 
 		if (fullredraw) {
-			g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, w, h);
+			g_system->copyRectToScreen(_surface.getPixels(), _surface.pitch, 0, 0, w, h);
 		} else {
 			rto.clip(clipRect);
 
 			if (rto.height() > 0 && rto.width() > 0) {
-				g_system->copyRectToScreen(_surface->getBasePtr(rto.left, rto.top), _surface->pitch, rto.left, rto.top, rto.width(), rto.height());
+				g_system->copyRectToScreen(_surface.getBasePtr(rto.left, rto.top), _surface.pitch, rto.left, rto.top, rto.width(), rto.height());
 			}
 		}
 
 		g_system->updateScreen();
-
-		g_lingo->executePerFrameHook(_currentFrame, i);
+		g_lingo->executePerFrameHook(t.frame, i);
 	}
 
 	delete currentFrame;
@@ -481,7 +477,7 @@ static uint32 randomSeed[33] = {
 	0x14000000UL, 0x32800000UL, 0x48000000UL, 0xa3000000UL
 };
 
-void Score::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+void Stage::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
 	uint w = clipRect.width();
 	uint h = clipRect.height();
 	uint realw = w, realh = h;
@@ -603,13 +599,13 @@ void Score::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::Mana
 					if (x < realw && y < realh) {
 						r.moveTo(x, y);
 						r.clip(clipRect);
-						_surface->copyRectToSurface(*nextFrame, x, y, r);
+						_surface.copyRectToSurface(*nextFrame, x, y, r);
 					}
 				} else {
 					mask = pixmask[x % -t.xStepSize];
 					x = x / -t.xStepSize;
 
-					byte *color1 = (byte *)_surface->getBasePtr(x, y);
+					byte *color1 = (byte *)_surface.getBasePtr(x, y);
 					byte *color2 = (byte *)nextFrame->getBasePtr(x, y);
 
 					*color1 = ((*color1 & ~mask) | (*color2 & mask)) & 0xff;
@@ -625,10 +621,10 @@ void Score::dissolveTrans(TransParams &t, Common::Rect &clipRect, Graphics::Mana
 			}
 		} while (rnd != seed);
 
-		g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, realw, realh);
+		g_system->copyRectToScreen(_surface.getPixels(), _surface.pitch, 0, 0, realw, realh);
 		g_system->updateScreen();
 
-		g_lingo->executePerFrameHook(_currentFrame, i + 1);
+		g_lingo->executePerFrameHook(t.frame, i + 1);
 
 		if (processQuitEvent(true))
 			break;
@@ -704,7 +700,7 @@ static byte dissolvePatterns[][8] = {
 	{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
 };
 
-void Score::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+void Stage::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
 	uint w = clipRect.width();
 	uint h = clipRect.height();
 
@@ -714,7 +710,7 @@ void Score::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphi
 	for (int i = 0; i < t.steps; i++) {
 		for (uint y = 0; y < h; y++) {
 			byte pat = dissolvePatterns[i][y % 8];
-			byte *dst = (byte *)_surface->getBasePtr(0, y);
+			byte *dst = (byte *)_surface.getBasePtr(0, y);
 			byte *src = (byte *)nextFrame->getBasePtr(0, y);
 
 			for (uint x = 0; x < w;) {
@@ -730,10 +726,10 @@ void Score::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphi
 			}
 		}
 
-		g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, w, h);
+		g_system->copyRectToScreen(_surface.getPixels(), _surface.pitch, 0, 0, w, h);
 		g_system->updateScreen();
 
-		g_lingo->executePerFrameHook(_currentFrame, i + 1);
+		g_lingo->executePerFrameHook(t.frame, i + 1);
 
 		if (processQuitEvent(true))
 			break;
@@ -742,7 +738,7 @@ void Score::dissolvePatternsTrans(TransParams &t, Common::Rect &clipRect, Graphi
 	}
 }
 
-void Score::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+void Stage::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
 	Common::Rect rto;
 	uint w = clipRect.width();
 	uint h = clipRect.height();
@@ -897,15 +893,15 @@ void Score::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::Man
 			rto.clip(clipRect);
 
 			if (rto.height() > 0 && rto.width() > 0) {
-				_surface->blitFrom(*nextFrame, rto, Common::Point(rto.left, rto.top));
-				g_system->copyRectToScreen(_surface->getBasePtr(rto.left, rto.top), _surface->pitch, rto.left, rto.top, rto.width(), rto.height());
+				_surface.blitFrom(*nextFrame, rto, Common::Point(rto.left, rto.top));
+				g_system->copyRectToScreen(_surface.getBasePtr(rto.left, rto.top), _surface.pitch, rto.left, rto.top, rto.width(), rto.height());
 			}
 		}
 		rects.clear();
 
 		g_system->updateScreen();
 
-		g_lingo->executePerFrameHook(_currentFrame, i);
+		g_lingo->executePerFrameHook(t.frame, i);
 
 		g_system->delayMillis(t.stepDuration);
 		if (processQuitEvent(true))
@@ -914,14 +910,14 @@ void Score::transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::Man
 	}
 }
 
-void Score::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
+void Stage::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *nextFrame) {
 	Common::Rect r = clipRect;
 	uint w = clipRect.width();
 	uint h = clipRect.height();
 
 	t.steps += 2;
 
-	Graphics::MacPlotData pd(_surface, nullptr, &g_director->_wm->getPatterns(), Graphics::kPatternCheckers, 0, 0, 1, 0);
+	Graphics::MacPlotData pd(&_surface, nullptr, &g_director->_wm->getPatterns(), Graphics::kPatternCheckers, 0, 0, 1, 0);
 
 	for (uint16 i = 1; i < t.steps; i++) {
 
@@ -949,10 +945,10 @@ void Score::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedS
 		r.setWidth(t.xStepSize * i * 2);
 		r.moveTo(w / 2 - t.xStepSize * i, h / 2 - t.yStepSize * i);
 
-		g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, w, h);
+		g_system->copyRectToScreen(_surface.getPixels(), _surface.pitch, 0, 0, w, h);
 		g_system->updateScreen();
 
-		g_lingo->executePerFrameHook(_currentFrame, i);
+		g_lingo->executePerFrameHook(t.frame, i);
 
 		g_system->delayMillis(t.stepDuration);
 		if (processQuitEvent(true))
@@ -960,7 +956,7 @@ void Score::transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedS
 	}
 }
 
-void Score::initTransParams(TransParams &t, Common::Rect &clipRect) {
+void Stage::initTransParams(TransParams &t, Common::Rect &clipRect) {
 	int w = clipRect.width();
 	int h = clipRect.height();
 	int m = MIN(w, h);


Commit: 30bd4de3a7b8d6e06886acad0a90083330ae091a
    https://github.com/scummvm/scummvm/commit/30bd4de3a7b8d6e06886acad0a90083330ae091a
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
Revert "GRAPHICS: MACGUI: Add QuickDraw class"

This reverts commit 3469ed858c47a8a0ce3c1eb192d2a494bdf5ba02.

Changed paths:
  R graphics/macgui/macshape.cpp
  R graphics/macgui/macshape.h
    graphics/module.mk


diff --git a/graphics/macgui/macshape.cpp b/graphics/macgui/macshape.cpp
deleted file mode 100644
index ad74fba90d..0000000000
--- a/graphics/macgui/macshape.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* 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.
- *
- */
-
-#include "graphics/macgui/macwindowmanager.h"
-#include "graphics/macgui/macshape.h"
-#include "graphics/primitives.h"
-
-namespace Graphics {
-
-MacShape::MacShape(MacShapeType shapeType, MacWidget *parent, int x, int y, int w, int h, MacWindowManager *wm, uint fgcolor, uint bgcolor, uint thickness, uint fillType) : _shapeType(shapeType), _thickness(thickness), _fillType(fillType), MacWidget(parent, x, y, w, h, wm, false, 0, 0, 0, fgcolor, bgcolor) {
-		_contentIsDirty = true;
-		render();
-	}
-
-void MacShape::setStyle(MacShapeType shapeType, uint fgcolor, uint bgcolor, uint thickness, uint fillType) {
-	_contentIsDirty = true;
-
-	_shapeType = shapeType;
-	_fgcolor = fgcolor;
-	_bgcolor = bgcolor;
-	_thickness = thickness;
-	_fillType = fillType;
-}
-
-void MacShape::render() {
-	if (!_contentIsDirty)
-		return;
-
-	_maskSurface->clear(0);
-	_composeSurface->clear(_bgcolor);
-
-	Common::Rect fillRect((int)_dims.width(), (int)_dims.height());
-	Graphics::MacPlotData plotFill(_composeSurface, _maskSurface, &_wm->getPatterns(), _fillType, -_dims.left, -_dims.top, 1, _bgcolor);
-
-	Common::Rect strokeRect(MAX((int)_dims.width() - _thickness, 0), MAX((int)_dims.height() - _thickness, 0));
-	Graphics::MacPlotData plotStroke(_composeSurface, _maskSurface, &_wm->getPatterns(), 1, -_dims.left, -_dims.top, _thickness, _bgcolor);
-
-	switch (_shapeType) {
-	case kShapeRectangle:
-		if (_fillType)
-			Graphics::drawFilledRect(fillRect, _fgcolor, Graphics::macDrawPixel, &plotFill);
-
-		Graphics::drawRect(strokeRect, _fgcolor, Graphics::macDrawPixel, &plotStroke);
-		break;
-
-	case kShapeRoundRect:
-		if (_fillType)
-			Graphics::drawRoundRect(fillRect, 12, _fgcolor, true, Graphics::macDrawPixel, &plotFill);
-
-		Graphics::drawRoundRect(strokeRect, 12, _fgcolor, false, Graphics::macDrawPixel, &plotStroke);
-		break;
-
-	case kShapeOval:
-		if (_fillType)
-			Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, _fgcolor, true, Graphics::macDrawPixel, &plotFill);
-
-		Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, _fgcolor, false, Graphics::macDrawPixel, &plotStroke);
-		break;
-
-	case kShapeLine:
-		warning("MacShape::render(): Line drawing not yet implemented");
-		break;
-	}
-
-	_contentIsDirty = true;
-}
-
-} // end of namespace Graphics
diff --git a/graphics/macgui/macshape.h b/graphics/macgui/macshape.h
deleted file mode 100644
index a47d69a99d..0000000000
--- a/graphics/macgui/macshape.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* 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.
- *
- */
-
-#ifndef GRAPHICS_MACGUI_MACSHAPE_H
-#define GRAPHICS_MACGUI_MACSHAPE_H
-
-#include "graphics/macgui/macwidget.h"
-
-namespace Graphics {
-
-enum MacShapeType {
-	kShapeRectangle,
-	kShapeRoundRect,
-	kShapeOval,
-	kShapeLine
-};
-
-class MacShape : public MacWidget {
-public:
-	MacShape(MacShapeType shapeType, MacWidget *parent, int x, int y, int w, int h, MacWindowManager *wm, uint fgcolor, uint bgcolor, uint thickness, uint fillType);
-
-	void setStyle(MacShapeType shapeType, uint fgcolor, uint bgcolor, uint thickness, uint fillType);
-	void render();
-
-private:
-	MacShapeType _shapeType;
-	int _thickness;
-	uint _fillType;
-};
-
-} // end of namespace Graphics
-
-#endif
diff --git a/graphics/module.mk b/graphics/module.mk
index 4a8ba148a7..142a2aab21 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -19,7 +19,6 @@ MODULE_OBJS := \
 	macgui/macmenu.o \
 	macgui/mactext.o \
 	macgui/mactextwindow.o \
-	macgui/macshape.o \
 	macgui/macwidget.o \
 	macgui/macwindow.o \
 	macgui/macwindowborder.o \


Commit: 1ddc4bf6bde5f791a14261b91e15eab09cb024ce
    https://github.com/scummvm/scummvm/commit/1ddc4bf6bde5f791a14261b91e15eab09cb024ce
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
GRAPHICS: MACGUI: MacButton: Properly mark dirty button states

Changed paths:
    graphics/macgui/macbutton.cpp


diff --git a/graphics/macgui/macbutton.cpp b/graphics/macgui/macbutton.cpp
index 6b413dece3..9b32f096d1 100644
--- a/graphics/macgui/macbutton.cpp
+++ b/graphics/macgui/macbutton.cpp
@@ -85,6 +85,8 @@ void MacButton::invertOuter() {
 		Graphics::drawEllipse(r.left + 1, r.top + 3, r.left + 10, r.top + 12, 0, false, Graphics::macInvertPixel, _composeSurface);
 		break;
 	}
+
+	_contentIsDirty = true;
 }
 
 void MacButton::invertInner() {
@@ -102,10 +104,12 @@ void MacButton::invertInner() {
 		Graphics::drawEllipse(r.left + 3, r.top + 5, r.left + 8, r.top + 10, 0, true, Graphics::macInvertPixel, _composeSurface);
 		break;
 	}
+
+	_contentIsDirty = true;
 }
 
 bool MacButton::draw(bool forceRedraw) {
-	if (!_contentIsDirty && !forceRedraw)
+	if ((!_contentIsDirty && !forceRedraw) || _active)
 		return false;
 
 	_maskSurface->clear(1);


Commit: 93a5c39381ca71b0f2b3e69e2a43c607b0150628
    https://github.com/scummvm/scummvm/commit/93a5c39381ca71b0f2b3e69e2a43c607b0150628
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
DIRECTOR: Cache widgets for all loaded cast

Changed paths:
    engines/director/cast.cpp
    engines/director/cast.h
    engines/director/score-loading.cpp
    engines/director/score.h
    engines/director/sprite.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 21f8fcf035..677bb58f78 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -44,11 +44,6 @@ Cast::Cast() {
 	_modified = true;
 }
 
-void Cast::createWidget() {
-	if (_widget)
-		error("TextCast::createWidget: Attempted to create widget twice");
-}
-
 BitmapCast::BitmapCast(Common::ReadStreamEndian &stream, uint32 castTag, uint16 version) {
 	_type = kCastBitmap;
 	_img = nullptr;
diff --git a/engines/director/cast.h b/engines/director/cast.h
index 2b9a0120e4..061ae35fee 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -57,7 +57,7 @@ public:
 	virtual bool isEditable() { return false; }
 	virtual bool setEditable(bool editable) { return false; }
 	virtual bool isModified() { return _modified; }
-	virtual void createWidget();
+	virtual void createWidget() {}
 
 	virtual void setColors(int *fgcolor, int *bgcolor) { return; }
 	virtual void getColors(int *fgcolor, int *bgcolor) { return; }
diff --git a/engines/director/score-loading.cpp b/engines/director/score-loading.cpp
index 175ccf982a..a2fb75083a 100644
--- a/engines/director/score-loading.cpp
+++ b/engines/director/score-loading.cpp
@@ -267,6 +267,7 @@ bool Score::loadArchive(bool isSharedCast) {
 
 	loadSpriteImages(isSharedCast);
 	loadSpriteSounds(isSharedCast);
+	createCastWidgets();
 	setSpriteCasts();
 
 	return true;
@@ -292,6 +293,15 @@ void Score::copyCastStxts() {
 	}
 }
 
+void Score::createCastWidgets() {
+	for (Common::HashMap<int, Cast *>::iterator c = _loadedCast->begin(); c != _loadedCast->end(); ++c) {
+		if (!c->_value)
+			continue;
+
+		c->_value->createWidget();
+	}
+}
+
 void Score::loadSpriteImages(bool isSharedCast) {
 	debugC(1, kDebugLoading, "****** Preloading sprite images");
 
diff --git a/engines/director/score.h b/engines/director/score.h
index 6d3143fec1..9b16890aa1 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -124,6 +124,7 @@ public:
 	void loadSpriteImages(bool isSharedCast);
 	void loadSpriteSounds(bool isSharedCast);
 	void copyCastStxts();
+	void createCastWidgets();
 
 	Common::Rect getCastMemberInitialRect(int castId);
 	void setCastMemberModified(int castId);
diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index 9e3a16ca35..84e3c54aed 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -76,18 +76,6 @@ void Sprite::updateCast() {
 	if (!_cast)
 		return;
 
-	if (!_cast->_widget) {
-		if (_cast->_type == kCastText && (_spriteType == kButtonSprite || _spriteType == kCheckboxSprite || _spriteType == kRadioButtonSprite)) {
-			// WORKAROUND: In D2/D3 there can be text casts that have button
-			// information set in the sprite.
-			warning("Sprite::updateCast: Working around D2/3 button glitch");
-			_cast->_type = kCastButton;
-			((TextCast *)_cast)->_buttonType = (ButtonType)(_spriteType - 8);
-		}
-
-		_cast->createWidget();
-	}
-
 	if (_cast->isEditable() != _editable && !_puppet)
 		_cast->setEditable(_editable);
 }
@@ -171,9 +159,21 @@ void Sprite::setCast(uint16 castId) {
 	if (castId == 0)
 		return;
 
-	if (member)
+	if (member) {
 		_cast = member;
-	else {
+
+		if (_cast->_type == kCastText &&
+				(_spriteType == kButtonSprite || _spriteType == kCheckboxSprite || _spriteType == kRadioButtonSprite)) {
+			// WORKAROUND: In D2/D3 there can be text casts that have button
+			// information set in the sprite.
+			warning("Sprite::updateCast: Working around D2/3 button glitch");
+
+			delete _cast->_widget;
+			_cast->_type = kCastButton;
+			((TextCast *)_cast)->_buttonType = (ButtonType)(_spriteType - 8);
+			((TextCast *)_cast)->createWidget();
+		}
+	} else {
 		warning("Sprite::setCast: Cast id %d has null member", castId);
 	}
 


Commit: 487dcec139c22354b6d4185e4bab49480f3b9d54
    https://github.com/scummvm/scummvm/commit/487dcec139c22354b6d4185e4bab49480f3b9d54
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
DIRECTOR: Remove shape cast hack

Changed paths:
    engines/director/sprite.cpp


diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index 84e3c54aed..1bcb88529c 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -202,8 +202,6 @@ void Sprite::setCast(uint16 castId) {
 				}
 			} else {
 				_castType = kCastShape;
-
-				g_director->getCurrentScore()->_loadedCast->setVal(_castId, new ShapeCast());
 			}
 			break;
 		case kTextSprite:


Commit: 713f9f2624f048d725c2389d6aeae9747c2ad032
    https://github.com/scummvm/scummvm/commit/713f9f2624f048d725c2389d6aeae9747c2ad032
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2020-06-30T12:38:46-04:00

Commit Message:
DIRECTOR: Unify sprite and channel internal rendering methods

Now we only need to check with the channel to see if its contents need to be
re-rendered, and the channel takes care of updating its internal locations, as
well as those of the widgets.

Changed paths:
    engines/director/lingo/lingo-the.cpp
    engines/director/score.cpp
    engines/director/score.h
    engines/director/sprite.cpp
    engines/director/sprite.h


diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 6f3d25f0b8..acdbcc952e 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -927,7 +927,7 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 	default:
 		warning("Lingo::setTheSprite(): Unprocessed setting field \"%s\" of sprite", field2str(field));
 	}
-	sprite->_dirty = true;
+	channel->_dirty = true;
 }
 
 Datum Lingo::getTheCast(Datum &id1, int field) {
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 2fd6e41301..f5ce27f522 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -53,6 +53,7 @@ Channel::Channel(Sprite *sp) {
 	_constraint = 0;
 
 	_visible = true;
+	_dirty = true;
 
 	if (_sprite && _sprite->_castType != kCastTypeNull) {
 		_sprite->updateCast();
@@ -67,6 +68,25 @@ Graphics::ManagedSurface *Channel::getSurface() {
 	}
 }
 
+bool Channel::isDirty(Sprite *nextSprite) {
+	// When a sprite is puppeted setTheSprite ensures that the dirty flag here is
+	// set. Otherwise, we need to rerender when the position, bounding box, or
+	// cast of the sprite changes.
+	bool isDirty = _dirty ||
+		_delta != Common::Point(0, 0) ||
+		((_sprite->_castType != kCastTypeNull) &&
+		 (_sprite->_cast && _sprite->_cast->isModified()));
+
+	if (nextSprite) {
+		isDirty |= _sprite->_castId != nextSprite->_castId ||
+			_sprite->getDims() != nextSprite->getDims() ||
+			(_currentPoint != nextSprite->_startPoint &&
+			 !_sprite->_puppet && !_sprite->_moveable);
+	}
+
+	return isDirty;
+}
+
 Common::Rect Channel::getBbox() {
 	Common::Rect bbox = _sprite->getDims();
 	bbox.moveTo(getPosition());
@@ -74,13 +94,30 @@ Common::Rect Channel::getBbox() {
 	return bbox;
 }
 
-void Channel::updateLocation() {
+void Channel::setClean(Sprite *nextSprite, int spriteId) {
+	_dirty = false;
+
+	if (!_sprite->_puppet) {
+		_sprite = nextSprite;
+		_sprite->updateCast();
+
+		// Sprites marked moveable are constrained to the same bounding box until
+		// the moveable is disabled
+		if (!_sprite->_moveable)
+			_currentPoint = _sprite->_startPoint;
+	}
+
 	_currentPoint += _delta;
 	_delta = Common::Point(0, 0);
 
 	if (_sprite->_cast && _sprite->_cast->_widget) {
 		Common::Point p(getPosition());
+		_sprite->_cast->_modified = false;
 		_sprite->_cast->_widget->_dims.moveTo(p.x, p.y);
+
+		_sprite->_cast->_widget->_priority = spriteId;
+		_sprite->_cast->_widget->draw();
+		_sprite->_cast->_widget->_contentIsDirty = false;
 	}
 }
 
@@ -115,10 +152,6 @@ Common::Point Channel::getPosition() {
 	return res;
 }
 
-void Channel::resetPosition() {
-	_delta = _sprite->_startPoint;
-}
-
 MacShape *Channel::getShape() {
 	MacShape *shape = new MacShape();
 
@@ -604,53 +637,22 @@ void Score::renderFrame(uint16 frameId, RenderMode mode) {
 }
 
 void Score::renderSprites(uint16 frameId, RenderMode mode) {
-	if (_vm->_newMovieStarted) {
-		// g_director->getStage()->reset();
+	if (_vm->_newMovieStarted)
 		mode = kRenderForceUpdate;
-	}
 
 	for (uint16 i = 0; i < _channels.size(); i++) {
 		Channel *channel = _channels[i];
 		Sprite *currentSprite = channel->_sprite;
 		Sprite *nextSprite = _frames[frameId]->_sprites[i];
 
-		// A sprite needs to be updated if one of the following happens:
-		// - The dimensions/bounding box of the sprite has changed (_dirty flag set)
-		// - The cast member ID of the sprite has changed (_dirty flag set)
-		// - The sprite slot from the current frame is different (cast member ID or bounding box) from the cached sprite slot
-		// (maybe we have to compare all the sprite attributes, not just these two?)
-		bool needsUpdate = currentSprite->isDirty() ||
-			currentSprite->_castId != nextSprite->_castId ||
-			channel->_delta != Common::Point(0, 0) ||
-			currentSprite->getDims() != nextSprite->getDims() ||
-			currentSprite->_ink != nextSprite->_ink ||
-			(channel->_currentPoint != nextSprite->_startPoint &&
-			 !currentSprite->_puppet && !currentSprite->_moveable);
-
-		if ((needsUpdate || mode == kRenderForceUpdate) && !currentSprite->_trails)
-			g_director->getStage()->addDirtyRect(channel->getBbox());
-
-		currentSprite->setClean();
+		bool needsUpdate = channel->isDirty(nextSprite) || mode == kRenderForceUpdate;
 
-		if (!currentSprite->_puppet) {
-			channel->_sprite = nextSprite;
-			channel->_sprite->updateCast();
-
-			// Sprites marked moveable are constrained to the same bounding box until
-			// the moveable is disabled
-			if (!channel->_sprite->_moveable)
-				channel->_currentPoint = channel->_sprite->_startPoint;
-		}
-
-		channel->updateLocation();
+		if (needsUpdate && !currentSprite->_trails)
+			g_director->getStage()->addDirtyRect(channel->getBbox());
 
-		if (channel->_sprite->_cast && channel->_sprite->_cast->_widget) {
-			channel->_sprite->_cast->_widget->_priority = i;
-			channel->_sprite->_cast->_widget->draw();
-			channel->_sprite->_cast->_widget->_contentIsDirty = false;
-		}
+		channel->setClean(nextSprite, i);
 
-		if (needsUpdate || mode == kRenderForceUpdate)
+		if (needsUpdate)
 			g_director->getStage()->addDirtyRect(channel->getBbox());
 	}
 }
diff --git a/engines/director/score.h b/engines/director/score.h
index 9b16890aa1..1ce09b715e 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -77,21 +77,21 @@ struct MacShape {
 struct Channel {
 	Sprite *_sprite;
 
+	bool _dirty;
 	bool _visible;
 	uint _constraint;
 	Common::Point _currentPoint;
 	Common::Point _delta;
 
 	Channel(Sprite *sp);
+	bool isDirty(Sprite *nextSprite = nullptr);
 	Common::Rect getBbox();
 	Common::Point getPosition();
 	MacShape *getShape();
 	Graphics::ManagedSurface *getSurface();
-	Graphics::ManagedSurface *getMask();
 
-	void updateLocation();
+	void setClean(Sprite *nextSprite, int spriteId);
 	void addDelta(Common::Point pos);
-	void resetPosition();
 };
 
 class Score {
diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index 1bcb88529c..2e30a38893 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -50,7 +50,6 @@ Sprite::Sprite() {
 	_cast = nullptr;
 
 	_thickness = 0;
-	_dirty = false;
 	_width = 0;
 	_height = 0;
 	_moveable = false;
@@ -80,10 +79,6 @@ void Sprite::updateCast() {
 		_cast->setEditable(_editable);
 }
 
-bool Sprite::isDirty() {
-	return _castType != kCastTypeNull && (_cast && _cast->isModified());
-}
-
 bool Sprite::isActive() {
 	if (_moveable || _puppet || _scriptId)
 		return true;
@@ -95,12 +90,6 @@ bool Sprite::isActive() {
 	return false;
 }
 
-void Sprite::setClean() {
-	_dirty = false;
-	if (_cast)
-		_cast->_modified = false;
-}
-
 uint16 Sprite::getPattern() {
 	switch (_spriteType) {
 	case kRectangleSprite:
@@ -224,8 +213,6 @@ void Sprite::setCast(uint16 castId) {
 			_castType = member->_type;
 		}
 	}
-
-	_dirty = true;
 }
 
 Common::Rect Sprite::getDims() {
diff --git a/engines/director/sprite.h b/engines/director/sprite.h
index b390d860b4..c36df70689 100644
--- a/engines/director/sprite.h
+++ b/engines/director/sprite.h
@@ -62,9 +62,7 @@ public:
 	~Sprite();
 
 	void updateCast();
-	bool isDirty();
 	bool isActive();
-	void setClean();
 	uint16 getPattern();
 	void setPattern(uint16 pattern);
 
@@ -90,7 +88,6 @@ public:
 	Cast *_cast;
 
 	byte _thickness;
-	bool _dirty;
 	Common::Point _startPoint;
 	int16 _width;
 	int16 _height;




More information about the Scummvm-git-logs mailing list