[Scummvm-cvs-logs] scummvm master -> 3e820ee9b36b1fc2cf6516d400f1433d2238082f

csnover csnover at users.noreply.github.com
Thu Feb 18 20:20:58 CET 2016


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

Summary:
7d54f0aaaf COMMON: Add methods for inserting and erasing with iterators
4ba0ff8deb COMMON: Add convenience method to Common::Rational for 1:1 ratios
75ccabc325 SCI: Implement accurate renderer architecture for SCI32
37539625d7 SCI: Fix palette equality check
b829e48228 SCI: Remove wrong comments in kernel.h and break apart logical sections
3bddd869ab SCI: Build kernel table for ScrollWindow and stub seen functions
20ccad80bf SCI: WIP GfxText32 code
2c0e64fdaf SCI: Add short-lived kSetFontRes kernel function
775c39102a SCI: Implement templated drawing subroutines
bb82350630 SCI: Fix broken LRU debugging
2f17ba2b0a SCI: Increase LRU resource cache for SCI32 games
3c9b930506 SCI: Add comments to plane code
03e3f2c68c SCI: Fix some rect off-by-ones
766cf6cee7 SCI: Fix too-fast rendering
5a1fa5efa1 SCI: When rewriting lookup tables, also actually record what scale they are for
3e820ee9b3 SCI: Fix bad positioning of relatively positioned pic cels


Commit: 7d54f0aaaf1854e421ee0e8eac3292c7bc0db284
    https://github.com/scummvm/scummvm/commit/7d54f0aaaf1854e421ee0e8eac3292c7bc0db284
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:01-06:00

Commit Message:
COMMON: Add methods for inserting and erasing with iterators

This provides improved feature parity to Common::List and is used
in SCI32 engine.

Changed paths:
    common/array.h
    test/common/array.h



diff --git a/common/array.h b/common/array.h
index f240a9c..db1a62b 100644
--- a/common/array.h
+++ b/common/array.h
@@ -141,6 +141,12 @@ public:
 		insert_aux(_storage + idx, array.begin(), array.end());
 	}
 
+	/**
+	 * Inserts element before pos.
+	 */
+	void insert(iterator pos, const T &element) {
+		insert_aux(pos, &element, &element + 1);
+	}
 
 	T remove_at(size_type idx) {
 		assert(idx < _size);
@@ -187,6 +193,14 @@ public:
 		_capacity = 0;
 	}
 
+	iterator erase(iterator pos) {
+		copy(pos + 1, _storage + _size, pos);
+		_size--;
+		// We also need to destroy the last object properly here.
+		_storage[_size].~T();
+		return pos;
+	}
+
 	bool empty() const {
 		return (_size == 0);
 	}
diff --git a/test/common/array.h b/test/common/array.h
index f0027ec..64354ab 100644
--- a/test/common/array.h
+++ b/test/common/array.h
@@ -44,6 +44,48 @@ class ArrayTestSuite : public CxxTest::TestSuite
 		TS_ASSERT_EQUALS(iter, array.end());
 	}
 
+	void test_erase_iterator() {
+		Common::Array<int> array;
+		Common::Array<int>::iterator iter;
+
+		// Fill the array with some random data
+		array.push_back(17);
+		array.push_back(33);
+		array.push_back(-11);
+
+		iter = array.begin();
+		++iter;
+
+		iter = array.erase(iter);
+		TS_ASSERT_DIFFERS(iter, array.end());
+		TS_ASSERT_EQUALS(*iter, -11);
+		TS_ASSERT_EQUALS(array.size(), (unsigned int)2);
+		TS_ASSERT_EQUALS(array[0], 17);
+		TS_ASSERT_EQUALS(array[1], -11);
+	}
+
+	void test_insert_iterator() {
+		Common::Array<int> array;
+		Common::Array<int>::iterator iter;
+
+		// Fill the array with some random data
+		array.push_back(17);
+		array.push_back(33);
+		array.push_back(-11);
+
+		iter = array.begin();
+		++iter;
+
+		array.insert(iter, 99);
+
+		TS_ASSERT_EQUALS(*iter, 99);
+		TS_ASSERT_EQUALS(array.size(), (unsigned int)4);
+		TS_ASSERT_EQUALS(array[0], 17);
+		TS_ASSERT_EQUALS(array[1], 99);
+		TS_ASSERT_EQUALS(array[2], 33);
+		TS_ASSERT_EQUALS(array[3], -11);
+	}
+
 	void test_direct_access() {
 		Common::Array<int> array;
 


Commit: 4ba0ff8deb57aba3b034462c6c00ecf13ee281c9
    https://github.com/scummvm/scummvm/commit/4ba0ff8deb57aba3b034462c6c00ecf13ee281c9
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
COMMON: Add convenience method to Common::Rational for 1:1 ratios

For SCI engine games, ratios may not be normalised and so to avoid
extra scaling, there needs to be a way to simply check whether a
ratio is 1:1.

Changed paths:
    common/rational.h
    test/common/rational.h



diff --git a/common/rational.h b/common/rational.h
index 55fb361..89caaf2 100644
--- a/common/rational.h
+++ b/common/rational.h
@@ -84,6 +84,8 @@ public:
 	int getNumerator() const { return _num; }
 	int getDenominator() const { return _denom; }
 
+	bool isOne() const { return _num == _denom; }
+
 	void debugPrint(int debuglevel = 0, const char *caption = "Rational:") const;
 
 private:
diff --git a/test/common/rational.h b/test/common/rational.h
index 46dfc27..23d0c10 100644
--- a/test/common/rational.h
+++ b/test/common/rational.h
@@ -130,4 +130,15 @@ public:
 		TS_ASSERT_EQUALS(r1 / 2, Common::Rational(1, 4));
 		TS_ASSERT_EQUALS(2 / r1, Common::Rational(4, 1));
 	}
+
+	void test_isOne() {
+		Common::Rational r0(5, 5);
+		Common::Rational r1(1, 2);
+		Common::Rational r2(2, 1);
+		Common::Rational r3(1, 1);
+		TS_ASSERT(r0.isOne());
+		TS_ASSERT(!r1.isOne());
+		TS_ASSERT(!r2.isOne());
+		TS_ASSERT(r3.isOne());
+	}
 };


Commit: 75ccabc325d56876dd34d4a55e2034ee66d33d0b
    https://github.com/scummvm/scummvm/commit/75ccabc325d56876dd34d4a55e2034ee66d33d0b
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Implement accurate renderer architecture for SCI32

Changed paths:
  A engines/sci/graphics/celobj32.cpp
  A engines/sci/graphics/celobj32.h
  A engines/sci/graphics/lists32.h
  A engines/sci/graphics/plane32.cpp
  A engines/sci/graphics/plane32.h
  A engines/sci/graphics/screen_item32.cpp
  A engines/sci/graphics/screen_item32.h
    engines/sci/console.cpp
    engines/sci/console.h
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/object.h
    engines/sci/engine/selector.cpp
    engines/sci/engine/selector.h
    engines/sci/engine/vm_types.cpp
    engines/sci/engine/vm_types.h
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/frameout.h
    engines/sci/graphics/helpers.h
    engines/sci/graphics/palette32.cpp
    engines/sci/graphics/palette32.h
    engines/sci/graphics/picture.cpp
    engines/sci/graphics/picture.h
    engines/sci/graphics/screen.h
    engines/sci/module.mk
    engines/sci/sci.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 438c725..bea67e6 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -137,6 +137,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
 	registerCmd("wl",                 WRAP_METHOD(Console, cmdWindowList));	// alias
 	registerCmd("plane_list",         WRAP_METHOD(Console, cmdPlaneList));
 	registerCmd("pl",                 WRAP_METHOD(Console, cmdPlaneList));	// alias
+	registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList));
+	registerCmd("vpl",                WRAP_METHOD(Console, cmdVisiblePlaneList));	// alias
 	registerCmd("plane_items",        WRAP_METHOD(Console, cmdPlaneItemList));
 	registerCmd("pi",                 WRAP_METHOD(Console, cmdPlaneItemList));	// alias
 	registerCmd("saved_bits",         WRAP_METHOD(Console, cmdSavedBits));
@@ -380,6 +382,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
 	debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n");
 	debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n");
 	debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n");
+	debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n");
 	debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n");
 	debugPrintf(" saved_bits - List saved bits on the hunk\n");
 	debugPrintf(" show_saved_bits - Display saved bits\n");
@@ -1766,6 +1769,21 @@ bool Console::cmdPlaneList(int argc, const char **argv) {
 	return true;
 }
 
+bool Console::cmdVisiblePlaneList(int argc, const char **argv) {
+#ifdef ENABLE_SCI32
+	if (_engine->_gfxFrameout) {
+		debugPrintf("Visible plane list:\n");
+		_engine->_gfxFrameout->printVisiblePlaneList(this);
+	} else {
+		debugPrintf("This SCI version does not have a list of planes\n");
+	}
+#else
+	debugPrintf("SCI32 isn't included in this compiled executable\n");
+#endif
+	return true;
+}
+
+
 bool Console::cmdPlaneItemList(int argc, const char **argv) {
 	if (argc != 2) {
 		debugPrintf("Shows the list of items for a plane\n");
diff --git a/engines/sci/console.h b/engines/sci/console.h
index 8b10912..7c4de02 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -96,6 +96,7 @@ private:
 	bool cmdAnimateList(int argc, const char **argv);
 	bool cmdWindowList(int argc, const char **argv);
 	bool cmdPlaneList(int argc, const char **argv);
+	bool cmdVisiblePlaneList(int argc, const char **argv);
 	bool cmdPlaneItemList(int argc, const char **argv);
 	bool cmdSavedBits(int argc, const char **argv);
 	bool cmdShowSavedBits(int argc, const char **argv);
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 8d41393..93e8b90 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -80,21 +80,18 @@ reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) {
-	if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL)
-		g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
-	else
-		g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
-	return s->r_acc;
+	g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
+	return NULL_REG;
 }
 
 reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) {
 	g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
-	return s->r_acc;
+	return NULL_REG;
 }
 
 reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) {
 	g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]);
-	return s->r_acc;
+	return NULL_REG;
 }
 
 reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) {
@@ -115,11 +112,12 @@ reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
 reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) {
 	reg_t planeObj = argv[0];
 	GuiResourceId pictureId = argv[1].toUint16();
-	int16 pictureX = argv[2].toSint16();
-	int16 pictureY = argv[3].toSint16();
+	int16 x = argv[2].toSint16();
+	int16 y = argv[3].toSint16();
+	bool mirrorX = argc > 4 ? argv[4].toSint16() : false;
 
-	g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY);
-	return s->r_acc;
+	g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX);
+	return NULL_REG;
 }
 
 reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
@@ -127,43 +125,13 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
-	GraphicsMgr &graphicsMgr = g_sci->_graphicsMgr;
-	if (graphicsMgr.palMorphNeeded) {
-		graphicsMgr.PalMorphFrameOut(&g_PalStyleRanges, false);
-	}
-	else {
-		// TODO: Not sure if this is a pointer or not yet.
-		if (g_ScrollState != nullptr) {
-			kFrameOutDoScroll();
-		}
-
-		bool showBits = true;
-		if (argc == 1) {
-			showBits = (bool) argv[0].toUint16();
-		}
-
-		rect SOL_Rect = { .left = 0, .top = 0, .right = UINT32_MAX, .bottom = UINT32_MAX };
-		graphicsMgr.FrameOut(showBits, &rect);
-	}
-*/
-	g_sci->_gfxFrameout->kernelFrameout();
+	bool showBits = argc > 0 ? argv[0].toUint16() : true;
+	g_sci->_gfxFrameout->kernelFrameout(showBits);
 	return NULL_REG;
 }
 
 reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
-	 uint16 start = argv[0].toUint16();
-	 uint16 end = argv[1].toUint16();
-	 if (end <= start) {
-		uint16 index = start;
-		while (index <= end) {
-			g_PalStyleRanges[index] = 0;
-		}
-	 }
-*/
-
-	kStub(s, argc, argv);
+	g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16());
 	return NULL_REG;
 }
 
@@ -266,72 +234,59 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
 }
 
 /**
- * Used for scene transitions, replacing (but reusing parts of) the old
- * transition code.
+ * Causes an immediate plane transition with an optional transition
+ * effect
  */
 reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
-	// Can be called with 7 or 8 parameters
-	// The style defines which transition to perform. Related to the transition
-	// tables inside graphics/transitions.cpp
-	uint16 showStyle = argv[0].toUint16();	// 0 - 15
-	reg_t planeObj = argv[1];	// the affected plane
-	Common::String planeObjName = s->_segMan->getObjectName(planeObj);
-	uint16 seconds = argv[2].toUint16();	// seconds that the transition lasts
-	uint16 backColor =  argv[3].toUint16();	// target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff
-	int16 priority = argv[4].toSint16();	// always 0xc8 (200) when fading in/out
-	uint16 animate = argv[5].toUint16();	// boolean, animate or not while the transition lasts
-	uint16 refFrame = argv[6].toUint16();	// refFrame, always 0 when fading in/out
+	ShowStyleType type = (ShowStyleType)argv[0].toUint16();
+	reg_t planeObj = argv[1];
+	int16 seconds = argv[2].toSint16();
+	// NOTE: This value seems to indicate whether the transition is an
+	// “exit” transition (0) or an “enter” transition (-1) for fade
+	// transitions. For other types of transitions, it indicates a palette
+	// index value to use when filling the screen.
+	int16 back = argv[3].toSint16();
+	int16 priority = argv[4].toSint16();
+	int16 animate = argv[5].toSint16();
+	// TODO: Rename to frameOutNow?
+	int16 refFrame = argv[6].toSint16();
+	int16 blackScreen;
+	reg_t pFadeArray;
 	int16 divisions;
 
-	// If the game has the pFadeArray selector, another parameter is used here,
-	// before the optional last parameter
-	bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0;
-	if (hasFadeArray) {
-		// argv[7]
-		divisions = (argc >= 9) ? argv[8].toSint16() : -1;	// divisions (transition steps?)
-	} else {
-		divisions = (argc >= 8) ? argv[7].toSint16() : -1;	// divisions (transition steps?)
+	// SCI 2–2.1early
+	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+		blackScreen = 0;
+		pFadeArray = NULL_REG;
+		divisions = argc > 7 ? argv[7].toSint16() : -1;
 	}
-
-	if (showStyle > 15) {
-		warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj));
-		return s->r_acc;
+	// SCI 2.1mid–2.1late
+	else if (getSciVersion() < SCI_VERSION_3) {
+		blackScreen = 0;
+		pFadeArray = argc > 7 ? argv[7] : NULL_REG;
+		divisions = argc > 8 ? argv[8].toSint16() : -1;
+	}
+	// SCI 3
+	else {
+		blackScreen = argv[7].toSint16();
+		pFadeArray = argc > 8 ? argv[8] : NULL_REG;
+		divisions = argc > 9 ? argv[9].toSint16() : -1;
 	}
 
-	// GK1 calls fadeout (13) / fadein (14) with the following parameters:
-	// seconds: 1
-	// backColor: 0 / -1
-	// fade: 200
-	// animate: 0
-	// refFrame: 0
-	// divisions: 0 / 20
-
-	// TODO: Check if the plane is in the list of planes to draw
-
-	Common::String effectName = "unknown";
+// TODO: Reuse later for SCI2 and SCI3 implementation and then discard
+//	warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, "
+//			"dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, "
+//			"pFadeArray: %04x:%04x (%s), divisions: %d",
+//			type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds,
+//			back, priority, animate, refFrame, blackScreen,
+//			PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions);
 
-	switch (showStyle) {
-	case 0:		// no transition / show
-		effectName = "show";
-		break;
-	case 13:	// fade out
-		effectName = "fade out";
-		// TODO
-		break;
-	case 14:	// fade in
-		effectName = "fade in";
-		// TODO
-		break;
-	default:
-		// TODO
-		break;
-	}
+	// NOTE: The order of planeObj and showStyle are reversed
+	// because this is how SCI3 called the corresponding method
+	// on the KernelMgr
+	g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
 
-	warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, "
-			"back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d",
-			showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(),
-			seconds, backColor, priority, animate, refFrame, divisions);
-	return s->r_acc;
+	return NULL_REG;
 }
 
 reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
@@ -359,6 +314,8 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
+	return kStub(s, argc, argv);
+#if 0
 	// Used by SQ6 and LSL6 hires for the text area in the bottom of the
 	// screen. The relevant scripts also exist in Phantasmagoria 1, but they're
 	// unused. This is always called by scripts 64906 (ScrollerWindow) and
@@ -464,6 +421,7 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
 	}
 
 	return s->r_acc;
+#endif
 }
 
 reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
@@ -497,6 +455,8 @@ reg_t kFont(EngineState *s, int argc, reg_t *argv) {
 // TODO: Eventually, all of the kBitmap operations should be put
 // in a separate class
 
+// NOTE: This size is correct only for SCI2.1mid; the size for
+// SCI2/2.1early is 36
 #define BITMAP_HEADER_SIZE 46
 
 reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
@@ -673,6 +633,8 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
+	return kStub(s, argc, argv);
+#if 0
 	reg_t plane = argv[0];
 	Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
 	Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
@@ -681,10 +643,15 @@ reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
 	byte priority = (byte)argv[7].toUint16();
 	byte control = (byte)argv[8].toUint16();
 	// argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
-	return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+//	return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+	return s->r_acc;
+#endif
 }
 
 reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
+	return kStub(s, argc, argv);
+
+#if 0
 	reg_t hunkId = argv[0];
 	reg_t plane = argv[1];
 	Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
@@ -694,14 +661,18 @@ reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
 	byte priority = (byte)argv[8].toUint16();
 	byte control = (byte)argv[9].toUint16();
 	// argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
-	g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
+//	g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
 	return s->r_acc;
+#endif
 }
 reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
+	return kStub(s, argc, argv);
+#if 0
 	reg_t hunkId = argv[0];
 	reg_t plane = argv[1];
-	g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+//	g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
 	return s->r_acc;
+#endif
 }
 
 reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
@@ -730,12 +701,7 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
 
 // Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270)
 reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) {
-	// TODO: g_sci->_gfxManager->palMorphIsOn = true
-	// This function sets the palMorphIsOn flag which causes kFrameOut to use
-	// an alternative FrameOut function (GraphicsMgr::PalMorphFrameOut instead
-	// of GraphicsMgr::FrameOut). At the end of the frame, kFrameOut sets the
-	// palMorphIsOn flag back to false.
-	kStub(s, argc, argv);
+	g_sci->_gfxFrameout->_palMorphIsOn = true;
 	return NULL_REG;
 }
 
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 0ae7ed2..cc9f5eb 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -41,8 +41,21 @@ enum {
 };
 
 enum infoSelectorFlags {
-	kInfoFlagClone = 0x0001,
-	kInfoFlagClass = 0x8000
+	kInfoFlagClone        = 0x0001,
+#ifdef ENABLE_SCI32
+	/**
+	 * When set, indicates to game scripts that a screen
+	 * item can be updated.
+	 */
+	kInfoFlagViewVisible  = 0x0008, // TODO: "dirty" ?
+
+	/**
+	 * When set, the object has an associated screen item in
+	 * the rendering tree.
+	 */
+	kInfoFlagViewInserted = 0x0010,
+#endif
+	kInfoFlagClass        = 0x8000
 };
 
 enum ObjectOffsets {
@@ -120,7 +133,24 @@ public:
 			_infoSelectorSci3 = info;
 	}
 
-	// No setter for the -info- selector
+#ifdef ENABLE_SCI32
+	void setInfoSelectorFlag(infoSelectorFlags flag) {
+		if (getSciVersion() < SCI_VERSION_3) {
+			_variables[_offset + 2] |= flag;
+		} else {
+			_infoSelectorSci3 |= flag;
+		}
+	}
+
+	// NOTE: In real engine, -info- is treated as byte size
+	void clearInfoSelectorFlag(infoSelectorFlags flag) {
+		if (getSciVersion() < SCI_VERSION_3) {
+			_variables[_offset + 2] &= ~flag;
+		} else {
+			_infoSelectorSci3 &= ~flag;
+		}
+	}
+#endif
 
 	reg_t getNameSelector() const {
 		if (getSciVersion() < SCI_VERSION_3)
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 910f1f8..05a9e6c 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -187,6 +187,7 @@ void Kernel::mapSelectors() {
 	FIND_SELECTOR(inLeft);
 	FIND_SELECTOR(inBottom);
 	FIND_SELECTOR(inRight);
+	FIND_SELECTOR(magnifier);
 #endif
 }
 
@@ -211,8 +212,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
 	if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable)
 		error("Selector '%s' of object at %04x:%04x could not be"
 		         " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
-	else
+	else {
 		*address.getPointer(segMan) = value;
+#ifdef ENABLE_SCI32
+		// TODO: Make this correct for all SCI versions
+		// Selectors 26 through 44 are selectors for View script objects
+		if (getSciVersion() >= SCI_VERSION_2 && selectorId >= 26 && selectorId <= 44) {
+			segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewVisible);
+		}
+#endif
+	}
 }
 
 void invokeSelector(EngineState *s, reg_t object, int selectorId,
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index b3dd393..a8b195f 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -153,6 +153,8 @@ struct SelectorCache {
 
 	Selector useInsetRect;
 	Selector inTop, inLeft, inBottom, inRight;
+
+	Selector magnifier;
 #endif
 };
 
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index cf008c4..53a5a5c 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -210,6 +210,20 @@ reg_t reg_t::operator^(const reg_t right) const {
 		return lookForWorkaround(right, "bitwise XOR");
 }
 
+#ifdef ENABLE_SCI32
+reg_t reg_t::operator&(int16 right) const {
+	return *this & make_reg(0, right);
+}
+
+reg_t reg_t::operator|(int16 right) const {
+	return *this | make_reg(0, right);
+}
+
+reg_t reg_t::operator^(int16 right) const {
+	return *this ^ make_reg(0, right);
+}
+#endif
+
 int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
 	if (getSegment() == right.getSegment()) { // can compare things in the same segment
 		if (treatAsUnsigned || !isNumber())
diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h
index af78bd0..a646478 100644
--- a/engines/sci/engine/vm_types.h
+++ b/engines/sci/engine/vm_types.h
@@ -136,6 +136,19 @@ struct reg_t {
 	reg_t operator|(const reg_t right) const;
 	reg_t operator^(const reg_t right) const;
 
+#ifdef ENABLE_SCI32
+	reg_t operator&(int16 right) const;
+	reg_t operator|(int16 right) const;
+	reg_t operator^(int16 right) const;
+
+	void operator&=(const reg_t &right) { *this = *this & right; }
+	void operator|=(const reg_t &right) { *this = *this | right; }
+	void operator^=(const reg_t &right) { *this = *this ^ right; }
+	void operator&=(int16 right) { *this = *this & right; }
+	void operator|=(int16 right) { *this = *this | right; }
+	void operator^=(int16 right) { *this = *this ^ right; }
+#endif
+
 private:
 	/**
 	 * Compares two reg_t's.
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
new file mode 100644
index 0000000..4b8d9af
--- /dev/null
+++ b/engines/sci/graphics/celobj32.cpp
@@ -0,0 +1,988 @@
+/* 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 "sci/resource.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/picture.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark CelScaler
+CelScaler *CelObj::_scaler = nullptr;
+
+void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
+	const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+	const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+	for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
+		if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
+			_activeIndex = i;
+			return;
+		}
+	}
+
+	int i = 1 - _activeIndex;
+	_activeIndex = i;
+	CelScalerTable &table = _scaleTables[i];
+
+	if (table.scaleX != scaleX) {
+		assert(screenWidth <= ARRAYSIZE(table.valuesX));
+		buildLookupTable(table.valuesX, scaleX, screenWidth);
+	}
+
+	if (table.scaleY != scaleY) {
+		assert(screenHeight <= ARRAYSIZE(table.valuesY));
+		buildLookupTable(table.valuesY, scaleY, screenHeight);
+	}
+}
+
+void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
+	int value = 0;
+	int remainder = 0;
+	int num = ratio.getNumerator();
+	for (int i = 0; i < size; ++i) {
+		*table++ = value;
+		remainder += ratio.getDenominator();
+		if (remainder >= num) {
+			value += remainder / num;
+			remainder %= num;
+		}
+	}
+}
+
+const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) {
+	activateScaleTables(scaleX, scaleY);
+	return &_scaleTables[_activeIndex];
+}
+
+#pragma mark -
+#pragma mark CelObj
+
+void CelObj::init() {
+	_nextCacheId = 1;
+	delete _scaler;
+	_scaler = new CelScaler();
+	delete _cache;
+	_cache = new CelCache;
+	_cache->resize(100);
+}
+
+void CelObj::deinit() {
+	delete _scaler;
+	_scaler = nullptr;
+	delete _cache;
+	_cache = nullptr;
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
+	const Buffer &priorityMap = g_sci->_gfxFrameout->getPriorityMap();
+	const Common::Point &scaledPosition = screenItem._scaledPosition;
+	const Ratio &scaleX = screenItem._ratioX;
+	const Ratio &scaleY = screenItem._ratioY;
+
+	if (_remap) {
+		if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
+			const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+
+			// NOTE: In the original engine code, there was a second branch for
+			// _remap here that would then call the following functions if _remap was false:
+			//
+			// drawHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+			// drawNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+			// drawUncompHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+			// drawUncompNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+			// scaleDraw(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
+			// scaleDrawUncomp(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
+			//
+			// However, obviously, _remap cannot be false here. This dead code branch existed in
+			// at least SCI2/GK1 and SCI2.1/SQ6.
+
+			if (scaleX.isOne() && scaleY.isOne()) {
+				if (_compressionType == kCelCompressionNone) {
+					if (_drawMirrored) {
+						drawUncompHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+					} else {
+						drawUncompNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+					}
+				} else {
+					if (_drawMirrored) {
+						drawHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+					} else {
+						drawNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+					}
+				}
+			} else {
+				if (_compressionType == kCelCompressionNone) {
+					scaleDrawUncompMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+				} else {
+					scaleDrawMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+				}
+			}
+		} else {
+			// NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
+			// but since we are already in a `_remap` branch, there is no reason to check it
+			// again
+			if (/* TODO: g_Remap_numActiveRemaps */ false) {
+				if (scaleX.isOne() && scaleY.isOne()) {
+					if (_compressionType == kCelCompressionNone) {
+						if (_drawMirrored) {
+							drawUncompHzFlipMap(target, targetRect, scaledPosition);
+						} else {
+							drawUncompNoFlipMap(target, targetRect, scaledPosition);
+						}
+					} else {
+						if (_drawMirrored) {
+							drawHzFlipMap(target, targetRect, scaledPosition);
+						} else {
+							drawNoFlipMap(target, targetRect, scaledPosition);
+						}
+					}
+				} else {
+					if (_compressionType == kCelCompressionNone) {
+						scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+					} else {
+						scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+					}
+				}
+			} else {
+				if (scaleX.isOne() && scaleY.isOne()) {
+					if (_compressionType == kCelCompressionNone) {
+						if (_drawMirrored) {
+							drawUncompHzFlip(target, targetRect, scaledPosition);
+						} else {
+							drawUncompNoFlip(target, targetRect, scaledPosition);
+						}
+					} else {
+						if (_drawMirrored) {
+							drawHzFlip(target, targetRect, scaledPosition);
+						} else {
+							drawNoFlip(target, targetRect, scaledPosition);
+						}
+					}
+				} else {
+					if (_compressionType == kCelCompressionNone) {
+						scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
+					} else {
+						scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
+					}
+				}
+			}
+		}
+	} else {
+		if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
+			const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+			if (scaleX.isOne() && scaleY.isOne()) {
+				if (_compressionType == kCelCompressionNone) {
+					if (_drawMirrored) {
+						drawUncompHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+					} else {
+						drawUncompNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+					}
+				} else {
+					if (_drawMirrored) {
+						drawHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+					} else {
+						drawNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+					}
+				}
+			} else {
+				if (_compressionType == kCelCompressionNone) {
+					scaleDrawUncompNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+				} else {
+					scaleDrawNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+				}
+			}
+		} else {
+			if (scaleX.isOne() && scaleY.isOne()) {
+				if (_compressionType == kCelCompressionNone) {
+					if (_transparent) {
+						if (_drawMirrored) {
+							drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+						} else {
+							drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+						}
+					} else {
+						if (_drawMirrored) {
+							drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
+						} else {
+							drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
+						}
+					}
+				} else {
+					if (_drawMirrored) {
+						drawHzFlipNoMD(target, targetRect, scaledPosition);
+					} else {
+						drawNoFlipNoMD(target, targetRect, scaledPosition);
+					}
+				}
+			} else {
+				if (_compressionType == kCelCompressionNone) {
+					scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+				} else {
+					scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+				}
+			}
+		}
+	}
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
+	_drawMirrored = mirrorX;
+	draw(target, screenItem, targetRect);
+}
+
+void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+	_drawMirrored = mirrorX;
+	Ratio square;
+	drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const {
+	if (_remap) {
+		if (scaleX.isOne() && scaleY.isOne()) {
+			if (_compressionType == kCelCompressionNone) {
+				if (_drawMirrored) {
+					drawUncompHzFlipMap(target, targetRect, scaledPosition);
+				} else {
+					drawUncompNoFlipMap(target, targetRect, scaledPosition);
+				}
+			} else {
+				if (_drawMirrored) {
+					drawHzFlipMap(target, targetRect, scaledPosition);
+				} else {
+					drawNoFlipMap(target, targetRect, scaledPosition);
+				}
+			}
+		} else {
+			if (_compressionType == kCelCompressionNone) {
+				scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+			} else {
+				scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+			}
+		}
+	} else {
+		if (scaleX.isOne() && scaleY.isOne()) {
+			if (_compressionType == kCelCompressionNone) {
+				if (_drawMirrored) {
+					drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+				} else {
+					drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+				}
+			} else {
+				if (_drawMirrored) {
+					drawHzFlipNoMD(target, targetRect, scaledPosition);
+				} else {
+					drawNoFlipNoMD(target, targetRect, scaledPosition);
+				}
+			}
+		} else {
+			if (_compressionType == kCelCompressionNone) {
+				scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+			} else {
+				scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+			}
+		}
+	}
+}
+
+uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
+	byte *resource = getResPointer();
+	byte *celHeader = resource + _celHeaderOffset;
+
+	if (mirrorX) {
+		x = _width - x - 1;
+	}
+
+	if (_compressionType == kCelCompressionNone) {
+		byte *pixels = resource + READ_SCI11ENDIAN_UINT32(celHeader + 24);
+		return pixels[y * _width + x];
+	} else {
+		byte buffer[1024];
+
+		uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+		uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+		uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+
+		// compressed data segment for row
+		byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
+
+		// uncompressed data segment for row
+		byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
+
+		uint8 length;
+		byte controlByte;
+		for (uint i = 0; i <= x; i += length) {
+			controlByte = *row++;
+			length = controlByte;
+
+			// Run-length encoded
+			if (controlByte & 0x80) {
+				length &= 0x3F;
+				assert(i + length < sizeof(buffer));
+
+				// Fill with skip color
+				if (controlByte & 0x40) {
+					memset(buffer + i, _transparentColor, length);
+				// Next value is fill colour
+				} else {
+					memset(buffer + i, *literal, length);
+					++literal;
+				}
+			// Uncompressed
+			} else {
+				assert(i + length < sizeof(buffer));
+				memcpy(buffer + i, literal, length);
+				literal += length;
+			}
+		}
+
+		return buffer[x];
+	}
+}
+
+void CelObj::submitPalette() const {
+	if (_hunkPaletteOffset) {
+		Palette palette;
+
+		byte *res = getResPointer();
+		// NOTE: In SCI engine this uses HunkPalette::Init.
+		// TODO: Use a better size value
+		g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette);
+		g_sci->_gfxPalette32->submit(palette);
+	}
+}
+
+#pragma mark -
+#pragma mark CelObj - Caching
+int CelObj::_nextCacheId = 1;
+CelCache *CelObj::_cache = nullptr;
+
+int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+	int oldestId = _nextCacheId + 1;
+	int oldestIndex = -1;
+
+	for (int i = 0, len = _cache->size(); i < len; ++i) {
+		CelCacheEntry &entry = (*_cache)[i];
+
+		if (entry.celObj != nullptr) {
+			if (entry.celObj->_info == celInfo) {
+				entry.id = ++_nextCacheId;
+				return i;
+			}
+
+			if (oldestId > entry.id) {
+				oldestId = entry.id;
+				oldestIndex = i;
+			}
+		} else if (oldestIndex == -1) {
+			oldestIndex = i;
+		}
+	}
+
+	// NOTE: Unlike the original SCI engine code, the out-param
+	// here is only updated if there was not a cache hit.
+	*nextInsertIndex = oldestIndex;
+	return -1;
+}
+
+void CelObj::putCopyInCache(const int cacheIndex) const {
+	if (cacheIndex == -1) {
+		error("Invalid cache index");
+	}
+
+	CelCacheEntry &entry = (*_cache)[cacheIndex];
+
+	if (entry.celObj != nullptr) {
+		delete entry.celObj;
+	}
+
+	entry.celObj = duplicate();
+	entry.id = ++_nextCacheId;
+}
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+void dummyFill(Buffer &target, const Common::Rect &targetRect) {
+	target.fillRect(targetRect, 250);
+}
+
+void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawHzFlip");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawNoFlip");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompNoFlip");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompHzFlip");
+	dummyFill(target, targetRect);
+}
+void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDraw");
+	dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDrawUncomp");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawHzFlipMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawNoFlipMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompNoFlipMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompHzFlipMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDrawMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDrawUncompMap");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawHzFlipNoMD");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	const int sourceX = targetRect.left - scaledPosition.x;
+	const int sourceY = targetRect.top - scaledPosition.y;
+
+	byte *targetPixel = (byte *)target.getPixels() + (targetRect.top * target.screenWidth) + targetRect.left;
+
+	const int stride = target.screenWidth - targetRect.width();
+
+	byte *resource = getResPointer();
+	byte *celHeader = resource + _celHeaderOffset;
+
+	byte buffer[1024];
+
+	uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+	uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+	uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+
+	for (int y = sourceY; y < sourceY + targetRect.height(); ++y) {
+		// compressed data segment for row
+		byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
+
+		// uncompressed data segment for row
+		byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
+
+		uint8 length;
+		byte controlByte;
+		for (int i = 0; i <= targetRect.width(); i += length) {
+			controlByte = *row++;
+			length = controlByte;
+
+			// Run-length encoded
+			if (controlByte & 0x80) {
+				length &= 0x3F;
+				assert(i + length < (int)sizeof(buffer));
+
+				// Fill with skip color
+				if (controlByte & 0x40) {
+					memset(buffer + i, _transparentColor, length);
+					// Next value is fill colour
+				} else {
+					memset(buffer + i, *literal, length);
+					++literal;
+				}
+				// Uncompressed
+			} else {
+				assert(i + length < (int)sizeof(buffer));
+				memcpy(buffer + i, literal, length);
+				literal += length;
+			}
+		}
+
+		for (int x = 0; x < targetRect.width(); ++x) {
+			byte pixel = buffer[sourceX + x];
+
+			if (pixel != _transparentColor) {
+				*targetPixel = pixel;
+			}
+
+			++targetPixel;
+		}
+
+		targetPixel += stride;
+	}
+}
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	const int sourceX = targetRect.left - scaledPosition.x;
+	const int sourceY = targetRect.top - scaledPosition.y;
+
+	const int sourceStride = _width - targetRect.width();
+	const int targetStride = target.screenWidth - targetRect.width();
+	const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+	byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
+	byte *targetPixel = (byte *)target.getPixels() + targetRect.top * target.screenWidth + targetRect.left;
+
+	for (int y = 0; y < targetRect.height(); ++y) {
+		for (int x = 0; x < targetRect.width(); ++x) {
+			byte pixel = *sourcePixel++;
+
+			if (pixel != _transparentColor) {
+				*targetPixel = pixel;
+			}
+
+			++targetPixel;
+		}
+
+		sourcePixel += sourceStride;
+		targetPixel += targetStride;
+	}
+}
+void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	const int sourceX = targetRect.left - scaledPosition.x;
+	const int sourceY = targetRect.top - scaledPosition.y;
+	const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+	byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
+
+	target.copyRectToSurface(sourcePixel, _width, targetRect.left, targetRect.top, targetRect.width(), targetRect.height());
+
+}
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompHzFlipNoMD");
+	dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("drawUncompHzFlipNoMDNoSkip");
+	dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDrawNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
+	dummyFill(target, targetRect);
+}
+
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	debug("scaleDrawUncompNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
+	if (targetRect.isEmpty()) {
+		return;
+	}
+
+	const CelScalerTable *table = _scaler->getScalerTable(scaleX, scaleY);
+
+	int pixelX[1024];
+	int pixelY[1024];
+
+	bool use2xOptimisedDrawRoutine = false /* TODO: scaleX.getDenominator() * 2 == scaleX.getNumerator() */;
+
+	int16 sourceX = (scaledPosition.x * scaleX.getInverse()).toInt();
+	int16 sourceY = (scaledPosition.y * scaleY.getInverse()).toInt();
+
+	if (_drawMirrored) {
+		for (int x = targetRect.left; x < targetRect.right; ++x) {
+			pixelX[x] = _width - 1 - (table->valuesX[x] - sourceX);
+		}
+	} else {
+		for (int x = targetRect.left; x < targetRect.right; ++x) {
+			pixelX[x] = table->valuesX[x] - sourceX;
+		}
+	}
+
+	for (int y = targetRect.top; y < targetRect.bottom; ++y) {
+		pixelY[y] = table->valuesY[y] - sourceY;
+	}
+
+	byte *sourcePixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+	for (int y = targetRect.top; y < targetRect.bottom; ++y) {
+		byte *targetPixel = target.getAddress(targetRect.left, y);
+		byte *sourcePixel = sourcePixels + pixelY[y] * _width;
+		const int *sourcePixelIndex = pixelX + targetRect.left;
+
+		if (/* TODO */ use2xOptimisedDrawRoutine) {
+			// WriteUncompScaleLine2();
+		} else {
+			// start WriteUncompScaleLine
+			for (int x = targetRect.left; x < targetRect.right; ++x) {
+				byte value = sourcePixel[*sourcePixelIndex++];
+				if (value != _transparentColor) {
+					*targetPixel = value;
+				}
+				++targetPixel;
+			}
+			// end WriteUncompScaleLine
+		}
+	}
+}
+
+// TODO: These functions may all be vestigial.
+void CelObj::drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+
+#pragma mark -
+#pragma mark CelObjView
+CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+	_info.type = kCelTypeView;
+	_info.resourceId = viewId;
+	_info.loopNo = loopNo;
+	_info.celNo = celNo;
+	_mirrorX = false;
+	_compressionType = kCelCompressionInvalid;
+	_transparent = true;
+
+	int cacheInsertIndex;
+	int cacheIndex = searchCache(_info, &cacheInsertIndex);
+	if (cacheIndex != -1) {
+		CelCacheEntry &entry = (*_cache)[cacheIndex];
+		*this = *dynamic_cast<CelObjView *>(entry.celObj);
+		entry.id = ++_nextCacheId;
+		return;
+	}
+
+	// TODO: The next code should be moved to a common file that
+	// generates view resource metadata for both SCI16 and SCI32
+	// implementations
+
+	Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+	// NOTE: SCI2.1/SQ6 just silently returns here.
+	if (!resource) {
+		warning("View resource %d not loaded", viewId);
+		return;
+	}
+
+	byte *data = resource->data;
+
+	_scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
+	_scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
+
+	if (_scaledWidth == 0 || _scaledHeight == 0) {
+		byte sizeFlag = data[5];
+		if (sizeFlag == 0) {
+			_scaledWidth = 320;
+			_scaledHeight = 200;
+		} else if (sizeFlag == 1) {
+			_scaledWidth = 640;
+			_scaledHeight = 480;
+		} else if (sizeFlag == 2) {
+			_scaledWidth = 640;
+			_scaledHeight = 400;
+		}
+	}
+
+	uint16 loopCount = data[2];
+	if (_info.loopNo >= loopCount) {
+		_info.loopNo = loopCount - 1;
+	}
+
+	// NOTE: This is the actual check, in the actual location,
+	// from SCI engine.
+	if (loopNo < 0) {
+		error("Loop is less than 0!");
+	}
+
+	const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+	const uint8 loopHeaderSize = data[12];
+	const uint8 viewHeaderFieldSize = 2;
+
+	byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+
+	if ((int8)loopHeader[0] != -1) {
+		if (loopHeader[1] == 1) {
+			_mirrorX = true;
+		}
+
+		loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+	}
+
+	uint8 celCount = loopHeader[2];
+	if (_info.celNo >= celCount) {
+		_info.celNo = celCount - 1;
+	}
+
+	_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
+	_celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
+
+	byte *celHeader = data + _celHeaderOffset;
+
+	_width = READ_SCI11ENDIAN_UINT16(celHeader);
+	_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+	_displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+	_displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
+	_transparentColor = celHeader[8];
+	_compressionType = (CelCompressionType)celHeader[9];
+
+	if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+		error("Compression type not supported - V: %d  L: %d  C: %d", _info.resourceId, _info.loopNo, _info.celNo);
+	}
+
+	if (celHeader[10] & 128) {
+		// NOTE: This is correct according to SCI2.1/SQ6/DOS;
+		// the engine re-reads the byte value as a word value
+		uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+		_transparent = flags & 1 ? true : false;
+		_remap = flags & 2 ? true : false;
+	} else if (_compressionType == kCelCompressionNone) {
+		_remap = analyzeUncompressedForRemap();
+	} else {
+		_remap = analyzeForRemap();
+	}
+
+	putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjView::analyzeUncompressedForRemap() const {
+	byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+	for (int i = 0; i < _width * _height; ++i) {
+		uint8 pixel = pixels[i];
+		if (/* TODO: pixel >= Remap::minRemapColor && pixel <= Remap::maxRemapColor */ false && pixel != _transparentColor) {
+			return true;
+		}
+	}
+	return false;
+}
+
+bool CelObjView::analyzeForRemap() const {
+	// TODO: Implement decompression and analysis
+	return false;
+}
+
+void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) {
+	_drawMirrored = mirrorX;
+	drawTo(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+CelObjView *CelObjView::duplicate() const {
+	return new CelObjView(*this);
+}
+
+byte *CelObjView::getResPointer() const {
+	return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjPic
+CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
+	_info.type = kCelTypePic;
+	_info.resourceId = picId;
+	_info.loopNo = 0;
+	_info.celNo = celNo;
+	_mirrorX = false;
+	_compressionType = kCelCompressionInvalid;
+	_transparent = true;
+	_remap = false;
+
+	int cacheInsertIndex;
+	int cacheIndex = searchCache(_info, &cacheInsertIndex);
+	if (cacheIndex != -1) {
+		CelCacheEntry &entry = (*_cache)[cacheIndex];
+		*this = *dynamic_cast<CelObjPic *>(entry.celObj);
+		entry.id = ++_nextCacheId;
+		return;
+	}
+
+	Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+
+	// NOTE: SCI2.1/SQ6 just silently returns here.
+	if (!resource) {
+		warning("Pic resource %d not loaded", picId);
+		return;
+	}
+
+	byte *data = resource->data;
+
+	_celCount = data[2];
+
+	if (_info.celNo >= _celCount) {
+		error("Cel number %d greater than cel count %d", _info.celNo, _celCount);
+	}
+
+	_celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
+	_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
+
+	byte *celHeader = data + _celHeaderOffset;
+
+	_width = READ_SCI11ENDIAN_UINT16(celHeader);
+	_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+	_displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+	_displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
+	_transparentColor = celHeader[8];
+	_compressionType = (CelCompressionType)celHeader[9];
+	_priority = READ_SCI11ENDIAN_UINT16(celHeader + 36);
+	_relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
+	_relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
+
+	uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+	uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+
+	if (sizeFlag2) {
+		_scaledWidth = sizeFlag1;
+		_scaledHeight = sizeFlag2;
+	} else if (sizeFlag1 == 0) {
+		_scaledWidth = 320;
+		_scaledHeight = 200;
+	} else if (sizeFlag1 == 1) {
+		_scaledWidth = 640;
+		_scaledHeight = 480;
+	} else if (sizeFlag1 == 2) {
+		_scaledWidth = 640;
+		_scaledHeight = 400;
+	}
+
+	if (celHeader[10] & 128) {
+		// NOTE: This is correct according to SCI2.1/SQ6/DOS;
+		// the engine re-reads the byte value as a word value
+		uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+		_transparent = flags & 1 ? true : false;
+		_remap = flags & 2 ? true : false;
+	} else {
+		_transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip();
+
+		if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+			error("Compression type not supported - P: %d  C: %d", picId, celNo);
+		}
+	}
+
+	putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjPic::analyzeUncompressedForSkip() const {
+	byte *resource = getResPointer();
+	byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+	for (int i = 0; i < _width * _height; ++i) {
+		uint8 pixel = pixels[i];
+		if (pixel == _transparentColor) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+	Ratio square;
+	_drawMirrored = mirrorX;
+	drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+CelObjPic *CelObjPic::duplicate() const {
+	return new CelObjPic(*this);
+}
+
+byte *CelObjPic::getResPointer() const {
+	return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjMem
+CelObjMem::CelObjMem(const reg_t bitmap) {
+	_info.type = kCelTypeMem;
+	_info.bitmap = bitmap;
+	_mirrorX = false;
+	_compressionType = kCelCompressionNone;
+	_celHeaderOffset = 0;
+	_transparent = true;
+
+	byte *bitmapData = g_sci->getEngineState()->_segMan->getHunkPointer(bitmap);
+	if (bitmapData == nullptr || READ_SCI11ENDIAN_UINT32(bitmapData + 28) != 46) {
+		error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
+	}
+
+	_width = READ_SCI11ENDIAN_UINT16(bitmapData);
+	_height = READ_SCI11ENDIAN_UINT16(bitmapData + 2);
+	_displace.x = READ_SCI11ENDIAN_UINT16(bitmapData + 4);
+	_displace.y = READ_SCI11ENDIAN_UINT16(bitmapData + 6);
+	_transparentColor = bitmapData[8];
+	_scaledWidth = READ_SCI11ENDIAN_UINT16(bitmapData + 36);
+	_scaledHeight = READ_SCI11ENDIAN_UINT16(bitmapData + 38);
+	_hunkPaletteOffset = READ_SCI11ENDIAN_UINT16(bitmapData + 20);
+	_remap = (READ_SCI11ENDIAN_UINT16(bitmapData + 10) & 2) ? true : false;
+}
+
+CelObjMem *CelObjMem::duplicate() const {
+	return new CelObjMem(*this);
+}
+
+byte *CelObjMem::getResPointer() const {
+	return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap);
+}
+
+#pragma mark -
+#pragma mark CelObjColor
+CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
+	_info.type = kCelTypeColor;
+	_info.color = color;
+	_displace.x = 0;
+	_displace.y = 0;
+	_scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	_scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+	_hunkPaletteOffset = 0;
+	_mirrorX = false;
+	_remap = false;
+	_width = width;
+	_height = height;
+}
+
+void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) {
+	// TODO: The original engine sets this flag but why? One cannot
+	// draw a solid colour mirrored.
+	_drawMirrored = mirrorX;
+	draw(target, targetRect);
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) {
+	error("Unsupported method");
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const {
+	target.fillRect(targetRect, _info.color);
+}
+
+CelObjColor *CelObjColor::duplicate() const {
+	return new CelObjColor(*this);
+}
+
+byte *CelObjColor::getResPointer() const {
+	error("Unsupported method");
+}
+}
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
new file mode 100644
index 0000000..8bda86e
--- /dev/null
+++ b/engines/sci/graphics/celobj32.h
@@ -0,0 +1,577 @@
+/* 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 SCI_GRAPHICS_CELOBJ32_H
+#define SCI_GRAPHICS_CELOBJ32_H
+
+#include "common/rational.h"
+#include "common/rect.h"
+#include "sci/resource.h"
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+typedef Common::Rational Ratio;
+
+enum CelType {
+	kCelTypeView  = 0,
+	kCelTypePic   = 1,
+	kCelTypeMem   = 2,
+	kCelTypeColor = 3
+};
+
+enum CelCompressionType {
+	kCelCompressionNone    = 0,
+	kCelCompressionRLE     = 138,
+	kCelCompressionInvalid = 1000
+};
+
+/**
+ * A CelInfo32 object describes the basic properties of a
+ * cel object.
+ */
+struct CelInfo32 {
+	/**
+	 * The type of the cel object.
+	 */
+	CelType type;
+
+	/**
+	 * For cel objects that draw from resources, the ID of
+	 * the resource to load.
+	 */
+	GuiResourceId resourceId;
+
+	/**
+	 * For CelObjView, the loop number to draw from the
+	 * view resource.
+	 */
+	int16 loopNo;
+
+	/**
+	 * For CelObjView and CelObjPic, the cel number to draw
+	 * from the view or pic resource.
+	 */
+	int16 celNo;
+
+	/**
+	 * For CelObjMem, a segment register pointing to a heap
+	 * resource containing headered bitmap data.
+	 */
+	reg_t bitmap;
+
+	/**
+	 * For CelObjColor, the fill colour.
+	 */
+	uint8 color;
+
+	// NOTE: In at least SCI2.1/SQ6, color is left
+	// uninitialised.
+	CelInfo32() :
+		type(kCelTypeMem),
+		resourceId(0),
+		loopNo(0),
+		celNo(0),
+		bitmap(NULL_REG) {}
+
+	// NOTE: This is the equivalence criteria used by
+	// CelObj::searchCache in at least SCI2.1/SQ6. Notably,
+	// it does not check the color field.
+	inline bool operator==(const CelInfo32 &other) {
+		return (
+			type == other.type &&
+			resourceId == other.resourceId &&
+			loopNo == other.loopNo &&
+			celNo == other.celNo &&
+			bitmap == other.bitmap
+		);
+	}
+};
+
+class CelObj;
+struct CelCacheEntry {
+	/**
+	 * A monotonically increasing cache ID used to identify
+	 * the least recently used item in the cache for
+	 * replacement.
+	 */
+	int id;
+	CelObj *celObj;
+	CelCacheEntry() : id(0), celObj(nullptr) {}
+};
+
+typedef Common::Array<CelCacheEntry> CelCache;
+
+#pragma mark -
+#pragma mark CelScaler
+
+struct CelScalerTable {
+	/**
+	 * A lookup table of indexes that should be used to find
+	 * the correct column to read from the source bitmap
+	 * when drawing a scaled version of the source bitmap.
+	 */
+	int valuesX[1024];
+
+	/**
+	 * The ratio used to generate the x-values.
+	 */
+	Ratio scaleX;
+
+	/**
+	 * A lookup table of indexes that should be used to find
+	 * the correct row to read from a source bitmap when
+	 * drawing a scaled version of the source bitmap.
+	 */
+	int valuesY[1024];
+
+	/**
+	 * The ratio used to generate the y-values.
+	 */
+	Ratio scaleY;
+};
+
+class CelScaler {
+	/**
+	 * Cached scale tables.
+	 */
+	CelScalerTable _scaleTables[2];
+
+	/**
+	 * The index of the most recently used scale table.
+	 */
+	int _activeIndex;
+
+	/**
+	 * Activates a scale table for the given X and Y ratios.
+	 * If there is no table that matches the given ratios,
+	 * the least most recently used table will be replaced
+	 * and activated.
+	 */
+	void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY);
+
+	/**
+	 * Builds a pixel lookup table in `table` for the given
+	 * ratio. The table will be filled up to the specified
+	 * size, which should be large enough to draw across the
+	 * entire target buffer.
+	 */
+	void buildLookupTable(int *table, const Ratio &ratio, const int size);
+
+public:
+	CelScaler() :
+	_scaleTables(),
+	_activeIndex(0) {
+		CelScalerTable &table = _scaleTables[_activeIndex];
+		table.scaleX = Ratio();
+		table.scaleY = Ratio();
+		for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) {
+			table.valuesX[i] = i;
+			table.valuesY[i] = i;
+		}
+	}
+
+	/**
+	 * Retrieves scaler tables for the given X and Y ratios.
+	 */
+	const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY);
+};
+
+#pragma mark -
+#pragma mark CelObj
+
+class ScreenItem;
+/**
+ * A cel object is the lowest-level rendering primitive in
+ * the SCI engine and draws itself directly to a target
+ * pixel buffer.
+ */
+class CelObj {
+private:
+	static CelScaler *_scaler;
+
+protected:
+	/**
+	 * When true, this cel will be horizontally mirrored
+	 * when it is drawn. This is an internal flag that is
+	 * set by draw methods based on the combination of the
+	 * cel's `_mirrorX` property and the owner screen item's
+	 * `_mirrorX` property.
+	 */
+	bool _drawMirrored;
+
+public:
+	/**
+	 * The basic identifying information for this cel. This
+	 * information effectively acts as a composite key for
+	 * a cel object, and any cel object can be recreated
+	 * from this data alone.
+	 */
+	CelInfo32 _info;
+
+	/**
+	 * The offset to the cel header for this cel within the
+	 * raw resource data.
+	 */
+	uint32 _celHeaderOffset;
+
+	/**
+	 * The offset to the embedded palette for this cel
+	 * within the raw resource data.
+	 */
+	uint32 _hunkPaletteOffset;
+
+	/**
+	 * The natural dimensions of the cel.
+	 */
+	uint16 _width, _height;
+
+	/**
+	 * TODO: Documentation
+	 */
+	Common::Point _displace;
+
+	/**
+	 * The dimensions of the original coordinate system for
+	 * the cel. Used to scale cels from their native size
+	 * to the correct size on screen.
+	 *
+	 * @note This is set to scriptWidth/Height for
+	 * CelObjColor. For other cel objects, the value comes
+	 * from the raw resource data. For text bitmaps, this is
+	 * the width/height of the coordinate system used to
+	 * generate the text, which also defaults to
+	 * scriptWidth/Height but seems to typically be changed
+	 * to more closely match the native screen resolution.
+	 */
+	uint16 _scaledWidth, _scaledHeight;
+
+	/**
+	 * The skip (transparent) colour for the cel. When
+	 * compositing, any pixels matching this colour will not
+	 * be copied to the buffer.
+	 */
+	uint8 _transparentColor;
+
+	/**
+	 * Whether or not this cel has any transparent regions.
+	 * This is used for optimised drawing of non-transparent
+	 * cels.
+	 */
+	bool _transparent; // TODO: probably "skip"?
+
+	/**
+	 * The compression type for the pixel data for this cel.
+	 */
+	CelCompressionType _compressionType;
+
+	/**
+	 * Whether or not this cel should be palette-remapped?
+	 */
+	bool _remap;
+
+	/**
+	 * If true, the cel contains pre-mirrored picture data.
+	 * This value comes directly from the resource data and
+	 * is XORed with the `_mirrorX` property of the owner
+	 * screen item when rendering.
+	 */
+	bool _mirrorX;
+
+	/**
+	 * Initialises static CelObj members.
+	 */
+	static void init();
+
+	/**
+	 * Frees static CelObj members.
+	 */
+	static void deinit();
+
+	virtual ~CelObj() {};
+
+	/**
+	 * Draws the cel to the target buffer using the priority
+	 * and positioning information from the given screen
+	 * item. The mirroring of the cel will be unchanged from
+	 * any previous call to draw.
+	 */
+	void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const;
+
+	/**
+	 * Draws the cel to the target buffer using the priority
+	 * and positioning information from the given screen
+	 * item and the given mirror flag.
+	 *
+	 * @note In SCI engine, this function was a virtual
+	 * function, but CelObjView, CelObjPic, and CelObjMem
+	 * all used the same function and the compiler
+	 * deduplicated the copies; we deduplicate the source by
+	 * putting the implementation on CelObj instead of
+	 * copying it to 3/4 of the subclasses.
+	 */
+	virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX);
+
+	/**
+	 * Draws the cel to the target buffer using the
+	 * positioning and mirroring information from the
+	 * provided arguments.
+	 *
+	 * @note In SCI engine, this function was a virtual
+	 * function, but CelObjView, CelObjPic, and CelObjMem
+	 * all used the same function and the compiler
+	 * deduplicated the copies; we deduplicate the source by
+	 * putting the implementation on CelObj instead of
+	 * copying it to 3/4 of the subclasses.
+	 */
+	virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX);
+
+	/**
+	 * Draws the cel to the target buffer using the given
+	 * position and scaling parameters. The mirroring of the
+	 * cel will be unchanged from any previous call to draw.
+	 */
+	void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+	/**
+	 * Creates a copy of this cel on the free store and
+	 * returns a pointer to the new object. The new cel will
+	 * point to a shared copy of bitmap/resource data.
+	 */
+	virtual CelObj *duplicate() const = 0;
+
+	/**
+	 * Retrieves a pointer to the raw resource data for this
+	 * cel. This method cannot be used with a CelObjColor.
+	 */
+	virtual byte *getResPointer() const = 0;
+
+	/**
+	 * Reads the pixel at the given coordinates. This method
+	 * is valid only for CelObjView and CelObjPic.
+	 */
+	inline uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+
+	/**
+	 * Submits the palette from this cel to the palette
+	 * manager for integration into the master screen
+	 * palette.
+	 */
+	void submitPalette() const;
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+private:
+	void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+	void drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+	void scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+
+#pragma mark -
+#pragma mark CelObj - Caching
+protected:
+	/**
+	 * A monotonically increasing cache ID used to identify
+	 * the least recently used item in the cache for
+	 * replacement.
+	 */
+	static int _nextCacheId;
+
+	/**
+	 * A cache of cel objects used to avoid reinitialisation
+	 * overhead for cels with the same CelInfo32.
+	 */
+	// NOTE: At least SQ6 uses a fixed cache size of 100.
+	static CelCache *_cache;
+
+	/**
+	 * Searches the cel cache for a CelObj matching the
+	 * provided CelInfo32. If not found, -1 is returned.
+	 * nextInsertIndex will receive the index of the oldest
+	 * item in the cache, which can be used to replace
+	 * the oldest item with a newer item.
+	 */
+	int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const;
+
+	/**
+	 * Puts a copy of this CelObj into the cache at the
+	 * given cache index.
+	 */
+	void putCopyInCache(int index) const;
+};
+
+#pragma mark -
+#pragma mark CelObjView
+
+/**
+ * A CelObjView is the drawing primitive for a View type
+ * resource. Each CelObjView corresponds to a single cel
+ * within a single loop of a view.
+ */
+class CelObjView : public CelObj {
+private:
+	/**
+	 * Analyses resources without baked-in remap flags
+	 * to determine whether or not they should be remapped.
+	 */
+	bool analyzeUncompressedForRemap() const;
+
+	/**
+	 * Analyses compressed resources without baked-in remap
+	 * flags to determine whether or not they should be
+	 * remapped.
+	 */
+	bool analyzeForRemap() const;
+
+public:
+	CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo);
+	virtual ~CelObjView() override {};
+
+	using CelObj::draw;
+
+	/**
+	 * Draws the cel to the target buffer using the
+	 * positioning, mirroring, and scaling information from
+	 * the provided arguments.
+	 */
+	void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY);
+
+	virtual CelObjView *duplicate() const override;
+	virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjPic
+
+/**
+ * A CelObjPic is the drawing primitive for a Picture type
+ * resource. Each CelObjPic corresponds to a single cel
+ * within a picture.
+ */
+class CelObjPic : public CelObj {
+private:
+	/**
+	 * Analyses uncompressed resources without baked-in skip
+	 * flags to determine whether or not they can use fast
+	 * blitting.
+	 */
+	bool analyzeUncompressedForSkip() const;
+
+public:
+	/**
+	 * The number of cels in the original picture resource.
+	 */
+	uint8 _celCount;
+
+	/**
+	 * The position of this cel relative to the top-left
+	 * corner of the picture.
+	 */
+	Common::Point _relativePosition;
+
+	/**
+	 * The z-buffer priority for this cel. Higher prorities
+	 * are drawn on top of lower priorities.
+	 */
+	int16 _priority;
+
+	CelObjPic(GuiResourceId pictureId, int16 celNo);
+	virtual ~CelObjPic() override {};
+
+	using CelObj::draw;
+	virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+	virtual CelObjPic *duplicate() const override;
+	virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjMem
+
+/**
+ * A CelObjMem is the drawing primitive for arbitrary
+ * bitmaps generated in memory. Generated bitmaps in SCI32
+ * include text & vector drawings and per-pixel screen
+ * transitions like dissolves.
+ */
+class CelObjMem : public CelObj {
+public:
+	CelObjMem(reg_t bitmap);
+	virtual ~CelObjMem() override {};
+
+	virtual CelObjMem *duplicate() const override;
+	virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjColor
+
+/**
+ * A CelObjColor is the drawing primitive for fast,
+ * low-memory, flat colour fills.
+ */
+class CelObjColor : public CelObj {
+public:
+	CelObjColor(uint8 color, int16 width, int16 height);
+	virtual ~CelObjColor() override {};
+
+	using CelObj::draw;
+	/**
+	 * Block fills the target buffer with the cel colour.
+	 */
+	void draw(Buffer &target, const Common::Rect &targetRect) const;
+	virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) override;
+	virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) override;
+
+	virtual CelObjColor *duplicate() const override;
+	virtual byte *getResPointer() const override;
+};
+}
+
+#endif
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index cb81fe8..d748cc0 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -47,962 +47,1857 @@
 #include "sci/graphics/palette32.h"
 #include "sci/graphics/picture.h"
 #include "sci/graphics/text32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
 #include "sci/graphics/frameout.h"
 #include "sci/video/robot_decoder.h"
 
 namespace Sci {
 
-// TODO/FIXME: This is all guesswork
+// TODO/FIXME: This is partially guesswork
 
-enum SciSpeciaPlanelPictureCodes {
-	kPlaneTranslucent  = 0xfffe,	// -2
-	kPlanePlainColored = 0xffff		// -1
+static int dissolveSequences[2][20] = {
+	/* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
+	/* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
+};
+static int16 divisionsDefaults[2][16] = {
+	/* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
+	/* SCI2.1mid+ */   { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20,  6, 10, 101, 101, 2 }
+};
+static int16 unknownCDefaults[2][16] = {
+	/* SCI2.1early- */ { 0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  0,  0,   0,   0 },
+	/* SCI2.1mid+ */   { 0,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,  0,  0,   7,   7, 0 }
 };
 
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32)
-	: _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32), _isHiRes(false) {
-
-	_coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
-	_curScrollText = -1;
-	_showScrollText = false;
-	_maxScrollTexts = 0;
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
+	_segMan(segMan),
+	_resMan(resMan),
+	_cache(cache),
+	_screen(screen),
+	_palette(palette),
+	_paint32(paint32),
+	_isHiRes(false),
+	_palMorphIsOn(false),
+	_showStyles(nullptr),
+	_remapOccurred(false),
+	_frameNowVisible(false),
+	// TODO: Stop using _gfxScreen
+	_currentBuffer(screen->getWidth(), screen->getHeight(), nullptr),
+	_priorityMap(screen->getWidth(), screen->getHeight(), nullptr),
+	_screenRect(screen->getWidth(), screen->getHeight()),
+	_overdrawThreshold(0) {
+
+	_currentBuffer.setPixels(calloc(1, screen->getWidth() * screen->getHeight()));
+
+	for (int i = 0; i < 236; i += 2) {
+		_styleRanges[i] = 0;
+		_styleRanges[i + 1] = -1;
+	}
+	for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
+		_styleRanges[i] = 0;
+	}
 
 	// TODO: Make hires detection work uniformly across all SCI engine
 	// versions (this flag is normally passed by SCI::MakeGraphicsMgr
-	// to the GraphicsMgr constructor depending upon video configuration)
+	// to the GraphicsMgr constructor depending upon video configuration,
+	// so should be handled upstream based on game configuration instead
+	// of here)
 	if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
 		_isHiRes = true;
 	}
+
+	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+		_dissolveSequenceSeeds = dissolveSequences[0];
+		_defaultDivisions = divisionsDefaults[0];
+		_defaultUnknownC = unknownCDefaults[0];
+	} else {
+		_dissolveSequenceSeeds = dissolveSequences[1];
+		_defaultDivisions = divisionsDefaults[1];
+		_defaultUnknownC = unknownCDefaults[1];
+	}
+
+	// TODO: Nothing in the renderer really uses this. Currently,
+	// the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
+	// do, but in the real engine (1) the cursor is handled in
+	// frameOut, and (2) functions do a very simple lookup of the
+	// plane and arithmetic with the plane's gameRect. In
+	// principle, CoordAdjuster could be reused for
+	// convertGameRectToPlaneRect, but it is not super clear yet
+	// what the benefit would be to do that.
+	_coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
+
+	// TODO: Script resolution is hard-coded per game;
+	// also this must be set or else the engine will crash
+	_coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
 }
 
 GfxFrameout::~GfxFrameout() {
+	CelObj::deinit();
 	clear();
 }
 
+void GfxFrameout::run() {
+	CelObj::init();
+	Plane::init();
+	ScreenItem::init();
+
+	// NOTE: This happens in SCI::InitPlane in the actual engine,
+	// and is a background fill plane to ensure hidden planes
+	// (planes with a priority of -1) are never drawn
+	Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth - 1, _currentBuffer.scriptHeight - 1));
+	initPlane->_priority = 0;
+	_planes.add(initPlane);
+}
+
 void GfxFrameout::clear() {
-	deletePlaneItems(NULL_REG);
 	_planes.clear();
-	deletePlanePictures(NULL_REG);
-	clearScrollTexts();
-}
-
-void GfxFrameout::clearScrollTexts() {
-	_scrollTexts.clear();
-	_curScrollText = -1;
-}
-
-void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) {
-	//reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow);
-	// HACK: We set the container dimensions manually
-	reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70);
-	ScrollTextEntry textEntry;
-	textEntry.bitmapHandle = bitmapHandle;
-	textEntry.kWindow = kWindow;
-	textEntry.x = x;
-	textEntry.y = y;
-	if (!replace || _scrollTexts.size() == 0) {
-		if (_scrollTexts.size() > _maxScrollTexts) {
-			_scrollTexts.remove_at(0);
-			_curScrollText--;
-		}
-		_scrollTexts.push_back(textEntry);
-		_curScrollText++;
+	_showList.clear();
+}
+
+#pragma mark -
+#pragma mark Screen items
+
+void GfxFrameout::kernelAddScreenItem(const reg_t object) {
+	const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+
+// TODO: Remove
+//	debug("Adding screen item %04x:%04x to plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
+
+	_segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
+
+	Plane *plane = _planes.findByObject(planeObject);
+	if (plane == nullptr) {
+		error("Invalid plane selector passed to kAddScreenItem");
+	}
+
+	ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+	if (screenItem != nullptr) {
+		screenItem->update(object);
 	} else {
-		_scrollTexts.pop_back();
-		_scrollTexts.push_back(textEntry);
+		screenItem = new ScreenItem(object);
+		plane->_screenItemList.add(screenItem);
 	}
 }
 
-void GfxFrameout::showCurrentScrollText() {
-	if (!_showScrollText || _curScrollText < 0)
-		return;
+void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
+	const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier));
+	if (magnifierObject.isNull()) {
+		const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+		Plane *plane = _planes.findByObject(planeObject);
+		if (plane == nullptr) {
+			error("Invalid plane selector passed to kUpdateScreenItem");
+		}
 
-	uint16 size = (uint16)_scrollTexts.size();
-	if (size > 0) {
-		assert(_curScrollText < size);
-		ScrollTextEntry textEntry = _scrollTexts[_curScrollText];
-		g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y);
-	}
-}
-
-extern void showScummVMDialog(const Common::String &message);
-
-void GfxFrameout::kernelAddPlane(reg_t object) {
-	PlaneEntry newPlane;
-
-	if (_planes.empty()) {
-		// There has to be another way for sierra sci to do this or maybe script resolution is compiled into
-		//  interpreter (TODO)
-		uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
-		uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
-
-		// Phantasmagoria 2 doesn't specify a script width/height
-		if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
-			scriptWidth = 640;
-			scriptHeight = 480;
-		}
-
-		assert(scriptWidth > 0 && scriptHeight > 0);
-		_coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight);
-	}
-
-	// Import of QfG character files dialog is shown in QFG4.
-	// Display additional popup information before letting user use it.
-	// For the SCI0-SCI1.1 version of this, check kDrawControl().
-	if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) {
-		showScummVMDialog("Characters saved inside ScummVM are shown "
-				"automatically. Character files saved in the original "
-				"interpreter need to be put inside ScummVM's saved games "
-				"directory and a prefix needs to be added depending on which "
-				"game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' "
-				"for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. "
-				"Example: 'qfg2-thief.sav'.");
-	}
-
-	newPlane.object = object;
-	newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority));
-	newPlane.lastPriority = -1; // hidden
-	newPlane.planeOffsetX = 0;
-	newPlane.planeOffsetY = 0;
-	newPlane.pictureId = kPlanePlainColored;
-	newPlane.planePictureMirrored = false;
-	newPlane.planeBack = 0;
-	_planes.push_back(newPlane);
-
-	kernelUpdatePlane(object);
-}
-
-void GfxFrameout::kernelUpdatePlane(reg_t object) {
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		if (it->object == object) {
-			// Read some information
-			it->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
-			GuiResourceId lastPictureId = it->pictureId;
-			it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture));
-			if (lastPictureId != it->pictureId) {
-				// picture got changed, load new picture
-				deletePlanePictures(object);
-				// Draw the plane's picture if it's not a translucent/plane colored frame
-				if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) {
-					// SQ6 gives us a bad picture number for the control menu
-					if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId)))
-						addPlanePicture(object, it->pictureId, 0);
-				}
-			}
-			it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
-			it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
-			it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
-			it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+		ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+		if (screenItem == nullptr) {
+			error("Invalid screen item passed to kUpdateScreenItem");
+		}
 
-			_coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left);
-			_coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right);
+		screenItem->update(object);
+	} else {
+		warning("TODO: Magnifier view not implemented yet!");
+	}
+}
 
-			// We get negative left in kq7 in scrolling rooms
-			if (it->planeRect.left < 0) {
-				it->planeOffsetX = -it->planeRect.left;
-				it->planeRect.left = 0;
-			} else {
-				it->planeOffsetX = 0;
-			}
+void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+	_segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
 
-			if (it->planeRect.top < 0) {
-				it->planeOffsetY = -it->planeRect.top;
-				it->planeRect.top = 0;
-			} else {
-				it->planeOffsetY = 0;
-			}
+	const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+	Plane *plane = _planes.findByObject(planeObject);
+	if (plane == nullptr) {
+	// TODO: Remove
+//		warning("Invalid plane selector %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
+		return;
+	}
 
-			// We get bad plane-bottom in sq6
-			if (it->planeRect.right > _screen->getWidth())
-				it->planeRect.right = _screen->getWidth();
-			if (it->planeRect.bottom > _screen->getHeight())
-				it->planeRect.bottom = _screen->getHeight();
+// TODO: Remove
+//	debug("Deleting screen item %04x:%04x from plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
 
-			it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height());
-			it->upscaledPlaneRect = it->planeRect;
-			it->upscaledPlaneClipRect = it->planeClipRect;
-			if (_screen->getUpscaledHires()) {
-				_screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left);
-				_screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right);
-				_screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left);
-				_screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right);
-			}
+	ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+	if (screenItem == nullptr) {
+// TODO: Remove
+//		warning("Invalid screen item %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
+		return;
+	}
 
-			it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored));
-			it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back));
+	if (screenItem->_created == 0) {
+		screenItem->_created = 0;
+		screenItem->_updated = 0;
+		screenItem->_deleted = getScreenCount();
+	} else {
+		plane->_screenItemList.erase(screenItem);
+		plane->_screenItemList.pack();
+	}
+}
 
-			sortPlanes();
+#pragma mark -
+#pragma mark Planes
 
-			// Update the items in the plane
-			for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
-				reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
-				if (object == itemPlane) {
-					kernelUpdateScreenItem((*listIterator)->object);
-				}
-			}
+void GfxFrameout::kernelAddPlane(const reg_t object) {
+	Plane *plane = _planes.findByObject(object);
+	if (plane != nullptr) {
+		plane->update(object);
+		updatePlane(*plane);
+	} else {
+		plane = new Plane(object);
+		addPlane(*plane);
+	}
+}
 
-			return;
-		}
+void GfxFrameout::kernelUpdatePlane(const reg_t object) {
+	Plane *plane = _planes.findByObject(object);
+	if (plane == nullptr) {
+		error("Invalid plane selector passed to kUpdatePlane");
 	}
-	error("kUpdatePlane called on plane that wasn't added before");
+
+	plane->update(object);
+	updatePlane(*plane);
 }
 
-void GfxFrameout::kernelDeletePlane(reg_t object) {
-	deletePlaneItems(object);
-	deletePlanePictures(object);
+void GfxFrameout::kernelDeletePlane(const reg_t object) {
+	Plane *plane = _planes.findByObject(object);
+	if (plane == nullptr) {
+		error("Invalid plane selector passed to kDeletePlane");
+	}
 
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		if (it->object == object) {
-			_planes.erase(it);
-			Common::Rect planeRect;
-			planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
-			planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
-			planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
-			planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+	if (plane->_created) {
+		// NOTE: The original engine calls some `AbortPlane` function that
+		// just ends up doing this anyway so we skip the extra indirection
+		_planes.erase(plane);
+	} else {
+		// TODO: Remove
+//		debug("Deleting plane %04x:%04x", PRINT_REG(object));
+		plane->_created = 0;
+		plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
+	}
+}
 
-			_coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left);
-			_coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right);
+int16 GfxFrameout::kernelGetHighPlanePri() {
+	return _planes.getTopSciPlanePriority();
+}
 
-			// Blackout removed plane rect
-			_paint32->fillRect(planeRect, 0);
-			return;
+void GfxFrameout::addPlane(Plane &plane) {
+	if (_planes.findByObject(plane._object) == nullptr) {
+		plane.clipScreenRect(_screenRect);
+		_planes.add(&plane);
+	} else {
+		plane._deleted = 0;
+		if (plane._created == 0) {
+			plane._moved = g_sci->_gfxFrameout->getScreenCount();
 		}
+		_planes.sort();
 	}
 }
 
-void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) {
-	if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent)	// sanity check
-		return;
+void GfxFrameout::updatePlane(Plane &plane) {
+	// NOTE: This assertion comes from SCI engine code.
+	assert(_planes.findByObject(plane._object) == &plane);
 
-	PlanePictureEntry newPicture;
-	newPicture.object = object;
-	newPicture.pictureId = pictureId;
-	newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
-	newPicture.startX = startX;
-	newPicture.startY = startY;
-	newPicture.pictureCels = 0;
-	_planePictures.push_back(newPicture);
+	Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
+	plane.sync(visiblePlane, _screenRect);
+	// NOTE: updateScreenRect was originally called a second time here,
+	// but it is already called at the end of the Plane::Update call
+	// in the original engine anyway.
+
+	_planes.sort();
 }
 
-void GfxFrameout::deletePlanePictures(reg_t object) {
-	PlanePictureList::iterator it = _planePictures.begin();
+#pragma mark -
+#pragma mark Pics
 
-	while (it != _planePictures.end()) {
-		if (it->object == object || object.isNull()) {
-			delete it->pictureCels;
-			delete it->picture;
-			it = _planePictures.erase(it);
-		} else {
-			++it;
-		}
+void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) {
+	Plane *plane = _planes.findByObject(planeObject);
+	if (plane == nullptr) {
+		error("Invalid plane selector passed to kAddPicAt");
 	}
+	plane->addPic(pictureId, Common::Point(x, y), mirrorX);
 }
 
-// Provides the same functionality as kGraph(DrawLine)
-reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		if (it->object == object) {
-			PlaneLineEntry line;
-			line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1);	// we basically use this for a unique ID
-			line.startPoint = startPoint;
-			line.endPoint = endPoint;
-			line.color = color;
-			line.priority = priority;
-			line.control = control;
-			it->lines.push_back(line);
-			return line.hunkId;
+#pragma mark -
+#pragma mark Rendering
+
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
+// TODO: Robot
+//	if (_robot != nullptr) {
+//		_robot.doRobot();
+//	}
+
+	// NOTE: The original engine allocated these as static arrays of 100
+	// pointers to ScreenItemList / RectList
+	ScreenItemListList screenItemLists;
+	EraseListList eraseLists;
+
+	screenItemLists.resize(_planes.size());
+	eraseLists.resize(_planes.size());
+
+	// _numActiveRemaps was a global in SCI engine
+	if (/* TODO Remap::_numActiveRemaps > 0 */ false && _remapOccurred) {
+		// remapMarkRedraw();
+	}
+
+	calcLists(screenItemLists, eraseLists, rect);
+
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		list->sort();
+	}
+
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+			(*drawItem)->screenItem->getCelObj().submitPalette();
 		}
 	}
 
-	return NULL_REG;
+	_remapOccurred = _palette->updateForFrame();
+
+	// NOTE: SCI engine set this to false on each loop through the
+	// planelist iterator below. Since that is a waste, we only set
+	// it once.
+	_frameNowVisible = false;
+
+	for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+		drawEraseList(eraseLists[i], *_planes[i]);
+		drawScreenItemList(screenItemLists[i]);
+	}
+
+// TODO: Robot
+//	if (_robot != nullptr) {
+//		_robot->frameAlmostVisible();
+//	}
+
+	_palette->updateHardware();
+
+	if (shouldShowBits) {
+		showBits();
+	}
+
+	_frameNowVisible = true;
+
+// TODO: Robot
+//	if (_robot != nullptr) {
+//		robot->frameNowVisible();
+//	}
 }
 
-void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
-	// Check if we're asked to update a line that was never added
-	if (hunkId.isNull())
-		return;
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
+	if (!r.intersects(other)) {
+		return -1;
+	}
+
+	int count = 0;
+	if (r.top < other.top) {
+		Common::Rect &t = outRects[count++];
+		t = r;
+		t.bottom = other.top;
+		r.top = other.top;
+	}
+
+	if (r.bottom > other.bottom) {
+		Common::Rect &t = outRects[count++];
+		t = r;
+		t.top = other.bottom;
+		r.bottom = other.bottom;
+	}
+
+	if (r.left < other.left) {
+		Common::Rect &t = outRects[count++];
+		t = r;
+		t.right = other.left;
+		r.left = other.left;
+	}
+
+	if (r.right > other.right) {
+		Common::Rect &t = outRects[count++];
+		t = r;
+		t.left = other.right;
+	}
+
+	return count;
+}
+
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
+	RectList rectlist;
+	Common::Rect outRects[4];
+
+	int deletedPlaneCount = 0;
+	bool addedToRectList = false;
+	int planeCount = _planes.size();
+	bool foundTransparentPlane = false;
+
+	if (!calcRect.isEmpty()) {
+		addedToRectList = true;
+		rectlist.add(calcRect);
+	}
+
+	for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
+		Plane *outerPlane = _planes[outerPlaneIndex];
+
+		if (outerPlane->_type == kPlaneTypeTransparent) {
+			foundTransparentPlane = true;
+		}
 
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		if (it->object == object) {
-			for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
-				if (it2->hunkId == hunkId) {
-					it2->startPoint = startPoint;
-					it2->endPoint = endPoint;
-					it2->color = color;
-					it2->priority = priority;
-					it2->control = control;
-					return;
+		Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
+
+		if (outerPlane->_deleted) {
+			if (visiblePlane != nullptr) {
+				if (!visiblePlane->_screenRect.isEmpty()) {
+					addedToRectList = true;
+					rectlist.add(visiblePlane->_screenRect);
+				}
+			}
+			++deletedPlaneCount;
+		} else if (visiblePlane != nullptr) {
+			if (outerPlane->_updated) {
+				--outerPlane->_updated;
+
+				int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+				if (splitcount) {
+					if (splitcount == -1) {
+						if (!visiblePlane->_screenRect.isEmpty()) {
+							rectlist.add(visiblePlane->_screenRect);
+						}
+					} else {
+						for (int i = 0; i < splitcount; ++i) {
+							rectlist.add(outRects[i]);
+						}
+					}
+
+					addedToRectList = true;
+				}
+
+				if (!outerPlane->_redrawAllCount) {
+					int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+					if (splitCount) {
+						for (int i = 0; i < splitCount; ++i) {
+							rectlist.add(outRects[i]);
+						}
+						addedToRectList = true;
+					}
 				}
 			}
 		}
-	}
-}
 
-void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) {
-	// Check if we're asked to delete a line that was never added (happens during the intro of LSL6)
-	if (hunkId.isNull())
-		return;
+		if (addedToRectList) {
+			for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
+				for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+					Plane *innerPlane = _planes[innerPlaneIndex];
+
+					if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
+						if (innerPlane->_redrawAllCount == 0) {
+							eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+						}
+
+						int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+						for (int i = 0; i < splitCount; ++i) {
+							rectlist.add(outRects[i]);
+						}
 
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		if (it->object == object) {
-			for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
-				if (it2->hunkId == hunkId) {
-					_segMan->freeHunkEntry(hunkId);
-					it2 = it->lines.erase(it2);
-					return;
+						rectlist.erase(rect);
+						break;
+					}
 				}
 			}
+
+			rectlist.pack();
 		}
 	}
-}
 
-// Adapted from GfxAnimate::applyGlobalScaling()
-void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) {
-	// Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY
-	int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale));
-	int16 maxCelHeight = (maxScale * celHeight) >> 7;
-	reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object
-	int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY));
+	// clean up deleted planes
+	if (deletedPlaneCount) {
+		for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) {
+			Plane *plane = _planes[planeIndex];
+
+			if (plane->_deleted) {
+				--plane->_deleted;
+				if (plane->_deleted <= 0) {
+					PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+					if (visiblePlaneIt != _visiblePlanes.end()) {
+						_visiblePlanes.erase(visiblePlaneIt);
+					}
 
-	int16 fixedPortY = planeRect.bottom - vanishingY;
-	int16 fixedEntryY = itemEntry->y - vanishingY;
-	if (!fixedEntryY)
-		fixedEntryY = 1;
+					_planes.remove_at(planeIndex);
+					eraseLists.remove_at(planeIndex);
+					drawLists.remove_at(planeIndex);
+				}
 
-	if ((celHeight == 0) || (fixedPortY == 0))
-		error("global scaling panic");
+				if (--deletedPlaneCount <= 0) {
+					break;
+				}
+			}
+		}
+	}
 
-	itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY;
-	itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight;
+	planeCount = _planes.size();
+	for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+		// "outer" just refers to the outer loop
+		Plane *outerPlane = _planes[outerIndex];
+		if (outerPlane->_priorityChanged) {
+			--outerPlane->_priorityChanged;
+
+			Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+
+			rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+
+			for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+				// "inner" just refers to the inner loop
+				Plane *innerPlane = _planes[innerIndex];
+				Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
+
+				int rectCount = rectlist.size();
+				for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+					int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+
+					if (splitCount == 0) {
+						if (visibleInnerPlane != nullptr) {
+							// same priority, or relative priority between inner/outer changed
+							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+								if (outerPlane->_priority <= innerPlane->_priority) {
+									eraseLists[innerIndex].add(*rectlist[rectIndex]);
+								} else {
+									eraseLists[outerIndex].add(*rectlist[rectIndex]);
+								}
+							}
+						}
+
+						rectlist.erase_at(rectIndex);
+					} else if (splitCount != -1) {
+						for (int i = 0; i < splitCount; ++i) {
+							rectlist.add(outRects[i]);
+						}
+
+						if (visibleInnerPlane != nullptr) {
+							// same priority, or relative priority between inner/outer changed
+							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+								*rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
+								if (outerPlane->_priority <= innerPlane->_priority) {
+									eraseLists[innerIndex].add(*rectlist[rectIndex]);
+								}
+								else {
+									eraseLists[outerIndex].add(*rectlist[rectIndex]);
+								}
+							}
+						}
+						rectlist.erase_at(rectIndex);
+					}
+				}
+				rectlist.pack();
+			}
+		}
+	}
 
-	// Make sure that the calculated value is sane
-	if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/)
-		itemEntry->scaleY = 128;
+	for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+		Plane *plane = _planes[planeIndex];
+		Plane *visiblePlane = nullptr;
 
-	itemEntry->scaleX = itemEntry->scaleY;
+		PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+		if (visiblePlaneIt != _visiblePlanes.end()) {
+			visiblePlane = *visiblePlaneIt;
+		}
 
-	// and set objects scale selectors
-	//writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX);
-	//writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY);
-}
+		if (plane->_redrawAllCount) {
+			plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+		} else {
+			if (visiblePlane == nullptr) {
+				error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object));
+			}
 
-void GfxFrameout::kernelAddScreenItem(reg_t object) {
-	// Ignore invalid items
-	if (!_segMan->isObject(object)) {
-		warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object));
-		return;
+			plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+		}
+
+		if (plane->_created) {
+			_visiblePlanes.add(new Plane(*plane));
+			--plane->_created;
+		} else if (plane->_moved) {
+			assert(visiblePlaneIt != _visiblePlanes.end());
+			**visiblePlaneIt = *plane;
+			--plane->_moved;
+		}
 	}
 
-	FrameoutEntry *itemEntry = new FrameoutEntry();
-	memset(itemEntry, 0, sizeof(FrameoutEntry));
-	itemEntry->object = object;
-	itemEntry->givenOrderNr = _screenItems.size();
-	itemEntry->visible = true;
-	_screenItems.push_back(itemEntry);
+	if (foundTransparentPlane) {
+		for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+			for (int i = planeIndex + 1; i < planeCount; ++i) {
+				if (_planes[i]->_type == kPlaneTypeTransparent) {
+					_planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
+				}
+			}
+
+			if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
+				for (int i = planeIndex - 1; i >= 0; --i) {
+					_planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
+				}
+
+				if (eraseLists[planeIndex].size() > 0) {
+					error("Transparent plane's erase list not absorbed");
+				}
+			}
 
-	kernelUpdateScreenItem(object);
+			for (int i = planeIndex + 1; i < planeCount; ++i) {
+				if (_planes[i]->_type == kPlaneTypeTransparent) {
+					_planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
+				}
+			}
+		}
+	}
 }
 
-void GfxFrameout::kernelUpdateScreenItem(reg_t object) {
-	// Ignore invalid items
-	if (!_segMan->isObject(object)) {
-		warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object));
+void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
+	if (plane._type != kPlaneTypeColored) {
 		return;
 	}
 
-	FrameoutEntry *itemEntry = findScreenItem(object);
-	if (!itemEntry) {
-		warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object));
-		return;
+	for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
+		mergeToShowList(**it, _showList, _overdrawThreshold);
+		_currentBuffer.fillRect(**it, plane._back);
+	}
+}
+
+void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
+	_hasRemappedScreenItem = false;
+	if (/* TODO: g_Remap_UnknownCounter2 */ false && !_priorityMap.isNull()) {
+		for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
+			if ((*it)->screenItem->getCelObj()._remap) {
+				_hasRemappedScreenItem = true;
+				break;
+			}
+		}
 	}
 
-	itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view));
-	itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
-	itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
-	itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x));
-	itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y));
-	itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z));
-	itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
-	if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0)
-		itemEntry->priority = itemEntry->y;
+	for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
+		DrawItem &drawItem = **it;
+		mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
+		ScreenItem &screenItem = *drawItem.screenItem;
+		// TODO: Remove
+//		debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), drawItem.rect.left, drawItem.rect.top, drawItem.rect.right, drawItem.rect.bottom);
+		CelObj &celObj = *screenItem._celObj;
+		celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
+	}
+}
 
-	itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal));
-	itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
+void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
+	Common::Rect merged(drawRect);
+
+	bool didDelete = true;
+	RectList::size_type count = showList.size();
+	while (didDelete && count) {
+		didDelete = false;
+
+		for (RectList::size_type i = 0; i < count; ++i) {
+			Common::Rect existing = *showList[i];
+			Common::Rect candidate;
+			candidate.left = MIN(merged.left, existing.left);
+			candidate.top = MIN(merged.top, existing.top);
+			candidate.right = MAX(merged.right, existing.right);
+			candidate.bottom = MAX(merged.bottom, existing.bottom);
+
+			if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
+				merged = candidate;
+				showList.erase_at(i);
+				didDelete = true;
+			}
+		}
 
-	if (itemEntry->scaleSignal & kScaleSignalDoScaling32) {
-		itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
-		itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
-	} else {
-		itemEntry->scaleX = 128;
-		itemEntry->scaleY = 128;
+		count = showList.pack();
 	}
-	itemEntry->visible = true;
 
-	// Check if the entry can be hidden
-	if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone)
-		itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible));
+	showList.add(merged);
 }
 
-void GfxFrameout::kernelDeleteScreenItem(reg_t object) {
-	FrameoutEntry *itemEntry = findScreenItem(object);
-	// If the item could not be found, it may already have been deleted
-	if (!itemEntry)
-		return;
+void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
+	Palette sourcePalette(*_palette->getNextPalette());
+	alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
 
-	_screenItems.remove(itemEntry);
-	delete itemEntry;
-}
+	// TODO: unsure if this is what this variable actually
+	// represents, but it is the correct variable number
+	int16 lastRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
 
-void GfxFrameout::deletePlaneItems(reg_t planeObject) {
-	FrameoutList::iterator listIterator = _screenItems.begin();
+	Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
+	_showList.add(rect);
+	showBits();
 
-	while (listIterator != _screenItems.end()) {
-		bool objectMatches = false;
-		if (!planeObject.isNull()) {
-			reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
-			objectMatches = (planeObject == itemPlane);
-		} else {
-			objectMatches = true;
+	Common::Rect calcRect(0, 0);
+
+	// NOTE: The original engine allocated these as static arrays of 100
+	// pointers to ScreenItemList / RectList
+	ScreenItemListList screenItemLists;
+	EraseListList eraseLists;
+
+	screenItemLists.resize(_planes.size());
+	eraseLists.resize(_planes.size());
+
+	// TODO: Remap
+	// _numActiveRemaps was a global in SCI engine
+	// if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
+		// _screen->remapMarkRedraw();
+	// }
+
+	calcLists(screenItemLists, eraseLists, calcRect);
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		list->sort();
+	}
+
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+			(*drawItem)->screenItem->getCelObj().submitPalette();
 		}
+	}
 
-		if (objectMatches) {
-			FrameoutEntry *itemEntry = *listIterator;
-			listIterator = _screenItems.erase(listIterator);
-			delete itemEntry;
-		} else {
-			++listIterator;
+	_remapOccurred = _palette->updateForFrame();
+	_frameNowVisible = false;
+
+	for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+		drawEraseList(eraseLists[i], *_planes[i]);
+		drawScreenItemList(screenItemLists[i]);
+	}
+
+	Palette nextPalette(*_palette->getNextPalette());
+
+	if (lastRoom < 1000) {
+		for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+			if (styleRanges[i] == -1 || styleRanges[i] == 0) {
+				sourcePalette.colors[i] = nextPalette.colors[i];
+				sourcePalette.colors[i].used = true;
+			}
+		}
+	} else {
+		for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+			if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
+				sourcePalette.colors[i] = nextPalette.colors[i];
+				sourcePalette.colors[i].used = true;
+			}
 		}
 	}
-}
 
-FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) {
-	for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
-		FrameoutEntry *itemEntry = *listIterator;
-		if (itemEntry->object == object)
-			return itemEntry;
+	_palette->submit(sourcePalette);
+	_palette->updateFFrame();
+	_palette->updateHardware();
+	alterVmap(nextPalette, sourcePalette, 1, _styleRanges);
+
+	if (showStyle->type > 0 && showStyle->type < 15) {
+// TODO: SCI2.1mid transition effects
+//		processEffects();
+		warning("Transition not implemented!");
+	} else {
+		showBits();
 	}
 
-	return NULL;
-}
+	_frameNowVisible = true;
 
-int16 GfxFrameout::kernelGetHighPlanePri() {
-	sortPlanes();
-	return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority));
-}
+	for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
+// TODO:
+//		plane->updateRedrawAllCount();
+	}
 
-void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) {
-	addPlanePicture(planeObj, pictureId, pictureX, pictureY);
-}
+	// TODO: Remap
+	// _numActiveRemaps was a global in SCI engine
+	// if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
+	// _screen->remapMarkRedraw();
+	// }
 
-bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) {
-	if (entry1->priority == entry2->priority) {
-		if (entry1->y == entry2->y)
-			return (entry1->givenOrderNr < entry2->givenOrderNr);
-		return (entry1->y < entry2->y);
+	calcLists(screenItemLists, eraseLists, calcRect);
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		list->sort();
 	}
-	return (entry1->priority < entry2->priority);
-}
 
-bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) {
-	if (entry1.priority < 0)
-		return true;
+	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+		for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+			(*drawItem)->screenItem->getCelObj().submitPalette();
+		}
+	}
+
+	_remapOccurred = _palette->updateForFrame();
+	// NOTE: During this second loop, `_frameNowVisible = false` is
+	// inside the next loop in SCI2.1mid
+	_frameNowVisible = false;
+
+	for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+		drawEraseList(eraseLists[i], *_planes[i]);
+		drawScreenItemList(screenItemLists[i]);
+	}
 
-	if (entry2.priority < 0)
-		return false;
+	_palette->submit(nextPalette);
+	_palette->updateFFrame();
+	_palette->updateHardware();
+	showBits();
 
-	return entry1.priority < entry2.priority;
+	_frameNowVisible = true;
 }
 
-void GfxFrameout::sortPlanes() {
-	// First, remove any invalid planes
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) {
-		if (!_segMan->isObject(it->object))
-			it = _planes.erase(it);
-		else
-			it++;
+// TODO: What does the bit masking for the show rects do,
+// and does it cause an off-by-one error in rect calculations
+// since SOL_Rect is BR inclusive and Common::Rect is BR
+// exclusive?
+void GfxFrameout::showBits() {
+	for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+		Common::Rect rounded(**rect);
+		// NOTE: SCI engine used BR-inclusive rects so used slightly
+		// different masking here to ensure that the width of rects
+		// was always even.
+		rounded.left &= ~1;
+		rounded.right = (rounded.right + 1) & ~1;
+
+		// TODO:
+		// _cursor->GonnaPaint(rounded);
 	}
 
-	// Sort the rest of them
-	Common::sort(_planes.begin(), _planes.end(), planeSortHelper);
+	// TODO:
+	// _cursor->PaintStarting();
+
+	for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+		Common::Rect rounded(**rect);
+		// NOTE: SCI engine used BR-inclusive rects so used slightly
+		// different masking here to ensure that the width of rects
+		// was always even.
+		rounded.left &= ~1;
+		rounded.right = (rounded.right + 1) & ~1;
+
+		byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left;
+
+		g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
+	}
+
+	// TODO:
+	// _cursor->DonePainting();
+
+	_showList.clear();
 }
 
-void GfxFrameout::showVideo() {
-	bool skipVideo = false;
-	RobotDecoder *videoDecoder = g_sci->_robotDecoder;
-	uint16 x = videoDecoder->getPos().x;
-	uint16 y = videoDecoder->getPos().y;
-	uint16 screenWidth = _screen->getWidth();
-	uint16 screenHeight = _screen->getHeight();
-	uint16 outputWidth;
-	uint16 outputHeight;
+void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
+	uint8 clut[256];
+
+	for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) {
+		int outerR = palette1.colors[paletteIndex].r;
+		int outerG = palette1.colors[paletteIndex].g;
+		int outerB = palette1.colors[paletteIndex].b;
+
+		if (styleRanges[paletteIndex] == style) {
+			int minDiff = 262140;
+			int minDiffIndex;
+
+			for (int i = 0; i < 236; ++i) {
+				if (styleRanges[i] != style) {
+					int r = palette1.colors[i].r;
+					int g = palette1.colors[i].g;
+					int b = palette1.colors[i].b;
+					int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+					if (diffSquared < minDiff) {
+						minDiff = diffSquared;
+						minDiffIndex = i;
+					}
+				}
+			}
 
-	if (videoDecoder->hasDirtyPalette())
-		g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+			clut[paletteIndex] = minDiffIndex;
+		}
 
-	while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
-		if (videoDecoder->needsUpdate()) {
-			const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
-			if (frame) {
-				// We need to clip here
-				//  At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro
-				outputWidth = frame->w > screenWidth ? screenWidth : frame->w;
-				outputHeight = frame->h > screenHeight ? screenHeight : frame->h;
-				g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight);
+		if (style == 1 && styleRanges[paletteIndex] == 0) {
+			int minDiff = 262140;
+			int minDiffIndex;
 
-				if (videoDecoder->hasDirtyPalette())
-					g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+			for (int i = 0; i < 236; ++i) {
+				int r = palette2.colors[i].r;
+				int g = palette2.colors[i].g;
+				int b = palette2.colors[i].b;
 
-				g_system->updateScreen();
+				int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+				if (diffSquared < minDiff) {
+					minDiff = diffSquared;
+					minDiffIndex = i;
+				}
 			}
+
+			clut[paletteIndex] = minDiffIndex;
 		}
+	}
 
-		Common::Event event;
-		while (g_system->getEventManager()->pollEvent(event)) {
-			if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
-				skipVideo = true;
+	// NOTE: This is currBuffer->ptr in SCI engine
+	byte *pixels = (byte *)_currentBuffer.getPixels();
+
+	// TODO: Guessing that display width/height is the correct
+	// equivalent to screen width/height in SCI engine
+	for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) {
+		byte currentValue = pixels[pixelIndex];
+		int8 styleRangeValue = styleRanges[currentValue];
+		if (styleRangeValue == -1 && styleRangeValue == style) {
+			currentValue = pixels[pixelIndex] = clut[currentValue];
+			styleRangeValue = styleRanges[clut[currentValue]];
 		}
 
-		g_system->delayMillis(10);
+		if (
+			(styleRangeValue == 1 && styleRangeValue == style) ||
+			(styleRangeValue == 0 && style == 1)
+		) {
+			pixels[pixelIndex] = clut[currentValue];
+		}
 	}
 }
 
-void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) {
-	// Copy screen items of the current frame to the list of items to be drawn
-	for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
-		reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
-		if (planeObject == itemPlane) {
-			kernelUpdateScreenItem((*listIterator)->object);	// TODO: Why is this necessary?
-			itemList.push_back(*listIterator);
+void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
+	if (toColor > fromColor) {
+		return;
+	}
+
+	for (int i = fromColor; i < toColor; ++i) {
+		_styleRanges[i] = 0;
+	}
+}
+
+inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const {
+	ShowStyleEntry *entry = _showStyles;
+	while (entry != nullptr) {
+		if (entry->plane == planeObj) {
+			break;
 		}
+		entry = entry->next;
 	}
 
-	for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
-		if (pictureIt->object == planeObject) {
-			GfxPicture *planePicture = pictureIt->picture;
-			// Allocate memory for picture cels
-			pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()];
+	return entry;
+}
+
+inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) {
+	ShowStyleEntry *lastEntry = nullptr;
 
-			// Add following cels to the itemlist
-			FrameoutEntry *picEntry = pictureIt->pictureCels;
-			int planePictureCels = planePicture->getSci32celCount();
-			for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) {
-				picEntry->celNo = pictureCelNr;
-				picEntry->object = NULL_REG;
-				picEntry->picture = planePicture;
-				picEntry->y = planePicture->getSci32celY(pictureCelNr);
-				picEntry->x = planePicture->getSci32celX(pictureCelNr);
-				picEntry->picStartX = pictureIt->startX;
-				picEntry->picStartY = pictureIt->startY;
-				picEntry->visible = true;
+	for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) {
+		if (testEntry == showStyle) {
+			break;
+		}
+		lastEntry = testEntry;
+	}
 
-				picEntry->priority = planePicture->getSci32celPriority(pictureCelNr);
+	if (lastEntry == nullptr) {
+		_showStyles = showStyle->next;
+		lastEntry = _showStyles;
+	} else {
+		lastEntry->next = showStyle->next;
+	}
 
-				itemList.push_back(picEntry);
-				picEntry++;
+	// NOTE: Differences from SCI2/2.1early engine:
+	// 1. Memory of ShowStyle-owned objects was freed before ShowStyle was
+	//    removed from the linked list, but since this operation is position
+	//    independent, it has been moved after removal from the list for
+	//    consistency with SCI2.1mid+
+	// 2. In SCI2, `screenItems` was a pointer to an array of pointers, so
+	//    extra deletes were performed here; we use an owned container object
+	//    instead, which is automatically freed when ShowStyle is freed
+#if 0
+	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+		uint8 type = showStyle->type;
+
+		if (type >= 1 && type <= 10) {
+			ScreenItemList &styleItems = showStyle->screenItems;
+			for (ScreenItemList::iterator it = styleItems.begin(); it != styleItems.end(); ++it) {
+				if (active) {
+					// TODO: _screen->deleteScreenItem(showStyle->plane, *it->id);
+					_screenItems.remove(*it);
+				}
+				delete *it;
+			}
+		} else if (type == 11 || type == 12) {
+			if (!showStyle->bitmapMemId.isNull()) {
+				_segMan->freeHunkEntry(showStyle->bitmapMemId);
+			}
+			if (showStyle->bitmapScreenItem != nullptr) {
+				// TODO: _screen->deleteScreenItem(showStyle->plane, showStyle->bitmapScreenItem->id);
+				_screenItems.remove(showStyle->bitmapScreenItem);
+				delete showStyle->bitmapScreenItem;
 			}
 		}
+	} else {
+#endif
+		delete[] showStyle->fadeColorRanges;
+#if 0
 	}
+#endif
+
+	delete showStyle;
 
-	// Now sort our itemlist
-	Common::sort(itemList.begin(), itemList.end(), sortHelper);
+	// TODO: Verify that this is the correct entry to return
+	// for the loop in processShowStyles to work correctly
+	return lastEntry;
 }
 
-bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) {
-	// Out of view horizontally (sanity checks)
-	int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x;
-	int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo);
-	int16 planeStartX = planeOffsetX;
-	int16 planeEndX = planeStartX + planeRect.width();
-	if (pictureCelEndX < planeStartX)
-		return true;
-	if (pictureCelStartX > planeEndX)
-		return true;
+// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
+// and need to be fixed in future
+// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3);
+// check to see if other versions use or if they are just always ignored
+void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen) {
+
+	bool hasDivisions = false;
+	bool hasFadeArray = false;
+	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+		hasDivisions = argc > 7;
+		hasFadeArray = false;
+	} else if (getSciVersion() < SCI_VERSION_3) {
+		hasDivisions = argc > 8;
+		hasFadeArray = argc > 7;
+	} else {
+		hasDivisions = argc > 9;
+		hasFadeArray = argc > 8;
+	}
 
-	// Out of view vertically (sanity checks)
-	int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y;
-	int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo);
-	int16 planeStartY = planeOffsetY;
-	int16 planeEndY = planeStartY + planeRect.height();
-	if (pictureCelEndY < planeStartY)
-		return true;
-	if (pictureCelStartY > planeEndY)
-		return true;
+	bool isFadeUp;
+	int16 color;
+	if (back != -1) {
+		isFadeUp = false;
+		color = back;
+	} else {
+		isFadeUp = true;
+		color = 0;
+	}
 
-	return false;
-}
+	if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) {
+		error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+	}
+
+	Plane *plane = _planes.findByObject(planeObj);
+	if (plane == nullptr) {
+		error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
+	}
+
+	// TODO: This is Plane.gameRect in SCI engine, not planeRect. Engine uses
+	// Plane::ConvGameRectToPlaneRect to convert from gameRect to planeRect.
+	// Also this never gets used by SQ6 so it is not clear what it does yet
+	//	Common::Rect gameRect = plane.planeRect;
+
+	bool createNewEntry = true;
+	ShowStyleEntry *entry = findShowStyleForPlane(planeObj);
+	if (entry != nullptr) {
+		bool useExisting = true;
+
+		if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+			useExisting = plane->_planeRect.width() == entry->width && plane->_planeRect.height() == entry->height;
+		}
+
+		if (useExisting) {
+			useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type];
+		}
 
-void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) {
-	int16 pictureOffsetX = planeOffsetX;
-	int16 pictureX = itemEntry->x;
-	if ((planeOffsetX) || (itemEntry->picStartX)) {
-		if (planeOffsetX <= itemEntry->picStartX) {
-			pictureX += itemEntry->picStartX - planeOffsetX;
-			pictureOffsetX = 0;
+		if (useExisting) {
+			createNewEntry = false;
+			isFadeUp = true;
+			entry->currentStep = 0;
 		} else {
-			pictureOffsetX = planeOffsetX - itemEntry->picStartX;
+			isFadeUp = true;
+			color = entry->color;
+			deleteShowStyleInternal(entry/*, true*/);
+			entry = nullptr;
 		}
 	}
 
-	int16 pictureOffsetY = planeOffsetY;
-	int16 pictureY = itemEntry->y;
-	if ((planeOffsetY) || (itemEntry->picStartY)) {
-		if (planeOffsetY <= itemEntry->picStartY) {
-			pictureY += itemEntry->picStartY - planeOffsetY;
-			pictureOffsetY = 0;
-		} else {
-			pictureOffsetY = planeOffsetY - itemEntry->picStartY;
+	if (type > 0) {
+		if (createNewEntry) {
+			entry = new ShowStyleEntry;
+			// NOTE: SCI2.1 engine tests if allocation returned a null pointer
+			// but then only avoids setting currentStep if this is so. Since
+			// this is a nonsensical approach, we do not do that here
+			entry->currentStep = 0;
+			entry->unknownC = _defaultUnknownC[type];
+			entry->processed = false;
+			entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+			entry->plane = planeObj;
+
+#if 0
+			if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+				entry->bitmapMemId = NULL_REG;
+				entry->screenItems.empty();
+				entry->width = plane->_planeRect.width();
+				entry->height = plane->_planeRect.height();
+			} else {
+#endif
+				entry->fadeColorRanges = nullptr;
+				if (hasFadeArray) {
+					// NOTE: SCI2.1mid engine does no check to verify that an array is
+					// successfully retrieved, and SegMan will cause a fatal error
+					// if we try to use a memory segment that is not an array
+					SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+
+					uint32 rangeCount = table->getSize();
+					entry->fadeColorRangesCount = rangeCount;
+
+					// NOTE: SCI engine code always allocates memory even if the range
+					// table has no entries, but this does not really make sense, so
+					// we avoid the allocation call in this case
+					if (rangeCount > 0) {
+						entry->fadeColorRanges = new uint16[rangeCount];
+						for (size_t i = 0; i < rangeCount; ++i) {
+							entry->fadeColorRanges[i] = table->getValue(i).toUint16();
+						}
+					}
+				} else {
+					entry->fadeColorRangesCount = 0;
+				}
+#if 0
+			}
+#endif
+		}
+
+		// NOTE: The original engine had no nullptr check and would just crash
+		// if it got to here
+		if (entry == nullptr) {
+			error("Cannot edit non-existing ShowStyle entry");
+		}
+
+		entry->fadeUp = isFadeUp;
+		entry->color = color;
+		entry->nextTick = g_sci->getTickCount();
+		entry->type = type;
+		entry->animate = animate;
+		entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
+
+		if (entry->delay == 0) {
+#if 0
+			if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE && entry->fadeColorRanges != nullptr) {
+#endif
+			if (entry->fadeColorRanges != nullptr) {
+				delete[] entry->fadeColorRanges;
+			}
+			delete entry;
+			error("ShowStyle has no duration");
+		}
+
+		if (frameOutNow) {
+			Common::Rect frameOutRect(0, 0);
+			frameOut(false, frameOutRect);
+		}
+
+		if (createNewEntry) {
+			// TODO: Implement SCI3, which may or may not actually have
+			// the same transitions as SCI2/SCI2.1early, but implemented
+			// differently
+#if 0
+			if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+				switch (entry->type) {
+					case kShowStyleHShutterIn:
+					case kShowStyleHShutterOut:
+						prepareShowStyleWipe(entry, priority, 2, true);
+						break;
+
+					case kShowStyleVShutterIn:
+					case kShowStyleVShutterOut:
+						prepareShowStyleWipe(entry, priority, 2, false);
+						break;
+
+					case kShowStyleHWipe1:
+					case kShowStyleHWipe2:
+						prepareShowStyleWipe(entry, priority, 1, true);
+						break;
+
+					case kShowStyleVWipe1:
+					case kShowStyleVWipe2:
+						prepareShowStyleWipe(entry, priority, 1, false);
+						break;
+
+					case kShowStyleIrisIn:
+					case kShowStyleIrisOut:
+						prepareShowStyleIris(entry, priority);
+						break;
+
+					case kShowStyle11:
+					case kShowStyle12:
+						prepareShowStylePixels(entry, priority, plane->planeRect);
+						break;
+
+					default:
+						break;
+				}
+			}
+#endif
+
+			entry->next = _showStyles;
+			_showStyles = entry;
 		}
 	}
+}
 
-	itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored);
-	//	warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority);
+#if 0
+void addFrameoutEntryInternal(ShowStyleEntry *const showStyle, const int16 priority, const CelInfo32 &celInfo, const Common::Rect &rect) {
+	ScreenItem *screenItem = new ScreenItem;
+	screenItem->plane = showStyle->plane;
+	screenItem->celInfo = celInfo;
+	screenItem->celRect = rect;
+	screenItem->isInList = true;
+	screenItem->priority = priority;
+	screenItem->visible = true;
+	showStyle->screenItems.push_back(screenItem);
 }
 
-/* TODO: This is the proper implementation of GraphicsMgr::FrameOut transcribed from SQ6 SCI engine disassembly.
-static DrawList* g_drawLists[100];
-static RectList* g_rectLists[100];
-void GfxFrameout::FrameOut(bool shouldShowBits, SOL_Rect *rect) {
-	if (robot) {
-		robot.doRobot();
+void GfxFrameout::prepareShowStyleWipe(ShowStyleEntry *const showStyle, const int16 priority, const int16 edgeCount, const bool horizontal) {
+	assert(edgeCount == 1 || edgeCount == 2);
+
+	const int numScreenItems = showStyle->divisions * edgeCount;
+	const int extra = edgeCount > 1 ? 1 : 0;
+
+	showStyle->edgeCount = edgeCount;
+	showStyle->screenItems.reserve(numScreenItems);
+
+	CelInfo32 celInfo;
+	celInfo.bitmap = NULL_REG;
+	celInfo.type = kCelObjTypeView;
+	celInfo.color = showStyle->color;
+
+	for (int i = 0; i < numScreenItems; ++i) {
+		Common::Rect rect;
+
+		if (horizontal) {
+			rect.top = 0;
+			rect.bottom = showStyle->height - 1;
+			rect.left = (showStyle->width * i) / (showStyle->divisions * edgeCount);
+			rect.right = ((i + 1) * (showStyle->width + extra)) / (showStyle->divisions * edgeCount) - 1;
+		} else {
+			rect.left = 0;
+			rect.right = showStyle->width - 1;
+			rect.top = (showStyle->height * i) / (showStyle->divisions * edgeCount);
+			rect.bottom = ((i + 1) * (showStyle->height + extra)) / (showStyle->divisions * edgeCount) - 1;
+		}
+
+		addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+		if (edgeCount == 2) {
+			if (horizontal) {
+				int temp = rect.left;
+				rect.left = showStyle->width - rect.right - 1;
+				rect.right = showStyle->width - temp - 1;
+			} else {
+				int temp = rect.top;
+				rect.top = showStyle->height - rect.bottom - 1;
+				rect.bottom = showStyle->height - temp - 1;
+			}
+
+			addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+		}
 	}
+}
 
-	auto planeCount = screen.planeList.planeCount;
-	if (planeCount > 0) {
-		for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
-			Plane plane = *screen.planeList[planeIndex];
+void GfxFrameout::prepareShowStyleIris(ShowStyleEntry *const showStyle, const int16 priority) {
+	const int edgeCount = 4;
+	const int numScreenItems = showStyle->divisions * edgeCount;
+
+	showStyle->edgeCount = edgeCount;
+	showStyle->screenItems.reserve(numScreenItems);
+
+	CelInfo32 celInfo;
+	celInfo.bitmap = NULL_REG;
+	celInfo.type = kCelObjTypeView;
+	celInfo.color = showStyle->color;
+
+	for (int i = 0; i < numScreenItems; ++i) {
+		Common::Rect rect;
+
+		rect.right = showStyle->width - ((showStyle->width * i) / (showStyle->divisions * 2)) - 1;
+		rect.left = (showStyle->width * i) / (showStyle->divisions * 2);
+		rect.top = (showStyle->height * i) / (showStyle->divisions * 2);
+		rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
+
+		addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+		{
+			int temp = rect.top;
+			rect.top = showStyle->height - rect.bottom - 1;
+			rect.bottom = showStyle->height - temp - 1;
+		}
+
+		addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+		rect.top = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2);
+		rect.right = ((i + 1) * (showStyle->width + 1)) / (showStyle->divisions * 2) - 1;
+		rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
 
-			DrawList* drawList = new DrawList();
-			g_drawLists[planeIndex] = drawList;
-			RectList* rectList = new RectList();
-			g_rectLists[planeIndex] = rectList;
+		addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+		{
+			int temp = rect.left;
+			rect.left = showStyle->width - rect.right - 1;
+			rect.right = showStyle->width - temp - 1;
 		}
+
+		addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
 	}
- 
-	if (g_Remap_numActiveRemaps > 0 && remapNeeded) {
-		screen.RemapMarkRedraw();
+}
+
+void GfxFrameout::prepareShowStylePixels(ShowStyleEntry *const showStyle, const int16 priority, const Common::Rect planeGameRect) {
+	const int bitmapSize = showStyle->width * showStyle->height;
+
+	// TODO: Verify that memory type 0x200 (what GK1 engine uses)
+	// is Hunk type
+	reg_t bitmapMemId = _segMan->allocateHunkEntry("ShowStylePixels()", bitmapSize + sizeof(GfxBitmapHeader));
+	showStyle->bitmapMemId = bitmapMemId;
+
+	// TODO: SCI2 GK1 uses a Bitmap constructor function to
+	// do this work
+	byte *bitmap = _segMan->getHunkPointer(bitmapMemId);
+	GfxBitmapHeader *header = (GfxBitmapHeader *)bitmap;
+	byte *bitmapData = bitmap + sizeof(GfxBitmapHeader);
+
+	// TODO: These are defaults from the Bitmap constructor in
+	// GK1, not specific values set by this function.
+	// TODO: This probably should not even be using a struct at
+	// all since this information is machine endian dependent
+	// and will be reversed for Mac versions or when running
+	// ScummVM on big-endian systems. GK1 used packed structs
+	// everywhere so this probably worked better there too.
+	header->field_18 = 36;
+	header->field_1c = 36;
+	memset(header, 0, sizeof(GfxBitmapHeader));
+
+	header->width = showStyle->width;
+	header->height = showStyle->height;
+	header->field_8 = 250;
+	header->size = bitmapSize;
+
+	// TODO: Scaled dimensions in bitmap headers was not added
+	// until SCI2.1mid. It is not clear what the right thing to
+	// do here is.
+	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+		header->scaledWidth = _currentBuffer.scriptWidth;
+		header->scaledHeight = _currentBuffer.scriptHeight;
 	}
- 
-	CalcLists(&g_drawLists, &g_rectLists, rect);
 
-	// SCI engine stores reference *after* CalcLists
-	planeCount = screen.planeList.planeCount;
-	if (planeCount > 0) {
-		for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
-			DrawList* drawList = g_drawLists[drawListIndex];
-			drawList->Sort();
-		}
+	Common::Rect copyRect;
+	// TODO: planeGameRect is supposedly in script coordinates,
+	// which are usually 320x200. If bitsSaveDisplayScreen is
+	// in native resolution then seemingly this function will
+	// not work properly since we will be not copy enough bits,
+	// or from the correct location.
+	copyRect.left = planeGameRect.left;
+	copyRect.top = planeGameRect.top;
+	copyRect.right = planeGameRect.left + showStyle->width;
+	copyRect.bottom = planeGameRect.top + showStyle->height;
+	_screen->bitsSaveDisplayScreen(copyRect, bitmapData);
+
+	CelInfo32 celInfo;
+	celInfo.bitmap = bitmapMemId;
+	celInfo.type = kCelObjTypeMem;
+
+	ScreenItem *screenItem = new ScreenItem;
+
+	screenItem->position.x = 0;
+	screenItem->position.y = 0;
+
+	showStyle->bitmapScreenItem = screenItem;
+	screenItem->priority = priority;
+
+	// TODO: Have not seen/identified this particular flag yet in
+	// SCI2.1mid (SQ6) engine; maybe (1) a duplicate of `created`,
+	// or (2) does not exist any more, or (3) one of the other
+	// still-unidentified fields. Probably need to look at the
+	// GK1 source for its use in drawing algorithms to determine
+	// if/how this correlates to ScreenItem members in the
+	// SCI2.1mid engine.
+//	screenItem->isInList = true;
+
+	Plane *plane = _planes.findByObject(showStyle.plane);
+	plane->_screenItemList.add(screenItem);
+}
+#endif
+
+// NOTE: Different version of SCI engine support different show styles
+// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14
+// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14
+// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14
+// TODO: Sierra code needs to be replaced with code that uses the
+// computed entry->delay property instead of just counting divisors,
+// as the latter is machine-speed-dependent and leads to wrong
+// transition speeds
+void GfxFrameout::processShowStyles() {
+	uint32 now = g_sci->getTickCount();
+
+	bool continueProcessing;
+
+	// TODO: Change to bool? Engine uses inc to set the value to true,
+	// but there does not seem to be any reason to actually count how
+	// many times it was set
+	int doFrameOut;
+	do {
+		continueProcessing = false;
+		doFrameOut = 0;
+		ShowStyleEntry *showStyle = _showStyles;
+		while (showStyle != nullptr) {
+			bool retval = false;
+
+			if (!showStyle->animate) {
+				++doFrameOut;
+			}
+
+			if (showStyle->nextTick < now || !showStyle->animate) {
+				// TODO: Different versions of SCI use different processors!
+				// This is the SQ6/KQ7/SCI2.1mid table.
+				switch (showStyle->type) {
+					case kShowStyleNone: {
+						retval = processShowStyleNone(showStyle);
+						break;
+					}
+					case kShowStyleHShutterOut:
+					case kShowStyleVShutterOut:
+					case kShowStyleWipeLeft:
+					case kShowStyleWipeUp:
+					case kShowStyleIrisOut:
+#if 0
+						if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+							retval = processShowStyleMorph(showStyle);
+#if 0
+						} else {
+							retval = processShowStyleWipe(-1, showStyle);
+						}
+#endif
+						break;
+					case kShowStyleHShutterIn:
+					case kShowStyleVShutterIn:
+					case kShowStyleWipeRight:
+					case kShowStyleWipeDown:
+					case kShowStyleIrisIn:
+#if 0
+						if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+							retval = processShowStyleMorph(showStyle);
+#if 0
+						} else {
+							retval = processShowStyleWipe(1, showStyle);
+						}
+#endif
+						break;
+					case kShowStyle11:
+#if 0
+						if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+							retval = processShowStyleMorph(showStyle);
+#if 0
+						} else {
+							retval = processShowStyle11(showStyle);
+						}
+#endif
+						break;
+					case kShowStyle12:
+					case kShowStyleUnknown: {
+#if 0
+						if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+							retval = processShowStyleMorph(showStyle);
+#if 0
+						} else {
+							retval = processShowStyle12(showStyle);
+						}
+#endif
+						break;
+					}
+					case kShowStyleFadeOut: {
+						retval = processShowStyleFade(-1, showStyle);
+						break;
+					}
+					case kShowStyleFadeIn: {
+						retval = processShowStyleFade(1, showStyle);
+						break;
+					}
+				}
+			}
 
-		for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
-			DrawList* drawList = g_drawLists[drawListIndex];
-			if (drawList == nullptr || drawList->count == 0) {
-				continue;
+			if (!retval) {
+				continueProcessing = true;
 			}
 
-			for (int screenItemIndex = 0, screenItemCount = drawList->count; screenItemIndex < screenItemCount; ++screenItemIndex) {
-				ScreenItem* screenItem = drawList->items[screenItemIndex];
-				screenItem->GetCelObj()->SubmitPalette();
+			if (retval && showStyle->processed) {
+				showStyle = deleteShowStyleInternal(showStyle);
+			} else {
+				showStyle = showStyle->next;
 			}
 		}
+
+		if (doFrameOut) {
+			Common::Rect frameOutRect(0, 0);
+			frameOut(true, frameOutRect);
+
+			// TODO: It seems like transitions without the “animate”
+			// flag are too fast in in SCI2–2.1early, but the throttle
+			// value is arbitrary. Someone on real hardware probably
+			// needs to test what the actual speed of transitions
+			// should be
+			//state->speedThrottler(30);
+			//state->_throttleTrigger = true;
+		}
+	} while(continueProcessing && doFrameOut);
+}
+
+bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) {
+	if (showStyle->fadeUp) {
+		_palette->setFade(100, 0, 255);
+	} else {
+		_palette->setFade(0, 0, 255);
 	}
 
-	// UpdateForFrame is where all palette mutations occur (cycles, varies, etc.)
-	bool remapNeeded = GPalette().UpdateForFrame();
-	if (planeCount > 0) {
-		frameNowVisible = false;
+	showStyle->processed = true;
+	return true;
+}
 
-		for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
-			Plane* plane = screen.planeList[planeIndex];
+bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) {
+	palMorphFrameOut(_styleRanges, showStyle);
+	showStyle->processed = true;
+	return true;
+}
 
-			DrawEraseList(g_rectLists[planeIndex], plane);
-			DrawScreenItemsList(g_drawLists[planeIndex]);
+// TODO: Normalise use of 'entry' vs 'showStyle'
+bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) {
+	bool unchanged = true;
+	if (showStyle->currentStep < showStyle->divisions) {
+		int percent;
+		if (direction <= 0) {
+			percent = showStyle->divisions - showStyle->currentStep - 1;
+		} else {
+			percent = showStyle->currentStep;
 		}
-	}
 
-	if (robot) {
-		robot.FrameAlmostVisible();
-	}
+		percent *= 100;
+		percent /= showStyle->divisions - 1;
 
-	GPalette().UpdateHardware();
+		if (showStyle->fadeColorRangesCount > 0) {
+			for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) {
+				_palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]);
+			}
+		} else {
+			_palette->setFade(percent, 0, 255);
+		}
 
-	if (shouldShowBits) {
-		ShowBits();
+		++showStyle->currentStep;
+		showStyle->nextTick += showStyle->delay;
+		unchanged = false;
 	}
 
-	frameNowVisible = true;
+	if (showStyle->currentStep >= showStyle->divisions && unchanged) {
+		if (direction > 0) {
+			showStyle->processed = true;
+		}
 
-	if (robot) {
-		robot.FrameNowVisible();
+		return true;
 	}
 
-	if (planeCount > 0) {
-		for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
-			if (g_rectLists[planeIndex] != nullptr) {
-				delete g_rectLists[planeIndex];
+	return false;
+}
+
+// TODO: Rect sizes are wrong, rects in SCI are inclusive of bottom/right but
+// in ScummVM are exclusive so extra ±1 operations here are wrong
+#if 0
+bool GfxFrameout::processShowStyleWipe(const ShowStyleEntry *const style) {
+	const int16 divisions = style->divisions;
+	Common::Rect rect(divisions, divisions);
+
+	const Plane *const plane = _visibleScreen->planeList->findByObject(style->plane);
+
+	const int16 planeLeft = plane->field_4C.left;
+	const int16 planeTop = plane->field_4C.top;
+	const int16 planeRight = plane->field_4C.right;
+	const int16 planeBottom = plane->field_4C.bottom;
+	const int16 planeWidth = planeRight - planeLeft + 1;
+	const int16 planeHeight = planeBottom - planeTop + 1;
+
+	const int16 divisionWidth = planeWidth / divisions - 1;
+	int16 shutterDivisionWidth = planeWidth / (2 * divisions);
+	if (shutterDivisionWidth >= 0) {
+		const int16 heightPerDivision = planeHeight / divisions;
+		int16 shutterMiddleX = divisions * shutterDivisionWidth;
+		for (int16 x = divisionWidth - shutterDivisionWidth; x <= divisionWidth; ++x) {
+			int16 divisionTop = 0;
+			for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
+				rect.top = planeTop + divisionTop;
+				rect.bottom = rect.top + divisions - 1;
+				rect.left = planeLeft + shutterMiddleX;
+				rect.right = rect.left + divisions - 1;
+				//				_screen->rectList.clear();
+				//				_screen->rectList.add(rect);
+				//				showBits();
+			}
+			// number of divisions does not divide evenly into plane height,
+			// draw the remainder
+			if (planeHeight % divisions) {
+				rect.top = planeTop + divisionTop;
+				rect.bottom = rect.top + planeHeight % divisions - 1;
+				rect.left = planeLeft + shutterMiddleX;
+				rect.right = rect.left + divisions - 1;
+				//				_screen->rectList.clear();
+				//				_screen->rectList.add(rect);
+				//				showBits();
 			}
-			if (g_drawLists[planeIndex] != nullptr) {
-				delete g_drawLists[planeIndex];
+
+			divisionTop = 0;
+			for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
+				rect.top = planeTop + divisionTop;
+				rect.bottom = rect.top + divisions - 1;
+				rect.left = planeLeft + divisions * x;
+				rect.right = rect.left + divisions - 1;
+				//				_screen->rectList.clear();
+				//				_screen->rectList.add(rect);
+				//				showBits();
+			}
+			if (planeHeight % divisions) {
+				rect.top = planeTop + divisionTop;
+				rect.bottom = rect.top + planeHeight % divisions - 1;
+				rect.left = planeLeft + divisions * x;
+				rect.right = rect.left + divisions - 1;
+				//				_screen->rectList.clear();
+				//				_screen->rectList.add(rect);
+				//				showBits();
 			}
+
+			shutterMiddleX -= divisions;
+			--shutterDivisionWidth;
 		}
 	}
-}
-void GfxFrameout::CalcLists(DrawList **drawLists, RectList **rectLists, SOL_Rect *rect) {
-	screen.CalcLists(&visibleScreen, drawLists, rectLists, rect);
-}
-*/
-void GfxFrameout::kernelFrameout() {
-	if (g_sci->_robotDecoder->isVideoLoaded()) {
-		showVideo();
-		return;
+
+	if (planeWidth % divisions) {
+		const int16 roundedPlaneWidth = divisions * divisionWidth;
+		int16 divisionTop = 0;
+		for (int16 y = 0; y < planeHeight / divisions; ++y, divisionTop += divisions) {
+			rect.top = planeTop + divisionTop;
+			rect.bottom = rect.top + divisions - 1;
+			rect.left = planeLeft + roundedPlaneWidth;
+			rect.right = rect.left + planeWidth % divisions + divisions - 1;
+			//			_screen->rectList.clear();
+			//			_screen->rectList.add(rect);
+			//			showBits();
+		}
+		if (planeHeight % divisions) {
+			rect.top = planeTop + divisionTop;
+			rect.bottom = rect.top + planeHeight % divisions - 1;
+			rect.left = planeLeft + roundedPlaneWidth;
+			rect.right = rect.left + planeWidth % divisions + divisions - 1;
+			//			_screen->rectList.clear();
+			//			_screen->rectList.add(rect);
+			//			showBits();
+		}
 	}
 
-	_palette->updateForFrame();
+	rect.right = planeRight;
+	rect.left = planeLeft;
+	rect.top = planeTop;
+	rect.bottom = planeBottom;
+	//	_screen->rectList.clear();
+	//	_screen->rectList.add(rect);
+	//	showBits();
+}
+#endif
+#if 0
+bool GfxFrameout::processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle) {
+	if (showStyle->currentStep < showStyle->divisions) {
+		int index;
+		if (direction <= 0) {
+			index = showStyle->divisions - showStyle->currentStep - 1;
+		} else {
+			index = showStyle->currentStep;
+		}
 
-	// TODO: Tons of drawing stuff should be here, see commented out implementation above
+		index *= showStyle->edgeCount;
 
-	_palette->updateHardware();
+		if (showStyle->edgeCount > 0) {
+			for (int i = 0; i < showStyle->edgeCount; ++i) {
+				if (showStyle->fadeUp) {
+					ScreenItem *screenItem = showStyle->screenItems[index + i];
+					if (screenItem != nullptr) {
+						// TODO: _screen->deleteScreenItem(screenItem);
+						_screenItems.remove(screenItem);
 
-	for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) {
-		reg_t planeObject = it->object;
+						delete screenItem;
+						showStyle->screenItems[index + i] = nullptr;
+					}
+				} else {
+					ScreenItem *screenItem = showStyle->screenItems[index + i];
+					// TODO: _screen->addScreenItem(screenItem);
+					_screenItems.push_back(screenItem);
+				}
+			}
 
-		// Draw any plane lines, if they exist
-		// These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires)
-		// FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires).
-		// Perhaps something is painted over them?
-		for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
-			Common::Point startPoint = it2->startPoint;
-			Common::Point endPoint = it2->endPoint;
-			_coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object);
-			_coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object);
-			_screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control);
+			++showStyle->currentStep;
+			showStyle->nextTick += showStyle->delay;
 		}
+	}
 
-		int16 planeLastPriority = it->lastPriority;
-
-		// Update priority here, sq6 sets it w/o UpdatePlane
-		int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
-
-		it->lastPriority = planePriority;
-		if (planePriority < 0) { // Plane currently not meant to be shown
-			// If plane was shown before, delete plane rect
-			if (planePriority != planeLastPriority)
-				_paint32->fillRect(it->planeRect, 0);
-			continue;
+	if (showStyle->currentStep >= showStyle->divisions) {
+		if (showStyle->fadeUp) {
+			showStyle->processed = true;
 		}
 
-		// There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX.
-		// Since I first wrote the patch, the race has stopped occurring for me though.
-		// I'll leave this for investigation later, when someone can reproduce.
-		//if (it->pictureId == kPlanePlainColored)	// FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes)
-		if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1))
-			_paint32->fillRect(it->planeRect, it->planeBack);
+		return true;
+	}
+
+	return false;
+}
 
-		_coordAdjuster->pictureSetDisplayArea(it->planeRect);
-		// Invoking drewPicture() with an invalid picture ID in SCI32 results in
-		// invalidating the palVary palette when a palVary effect is active. This
-		// is quite obvious in QFG4, where the day time palette is incorrectly
-		// shown when exiting the caves, and the correct night time palette
-		// flashes briefly each time that kPalVaryInit is called.
-		if (it->pictureId != 0xFFFF)
-			_palette->drewPicture(it->pictureId);
+void fillRect(byte *data, const Common::Rect &rect, const int16 color, const int16 stride) {
 
-		FrameoutList itemList;
+}
 
-		createPlaneItemList(planeObject, itemList);
+bool GfxFrameout::processShowStyle11(ShowStyleEntry *const showStyle) {
+	int divisions = showStyle->divisions * showStyle->divisions;
 
-		for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) {
-			FrameoutEntry *itemEntry = *listIterator;
+	byte *bitmapData = _segMan->getHunkPointer(showStyle->bitmapMemId) + sizeof(GfxBitmapHeader);
 
-			if (!itemEntry->visible)
-				continue;
+	int ebx;
 
-			if (itemEntry->object.isNull()) {
-				// Picture cel data
-				_coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
-				_coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX);
+	if (showStyle->currentStep == 0) {
+		int ctr = 0;
+		int bloot = divisions;
+		do {
+			bloot >>= 1;
+			++ctr;
+		} while (bloot != 1);
 
-				if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY))
-					drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored);
+		showStyle->dissolveSeed = _dissolveSequenceSeeds[ctr];
+		ebx = 800;
+		showStyle->unknown3A = 800;
+		showStyle->dissolveInitial = 800;
+	} else {
+		int ebx = showStyle->unknown3A;
+		do {
+			int eax = ebx >> 1;
+			if (ebx & 1) {
+				ebx = showStyle->dissolveSeed ^ eax;
 			} else {
-				GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL;
-				int16 dummyX = 0;
-
-				if (view && view->isSci2Hires()) {
-					view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x);
-					view->adjustToUpscaledCoordinates(itemEntry->z, dummyX);
-				} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
-					_coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
-					_coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX);
-				}
+				ebx = eax;
+			}
+		} while (ebx >= divisions);
 
-				// Adjust according to current scroll position
-				itemEntry->x -= it->planeOffsetX;
-				itemEntry->y -= it->planeOffsetY;
-
-				uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect));
-				if (useInsetRect) {
-					itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop));
-					itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft));
-					itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom));
-					itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight));
-					if (view && view->isSci2Hires()) {
-						view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left);
-						view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right);
-					}
-					itemEntry->celRect.translate(itemEntry->x, itemEntry->y);
-					// TODO: maybe we should clip the cels rect with this, i'm not sure
-					//  the only currently known usage is game menu of gk1
-				} else if (view) {
-					// Process global scaling, if needed.
-					// TODO: Seems like SCI32 always processes global scaling for scaled objects
-					// TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY)
-					if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) &&
-					   !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) &&
-					    (itemEntry->scaleX == itemEntry->scaleY) &&
-						itemEntry->scaleX != 128)
-						applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo));
-
-					if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
-						view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
-							itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
-					else
-						view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo,
-							itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX,
-							itemEntry->scaleY, itemEntry->celRect);
-
-					Common::Rect nsRect = itemEntry->celRect;
-					// Translate back to actual coordinate within scrollable plane
-					nsRect.translate(it->planeOffsetX, it->planeOffsetY);
-
-					if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
-						// HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now.
-						// TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen.
-						if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable)
-							continue;
-					}
+		if (ebx == showStyle->dissolveInitial) {
+			ebx = 0;
+		}
+	}
 
-					if (view && view->isSci2Hires()) {
-						view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
-						view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
-						g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
-					} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _isHiRes) {
-						_coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
-						_coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
-						g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
-					}
+	Common::Rect rect;
 
-					// TODO: For some reason, the top left nsRect coordinates get
-					// swapped in the GK1 inventory screen, investigate why.
-					// This is also needed for GK1 rooms 710 and 720 (catacombs, inner and
-					// outer circle), for handling the tiles and talking to Wolfgang.
-					// HACK: Fix the coordinates by explicitly setting them here for GK1.
-					// Also check bug #6729, for another case where this is needed.
-					if (g_sci->getGameId() == GID_GK1)
-						g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
-				}
+	rect.left = (showStyle->width + showStyle->divisions - 1) / showStyle->divisions;
+	rect.top = (showStyle->height + showStyle->divisions - 1) / showStyle->divisions;
 
-				// Don't attempt to draw sprites that are outside the visible
-				// screen area. An example is the random people walking in
-				// Jackson Square in GK1.
-				if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top  >= _screen->getDisplayHeight() ||
-				    itemEntry->celRect.right  < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth())
-					continue;
+	if (showStyle->currentStep <= showStyle->divisions) {
+		int ebp = 0;
+		do {
+			int ecx = ebx % showStyle->divisions;
 
-				Common::Rect clipRect, translatedClipRect;
-				clipRect = itemEntry->celRect;
+			Common::Rect drawRect;
+			drawRect.left = rect.left * ecx;
+			drawRect.right = drawRect.left + rect.left - 1;
+			drawRect.top = rect.top * ebx;
+			drawRect.bottom = rect.top * ebx + rect.top - 1;
 
-				if (view && view->isSci2Hires()) {
-					clipRect.clip(it->upscaledPlaneClipRect);
-					translatedClipRect = clipRect;
-					translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top);
-				} else {
-					// QFG4 passes invalid rectangles when a battle is starting
-					if (!clipRect.isValidRect())
-						continue;
-					clipRect.clip(it->planeClipRect);
-					translatedClipRect = clipRect;
-					translatedClipRect.translate(it->planeRect.left, it->planeRect.top);
+			bool doit;
+			if (drawRect.right >= 0 && drawRect.bottom >= 0 && drawRect.left <= rect.right && drawRect.top <= rect.bottom) {
+				doit = true;
+			} else {
+				doit = false;
+			}
+
+			if (doit) {
+				if (drawRect.left < 0) {
+					drawRect.left = 0;
 				}
 
-				if (view) {
-					if (!clipRect.isEmpty()) {
-						if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
-							view->draw(itemEntry->celRect, clipRect, translatedClipRect,
-								itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires());
-						else
-							view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
-								itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY);
-					}
+				if (drawRect.top < 0) {
+					drawRect.top = 0;
 				}
 
-				// Draw text, if it exists
-				if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) {
-					g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object);
+				if (drawRect.right > rect.right) {
+					drawRect.right = rect.right;
 				}
+
+				if (drawRect.bottom > rect.bottom) {
+					drawRect.bottom = rect.bottom;
+				}
+			} else {
+				drawRect.right = 0;
+				drawRect.bottom = 0;
+				drawRect.left = 0;
+				drawRect.top = 0;
 			}
-		}
 
-		for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
-			if (pictureIt->object == planeObject) {
-				delete[] pictureIt->pictureCels;
-				pictureIt->pictureCels = 0;
+			fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
+
+			int eax = ebx;
+			do {
+				eax >>= 1;
+				if (ebx & 1) {
+					ebx = showStyle->dissolveSeed;
+					ebx ^= eax;
+				} else {
+					ebx = eax;
+				}
+			} while (ebx >= divisions);
+
+			if (showStyle->currentStep != showStyle->divisions) {
+				ebp++;
+			} else {
+				drawRect.left = 0;
+				drawRect.top = 0;
+				drawRect.right = showStyle->width - 1;
+				drawRect.bottom = showStyle->height - 1;
+				fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
 			}
-		}
+
+		} while (ebp <= showStyle->divisions);
+
+		showStyle->unknown3A = ebx;
+		++showStyle->currentStep;
+		showStyle->nextTick += showStyle->delay;
+		// _screen->updateScreenItem(showStyle->bitmapScreenItem);
 	}
 
-	showCurrentScrollText();
+	if (showStyle->currentStep >= showStyle->divisions) {
+		if (showStyle->fadeUp) {
+			showStyle->processed = true;
+		}
 
-	_screen->copyToScreen();
+		return true;
+	}
 
-	g_sci->getEngineState()->_throttleTrigger = true;
+	return false;
 }
 
-void GfxFrameout::printPlaneList(Console *con) {
-	for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
-		PlaneEntry p = *it;
-		Common::String curPlaneName = _segMan->getObjectName(p.object);
-		Common::Rect r = p.upscaledPlaneRect;
-		Common::Rect cr = p.upscaledPlaneClipRect;
+bool GfxFrameout::processShowStyle12(ShowStyleEntry *const showStyle) {
+	return true;
+}
+#endif
+
+void GfxFrameout::kernelFrameout(const bool shouldShowBits) {
+	if (_showStyles != nullptr) {
+		processShowStyles();
+	} else if (_palMorphIsOn) {
+		palMorphFrameOut(_styleRanges, nullptr);
+		_palMorphIsOn = false;
+	} else {
+// TODO: Window scroll
+//		if (g_ScrollWindow) {
+//			doScroll();
+//		}
 
-		con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n",
-							PRINT_REG(p.object), curPlaneName.c_str(),
-							(int16)p.priority, (int16)p.lastPriority,
-							p.planeOffsetX, p.planeOffsetY, p.pictureId,
-							p.planePictureMirrored, p.planeBack);
-		con->debugPrintf("  rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n",
-							r.left, r.top, r.right, r.bottom,
-							cr.left, cr.top, cr.right, cr.bottom);
+		Common::Rect frameOutRect(0, 0);
+		frameOut(shouldShowBits, frameOutRect);
+	}
+}
 
-		if (p.pictureId != 0xffff && p.pictureId != 0xfffe) {
-			con->debugPrintf("Pictures:\n");
+#pragma mark -
+#pragma mark Debugging
 
-			for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
-				if (pictureIt->object == p.object) {
-					con->debugPrintf("    Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY);
-				}
-			}
-		}
+void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const {
+	for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) {
+		Plane *p = *it;
+		p->printDebugInfo(con);
 	}
 }
 
-void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) {
-	for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
-		FrameoutEntry *e = *listIterator;
-		reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane));
+void GfxFrameout::printPlaneList(Console *con) const {
+	printPlaneListInternal(con, _planes);
+}
+
+void GfxFrameout::printVisiblePlaneList(Console *con) const {
+	printPlaneListInternal(con, _visiblePlanes);
+}
 
-		if (planeObject == itemPlane) {
-			Common::String curItemName = _segMan->getObjectName(e->object);
-			Common::Rect icr = e->celRect;
-			GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0;
+void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
+	Plane *p = _planes.findByObject(planeObject);
 
-			con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, "
-							 "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), "
-							 "pic %d, picX %d, picY %d, visible %d\n",
-							 e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(),
-							 e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z,
-							 e->signal, e->scaleSignal, e->scaleX, e->scaleY,
-							 icr.left, icr.top, icr.right, icr.bottom,
-							 picId, e->picStartX, e->picStartY, e->visible);
-		}
+	if (p == nullptr) {
+		con->debugPrintf("Plane does not exist");
+		return;
+	}
+
+	ScreenItemList::size_type i = 0;
+	for (ScreenItemList::iterator sit = p->_screenItemList.begin(); sit != p->_screenItemList.end(); sit++) {
+		ScreenItem *screenItem = *sit;
+		con->debugPrintf("%2d: ", i++);
+		screenItem->printDebugInfo(con);
 	}
 }
 
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index d1a706e..5323a2a 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -23,86 +23,190 @@
 #ifndef SCI_GRAPHICS_FRAMEOUT_H
 #define SCI_GRAPHICS_FRAMEOUT_H
 
-namespace Sci {
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
 
-class GfxPicture;
+namespace Sci {
+// TODO: Don't do this this way
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
 
-struct PlaneLineEntry {
-	reg_t hunkId;
-	Common::Point startPoint;
-	Common::Point endPoint;
-	byte color;
-	byte priority;
-	byte control;
+// TODO: Verify display styles and adjust names appropriately for
+// types 1 through 12 & 15 (others are correct)
+// Names should be:
+// * VShutterIn, VShutterOut
+// * HShutterIn, HShutterOut
+// * WipeLeft, WipeRight, WipeDown, WipeUp
+// * PixelDissolve
+// * ShutDown and Kill? (and Plain and Fade?)
+enum ShowStyleType /* : uint8 */ {
+	kShowStyleNone = 0,
+	kShowStyleHShutterOut = 1,
+	kShowStyleHShutterIn = 2,
+	kShowStyleVShutterOut = 3,
+	kShowStyleVShutterIn = 4,
+	kShowStyleWipeLeft = 5,
+	kShowStyleWipeRight = 6,
+	kShowStyleWipeUp = 7,
+	kShowStyleWipeDown = 8,
+	kShowStyleIrisOut = 9,
+	kShowStyleIrisIn = 10,
+	kShowStyle11 = 11,
+	kShowStyle12 = 12,
+	kShowStyleFadeOut = 13,
+	kShowStyleFadeIn = 14,
+	// TODO: Only in SCI3
+	kShowStyleUnknown = 15
 };
 
-typedef Common::List<PlaneLineEntry> PlaneLineList;
-
-struct PlaneEntry {
-	reg_t object;
-	int16 priority;
-	int16 lastPriority;
-	int16 planeOffsetX;
-	int16 planeOffsetY;
-	GuiResourceId pictureId;
-	Common::Rect planeRect;
-	Common::Rect planeClipRect;
-	Common::Rect upscaledPlaneRect;
-	Common::Rect upscaledPlaneClipRect;
-	bool planePictureMirrored;
-	byte planeBack;
-	PlaneLineList lines;
-};
+/**
+ * Show styles represent transitions applied to draw planes.
+ * One show style per plane can be active at a time.
+ */
+struct ShowStyleEntry {
+	/**
+	 * The ID of the plane this show style belongs to.
+	 * In SCI2.1mid (at least SQ6), per-plane transitions
+	 * were removed and a single plane ID is used.
+	 */
+	reg_t plane;
 
-typedef Common::List<PlaneEntry> PlaneList;
-
-struct FrameoutEntry {
-	uint16 givenOrderNr;
-	reg_t object;
-	GuiResourceId viewId;
-	int16 loopNo;
-	int16 celNo;
-	int16 x, y, z;
-	int16 priority;
-	uint16 signal;
-	uint16 scaleSignal;
-	int16 scaleX;
-	int16 scaleY;
-	Common::Rect celRect;
-	GfxPicture *picture;
-	int16 picStartX;
-	int16 picStartY;
-	bool visible;
-};
+	/**
+	 * The type of the transition.
+	 */
+	ShowStyleType type;
 
-typedef Common::List<FrameoutEntry *> FrameoutList;
+	// TODO: This name is probably incorrect
+	bool fadeUp;
 
-struct PlanePictureEntry {
-	reg_t object;
-	int16 startX;
-	int16 startY;
-	GuiResourceId pictureId;
-	GfxPicture *picture;
-	FrameoutEntry *pictureCels; // temporary
-};
+	/**
+	 * The number of steps for the show style.
+	 */
+	int16 divisions;
 
-typedef Common::List<PlanePictureEntry> PlanePictureList;
+	// NOTE: This property exists from SCI2 through at least
+	// SCI2.1mid but is never used in the actual processing
+	// of the styles?
+	int unknownC;
 
-struct ScrollTextEntry {
-	reg_t bitmapHandle;
-	reg_t kWindow;
-	uint16 x;
-	uint16 y;
-};
+	/**
+	 * The colour used by transitions that draw CelObjColor
+	 * screen items. -1 for transitions that do not draw
+	 * screen items.
+	 */
+	int16 color;
+
+	// TODO: Probably uint32
+	// TODO: This field probably should be used in order to
+	// provide time-accurate processing of show styles. In the
+	// actual SCI engine (at least 2–2.1mid) it appears that
+	// style transitions are drawn “as fast as possible”, one
+	// step per loop, even though this delay field exists
+	int delay;
+
+	// TODO: Probably bool, but never seems to be true?
+	int animate;
+
+	/**
+	 * The wall time at which the next step of the animation
+	 * should execute.
+	 */
+	uint32 nextTick;
+
+	/**
+	 * During playback of the show style, the current step
+	 * (out of divisions).
+	 */
+	int currentStep;
+
+	/**
+	 * The next show style.
+	 */
+	ShowStyleEntry *next;
+
+	/**
+	 * Whether or not this style has finished running and
+	 * is ready for disposal.
+	 */
+	bool processed;
+
+	//
+	// Engine-specific properties for SCI2 through 2.1early
+	//
+
+	// TODO: Could union this stuff to save literally
+	// several bytes of memory.
+
+	/**
+	 * The width of the plane. Used to determine the correct
+	 * size of screen items for wipes.
+	 */
+	int width;
+
+	/**
+	 * The height of the plane. Used to determine the correct
+	 * size of screen items for wipes.
+	 */
+	int height;
+
+	/**
+	 * The number of edges that a transition operates on.
+	 * Slide wipe: 1 edge
+	 * Reveal wipe: 2 edges
+	 * Iris wipe: 4 edges
+	 */
+	// TODO: I have no idea why SCI engine stores this instead
+	// of a screenItems count
+	int edgeCount;
+
+	/**
+	 * Used by transition types 1 through 10.
+	 * One screen item per division per edge.
+	 */
+	ScreenItemList screenItems;
+
+	/**
+	 * Used by transition types 11 and 12. A copy of the
+	 * visible frame buffer.
+	 */
+	// TODO: This is a reg_t in SCI engine; not sure if
+	// we can avoid allocation through SegMan or not.
+	reg_t bitmapMemId;
+
+	/**
+	 * Used by transition types 11 and 12. A screen item
+	 * used to display the associated bitmap data.
+	 */
+	ScreenItem *bitmapScreenItem;
 
-typedef Common::Array<ScrollTextEntry> ScrollTextList;
+	/**
+	 * A number used to pick pixels to dissolve by types
+	 * 11 and 12.
+	 */
+	int dissolveSeed;
+	int unknown3A;
+	// max?
+	int dissolveInitial;
 
-enum ViewScaleSignals32 {
-	kScaleSignalDoScaling32				= 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
-	kScaleSignalUnk1					= 0x0002, // unknown
-	kScaleSignalDisableGlobalScaling32	= 0x0004
+	//
+	// Engine specific properties for SCI2.1mid through SCI3
+	//
+
+	/**
+	 * The number of entries in the fadeColorRanges array.
+	 */
+	uint8 fadeColorRangesCount;
+
+	/**
+	 * A pointer to an dynamically sized array of palette
+	 * indexes, in the order [ fromColor, toColor, ... ].
+	 * Only colors within this range are transitioned.
+	 */
+	uint16 *fadeColorRanges;
 };
 
+typedef Common::Array<DrawList> ScreenItemListList;
+typedef Common::Array<RectList> EraseListList;
+
 class GfxCache;
 class GfxCoordAdjuster32;
 class GfxPaint32;
@@ -114,69 +218,285 @@ class GfxScreen;
  * Roughly equivalent to GraphicsMgr in the actual SCI engine.
  */
 class GfxFrameout {
+private:
+	bool _isHiRes;
+	GfxCache *_cache;
+	GfxCoordAdjuster32 *_coordAdjuster;
+	GfxPalette32 *_palette;
+	ResourceManager *_resMan;
+	GfxScreen *_screen;
+	SegManager *_segMan;
+	GfxPaint32 *_paint32;
+
 public:
 	GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
 	~GfxFrameout();
 
-	void kernelAddPlane(reg_t object);
-	void kernelUpdatePlane(reg_t object);
-	void kernelDeletePlane(reg_t object);
-	void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight);
-	void kernelAddScreenItem(reg_t object);
-	void kernelUpdateScreenItem(reg_t object);
-	void kernelDeleteScreenItem(reg_t object);
-	void deletePlaneItems(reg_t planeObject);
-	FrameoutEntry *findScreenItem(reg_t object);
-	int16 kernelGetHighPlanePri();
-	void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY);
-	void kernelFrameout();
-
-	void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0);
-	void deletePlanePictures(reg_t object);
-	reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
-	void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
-	void deletePlaneLine(reg_t object, reg_t hunkId);
 	void clear();
+	void run();
 
-	// Scroll text functions
-	void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace);
-	void showCurrentScrollText();
-	void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; }
-	void clearScrollTexts();
-	void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; }
-	void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; }
-	void prevScrollText() { if (_curScrollText > 0) _curScrollText--; }
-	void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; }
-	void toggleScrollText(bool show) { _showScrollText = show; }
+#pragma mark -
+#pragma mark Screen items
+private:
+	void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
 
-	void printPlaneList(Console *con);
-	void printPlaneItemList(Console *con, reg_t planeObject);
+public:
+	void kernelAddScreenItem(const reg_t object);
+	void kernelUpdateScreenItem(const reg_t object);
+	void kernelDeleteScreenItem(const reg_t object);
 
+#pragma mark -
+#pragma mark Planes
 private:
-	bool _isHiRes;
+	/**
+	 * The list of planes (i.e. layers) that have been added
+	 * to the screen.
+	 *
+	 * @note This field is on `GraphicsMgr.screen` in SCI
+	 * engine.
+	 */
+	PlaneList _planes;
 
-	void showVideo();
-	void createPlaneItemList(reg_t planeObject, FrameoutList &itemList);
-	bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY);
-	void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored);
+	/**
+	 * Creates and adds a new plane to the plane list, or
+	 * cancels deletion and updates an already-existing
+	 * plane if a plane matching the given plane VM object
+	 * already exists within the current plane list.
+	 *
+	 * @note This method is on Screen in SCI engine, but it
+	 * is only ever called on `GraphicsMgr.screen`.
+	 */
+	void addPlane(Plane &plane);
 
-	SegManager *_segMan;
-	ResourceManager *_resMan;
-	GfxCoordAdjuster32 *_coordAdjuster;
-	GfxCache *_cache;
-	GfxPalette32 *_palette;
-	GfxScreen *_screen;
-	GfxPaint32 *_paint32;
+	/**
+	 * Updates an existing plane with properties from the
+	 * given VM object.
+	 */
+	void updatePlane(Plane &plane);
 
-	FrameoutList _screenItems;
-	PlaneList _planes;
-	PlanePictureList _planePictures;
-	ScrollTextList _scrollTexts;
-	int16 _curScrollText;
-	bool _showScrollText;
-	uint16 _maxScrollTexts;
+public:
+	const PlaneList &getPlanes() const {
+		return _planes;
+	}
+	void kernelAddPlane(const reg_t object);
+	void kernelUpdatePlane(const reg_t object);
+	void kernelDeletePlane(const reg_t object);
+	int16 kernelGetHighPlanePri();
+
+#pragma mark -
+#pragma mark Pics
+public:
+	void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX);
+
+#pragma mark -
+
+	// TODO: Remap-related?
+	void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
+
+#pragma mark -
+#pragma mark Transitions
+private:
+	int *_dissolveSequenceSeeds;
+	int16 *_defaultDivisions;
+	int16 *_defaultUnknownC;
+
+	/**
+	 * TODO: Documentation
+	 */
+	ShowStyleEntry *_showStyles;
+
+	inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const;
+	inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle);
+	void processShowStyles();
+	bool processShowStyleNone(ShowStyleEntry *showStyle);
+	bool processShowStyleMorph(ShowStyleEntry *showStyle);
+	bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle);
+#if 0
+	bool processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle);
+#endif
+
+public:
+	// NOTE: This signature is taken from SCI3 Phantasmagoria 2
+	// and is valid for all implementations of SCI32
+	void kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen);
+
+#pragma mark -
+#pragma mark Rendering
+private:
+	/**
+	 * TODO: Documentation
+	 */
+	int8 _styleRanges[256];
+
+	/**
+	 * The internal display pixel buffer. During frameOut,
+	 * this buffer is drawn into according to the draw and
+	 * erase rects calculated by `calcLists`, then drawn out
+	 * to the hardware surface according to the `_showList`
+	 * rects (which are also calculated by `calcLists`).
+	 */
+	Buffer _currentBuffer;
+
+	// TODO: In SCI2.1/SQ6, priority map pixels are not allocated
+	// by default. In SCI2/GK1, pixels are allocated, but not used
+	// anywhere except within CelObj::Draw in seemingly the same
+	// way they are used in SCI2.1/SQ6: that is, never read, only
+	// written.
+	Buffer _priorityMap;
+
+	/**
+	 * TODO: Documentation
+	 */
+	bool _remapOccurred;
 
-	void sortPlanes();
+	/**
+	 * Whether or not the data in the current buffer is what
+	 * is visible to the user. During rendering updates,
+	 * this flag is set to false.
+	 */
+	bool _frameNowVisible;
+
+	/**
+	 * TODO: Document
+	 * TODO: Depending upon if the engine ever modifies this
+	 * rect, it may be stupid to store it separately instead
+	 * of just getting width/height from GfxScreen.
+	 *
+	 * @note This field is on `GraphicsMgr.screen` in SCI
+	 * engine.
+	 */
+	Common::Rect _screenRect;
+
+	/**
+	 * A list of rectangles, in display coordinates, that
+	 * represent portions of the internal screen buffer that
+	 * should be drawn to the hardware display surface.
+	 *
+	 * @note This field is on `GraphicsMgr.screen` in SCI
+	 * engine.
+	 */
+	RectList _showList;
+
+	/**
+	 * The amount of extra overdraw that is acceptable when
+	 * merging two show list rectangles together into a
+	 * single larger rectangle.
+	 *
+	 * @note This field is on `GraphicsMgr.screen` in SCI
+	 * engine.
+	 */
+	int _overdrawThreshold;
+
+	/**
+	 * A list of planes that are currently drawn to the
+	 * hardware display surface. Used to calculate
+	 * differences in plane properties between the last
+	 * frame and current frame.
+	 *
+	 * @note This field is on `GraphicsMgr.visibleScreen` in
+	 * SCI engine.
+	 */
+	PlaneList _visiblePlanes;
+
+	/**
+	 * Calculates the location and dimensions of dirty rects
+	 * over the entire screen for rendering the next frame.
+	 * The draw and erase lists in `drawLists` and
+	 * `eraseLists` each represent one plane on the screen.
+	 */
+	void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect);
+
+	/**
+	 * Erases the areas in the given erase list from the
+	 * visible screen buffer by filling them with the color
+	 * from the corresponding plane. This is an optimisation
+	 * for coloured-type planes only; other plane types have
+	 * to be redrawn from pixel data.
+	 */
+	void drawEraseList(const RectList &eraseList, const Plane &plane);
+
+	/**
+	 * Draws all screen items from the given draw list to
+	 * the visible screen buffer.
+	 */
+	void drawScreenItemList(const DrawList &screenItemList);
+
+	/**
+	 * Updates the internal screen buffer for the next
+	 * frame. If `shouldShowBits` is true, also sends the
+	 * buffer to hardware.
+	 */
+	void frameOut(const bool shouldShowBits, const Common::Rect &rect);
+
+	/**
+	 * Adds a new rectangle to the list of regions to write
+	 * out to the hardware. The provided rect may be merged
+	 * into an existing rectangle to reduce the number of
+	 * blit operations.
+	 */
+	void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold);
+
+	/**
+	 * TODO: Documentation
+	 */
+	void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle);
+
+	/**
+	 * Writes the internal frame buffer out to hardware and
+	 * clears the show list.
+	 */
+	void showBits();
+
+public:
+	/**
+	 * TODO: Document
+	 * This is used by CelObj::Draw.
+	 */
+	bool _hasRemappedScreenItem;
+
+	/**
+	 * Whether palMorphFrameOut should be used instead of
+	 * frameOut for rendering. Used by kMorphOn to
+	 * explicitly enable palMorphFrameOut for one frame.
+	 */
+	bool _palMorphIsOn;
+
+	inline Buffer &getCurrentBuffer() {
+		return _currentBuffer;
+	}
+
+	void kernelFrameout(const bool showBits);
+
+	/**
+	 * Modifies the raw pixel data for the next frame with
+	 * new palette indexes based on matched style ranges.
+	 */
+	void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges);
+
+	// TODO: SCI2 engine never uses priority map?
+	inline Buffer &getPriorityMap() {
+		return _priorityMap;
+	}
+
+	// NOTE: This function is used within ScreenItem subsystem and assigned
+	// to various booleanish fields that seem to represent the state of the
+	// screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k,
+	// SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If
+	// you know of any game/environment where this function returns some
+	// value other than 1, or if you used to work at Sierra and can explain
+	// why this is a thing (and if anyone needs to care about it), please
+	// open a ticket!!
+	inline int getScreenCount() const {
+		return 1;
+	};
+
+#pragma mark -
+#pragma mark Debugging
+public:
+	void printPlaneList(Console *con) const;
+	void printVisiblePlaneList(Console *con) const;
+	void printPlaneListInternal(Console *con, const PlaneList &planeList) const;
+	void printPlaneItemList(Console *con, const reg_t planeObject) const;
 };
 
 } // End of namespace Sci
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index e5b9f2a..c48ad4c 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -26,6 +26,11 @@
 #include "common/endian.h"	// for READ_LE_UINT16
 #include "common/rect.h"
 #include "common/serializer.h"
+#ifdef ENABLE_SCI32
+#include "common/rational.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#endif
 #include "sci/engine/vm_types.h"
 
 namespace Sci {
@@ -45,6 +50,9 @@ typedef int16 TextAlignment;
 #define PORTS_FIRSTWINDOWID 2
 #define PORTS_FIRSTSCRIPTWINDOWID 3
 
+#ifdef ENABLE_SCI32
+#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom
+#endif
 
 struct Port {
 	uint16 id;
@@ -118,6 +126,79 @@ struct Window : public Port, public Common::Serializable {
 	}
 };
 
+#ifdef ENABLE_SCI32
+/**
+ * Multiplies a number by a rational number, rounding up to
+ * the nearest whole number.
+ */
+inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
+	int num = (value + extra) * ratio.getNumerator();
+	int result = num / ratio.getDenominator();
+	if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
+		++result;
+	}
+	return result - extra;
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * point directly.
+ */
+inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+	point.x = mulru(point.x, ratioX);
+	point.y = mulru(point.y, ratioY);
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * rect directly.
+ */
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int brExtra = 0) {
+	rect.left = mulru(rect.left, ratioX);
+	rect.top = mulru(rect.top, ratioY);
+	rect.right = mulru(rect.right, ratioX, brExtra);
+	rect.bottom = mulru(rect.bottom, ratioY, brExtra);
+}
+
+struct Buffer : public Graphics::Surface {
+	uint16 screenWidth;
+	uint16 screenHeight;
+	uint16 scriptWidth;
+	uint16 scriptHeight;
+
+	Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
+		screenWidth(width),
+		screenHeight(height),
+		// TODO: These values are not correct for all games. Script
+		// dimensions were hard-coded per game in the original
+		// interpreter. Search all games for their internal script
+		// dimensions and set appropriately. (This code does not
+		// appear to exist at all in SCI3, which uses 640x480.)
+		scriptWidth(320),
+		scriptHeight(200) {
+		init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8());
+	}
+
+	void clear(const uint8 value) {
+		memset(pixels, value, w * h);
+	}
+
+	inline uint8 *getAddress(const uint16 x, const uint16 y) {
+		return (uint8 *)getBasePtr(x, y);
+	}
+
+	inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) {
+		return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth);
+	}
+
+	bool isNull() {
+		return pixels == nullptr;
+	}
+};
+#endif
+
 struct Color {
 	byte used;
 	byte r, g, b;
diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h
new file mode 100644
index 0000000..b12cd55
--- /dev/null
+++ b/engines/sci/graphics/lists32.h
@@ -0,0 +1,192 @@
+/* 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 SCI_GRAPHICS_LISTS32_H
+#define SCI_GRAPHICS_LISTS32_H
+
+#include "common/array.h"
+
+namespace Sci {
+
+/**
+ * StablePointerArray holds pointers in a fixed-size array
+ * that maintains position of erased items until `pack` is
+ * called. It is used by DrawList, RectList, and
+ * ScreenItemList. StablePointerArray takes ownership of
+ * all pointers that are passed to it and deletes them when
+ * calling `erase` or when destroying the
+ * StablePointerArray.
+ */
+template <class T, uint N>
+class StablePointerArray {
+	uint _size;
+	T *_items[N];
+
+public:
+	typedef T **iterator;
+	typedef T *const *const_iterator;
+	typedef T *value_type;
+	typedef uint size_type;
+
+	StablePointerArray() : _size(0), _items() {}
+	StablePointerArray(const StablePointerArray &other) : _size(other._size) {
+		for (size_type i = 0; i < _size; ++i) {
+			if (other._items[i] == nullptr) {
+				_items[i] = nullptr;
+			} else {
+				_items[i] = new T(*other._items[i]);
+			}
+		}
+	}
+	~StablePointerArray() {
+		for (size_type i = 0; i < _size; ++i) {
+			delete _items[i];
+		}
+	}
+
+	void operator=(const StablePointerArray &other) {
+		clear();
+		_size = other._size;
+		for (size_type i = 0; i < _size; ++i) {
+			if (other._items[i] == nullptr) {
+				_items[i] = nullptr;
+			} else {
+				_items[i] = new T(*other._items[i]);
+			}
+		}
+	}
+
+	T *const &operator[](size_type index) const {
+		assert(index >= 0 && index < _size);
+		return _items[index];
+	}
+
+	T *&operator[](size_type index) {
+		assert(index >= 0 && index < _size);
+		return _items[index];
+	}
+
+	/**
+	 * Adds a new pointer to the array.
+	 */
+	void add(T *item) {
+		assert(_size < N);
+		_items[_size++] = item;
+	}
+
+	iterator begin() {
+		return _items;
+	}
+
+	const_iterator begin() const {
+		return _items;
+	}
+
+	void clear() {
+		for (size_type i = 0; i < _size; ++i) {
+			delete _items[i];
+			_items[i] = nullptr;
+		}
+
+		_size = 0;
+	}
+
+	iterator end() {
+		return _items + _size;
+	}
+
+	const_iterator end() const {
+		return _items + _size;
+	}
+
+	/**
+	 * Erases the object pointed to by the given iterator.
+	 */
+	void erase(T *item) {
+		for (iterator it = begin(); it != end(); ++it) {
+			if (*it == item) {
+				delete *it;
+				*it = nullptr;
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Erases the object pointed to by the given iterator.
+	 */
+	void erase(iterator &it) {
+		assert(it >= _items && it < _items + _size);
+		delete *it;
+		*it = nullptr;
+	}
+
+	/**
+	 * Erases the object pointed to at the given index.
+	 */
+	void erase_at(size_type index) {
+		assert(index >= 0 && index < _size);
+
+		delete _items[index];
+		_items[index] = nullptr;
+	}
+
+	/**
+	 * Removes freed pointers from the pointer list.
+	 */
+	size_type pack() {
+		iterator freePtr = begin();
+		size_type newSize = 0;
+
+		for (iterator it = begin(), last = end(); it != last; ++it) {
+			if (*it != nullptr) {
+				*freePtr = *it;
+				++freePtr;
+				++newSize;
+			}
+		}
+
+		_size = newSize;
+		return newSize;
+	}
+
+	/**
+	 * The number of populated slots in the array. The size
+	 * of the array will only go down once `pack` is called.
+	 */
+	size_type size() const {
+		return _size;
+	}
+};
+
+template <typename T>
+class FindByObject {
+	const reg_t &_object;
+public:
+	FindByObject(const reg_t &object) : _object(object) {}
+	bool operator()(const T entry) const {
+		return entry->_object == _object;
+	}
+};
+
+}
+#endif
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index e61ac5d..40e4021 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -47,11 +47,10 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
 	_version(1), _versionUpdated(false) {
 		_varyPercent = _varyTargetPercent;
 		memset(_fadeTable, 100, sizeof(_fadeTable));
-
 		// NOTE: In SCI engine, the palette manager constructor loads
 		// the default palette, but in ScummVM this initialisation
 		// is performed by SciEngine::run; see r49523 for details
-	}
+}
 
 GfxPalette32::~GfxPalette32() {
 	unloadClut();
@@ -67,6 +66,10 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
 	}
 }
 
+const Palette *GfxPalette32::getNextPalette() const {
+	return &_nextPalette;
+}
+
 void GfxPalette32::submit(Palette &palette) {
 	// TODO: The resource manager in SCI32 retains raw data of palettes from
 	// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
@@ -206,10 +209,20 @@ int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const i
 	return bestIndex;
 }
 
-void GfxPalette32::updateForFrame() {
+bool GfxPalette32::updateForFrame() {
 	applyAll();
 	_versionUpdated = false;
 	// TODO: Implement remapping
+	// return g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
+	return false;
+}
+
+void GfxPalette32::updateFFrame() {
+	for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
+		_nextPalette.colors[i] = _sourcePalette.colors[i];
+	}
+	_versionUpdated = false;
+	// TODO: Implement remapping
 	// g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
 }
 
@@ -410,7 +423,7 @@ void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int
 }
 
 int16 GfxPalette32::getVaryPercent() const {
-	return abs(_varyPercent);
+	return ABS(_varyPercent);
 }
 
 void GfxPalette32::varyOff() {
@@ -773,6 +786,12 @@ void GfxPalette32::applyCycles() {
 // Palette fading
 //
 
+// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
+// setFade with numColorsToFade set to 256, but other parts of the engine like
+// processShowStyleNone use 255 instead of 256. It is not clear if this is because
+// the last palette entry is intentionally left unmodified, or if this is a bug
+// in the engine. It certainly seems confused because all other places that accept
+// colour ranges typically receive values in the range of 0–255.
 void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) {
 	if (fromColor > numColorsToFade) {
 		return;
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index 8744687..9110a8a 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -25,6 +25,7 @@
 
 #include "sci/graphics/palette.h"
 
+namespace Sci {
 enum PalCyclerDirection {
 	PalCycleBackward = 0,
 	PalCycleForward = 1
@@ -71,188 +72,206 @@ struct PalCycler {
 	uint16 numTimesPaused;
 };
 
-namespace Sci {
-	class GfxPalette32 : public GfxPalette {
-	public:
-		GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
-		~GfxPalette32();
-
-	protected:
-		/**
-		 * The palette revision version. Increments once per game
-		 * loop that changes the source palette. TODO: Possibly
-		 * other areas also change _version, double-check once it
-		 * is all implemented.
-		 */
-		uint32 _version;
-
-		/**
-		 * Whether or not the palette manager version was updated
-		 * during this loop.
-		 */
-		bool _versionUpdated;
-
-		/**
-		 * The unmodified source palette loaded by kPalette. Additional
-		 * palette entries may be mixed into the source palette by
-		 * CelObj objects, which contain their own palettes.
-		 */
-		Palette _sourcePalette;
-
-		/**
-		 * The palette to be used when the hardware is next updated.
-		 * On update, _nextPalette is transferred to _sysPalette.
-		 */
-		Palette _nextPalette;
-
-		// SQ6 defines 10 cyclers
-		PalCycler *_cyclers[10];
-
-		/**
-		 * The cycle map is used to detect overlapping cyclers.
-		 * According to SCI engine code, when two cyclers overlap,
-		 * a fatal error has occurred and the engine will display
-		 * an error and then exit.
-		 */
-		bool _cycleMap[256];
-		inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
-		inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
-		inline PalCycler *getCycler(uint16 fromColor);
-
-		/**
-		 * The fade table records the expected intensity level of each pixel
-		 * in the palette that will be displayed on the next frame.
-		 */
-		byte _fadeTable[256];
-
-		/**
-		 * An optional lookup table used to remap RGB565 colors to a palette
-		 * index. Used by Phantasmagoria 2 in 8-bit color environments.
-		 */
-		byte *_clutTable;
-
-		/**
-		 * An optional palette used to describe the source colors used
-		 * in a palette vary operation. If this palette is not specified,
-		 * sourcePalette is used instead.
-		 */
-		Palette *_varyStartPalette;
-
-		/**
-		 * An optional palette used to describe the target colors used
-		 * in a palette vary operation.
-		 */
-		Palette *_varyTargetPalette;
-
-		/**
-		 * The minimum palette index that has been varied from the
-		 * source palette. 0–255
-		 */
-		uint8 _varyFromColor;
-
-		/**
-		 * The maximum palette index that is has been varied from the
-		 * source palette. 0-255
-		 */
-		uint8 _varyToColor;
-
-		/**
-		 * The tick at the last time the palette vary was updated.
-		 */
-		uint32 _varyLastTick;
-
-		/**
-		 * TODO: Document
-		 * The velocity of change in percent?
-		 */
-		int _varyTime;
-
-		/**
-		 * TODO: Better documentation
-		 * The direction of change, -1, 0, or 1.
-		 */
-		int16 _varyDirection;
-
-		/**
-		 * The amount, in percent, that the vary color is currently
-		 * blended into the source color.
-		 */
-		int16 _varyPercent;
-
-		/**
-		 * The target amount that a vary color will be blended into
-		 * the source color.
-		 */
-		int16 _varyTargetPercent;
-
-		/**
-		 * The number of time palette varying has been paused.
-		 */
-		uint16 _varyNumTimesPaused;
-
-		/**
-		 * Submits a palette to display. Entries marked as “used” in the
-		 * submitted palette are merged into the existing entries of
-		 * _sourcePalette.
-		 */
-		void submit(Palette &palette);
-
-	public:
-		virtual void saveLoadWithSerializer(Common::Serializer &s) override;
-
-		bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
-		int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
-		void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
-		int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
-
-		void updateForFrame();
-		void updateHardware();
-		void applyAll();
-
-		bool loadClut(uint16 clutId);
-		byte matchClutColor(uint16 color);
-		void unloadClut();
-
-		void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
-		void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
-		void kernelPalVarySetTarget(const GuiResourceId paletteId);
-		void kernelPalVarySetStart(const GuiResourceId paletteId);
-		void kernelPalVaryMergeStart(const GuiResourceId paletteId);
-		virtual void kernelPalVaryPause(bool pause) override;
-
-		void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
-		void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
-		int16 getVaryPercent() const;
-		void varyOff();
-		void mergeTarget(const Palette *const palette);
-		void varyPause();
-		void varyOn();
-		void setVaryTime(const int time);
-		void setTarget(const Palette *const palette);
-		void setStart(const Palette *const palette);
-		void mergeStart(const Palette *const palette);
-	private:
-		bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
-		Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
-		void setVaryTimeInternal(const int16 percent, const int time);
-	public:
-		void applyVary();
-
-		void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
-		void doCycle(const uint8 fromColor, const int16 speed);
-		void cycleOn(const uint8 fromColor);
-		void cyclePause(const uint8 fromColor);
-		void cycleAllOn();
-		void cycleAllPause();
-		void cycleOff(const uint8 fromColor);
-		void cycleAllOff();
-		void applyAllCycles();
-		void applyCycles();
-
-		void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
-		void fadeOff();
-		void applyFade();
-	};
+class GfxPalette32 : public GfxPalette {
+public:
+	GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
+	~GfxPalette32();
+
+private:
+	// NOTE: currentPalette in SCI engine is called _sysPalette
+	// here.
+
+	/**
+	 * The palette revision version. Increments once per game
+	 * loop that changes the source palette. TODO: Possibly
+	 * other areas also change _version, double-check once it
+	 * is all implemented.
+	 */
+	uint32 _version;
+
+	/**
+	 * Whether or not the palette manager version was updated
+	 * during this loop.
+	 */
+	bool _versionUpdated;
+
+	/**
+	 * The unmodified source palette loaded by kPalette. Additional
+	 * palette entries may be mixed into the source palette by
+	 * CelObj objects, which contain their own palettes.
+	 */
+	Palette _sourcePalette;
+
+	/**
+	 * The palette to be used when the hardware is next updated.
+	 * On update, _nextPalette is transferred to _sysPalette.
+	 */
+	Palette _nextPalette;
+
+	bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
+	Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+
+public:
+	virtual void saveLoadWithSerializer(Common::Serializer &s) override;
+	const Palette *getNextPalette() const;
+
+	bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
+	int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
+	void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
+	int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
+
+	/**
+	 * Submits a palette to display. Entries marked as “used” in the
+	 * submitted palette are merged into the existing entries of
+	 * _sourcePalette.
+	 */
+	void submit(Palette &palette);
+
+	bool updateForFrame();
+	void updateFFrame();
+	void updateHardware();
+	void applyAll();
+
+#pragma mark -
+#pragma mark Colour look-up
+private:
+	/**
+	 * An optional lookup table used to remap RGB565 colors to a palette
+	 * index. Used by Phantasmagoria 2 in 8-bit color environments.
+	 */
+	byte *_clutTable;
+
+public:
+	bool loadClut(uint16 clutId);
+	byte matchClutColor(uint16 color);
+	void unloadClut();
+
+#pragma mark -
+#pragma mark Varying
+private:
+	/**
+	 * An optional palette used to describe the source colors used
+	 * in a palette vary operation. If this palette is not specified,
+	 * sourcePalette is used instead.
+	 */
+	Palette *_varyStartPalette;
+
+	/**
+	 * An optional palette used to describe the target colors used
+	 * in a palette vary operation.
+	 */
+	Palette *_varyTargetPalette;
+
+	/**
+	 * The minimum palette index that has been varied from the
+	 * source palette. 0–255
+	 */
+	uint8 _varyFromColor;
+
+	/**
+	 * The maximum palette index that is has been varied from the
+	 * source palette. 0-255
+	 */
+	uint8 _varyToColor;
+
+	/**
+	 * The tick at the last time the palette vary was updated.
+	 */
+	uint32 _varyLastTick;
+
+	/**
+	 * The amount of time to elapse, in ticks, between each cycle
+	 * of a palette vary animation.
+	 */
+	int _varyTime;
+
+	/**
+	 * The direction of change: -1, 0, or 1.
+	 */
+	int16 _varyDirection;
+
+	/**
+	 * The amount, in percent, that the vary color is currently
+	 * blended into the source color.
+	 */
+	int16 _varyPercent;
+
+	/**
+	 * The target amount that a vary color will be blended into
+	 * the source color.
+	 */
+	int16 _varyTargetPercent;
+
+	/**
+	 * The number of time palette varying has been paused.
+	 */
+	uint16 _varyNumTimesPaused;
+
+public:
+	void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+	void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+	void kernelPalVarySetTarget(const GuiResourceId paletteId);
+	void kernelPalVarySetStart(const GuiResourceId paletteId);
+	void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+	virtual void kernelPalVaryPause(bool pause) override;
+
+	void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+	void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
+	int16 getVaryPercent() const;
+	void varyOff();
+	void mergeTarget(const Palette *const palette);
+	void varyPause();
+	void varyOn();
+	void setVaryTime(const int time);
+	void setTarget(const Palette *const palette);
+	void setStart(const Palette *const palette);
+	void mergeStart(const Palette *const palette);
+	void setVaryTimeInternal(const int16 percent, const int time);
+	void applyVary();
+
+#pragma mark -
+#pragma mark Cycling
+private:
+	// SQ6 defines 10 cyclers
+	PalCycler *_cyclers[10];
+
+	/**
+	 * The cycle map is used to detect overlapping cyclers.
+	 * According to SCI engine code, when two cyclers overlap,
+	 * a fatal error has occurred and the engine will display
+	 * an error and then exit.
+	 */
+	bool _cycleMap[256];
+	inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
+	inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
+	inline PalCycler *getCycler(uint16 fromColor);
+
+public:
+	void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+	void doCycle(const uint8 fromColor, const int16 speed);
+	void cycleOn(const uint8 fromColor);
+	void cyclePause(const uint8 fromColor);
+	void cycleAllOn();
+	void cycleAllPause();
+	void cycleOff(const uint8 fromColor);
+	void cycleAllOff();
+	void applyAllCycles();
+	void applyCycles();
+
+#pragma mark -
+#pragma mark Fading
+private:
+	/**
+	 * The fade table records the expected intensity level of each pixel
+	 * in the palette that will be displayed on the next frame.
+	 */
+	byte _fadeTable[256];
+
+public:
+	void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
+	void fadeOff();
+	void applyFade();
+};
 }
 
 #endif
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index d7ef84d..2eab391 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu
 	}
 
 	// Header
-	// [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD]
+	// 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD]
 	// cel-header follow afterwards, each is 42 bytes
 	// Cel-Header
-	// [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE]
+	// 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE]
 	//  offset 10-23 is unknown
-	// [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD]
+	// 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD]
 
 	cel_headerPos += 42 * celNo;
 
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 2404f99..942fa0f 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,6 +38,9 @@ enum {
 class GfxPorts;
 class GfxScreen;
 class GfxPalette;
+class GfxCoordAdjuster;
+class ResourceManager;
+class Resource;
 
 /**
  * Picture class, handles loading and displaying of picture resources
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
new file mode 100644
index 0000000..548e7e9
--- /dev/null
+++ b/engines/sci/graphics/plane32.cpp
@@ -0,0 +1,841 @@
+/* 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 "sci/console.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+#pragma mark DrawList
+void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
+	DrawItem *drawItem = new DrawItem;
+	drawItem->screenItem = screenItem;
+	drawItem->rect = rect;
+	DrawListBase::add(drawItem);
+}
+
+#pragma mark -
+#pragma mark Plane
+uint16 Plane::_nextObjectId = 20000;
+
+Plane::Plane(const Common::Rect &gameRect) :
+_gameRect(gameRect),
+_object(make_reg(0, _nextObjectId++)),
+_back(0),
+_pictureId(kPlanePicColored),
+_mirrored(false),
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_deleted(0),
+_updated(0),
+_priorityChanged(0),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()) {
+	convertGameRectToPlaneRect();
+	_priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1);
+	setType();
+	_screenRect = _planeRect;
+}
+
+Plane::Plane(reg_t object) :
+_object(object),
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_deleted(0),
+_updated(0),
+_priorityChanged(false),
+_moved(0) {
+	SegManager *segMan = g_sci->getEngineState()->_segMan;
+	_vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+	_vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+
+	_gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+	_gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+	convertGameRectToPlaneRect();
+
+	_back = readSelectorValue(segMan, object, SELECTOR(back));
+	_priority = readSelectorValue(segMan, object, SELECTOR(priority));
+	_pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+	setType();
+
+	_mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+	_screenRect = _planeRect;
+	changePic();
+}
+
+Plane::Plane(const Plane &other) :
+_object(other._object),
+_priority(other._priority),
+_pictureId(other._pictureId),
+_mirrored(other._mirrored),
+_back(other._back),
+_field_34(other._field_34), _field_38(other._field_38),
+_field_3C(other._field_3C), _field_40(other._field_40),
+_planeRect(other._planeRect),
+_gameRect(other._gameRect),
+_screenRect(other._screenRect),
+_screenItemList(other._screenItemList) {}
+
+void Plane::operator=(const Plane &other) {
+	_gameRect = other._gameRect;
+	_planeRect = other._planeRect;
+	_vanishingPoint = other._vanishingPoint;
+	_pictureId = other._pictureId;
+	_type = other._type;
+	_mirrored = other._mirrored;
+	_priority = other._priority;
+	_back = other._back;
+	_width = other._width;
+	_field_34 = other._field_34;
+	_height = other._height;
+	_screenRect = other._screenRect;
+	_field_3C = other._field_3C;
+	_priorityChanged = other._priorityChanged;
+}
+
+void Plane::init() {
+	_nextObjectId = 20000;
+}
+
+void Plane::convertGameRectToPlaneRect() {
+	const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+	const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+	const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+	const Ratio ratioX = Ratio(screenWidth, scriptWidth);
+	const Ratio ratioY = Ratio(screenHeight, scriptHeight);
+
+	_planeRect = _gameRect;
+	mulru(_planeRect, ratioX, ratioY, 1);
+}
+
+void Plane::printDebugInfo(Console *con) const {
+	Common::String name;
+
+	if (_object.isNumber()) {
+		name = "-scummvm-";
+	} else {
+		name = g_sci->getEngineState()->_segMan->getObjectName(_object);
+	}
+
+	con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n",
+		PRINT_REG(_object),
+		name.c_str(),
+		_type,
+		_priority,
+		_pictureId,
+		_mirrored,
+		_back
+	);
+	con->debugPrintf("  game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n  screen rect: (%d, %d, %d, %d)\n",
+		PRINT_RECT(_gameRect),
+		PRINT_RECT(_planeRect),
+		PRINT_RECT(_screenRect)
+	);
+	con->debugPrintf("  # screen items: %d\n", _screenItemList.size());
+}
+
+#pragma mark -
+#pragma mark Plane - Pic
+
+void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
+
+	uint16 celCount = 1000;
+	for (uint16 celNo = 0; celNo < celCount; ++celNo) {
+		CelObjPic *celObj = new CelObjPic(pictureId, celNo);
+		if (celCount == 1000) {
+			celCount = celObj->_celCount;
+		}
+
+		ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
+		screenItem->_pictureId = pictureId;
+		screenItem->_mirrorX = mirrorX;
+		screenItem->_priority = celObj->_priority;
+		screenItem->_fixPriority = true;
+		if (position != nullptr) {
+			screenItem->_position = *position;
+		} else {
+			screenItem->_position = celObj->_relativePosition;
+		}
+		_screenItemList.add(screenItem);
+
+		delete screenItem->_celObj;
+		screenItem->_celObj = celObj;
+	}
+}
+
+void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
+	deletePic(pictureId);
+	addPicInternal(pictureId, &position, mirrorX);
+	// NOTE: In SCI engine this method returned the pictureId of the
+	// plane, but this return value was never used
+}
+
+void Plane::changePic() {
+	_pictureChanged = false;
+
+	if (_type != kPlaneTypePicture) {
+		return;
+	}
+
+	addPicInternal(_pictureId, nullptr, _mirrored);
+}
+
+void Plane::deletePic(const GuiResourceId pictureId) {
+	for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+		ScreenItem *screenItem = *it;
+		if (screenItem->_pictureId == pictureId) {
+			screenItem->_created = 0;
+			screenItem->_updated = 0;
+			screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+		}
+	}
+}
+
+void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) {
+	deletePic(oldPictureId);
+	_pictureId = newPictureId;
+}
+
+void Plane::deleteAllPics() {
+	for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+		ScreenItem *screenItem = *it;
+		if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) {
+			if (screenItem->_created == 0) {
+				screenItem->_created = 0;
+				screenItem->_updated = 0;
+				screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+			} else {
+				_screenItemList.erase(it);
+			}
+		}
+	}
+
+	_screenItemList.pack();
+}
+
+#pragma mark -
+#pragma mark Plane - Rendering
+
+void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
+	int index = planeList.findIndexByObject(_object);
+
+	for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+		for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+			if (planeList[j]->_type != kPlaneTypeTransparent) {
+				Common::Rect ptr[4];
+				int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
+				if (count != -1) {
+					for (int k = count - 1; k >= 0; --k) {
+						drawList.add(drawList[i]->screenItem, ptr[k]);
+					}
+
+					drawList.erase_at(i);
+					break;
+				}
+			}
+		}
+	}
+	drawList.pack();
+}
+
+void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
+	int index = planeList.findIndexByObject(_object);
+
+	for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+		for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+			if (planeList[j]->_type != kPlaneTypeTransparent) {
+				Common::Rect ptr[4];
+
+				int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
+				if (count != -1) {
+					for (int k = count - 1; k >= 0; --k) {
+						eraseList.add(ptr[k]);
+					}
+
+					eraseList.erase_at(i);
+					break;
+				}
+			}
+		}
+	}
+	eraseList.pack();
+}
+
+void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+	ScreenItemList::size_type planeItemCount = _screenItemList.size();
+	ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+	for (PlaneList::size_type i = 0; i < planeItemCount; ++i) {
+		ScreenItem *vitem = nullptr;
+		// NOTE: The original engine used an array without bounds checking
+		// so could just get the visible screen item directly; we need to
+		// verify that the index is actually within the valid range for
+		// the visible plane before accessing the item to avoid a range
+		// error.
+		if (i < visiblePlaneItemCount) {
+			vitem = visiblePlane._screenItemList[i];
+		}
+		ScreenItem *item = _screenItemList[i];
+
+		if (i < _screenItemList.size() && item != nullptr) {
+			if (item->_deleted) {
+				// add item's rect to erase list
+				if (i < visiblePlane._screenItemList.size() && vitem != nullptr) {
+					if (!vitem->_screenRect.isEmpty()) {
+						if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
+							mergeToRectList(vitem->_screenRect, eraseList);
+						} else {
+							eraseList.add(vitem->_screenRect);
+						}
+					}
+				}
+			} else if (item->_created) {
+				// add item to draw list
+				item->getCelObj();
+				item->calcRects(*this);
+
+				if(!item->_screenRect.isEmpty()) {
+					if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
+						drawList.add(item, item->_screenRect);
+						mergeToRectList(item->_screenRect, eraseList);
+					} else {
+						drawList.add(item, item->_screenRect);
+					}
+				}
+			} else if (item->_updated) {
+				// add old rect to erase list, new item to draw list
+				item->getCelObj();
+				item->calcRects(*this);
+				if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps
+					// if item and vitem don't overlap, ...
+					if (item->_screenRect.isEmpty() ||
+						i >= visiblePlaneItemCount ||
+						vitem == nullptr ||
+						vitem->_screenRect.isEmpty() ||
+						!vitem->_screenRect.intersects(item->_screenRect)
+					) {
+						// add item to draw list, and old rect to erase list
+						if (!item->_screenRect.isEmpty()) {
+							drawList.add(item, item->_screenRect);
+							mergeToRectList(item->_screenRect, eraseList);
+						}
+						if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
+							mergeToRectList(vitem->_screenRect, eraseList);
+						}
+					} else {
+						// otherwise, add bounding box of old+new to erase list,
+						// and item to draw list
+
+						// TODO: This was changed from disasm, verify please!
+						Common::Rect extendedScreenItem = vitem->_screenRect;
+						extendedScreenItem.extend(item->_screenRect);
+						drawList.add(item, item->_screenRect);
+						mergeToRectList(extendedScreenItem, eraseList);
+					}
+				} else {
+					// if no active remaps, just add item to draw list and old rect
+					// to erase list
+					if (!item->_screenRect.isEmpty()) {
+						drawList.add(item, item->_screenRect);
+					}
+					if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
+						eraseList.add(vitem->_screenRect);
+					}
+				}
+			}
+		}
+	}
+
+	breakEraseListByPlanes(eraseList, planeList);
+	breakDrawListByPlanes(drawList, planeList);
+
+	if (/* TODO: dword_C6288 */ false) {  // "high resolution pictures"????
+		_screenItemList.sort();
+		bool encounteredPic = false;
+		bool v81 = false;
+
+		for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+			Common::Rect *rect = eraseList[i];
+
+			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+				ScreenItem *item = _screenItemList[j];
+
+				if (j < _screenItemList.size() && item != nullptr) {
+					if (rect->intersects(item->_screenRect)) {
+						Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
+						if (!item->_deleted) {
+							if (encounteredPic) {
+								if (item->_celInfo.type == kCelTypePic) {
+									if (v81 || item->_celInfo.celNo == 0) {
+										drawList.add(item, intersection);
+									}
+								} else {
+									if (!item->_updated && !item->_created) {
+										drawList.add(item, intersection);
+									}
+									v81 = true;
+								}
+							} else {
+								if (!item->_updated && !item->_created) {
+									drawList.add(item, intersection);
+								}
+								if (item->_celInfo.type == kCelTypePic) {
+									encounteredPic = true;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+
+		_screenItemList.unsort();
+	} else {
+		// add all items overlapping the erase list to the draw list
+		for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+				ScreenItem *item = _screenItemList[j];
+				if (j < _screenItemList.size() && item != nullptr && !item->_updated && !item->_deleted && !item->_created && eraseList[i]->intersects(item->_screenRect)) {
+					drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+				}
+			}
+		}
+	}
+	if (/* TODO: g_Remap_numActiveRemaps */ false) { // no remaps active?
+		// Add all items that overlap with items in the drawlist and have higher
+		// priority
+		for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+			DrawItem *dli = drawList[i];
+
+			for (PlaneList::size_type j = 0; j < planeItemCount; ++j) {
+				ScreenItem *sli = _screenItemList[j];
+
+				if (i < drawList.size() && dli) {
+					if (j < _screenItemList.size() && sli) {
+						if (!sli->_updated && !sli->_deleted && !sli->_created) {
+							ScreenItem *item = dli->screenItem;
+							if (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) {
+								if (dli->rect.intersects(sli->_screenRect)) {
+									drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	decrementScreenItemArrayCounts(&visiblePlane, false);
+	_screenItemList.pack();
+	visiblePlane._screenItemList.pack();
+}
+
+void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
+	// The size of the screenItemList may change, so it is
+	// critical to re-check the size on each iteration
+	for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+		ScreenItem *item = _screenItemList[i];
+
+		if (item != nullptr) {
+			// update item in visiblePlane if item is updated
+			if (
+				item->_updated ||
+				(
+					forceUpdate &&
+					visiblePlane != nullptr &&
+					Common::find(visiblePlane->_screenItemList.begin(), visiblePlane->_screenItemList.end(), item) != visiblePlane->_screenItemList.end()
+				)
+			) {
+				*visiblePlane->_screenItemList[i] = *_screenItemList[i];
+			}
+
+			if (item->_updated) {
+				item->_updated--;
+			}
+
+			// create new item in visiblePlane if item was added
+			if (item->_created) {
+				item->_created--;
+				if (visiblePlane != nullptr) {
+					ScreenItem *n = new ScreenItem(*item);
+					visiblePlane->_screenItemList.add(n);
+				}
+			}
+
+			// delete item from both planes if it was deleted
+			if (item->_deleted) {
+				item->_deleted--;
+				if (!item->_deleted) {
+					visiblePlane->_screenItemList.erase_at(i);
+					_screenItemList.erase_at(i);
+				}
+			}
+		}
+	}
+}
+
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const {
+	if (_type == kPlaneTypeTransparent) {
+		for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+			Common::Rect *r = transparentEraseList[i];
+			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+				ScreenItem *item = _screenItemList[j];
+				if (item != nullptr) {
+					if (r->intersects(item->_screenRect)) {
+						mergeToDrawList(j, *r, drawList);
+					}
+				}
+			}
+		}
+	} else {
+		for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+			Common::Rect *r = transparentEraseList[i];
+			if (r->intersects(_screenRect)) {
+				r->clip(_screenRect);
+				mergeToRectList(*r, eraseList);
+
+				for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+					ScreenItem *item = _screenItemList[j];
+
+					if (item != nullptr) {
+						if (r->intersects(item->_screenRect)) {
+							mergeToDrawList(j, *r, drawList);
+						}
+					}
+				}
+
+				Common::Rect ptr[4];
+				Common::Rect *r2 = transparentEraseList[i];
+				int count = splitRects(*r2, *r, ptr);
+				for (int k = count - 1; k >= 0; --k) {
+					transparentEraseList.add(ptr[k]);
+				}
+				transparentEraseList.erase_at(i);
+			}
+		}
+
+		transparentEraseList.pack();
+	}
+}
+
+void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
+	for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+		Common::Rect &r = drawList[i]->rect;
+
+		for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+			ScreenItem *item = _screenItemList[j];
+			if (item != nullptr) {
+				if (r.intersects(item->_screenRect)) {
+					mergeToDrawList(j, r, transparentDrawList);
+				}
+			}
+		}
+	}
+}
+
+void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const {
+	for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+		Common::Rect &r = *eraseList[i];
+		for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+			ScreenItem *item = _screenItemList[j];
+
+			if (item != nullptr) {
+				if (r.intersects(item->_screenRect)) {
+					mergeToDrawList(j, r, drawList);
+				}
+			}
+		}
+	}
+}
+
+void Plane::mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
+	RectList rects;
+
+	Common::Rect r = _screenItemList[index]->_screenRect;
+	r.clip(rect);
+
+	rects.add(r);
+	ScreenItem *item = _screenItemList[index];
+
+	for (RectList::size_type i = 0; i < rects.size(); ++i) {
+		r = *rects[i];
+
+		for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
+			DrawItem *drawitem = drawList[j];
+			if (item->_object == drawitem->screenItem->_object) {
+				if (drawitem->rect.contains(r)) {
+					rects.erase_at(i);
+					break;
+				}
+
+				Common::Rect outRects[4];
+				int count = splitRects(r, drawitem->rect, outRects);
+				if (count != -1) {
+					for (int k = count - 1; k >= 0; --k) {
+						rects.add(outRects[k]);
+					}
+
+					rects.erase_at(i);
+
+					// proceed to the next rect
+					r = *rects[++i];
+				}
+			}
+		}
+	}
+
+	rects.pack();
+
+	for (RectList::size_type i = 0; i < rects.size(); ++i) {
+		drawList.add(item, *rects[i]);
+	}
+}
+
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
+	RectList temp;
+	temp.add(rect);
+
+	for (RectList::size_type i = 0; i < temp.size(); ++i) {
+		Common::Rect *outerRect = temp[i];
+		for (RectList::size_type j = 0; j < rectList.size(); ++j) {
+			Common::Rect *innerRect = rectList[i];
+			if (innerRect->intersects(*outerRect)) {
+				Common::Rect out[4];
+				int count = splitRects(*outerRect, *innerRect, out);
+				for (int k = count - 1; k >= 0; --k) {
+					temp.add(out[k]);
+				}
+				temp.erase_at(i);
+			} else {
+				temp.erase_at(i);
+			}
+		}
+	}
+
+	temp.pack();
+
+	for (RectList::size_type i = 0; i < temp.size(); ++i) {
+		rectList.add(*temp[i]);
+	}
+}
+
+void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+	for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
+		if (*screenItemPtr != nullptr) {
+			ScreenItem &screenItem = **screenItemPtr;
+			if (!screenItem._deleted) {
+				screenItem.getCelObj();
+				screenItem.calcRects(*this);
+				if (!screenItem._screenRect.isEmpty()) {
+					drawList.add(&screenItem, screenItem._screenRect);
+				}
+			}
+		}
+	}
+
+	eraseList.clear();
+
+	if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) {
+		eraseList.add(_screenRect);
+	}
+	breakEraseListByPlanes(eraseList, planeList);
+	breakDrawListByPlanes(drawList, planeList);
+	--_redrawAllCount;
+	decrementScreenItemArrayCounts(visiblePlane, true);
+	_screenItemList.pack();
+	if (visiblePlane != nullptr) {
+		visiblePlane->_screenItemList.pack();
+	}
+}
+
+void Plane::setType() {
+	if (_pictureId == kPlanePicOpaque) {
+		_type = kPlaneTypeOpaque;
+	} else if (_pictureId == kPlanePicTransparent) {
+		_type = kPlaneTypeTransparent;
+	} else if (_pictureId == kPlanePicColored) {
+		_type = kPlaneTypeColored;
+	} else {
+		_type = kPlaneTypePicture;
+	}
+}
+
+void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
+	if (other == nullptr) {
+		if (_pictureChanged) {
+			deleteAllPics();
+			setType();
+			changePic();
+			_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+		} else {
+			setType();
+		}
+	} else {
+		if (
+			_planeRect.top != other->_planeRect.top ||
+			_planeRect.left != other->_planeRect.left ||
+			_planeRect.right > other->_planeRect.right ||
+			_planeRect.bottom > other->_planeRect.bottom
+		) {
+			_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+			_updated = g_sci->_gfxFrameout->getScreenCount();
+		} else if (_planeRect != other->_planeRect) {
+			_updated = g_sci->_gfxFrameout->getScreenCount();
+		}
+
+		if (_priority != other->_priority) {
+			_priorityChanged = g_sci->_gfxFrameout->getScreenCount();
+		}
+
+		if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) {
+			deleteAllPics();
+			setType();
+			changePic();
+			_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+		}
+
+		if (_back != other->_back) {
+			_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+		}
+	}
+
+	_deleted = 0;
+	if (_created == 0) {
+		_moved = g_sci->_gfxFrameout->getScreenCount();
+	}
+
+	convertGameRectToPlaneRect();
+	_width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	_height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+	_screenRect = _planeRect;
+	// NOTE: screenRect originally was retrieved through globals
+	// instead of being passed into the function
+	clipScreenRect(screenRect);
+}
+
+void Plane::update(const reg_t object) {
+	SegManager *segMan = g_sci->getEngineState()->_segMan;
+	_vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+	_vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+	_gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+	_gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+	convertGameRectToPlaneRect();
+
+	_priority = readSelectorValue(segMan, object, SELECTOR(priority));
+	GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+	if (_pictureId != pictureId) {
+		_pictureId = pictureId;
+		_pictureChanged = true;
+	}
+
+	_mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+	_back = readSelectorValue(segMan, object, SELECTOR(back));
+}
+
+#pragma mark -
+#pragma mark PlaneList
+void PlaneList::clear() {
+	for (iterator it = begin(); it != end(); ++it) {
+		delete *it;
+	}
+
+	PlaneListBase::clear();
+}
+
+void PlaneList::erase(Plane *plane) {
+	for (iterator it = begin(); it != end(); ++it) {
+		if (*it == plane) {
+			erase(it);
+			break;
+		}
+	}
+}
+
+int PlaneList::findIndexByObject(const reg_t object) const {
+	for (size_type i = 0; i < size(); ++i) {
+		if ((*this)[i] != nullptr && (*this)[i]->_object == object) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+Plane *PlaneList::findByObject(const reg_t object) const {
+	const_iterator planeIt = Common::find_if(begin(), end(), FindByObject<Plane *>(object));
+
+	if (planeIt == end()) {
+		return nullptr;
+	}
+
+	return *planeIt;
+}
+
+int16 PlaneList::getTopPlanePriority() const {
+	if (size() > 0) {
+		return (*this)[size() - 1]->_priority;
+	}
+
+	return 0;
+}
+
+int16 PlaneList::getTopSciPlanePriority() const {
+	int16 priority = 0;
+
+	for (const_iterator it = begin(); it != end(); ++it) {
+		if ((*it)->_priority >= 10000) {
+			break;
+		}
+
+		priority = (*it)->_priority;
+	}
+
+	return priority;
+}
+
+void PlaneList::add(Plane *plane) {
+	for (iterator it = begin(); it != end(); ++it) {
+		if ((*it)->_priority < plane->_priority) {
+			insert(it, plane);
+			return;
+		}
+	}
+
+	push_back(plane);
+}
+
+}
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
new file mode 100644
index 0000000..a68700a
--- /dev/null
+++ b/engines/sci/graphics/plane32.h
@@ -0,0 +1,465 @@
+/* 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 SCI_GRAPHICS_PLANE32_H
+#define SCI_GRAPHICS_PLANE32_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "sci/engine/vm_types.h"
+#include "sci/graphics/helpers.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+enum PlaneType {
+	kPlaneTypeColored     = 0,
+	kPlaneTypePicture     = 1,
+	kPlaneTypeTransparent = 2,
+	kPlaneTypeOpaque      = 3
+};
+
+enum PlanePictureCodes {
+	// NOTE: Any value at or below 65532 means the plane
+	// is a kPlaneTypePicture.
+	kPlanePic            = 65532,
+	kPlanePicOpaque      = 65533,
+	kPlanePicTransparent = 65534,
+	kPlanePicColored     = 65535
+};
+
+#pragma mark -
+#pragma mark RectList
+
+typedef StablePointerArray<Common::Rect, 200> RectListBase;
+class RectList : public RectListBase {
+public:
+	void add(const Common::Rect &rect) {
+		RectListBase::add(new Common::Rect(rect));
+	}
+};
+
+#pragma mark -
+#pragma mark DrawList
+
+struct DrawItem {
+	ScreenItem *screenItem;
+	Common::Rect rect;
+
+	inline bool operator<(const DrawItem &other) const {
+		return *screenItem < *other.screenItem;
+	}
+};
+
+typedef StablePointerArray<DrawItem, 250> DrawListBase;
+class DrawList : public DrawListBase {
+private:
+	inline static bool sortHelper(const DrawItem *a, const DrawItem *b) {
+		return *a < *b;
+	}
+public:
+	void add(ScreenItem *screenItem, const Common::Rect &rect);
+	inline void sort() {
+		pack();
+		Common::sort(begin(), end(), sortHelper);
+	}
+};
+
+class PlaneList;
+
+#pragma mark -
+#pragma mark Plane
+
+/**
+ * A plane is a grouped layer of screen items.
+ */
+class Plane {
+private:
+	/**
+	 * A serial used for planes that are generated inside
+	 * the graphics engine, rather than the interpreter.
+	 */
+	static uint16 _nextObjectId;
+
+	/**
+	 * The dimensions of the plane, in game script
+	 * coordinates.
+	 * TODO: These are never used and are always
+	 * scriptWidth x scriptHeight in SCI engine? The actual
+	 * dimensions of the plane are always in
+	 * gameRect/planeRect.
+	 */
+	int16 _width, _height;
+
+	/**
+	 * For planes that are used to render picture data, the
+	 * resource ID of the picture to be displayed. This
+	 * value may also be one of the special
+	 * PlanePictureCodes, in which case the plane becomes a
+	 * non-picture plane.
+	 */
+	GuiResourceId _pictureId;
+
+	/**
+	 * Whether or not the contents of picture planes should
+	 * be drawn horizontally mirrored. Only applies to
+	 * planes of type kPlaneTypePicture.
+	 */
+	bool _mirrored;
+
+	/**
+	 * Whether the picture ID for this plane has changed.
+	 * This flag is set when the plane is created or updated
+	 * from a VM object, and is cleared when the plane is
+	 * synchronised to another plane (which calls
+	 * changePic).
+	 */
+	bool _pictureChanged; // ?
+
+	// TODO: Are these ever actually used?
+	int _field_34, _field_38; // probably a point or ratio
+	int _field_3C, _field_40; // probably a point or ratio
+
+	/**
+	 * Converts the dimensions of the game rect used by
+	 * scripts to the dimensions of the plane rect used to
+	 * render content to the screen. Coordinates with
+	 * remainders are rounded up to the next whole pixel.
+	 */
+	void convertGameRectToPlaneRect();
+
+	/**
+	 * Sets the type of the plane according to its assigned
+	 * picture resource ID.
+	 */
+	void setType();
+
+public:
+	/**
+	 * The colour to use when erasing the plane. Only
+	 * applies to planes of type kPlaneTypeColored.
+	 */
+	byte _back;
+
+	/**
+	 * Whether the priority of this plane has changed.
+	 * This flag is set when the plane is updated from
+	 * another plane and cleared when draw list calculation
+	 * occurs.
+	 */
+	int _priorityChanged; // ?
+
+	/**
+	 * A handle to the VM object corresponding to this
+	 * plane. Some planes are generated purely within the
+	 * graphics engine and have a numeric object value.
+	 */
+	reg_t _object;
+
+	/**
+	 * The rendering priority of the plane. Higher
+	 * priorities are drawn above lower priorities.
+	 */
+	int16 _priority;
+
+	/**
+	 * TODO: Document
+	 */
+	int _redrawAllCount;
+
+	PlaneType _type;
+
+	/**
+	 * Flags indicating the state of the plane.
+	 * - `created` is set when the plane is first created,
+	 *   either from a VM object or from within the engine
+	 *   itself
+	 * - `updated` is set when the plane is updated from
+	 *   another plane and the two planes' `planeRect`s do
+	 *   not match
+	 * - `deleted` is set when the plane is deleted by a
+	 *   kernel call
+	 * - `moved` is set when the plane is synchronised from
+	 *   another plane and is not already in the "created"
+	 *   state
+	 */
+	int _created, _updated, _deleted, _moved;
+
+	/**
+	 * The vanishing point for the plane. Used when
+	 * calculating the correct scaling of the plane's screen
+	 * items according to their position.
+	 */
+	Common::Point _vanishingPoint;
+
+	/**
+	 * The position & dimensions of the plane in screen
+	 * coordinates. This rect is not clipped to the screen,
+	 * so may include coordinates that are offscreen.
+	 */
+	Common::Rect _planeRect;
+
+	/**
+	 * The position & dimensions of the plane in game script
+	 * coordinates.
+	 */
+	Common::Rect _gameRect;
+
+	/**
+	 * The position & dimensions of the plane in screen
+	 * coordinates. This rect is clipped to the screen.
+	 */
+	Common::Rect _screenRect;
+
+	/**
+	 * The list of screen items grouped within this plane.
+	 */
+	ScreenItemList _screenItemList;
+
+public:
+	/**
+	 * Initialises static Plane members.
+	 */
+	static void init();
+
+	Plane(const Common::Rect &gameRect);
+	Plane(const reg_t object);
+	Plane(const Plane &other);
+	void operator=(const Plane &other);
+	inline bool operator<(const Plane &other) const {
+		// TODO: In SCI engine, _object is actually a uint16 and can either
+		// contain a MemID (a handle to MemoryMgr, similar to reg_t) or
+		// a serial (Plane::_nextObjectId). These numbers can be compared
+		// directly in the real engine and the lowest MemID wins, but in
+		// ScummVM reg_t pointers are not comparable so we have to use a
+		// different strategy when two planes generated by scripts conflict.
+		// For now we just don't check if the priority is below 0, since
+		// that priority is used to represent hidden planes and is guaranteed
+		// to generate conflicts with script-generated planes. If there are
+		// other future conflicts with script-generated planes then we need
+		// to come up with a solution that works, similar to
+		// reg_t::pointerComparisonWithInteger used by SCI16.
+		return _priority < other._priority || (_priority == other._priority && _priority > -1 && _object < other._object);
+	}
+
+	/**
+	 * Clips the screen rect of this plane to fit within the
+	 * given screen rect.
+	 */
+	inline void clipScreenRect(const Common::Rect &screenRect) {
+		if (_screenRect.intersects(screenRect)) {
+			_screenRect.clip(screenRect);
+		} else {
+			_screenRect.left = 0;
+			_screenRect.top = 0;
+			_screenRect.right = 0;
+			_screenRect.bottom = 0;
+		}
+	}
+
+	void printDebugInfo(Console *con) const;
+
+	/**
+	 * Compares the properties of the current plane against
+	 * the properties of the `other` plane (which is the
+	 * corresponding plane from the visible plane list) to
+	 * discover which properties have been changed on this
+	 * plane by a call to `update(reg_t)`.
+	 *
+	 * @note This method was originally called UpdatePlane
+	 * in SCI engine.
+	 */
+	void sync(const Plane *other, const Common::Rect &screenRect);
+
+	/**
+	 * Updates the plane to match the state of the plane
+	 * object from the virtual machine.
+	 *
+	 * @note This method was originally called UpdatePlane
+	 * in SCI engine.
+	 */
+	void update(const reg_t object);
+
+#pragma mark -
+#pragma mark Plane - Pic
+private:
+	/**
+	 * Adds all cels from the specified picture resource to
+	 * the plane as screen items. If a position is provided,
+	 * the screen items will be given that position;
+	 * otherwise, the default relative positions for each
+	 * cel will be taken from the picture resource data.
+	 */
+	inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);
+
+	/**
+	 * If the plane is a picture plane, re-adds all cels
+	 * from its picture resource to the plane.
+	 */
+	void changePic();
+
+	/**
+	 * Marks all screen items to be deleted that are within
+	 * this plane and match the given picture ID.
+	 */
+	void deletePic(const GuiResourceId pictureId);
+
+	/**
+	 * Marks all screen items to be deleted that are within
+	 * this plane and match the given picture ID, then sets
+	 * the picture ID of the plane to the new picture ID
+	 * without adding any screen items.
+	 */
+	void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
+
+	/**
+	 * Marks all screen items to be deleted that are within
+	 * this plane and are picture cels.
+	 */
+	void deleteAllPics();
+
+public:
+	/**
+	 * Marks all existing screen items matching the current
+	 * picture to be deleted, then adds all cels from the
+	 * new picture resource to the plane at the given
+	 * position.
+	 */
+	void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX);
+
+#pragma mark -
+#pragma mark Plane - Rendering
+private:
+	/**
+	 * Splits all rects in the given draw list at the edges
+	 * of all non-transparent planes above the current
+	 * plane.
+	 */
+	void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
+
+	/**
+	 * Splits all rects in the given erase list rects at the
+	 * edges of all non-transparent planes above the current
+	 * plane.
+	 */
+	void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
+
+	/**
+	 * Synchronises changes to screen items from the current
+	 * plane to the visible plane and deletes screen items
+	 * from the current plane that have been marked as
+	 * deleted. If `forceUpdate` is true, all screen items
+	 * on the visible plane will be updated, even if they
+	 * are not marked as having changed.
+	 */
+	void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
+
+	/**
+	 * Merges the screen item from this plane at the given
+	 * index into the given draw list, clipped to the given
+	 * rect. TODO: Finish documenting
+	 */
+	void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
+
+	/**
+	 * Adds the given rect into the given rect list,
+	 * merging it with other rects already inside the list,
+	 * if possible, to avoid overdraw. TODO: Finish
+	 * documenting
+	 */
+	void mergeToRectList(const Common::Rect &rect, RectList &rectList) const;
+
+public:
+	/**
+	 * Calculates the location and dimensions of dirty rects
+	 * of the screen items in this plane and adds them to
+	 * the given draw and erase lists, and synchronises this
+	 * plane's list of screen items to the given visible
+	 * plane.
+	 */
+	void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+	/**
+	 * TODO: Documentation
+	 */
+	void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const;
+
+	/**
+	 * TODO: Documentation
+	 */
+	void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const;
+
+	/**
+	 * TODO: Documentation
+	 */
+	void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const;
+
+	/**
+	 * Updates all of the plane's non-deleted screen items
+	 * and adds them to the given draw and erase lists.
+	 */
+	void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+};
+
+#pragma mark -
+#pragma mark PlaneList
+
+typedef Common::Array<Plane *> PlaneListBase;
+class PlaneList : public PlaneListBase {
+private:
+	inline static bool sortHelper(const Plane *a, const Plane *b) {
+		return *a < *b;
+	}
+
+	using PlaneListBase::push_back;
+
+public:
+	// A method for finding the index of a plane inside a
+	// PlaneList is used because entries in the main plane
+	// list and visible plane list of GfxFrameout are
+	// synchronised by index
+	int findIndexByObject(const reg_t object) const;
+	Plane *findByObject(const reg_t object) const;
+
+	/**
+	 * Gets the priority of the top plane in the plane list.
+	 */
+	int16 getTopPlanePriority() const;
+
+	/**
+	 * Gets the priority of the top plane in the plane list
+	 * created by a game script.
+	 */
+	int16 getTopSciPlanePriority() const;
+
+	void add(Plane *plane);
+	void clear();
+	using PlaneListBase::erase;
+	void erase(Plane *plane);
+	inline void sort() {
+		Common::sort(begin(), end(), sortHelper);
+	}
+};
+
+}
+
+#endif
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 49c63d4..6541625 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -76,6 +76,11 @@ public:
 	byte getColorWhite() { return _colorWhite; }
 	byte getColorDefaultVectorData() { return _colorDefaultVectorData; }
 
+#ifdef ENABLE_SCI32
+	byte *getDisplayScreen() { return _displayScreen; }
+	byte *getPriorityScreen() { return _priorityScreen; }
+#endif
+
 	void clearForRestoreGame();
 	void copyToScreen();
 	void copyFromScreen(byte *buffer);
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
new file mode 100644
index 0000000..300912f
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -0,0 +1,534 @@
+/* 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 "sci/console.h"
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark ScreenItem
+
+uint16 ScreenItem::_nextObjectId = 20000;
+
+ScreenItem::ScreenItem(const reg_t object) :
+_mirrorX(false),
+_pictureId(-1),
+_celObj(nullptr),
+_object(object),
+_deleted(0),
+_updated(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {
+	SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+	setFromObject(segMan, object, true, true);
+	_plane = readSelector(segMan, object, SELECTOR(plane));
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) :
+_position(0, 0),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
+_position(rect.left, rect.top),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {
+	if (celInfo.type == kCelTypeColor) {
+		_insetRect = rect;
+	}
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo) :
+_position(rect.left, rect.top),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_scale(scaleInfo) {}
+
+ScreenItem::ScreenItem(const ScreenItem &other) :
+_object(other._object),
+_plane(other._plane),
+_celInfo(other._celInfo),
+_celObj(nullptr),
+_screenRect(other._screenRect),
+_mirrorX(other._mirrorX),
+_useInsetRect(other._useInsetRect),
+_scale(other._scale),
+_scaledPosition(other._scaledPosition) {
+	if (other._useInsetRect) {
+		_insetRect = other._insetRect;
+	}
+}
+
+void ScreenItem::operator=(const ScreenItem &other) {
+	_celInfo = other._celInfo;
+	_screenRect = other._screenRect;
+	_mirrorX = other._mirrorX;
+	_useInsetRect = other._useInsetRect;
+	if (other._useInsetRect) {
+		_insetRect = other._insetRect;
+	}
+	_scale = other._scale;
+	_scaledPosition = other._scaledPosition;
+}
+
+void ScreenItem::init() {
+	_nextObjectId = 20000;
+}
+
+void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) {
+	_position.x = readSelectorValue(segMan, object, SELECTOR(x));
+	_position.y = readSelectorValue(segMan, object, SELECTOR(y));
+	_scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX));
+	_scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY));
+	_scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale));
+	_scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3);
+
+	if (updateCel) {
+		_celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view));
+		_celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+		_celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+		if (_celInfo.resourceId <= kPlanePic) {
+			// TODO: Enhance GfxView or ResourceManager to allow
+			// metadata for resources to be retrieved once, from a
+			// single location
+			Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false);
+			if (!view) {
+				error("Failed to load resource %d", _celInfo.resourceId);
+			}
+
+			// NOTE: +2 because the header size field itself is excluded from
+			// the header size in the data
+			const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2;
+			const uint8 loopCount = view->data[2];
+			const uint8 loopSize = view->data[12];
+
+			if (_celInfo.loopNo >= loopCount) {
+				const int maxLoopNo = loopCount - 1;
+				_celInfo.loopNo = maxLoopNo;
+				writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
+			}
+
+			byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize);
+			const int8 seekEntry = loopData[0];
+			if (seekEntry != -1) {
+				loopData = view->data + headerSize + (seekEntry * loopSize);
+			}
+			const uint8 celCount = loopData[2];
+			if (_celInfo.celNo >= celCount) {
+				const int maxCelNo = celCount - 1;
+				_celInfo.celNo = maxCelNo;
+				writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
+			}
+		}
+	}
+
+	if (updateBitmap) {
+		const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap));
+		if (!bitmap.isNull()) {
+			_celInfo.bitmap = bitmap;
+			_celInfo.type = kCelTypeMem;
+		} else {
+			_celInfo.bitmap = NULL_REG;
+			_celInfo.type = kCelTypeView;
+		}
+	}
+
+	if (updateCel || updateBitmap) {
+		delete _celObj;
+		_celObj = nullptr;
+	}
+
+	if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
+		_fixPriority = true;
+		_priority = readSelectorValue(segMan, object, SELECTOR(priority));
+	} else {
+		_fixPriority = false;
+		writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
+	}
+
+	_z = readSelectorValue(segMan, object, SELECTOR(z));
+	_position.y -= _z;
+
+	if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
+		_useInsetRect = true;
+		_insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+		_insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+		_insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+		_insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+	} else {
+		_useInsetRect = false;
+	}
+
+	// TODO: SCI2.1/SQ6 engine clears this flag any time ScreenItem::Update(MemID)
+	// or ScreenItem::ScreenItem(MemID) are called, but doing this breaks
+	// view cycling because the flag isn't being set again later. There are over
+	// 100 places in the engine code where this flag is set, so it is probably
+	// a matter of figuring out what all of those calls are that re-set it. For
+	// now, since these are the *only* calls that clear this flag, we can just
+	// leave it set all the time.
+	// segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible);
+}
+
+void ScreenItem::calcRects(const Plane &plane) {
+	const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+	const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+	const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+	Common::Rect celRect(_celObj->_width, _celObj->_height);
+	if (_useInsetRect) {
+		if (_insetRect.intersects(celRect)) {
+			_insetRect.clip(celRect);
+		} else {
+			_insetRect = Common::Rect();
+		}
+	} else {
+		_insetRect = celRect;
+	}
+
+	Ratio newRatioX;
+	Ratio newRatioY;
+
+	if (_scale.signal & kScaleSignalDoScaling32) {
+		if (_scale.signal & kScaleSignalUseVanishingPoint) {
+			int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+			newRatioX = Ratio(num, 128);
+			newRatioY = Ratio(num, 128);
+		} else {
+			newRatioX = Ratio(_scale.x, 128);
+			newRatioY = Ratio(_scale.y, 128);
+		}
+	}
+
+	if (newRatioX.getNumerator() && newRatioY.getNumerator()) {
+		_screenItemRect = _insetRect;
+
+		if (_celObj->_scaledWidth != scriptWidth || _celObj->_scaledHeight != scriptHeight) {
+			if (_useInsetRect) {
+				Ratio celScriptXRatio(_celObj->_scaledWidth, scriptWidth);
+				Ratio celScriptYRatio(_celObj->_scaledHeight, scriptHeight);
+				mulru(_screenItemRect, celScriptXRatio, celScriptYRatio);
+
+				if (_screenItemRect.intersects(celRect)) {
+					_screenItemRect.clip(celRect);
+				} else {
+					_screenItemRect = Common::Rect();
+				}
+			}
+
+			int displaceX = _celObj->_displace.x;
+			int displaceY = _celObj->_displace.y;
+
+			if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
+				displaceX = _celObj->_width - _celObj->_displace.x - 1;
+			}
+
+			if (!newRatioX.isOne() || !newRatioY.isOne()) {
+				mulru(_screenItemRect, newRatioX, newRatioY);
+				displaceX = (displaceX * newRatioX).toInt();
+				displaceY = (displaceY * newRatioY).toInt();
+			}
+
+			Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
+			Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
+
+			displaceX = (displaceX * celXRatio).toInt();
+			displaceY = (displaceY * celYRatio).toInt();
+
+			mulru(_screenItemRect, celXRatio, celYRatio);
+
+			if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) {
+				_scaledPosition.x = _position.x;
+				_scaledPosition.y = _position.y;
+			} else {
+				_scaledPosition.x = (_position.x * screenWidth / scriptWidth) - displaceX;
+				_scaledPosition.y = (_position.y * screenHeight / scriptHeight) - displaceY;
+			}
+
+			_screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+			if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+				Common::Rect temp(_insetRect);
+
+				if (!newRatioX.isOne()) {
+					mulru(temp, newRatioX, Ratio());
+				}
+
+				mulru(temp, celXRatio, Ratio());
+
+				CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+
+				temp.translate(celObjPic->_relativePosition.x * screenWidth / scriptWidth - displaceX, 0);
+
+				// TODO: This is weird, and probably wrong calculation of widths
+				// due to BR-inclusion
+				int deltaX = plane._planeRect.right - plane._planeRect.left + 1 - temp.right - 1 - temp.left;
+
+				_scaledPosition.x += deltaX;
+				_screenItemRect.translate(deltaX, 0);
+			}
+
+			_scaledPosition.x += plane._planeRect.left;
+			_scaledPosition.y += plane._planeRect.top;
+			_screenItemRect.translate(plane._planeRect.left, plane._planeRect.top);
+
+			_ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
+			_ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+		} else {
+			int displaceX = _celObj->_displace.x;
+			if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
+				displaceX = _celObj->_width - _celObj->_displace.x - 1;
+			}
+
+			if (!newRatioX.isOne() || !newRatioY.isOne()) {
+				mulru(_screenItemRect, newRatioX, newRatioY);
+				// TODO: This was in the original code, baked into the
+				// multiplication though it is  not immediately clear
+				// why this is the only one that reduces the BR corner
+				_screenItemRect.right -= 1;
+				_screenItemRect.bottom -= 1;
+			}
+
+			_scaledPosition.x = _position.x - (displaceX * newRatioX).toInt();
+			_scaledPosition.y = _position.y - (_celObj->_displace.y * newRatioY).toInt();
+			_screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+			if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+				Common::Rect temp(_insetRect);
+
+				if (!newRatioX.isOne()) {
+					mulru(temp, newRatioX, Ratio());
+					temp.right -= 1;
+				}
+
+				CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+				temp.translate(celObjPic->_relativePosition.x - (displaceX * newRatioX).toInt(), celObjPic->_relativePosition.y - (_celObj->_displace.y * newRatioY).toInt());
+
+				// TODO: This is weird, and probably wrong calculation of widths
+				// due to BR-inclusion
+				int deltaX = plane._gameRect.right - plane._gameRect.left + 1 - temp.right - 1 - temp.left;
+
+				_scaledPosition.x += deltaX;
+				_screenItemRect.translate(deltaX, 0);
+			}
+
+			_scaledPosition.x += plane._gameRect.left;
+			_scaledPosition.y += plane._gameRect.top;
+			_screenItemRect.translate(plane._gameRect.left, plane._gameRect.top);
+
+			if (screenWidth != _celObj->_scaledWidth || _celObj->_scaledHeight != screenHeight) {
+				Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
+				Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
+				mulru(_scaledPosition, celXRatio, celYRatio);
+				mulru(_screenItemRect, celXRatio, celYRatio, 1);
+			}
+
+			_ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
+			_ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+		}
+
+		_screenRect = _screenItemRect;
+
+		if (_screenRect.intersects(plane._screenRect)) {
+			_screenRect.clip(plane._screenRect);
+		} else {
+			_screenRect.right = 0;
+			_screenRect.bottom = 0;
+			_screenRect.left = 0;
+			_screenRect.top = 0;
+		}
+
+		if (!_fixPriority) {
+			_priority = _z + _position.y;
+		}
+	} else {
+		_screenRect.left = 0;
+		_screenRect.top = 0;
+		_screenRect.right = 0;
+		_screenRect.bottom = 0;
+	}
+}
+
+CelObj &ScreenItem::getCelObj() {
+	if (_celObj == nullptr) {
+		switch (_celInfo.type) {
+			case kCelTypeView:
+				_celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo);
+			break;
+			case kCelTypePic:
+				error("Internal error, pic screen item with no cel.");
+			break;
+			case kCelTypeMem:
+				_celObj = new CelObjMem(_celInfo.bitmap);
+			break;
+			case kCelTypeColor:
+				_celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height());
+			break;
+		}
+	}
+
+	return *_celObj;
+}
+
+void ScreenItem::printDebugInfo(Console *con) const {
+	con->debugPrintf("prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
+		_priority,
+		_position.x,
+		_position.y,
+		_z,
+		_scaledPosition.x,
+		_scaledPosition.y,
+		_created | (_updated << 1) | (_deleted << 2)
+	);
+	con->debugPrintf("    screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect));
+	if (_useInsetRect) {
+		con->debugPrintf("    inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect));
+	}
+
+	Common::String celType;
+	switch (_celInfo.type) {
+		case kCelTypePic:
+			celType = "pic";
+			break;
+		case kCelTypeView:
+			celType = "view";
+			break;
+		case kCelTypeColor:
+			celType = "color";
+			break;
+		case kCelTypeMem:
+			celType = "mem";
+			break;
+	}
+
+	con->debugPrintf("    type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n",
+		celType.c_str(),
+		_celInfo.resourceId,
+		_celInfo.loopNo,
+		_celInfo.celNo,
+		PRINT_REG(_celInfo.bitmap),
+		_celInfo.color
+	);
+	if (_celObj != nullptr) {
+		con->debugPrintf("    width %d, height %d, scaledWidth %d, scaledHeight %d\n",
+			_celObj->_width,
+			_celObj->_height,
+			_celObj->_scaledWidth,
+			_celObj->_scaledHeight
+		);
+	}
+}
+
+void ScreenItem::update(const reg_t object) {
+	SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+	const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view));
+	const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+	const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+	const bool updateCel = (
+		_celInfo.resourceId != view ||
+		_celInfo.loopNo != loopNo ||
+		_celInfo.celNo != celNo
+	);
+
+	const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull();
+
+	setFromObject(segMan, object, updateCel, updateBitmap);
+
+	if (!_created) {
+		_updated = g_sci->_gfxFrameout->getScreenCount();
+	}
+
+	_deleted = 0;
+}
+
+#pragma mark -
+#pragma mark ScreenItemList
+ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
+	const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject<ScreenItem *>(object));
+
+	if (screenItemIt == end()) {
+		return nullptr;
+	}
+
+	return *screenItemIt;
+}
+void ScreenItemList::sort() {
+	// TODO: SCI engine used _unsorted as an array of indexes into the
+	// list itself and then performed the same swap operations on the
+	// _unsorted array as the _storage array during sorting, but the
+	// only reason to do this would be if some of the pointers in the
+	// list were replaced so the pointer values themselves couldn’t
+	// simply be recorded and then restored later. It is not yet
+	// verified whether this simplification of the sort/unsort is
+	// safe.
+	for (size_type i = 0; i < size(); ++i) {
+		_unsorted[i] = (*this)[i];
+	}
+
+	Common::sort(begin(), end(), sortHelper);
+}
+void ScreenItemList::unsort() {
+	for (size_type i = 0; i < size(); ++i) {
+		(*this)[i] = _unsorted[i];
+	}
+}
+
+}
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
new file mode 100644
index 0000000..d3968ef
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.h
@@ -0,0 +1,280 @@
+/* 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 SCI_GRAPHICS_SCREEN_ITEM32_H
+#define SCI_GRAPHICS_SCREEN_ITEM32_H
+
+#include "common/rect.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/lists32.h"
+
+namespace Sci {
+
+enum ScaleSignals32 {
+	kScaleSignalNone                    = 0,
+	kScaleSignalDoScaling32				= 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
+	kScaleSignalUseVanishingPoint       = 2,
+	// TODO: Is this actually a thing? I have not seen it and
+	// the original engine masks &3 where it uses scale signals.
+	kScaleSignalDisableGlobalScaling32	= 4
+};
+
+struct ScaleInfo {
+	int x, y, max;
+	ScaleSignals32 signal;
+	ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {}
+};
+
+class CelObj;
+class Plane;
+class SegManager;
+
+#pragma mark -
+#pragma mark ScreenItem
+
+/**
+ * A ScreenItem is the engine-side representation of a
+ * game script View.
+ */
+class ScreenItem {
+private:
+	/**
+	 * A serial used for screen items that are generated
+	 * inside the graphics engine, rather than the
+	 * interpreter.
+	 */
+	static uint16 _nextObjectId;
+
+	/**
+	 * The parent plane of this screen item.
+	 */
+	reg_t _plane;
+
+	/**
+	 * Scaling data used to calculate the final screen
+	 * dimensions of the screen item as well as the scaling
+	 * ratios used when drawing the item to screen.
+	 */
+	ScaleInfo _scale;
+
+	/**
+	 * The position & dimensions of the screen item in
+	 * screen coordinates. This rect includes the offset
+	 * of the parent plane, but is not clipped to the
+	 * screen, so may include coordinates that are
+	 * offscreen.
+	 */
+	Common::Rect _screenItemRect;
+
+	/**
+	 * TODO: Document
+	 */
+	bool _useInsetRect;
+
+	/**
+	 * TODO: Documentation
+	 * The insetRect is also used to describe the fill
+	 * rectangle of a screen item that is drawn using
+	 * CelObjColor.
+	 */
+	Common::Rect _insetRect;
+
+	/**
+	 * The z-index of the screen item in pseudo-3D space.
+	 * Higher values are drawn on top of lower values.
+	 */
+	int _z;
+
+	/**
+	 * Sets the common properties of a screen item that must
+	 * be set both during creation and update of a screen
+	 * item.
+	 */
+	void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap);
+
+public:
+	/**
+	 * A descriptor for the cel object represented by the
+	 * screen item.
+	 */
+	CelInfo32 _celInfo;
+
+	/**
+	 * The cel object used to actually render the screen
+	 * item. This member is populated by calling
+	 * `getCelObj`.
+	 */
+	CelObj *_celObj;
+
+	/**
+	 * If set, the priority for this screen item is fixed
+	 * in place. Otherwise, the priority of the screen item
+	 * is calculated from its y-position + z-index.
+	 */
+	bool _fixPriority;
+
+	/**
+	 * The rendering priority of the screen item, relative
+	 * only to the other screen items within the same plane.
+	 * Higher priorities are drawn above lower priorities.
+	 */
+	int16 _priority;
+
+	/**
+	 * The top-left corner of the screen item, in game
+	 * script coordinates, relative to the parent plane.
+	 */
+	Common::Point _position;
+
+	/**
+	 * The associated View script object that was
+	 * used to create the ScreenItem, or a numeric
+	 * value in the case of a ScreenItem that was
+	 * generated outside of the VM.
+	 */
+	reg_t _object;
+
+	/**
+	 * For screen items representing picture resources,
+	 * the resource ID of the picture.
+	 */
+	GuiResourceId _pictureId;
+
+	/**
+	 * Flags indicating the state of the screen item.
+	 * - `created` is set when the screen item is first
+	 *   created, either from a VM object or from within the
+	 *   engine itself
+	 * - `updated` is set when `created` is not already set
+	 *   and the screen item is updated from a VM object
+	 * - `deleted` is set by the parent plane, if the parent
+	 *   plane is a pic type and its picture resource ID has
+	 *   changed
+	 */
+	int _created, _updated, _deleted; // ?
+
+	/**
+	 * For screen items that represent picture cels, this
+	 * value is set to match the `_mirrorX` property of the
+	 * parent plane and indicates that the cel should be
+	 * drawn horizontally mirrored. For final drawing, it is
+	 * XORed with the `_mirrorX` property of the cel object.
+	 * The cel object's `_mirrorX` property comes from the
+	 * resource data itself.
+	 */
+	bool _mirrorX;
+
+	/**
+	 * The scaling ratios to use when drawing this screen
+	 * item. These values are calculated according to the
+	 * scale info whenever the screen item is updated.
+	 */
+	Ratio _ratioX, _ratioY;
+
+	/**
+	 * The top-left corner of the screen item, in screen
+	 * coordinates.
+	 */
+	Common::Point _scaledPosition;
+
+	/**
+	 * The position & dimensions of the screen item in
+	 * screen coordinates. This rect includes the offset of
+	 * the parent plane and is clipped to the screen.
+	 */
+	Common::Rect _screenRect;
+
+	/**
+	 * Initialises static Plane members.
+	 */
+	static void init();
+
+	ScreenItem(const reg_t screenItem);
+	ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
+	ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
+	ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo);
+	ScreenItem(const ScreenItem &other);
+	void operator=(const ScreenItem &);
+
+	inline bool operator<(const ScreenItem &other) const {
+		if (_priority < other._priority) {
+			return true;
+		}
+
+		if (_priority == other._priority) {
+			if (_position.y + _z < other._position.y + other._z) {
+				return true;
+			}
+
+			if (_position.y + _z == other._position.y + other._z) {
+				return false;
+				// TODO: Failure in SQ6 room 220
+//				return _object < other._object;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Calculates the dimensions and scaling parameters for
+	 * the screen item, using the given plane as the parent
+	 * plane for screen rect positioning.
+	 *
+	 * @note This method was called Update in SCI engine.
+	 */
+	void calcRects(const Plane &plane);
+
+	/**
+	 * Retrieves the corresponding cel object for this
+	 * screen item. If a cel object does not already exist,
+	 * one will be created and assigned.
+	 */
+	CelObj &getCelObj();
+
+	void printDebugInfo(Console *con) const;
+
+	/**
+	 * Updates the properties of the screen item from a
+	 * VM object.
+	 */
+	void update(const reg_t object);
+};
+
+#pragma mark -
+#pragma mark ScreenItemList
+
+typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
+class ScreenItemList : public ScreenItemListBase {
+	static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) {
+		return *a < *b;
+	}
+public:
+	ScreenItem *_unsorted[250];
+
+	ScreenItem *findByObject(const reg_t object) const;
+	void sort();
+	void unsort();
+};
+}
+
+#endif
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 08e5ea8..0b55874 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -81,10 +81,13 @@ MODULE_OBJS := \
 ifdef ENABLE_SCI32
 MODULE_OBJS += \
 	engine/kgraphics32.o \
+	graphics/celobj32.o \
 	graphics/controls32.o \
 	graphics/frameout.o \
 	graphics/paint32.o \
+	graphics/plane32.o \
 	graphics/palette32.o \
+	graphics/screen_item32.o\
 	graphics/text32.o \
 	video/robot_decoder.o
 endif
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 93cafad..9ef28b2 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -694,6 +694,7 @@ void SciEngine::initGraphics() {
 		_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
 		_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
 		_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
+		_gfxFrameout->run();
 	} else {
 #endif
 		// SCI0-SCI1.1 graphic objects creation


Commit: 37539625d753bba9b8653a68623fb305d29181a6
    https://github.com/scummvm/scummvm/commit/37539625d753bba9b8653a68623fb305d29181a6
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Fix palette equality check

Changed paths:
    engines/sci/graphics/helpers.h
    engines/sci/graphics/palette32.cpp



diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index c48ad4c..e2fdedb 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -227,7 +227,7 @@ struct Palette {
 			}
 		}
 
-		return false;
+		return true;
 	}
 	inline bool operator!=(const Palette &other) const {
 		return !(*this == other);
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 40e4021..a164dde 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -228,8 +228,6 @@ void GfxPalette32::updateFFrame() {
 
 void GfxPalette32::updateHardware() {
 	if (_sysPalette == _nextPalette) {
-		// TODO: This condition has never been encountered yet
-		debug("Skipping hardware update because palettes are identical");
 		return;
 	}
 


Commit: b829e482289ab9cdee50e0809128bb60354bc581
    https://github.com/scummvm/scummvm/commit/b829e482289ab9cdee50e0809128bb60354bc581
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Remove wrong comments in kernel.h and break apart logical sections

Changed paths:
    engines/sci/engine/kernel.h



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index f62c438..1d7a850 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -444,14 +444,14 @@ reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
 reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
 reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv);
 reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
-// "Screen items" in SCI32 are views
+
 reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
 reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
 reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv);
-// Text
+
 reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv);
 reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv);
-// "Planes" in SCI32 are pictures
+
 reg_t kAddPlane(EngineState *s, int argc, reg_t *argv);
 reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv);
 reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv);
@@ -473,6 +473,7 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv);
 reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
 reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
 reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
+
 reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
 reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv);
 reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv);


Commit: 3bddd869abbd6ba6dd88b0911f80aa305c8d464e
    https://github.com/scummvm/scummvm/commit/3bddd869abbd6ba6dd88b0911f80aa305c8d464e
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Build kernel table for ScrollWindow and stub seen functions

Signatures in subops table are correct for length but unknown
types are marked as . instead of the correct type.

Changed paths:
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics32.cpp



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 1d7a850..53f5878 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -441,6 +441,12 @@ reg_t kStringLower(EngineState *s, int argc, reg_t *argv);
 reg_t kStringTrn(EngineState *s, int argc, reg_t *argv);
 reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
 
+reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);
+
 reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
 reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv);
 reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index fce3230..ad0b86b 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -352,6 +352,31 @@ static const SciKernelMapSubEntry kString_subops[] = {
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
+//    version,         subId, function-mapping,                    signature,              workarounds
+static const SciKernelMapSubEntry kScrollWindow_subops[] = {
+	{ SIG_SCI32,           0, MAP_CALL(ScrollWindowCreate),        "oi",                   NULL },
+	{ SIG_SCI32,           1, MAP_CALL(ScrollWindowAdd),           "o.ii.(.)",             NULL },
+	{ SIG_SCI32,           2, MAP_DUMMY(ScrollWindowClear),        "o",                    NULL },
+	{ SIG_SCI32,           3, MAP_DUMMY(ScrollWindowPageUp),       "o",                    NULL },
+	{ SIG_SCI32,           4, MAP_DUMMY(ScrollWindowPageDown),     "o",                    NULL },
+	{ SIG_SCI32,           5, MAP_DUMMY(ScrollWindowUpArrow),      "o",                    NULL },
+	{ SIG_SCI32,           6, MAP_DUMMY(ScrollWindowDownArrow),    "o",                    NULL },
+	{ SIG_SCI32,           7, MAP_DUMMY(ScrollWindowHome),         "o",                    NULL },
+	{ SIG_SCI32,           8, MAP_DUMMY(ScrollWindowEnd),          "o",                    NULL },
+	{ SIG_SCI32,           9, MAP_DUMMY(ScrollWindowResize),       "o.",                   NULL },
+	{ SIG_SCI32,          10, MAP_CALL(ScrollWindowWhere),         "oi",                   NULL },
+	{ SIG_SCI32,          11, MAP_DUMMY(ScrollWindowGo),           "o..",                  NULL },
+	{ SIG_SCI32,          12, MAP_DUMMY(ScrollWindowInsert),       "o.....",               NULL },
+	{ SIG_SCI32,          13, MAP_DUMMY(ScrollWindowDelete),       "o.",                   NULL },
+	{ SIG_SCI32,          14, MAP_DUMMY(ScrollWindowModify),       "o.....(.)",            NULL },
+	{ SIG_SCI32,          15, MAP_DUMMY(ScrollWindowHide),         "o",                    NULL },
+	{ SIG_SCI32,          16, MAP_CALL(ScrollWindowShow),          "o",                    NULL },
+	{ SIG_SCI32,          17, MAP_CALL(ScrollWindowDestroy),       "o",                    NULL },
+	{ SIG_SCI32,          18, MAP_DUMMY(ScrollWindowText),         "o",                    NULL },
+	{ SIG_SCI32,          19, MAP_DUMMY(ScrollWindowReconstruct),  "o.",                   NULL },
+	SCI_SUBOPENTRY_TERMINATOR
+};
+
 #endif
 
 struct SciKernelMapEntry {
@@ -562,7 +587,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(DeletePlane),       SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(DeleteScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(DisposeTextBitmap), SIG_EVERYWHERE,           "r",                     NULL,            NULL },
-	{ MAP_CALL(FrameOut),          SIG_EVERYWHERE,           "",                      NULL,            NULL },
+	{ MAP_CALL(FrameOut),          SIG_EVERYWHERE,           "(i)",                   NULL,            NULL },
 	{ MAP_CALL(GetHighPlanePri),   SIG_EVERYWHERE,           "",                      NULL,            NULL },
 	{ MAP_CALL(InPolygon),         SIG_EVERYWHERE,           "iio",                   NULL,            NULL },
 	{ MAP_CALL(IsHiRes),           SIG_EVERYWHERE,           "",                      NULL,            NULL },
@@ -644,7 +669,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE,         "rri",                   NULL,            NULL },
 	{ MAP_CALL(CelInfo),           SIG_EVERYWHERE,           "iiiiii",                NULL,            NULL },
 	{ MAP_CALL(SetLanguage),       SIG_EVERYWHERE,           "r",                     NULL,            NULL },
-	{ MAP_CALL(ScrollWindow),      SIG_EVERYWHERE,           "io(.*)",                NULL,            NULL },
+	{ MAP_CALL(ScrollWindow),      SIG_EVERYWHERE,           "i(.*)",                 kScrollWindow_subops, NULL },
 	{ MAP_CALL(SetFontRes),        SIG_EVERYWHERE,           "ii",                    NULL,            NULL },
 	{ MAP_CALL(Font),              SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL },
 	{ MAP_CALL(Bitmap),            SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 93e8b90..64ef7ca 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -314,8 +314,39 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
-	return kStub(s, argc, argv);
+	if (!s)
+		return make_reg(0, getSciVersion());
+	error("not supposed to call this");
+}
+
+reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) {
+	debug("kScrollWindowCreate");
+	kStub(s, argc, argv);
+	return argv[0];
+}
+
+reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) {
+	debug("kScrollWindowAdd");
+	return kStubNull(s, argc, argv);
+}
+
+reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) {
+	debug("kScrollWindowWhere");
+	return kStubNull(s, argc, argv);
+}
+
+reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) {
+	debug("kScrollWindowShow");
+	return kStubNull(s, argc, argv);
+}
+
+reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
+	debug("kScrollWindowDestroy");
+	return kStubNull(s, argc, argv);
+}
+
 #if 0
+reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
 	// Used by SQ6 and LSL6 hires for the text area in the bottom of the
 	// screen. The relevant scripts also exist in Phantasmagoria 1, but they're
 	// unused. This is always called by scripts 64906 (ScrollerWindow) and
@@ -421,8 +452,8 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
 	}
 
 	return s->r_acc;
-#endif
 }
+#endif
 
 reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
 	// TODO: This defines the resolution that the fonts are supposed to be displayed


Commit: 20ccad80bfb689822be9f70512901557cbf244bf
    https://github.com/scummvm/scummvm/commit/20ccad80bfb689822be9f70512901557cbf244bf
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: WIP GfxText32 code

This at least prevents SQ6 from crashing when going into the
introduction

Changed paths:
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/selector.cpp
    engines/sci/engine/selector.h
    engines/sci/graphics/helpers.h
    engines/sci/graphics/text32.cpp
    engines/sci/graphics/text32.h
    engines/sci/sci.cpp
    engines/sci/util.cpp
    engines/sci/util.h



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index ad0b86b..b4a092a 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -670,7 +670,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(CelInfo),           SIG_EVERYWHERE,           "iiiiii",                NULL,            NULL },
 	{ MAP_CALL(SetLanguage),       SIG_EVERYWHERE,           "r",                     NULL,            NULL },
 	{ MAP_CALL(ScrollWindow),      SIG_EVERYWHERE,           "i(.*)",                 kScrollWindow_subops, NULL },
-	{ MAP_CALL(SetFontRes),        SIG_EVERYWHERE,           "ii",                    NULL,            NULL },
+	{ MAP_DUMMY(SetFontRes),       SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_CALL(Font),              SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL },
 	{ MAP_CALL(Bitmap),            SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_CALL(AddLine),           SIG_EVERYWHERE,           "oiiiiiiiii",            NULL,            NULL },
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 64ef7ca..a83836b 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -174,41 +174,53 @@ reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
-	switch (argv[0].toUint16()) {
-	case 0: {
-		if (argc != 4) {
-			warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc);
-			return NULL_REG;
-		}
-		reg_t object = argv[3];
-		Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text)));
-		debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)",
-				PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3]));
-		debugC(kDebugLevelStrings, "%s", text.c_str());
-		int16 maxWidth = argv[1].toUint16();
-		int16 maxHeight = argv[2].toUint16();
-		g_sci->_gfxCoordAdjuster->fromScriptToDisplay(maxHeight, maxWidth);
-		// These values can be larger than the screen in the SQ6 demo, room 100
-		// TODO: Find out why. For now, don't show any text in that room.
-		if (g_sci->getGameId() == GID_SQ6 && g_sci->isDemo() && s->currentRoomNumber() == 100)
-			return NULL_REG;
-		return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight);
-	}
-	case 1: {
-		if (argc != 2) {
-			warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc);
-			return NULL_REG;
-		}
-		reg_t object = argv[1];
-		Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text)));
-		debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1]));
-		debugC(kDebugLevelStrings, "%s", text.c_str());
-		return g_sci->_gfxText32->createTextBitmap(object);
-	}
-	default:
-		warning("CreateTextBitmap(%d)", argv[0].toUint16());
+	SegManager *segMan = s->_segMan;
+
+	int16 subop = argv[0].toUint16();
+
+	int16 width;
+	int16 height;
+	reg_t object;
+
+	if (subop == 0) {
+		width = argv[1].toUint16();
+		height = argv[2].toUint16();
+		object = argv[3];
+	} else if (subop == 1) {
+		object = argv[1];
+	} else {
+		warning("Invalid kCreateTextBitmap subop %d", subop);
 		return NULL_REG;
 	}
+
+	Common::String text = segMan->getString(readSelector(segMan, object, SELECTOR(text)));
+	int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore));
+	int16 backColor = readSelectorValue(segMan, object, SELECTOR(back));
+	int16 skipColor = readSelectorValue(segMan, object, SELECTOR(skip));
+	GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font));
+	int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor));
+	int16 dimmed = readSelectorValue(segMan, object, SELECTOR(dimmed));
+
+	Common::Rect rect(
+		readSelectorValue(segMan, object, SELECTOR(textLeft)),
+		readSelectorValue(segMan, object, SELECTOR(textTop)),
+		readSelectorValue(segMan, object, SELECTOR(textRight)),
+		readSelectorValue(segMan, object, SELECTOR(textBottom))
+	);
+
+	if (subop == 0) {
+		TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode));
+		reg_t out;
+		return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, 1, &out);
+	} else {
+		CelInfo32 celInfo;
+		celInfo.type = kCelTypeView;
+		celInfo.resourceId = readSelectorValue(segMan, object, SELECTOR(view));
+		celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+		celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+		reg_t out;
+		return g_sci->_gfxText32->createTitledFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed, &out);
+	}
 }
 
 reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) {
@@ -455,34 +467,26 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
 }
 #endif
 
-reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
-	// TODO: This defines the resolution that the fonts are supposed to be displayed
-	// in. Currently, this is only used for showing high-res fonts in GK1 Mac, but
-	// should be extended to handle other font resolutions such as those
-
-	int xResolution = argv[0].toUint16();
-	//int yResolution = argv[1].toUint16();
-
-	g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 &&
-			g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED);
-
-	return s->r_acc;
-}
-
 reg_t kFont(EngineState *s, int argc, reg_t *argv) {
-	// Handle font settings for SCI2.1
+	// TODO: Handle font settings for SCI2.1
 
 	switch (argv[0].toUint16()) {
 	case 1:
-		// Set font resolution
-		return kSetFontRes(s, argc - 1, argv + 1);
+		g_sci->_gfxText32->_scaledWidth = argv[1].toUint16();
+		g_sci->_gfxText32->_scaledHeight = argv[2].toUint16();
+		return NULL_REG;
 	default:
-		warning("kFont: unknown subop %d", argv[0].toUint16());
+		error("kFont: unknown subop %d", argv[0].toUint16());
 	}
 
 	return s->r_acc;
 }
 
+// TODO: Is this actually a thing??
+reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
+	return kStub(s, argc, argv);
+}
+
 // TODO: Eventually, all of the kBitmap operations should be put
 // in a separate class
 
@@ -516,8 +520,16 @@ reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
 		memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height);
 		// Save totalWidth, totalHeight
 		// TODO: Save the whole bitmap header, like SSCI does
-		WRITE_LE_UINT16(memoryPtr, width);
-		WRITE_LE_UINT16(memoryPtr + 2, height);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr, width);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0);
+		memoryPtr[8] = 0;
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE);
+		WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width);
+		WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, width);
 		return memoryId;
 		}
 		break;
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 05a9e6c..b86e0ff 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -179,6 +179,7 @@ void Kernel::mapSelectors() {
 	FIND_SELECTOR(fore);
 	FIND_SELECTOR(back);
 	FIND_SELECTOR(skip);
+	FIND_SELECTOR(borderColor);
 	FIND_SELECTOR(fixPriority);
 	FIND_SELECTOR(mirrored);
 	FIND_SELECTOR(visible);
@@ -187,6 +188,10 @@ void Kernel::mapSelectors() {
 	FIND_SELECTOR(inLeft);
 	FIND_SELECTOR(inBottom);
 	FIND_SELECTOR(inRight);
+	FIND_SELECTOR(textTop);
+	FIND_SELECTOR(textLeft);
+	FIND_SELECTOR(textBottom);
+	FIND_SELECTOR(textRight);
 	FIND_SELECTOR(magnifier);
 #endif
 }
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index a8b195f..6b724ed 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -146,6 +146,7 @@ struct SelectorCache {
 	Selector back;
 	Selector skip;
 	Selector dimmed;
+	Selector borderColor;
 
 	Selector fixPriority;
 	Selector mirrored;
@@ -153,6 +154,7 @@ struct SelectorCache {
 
 	Selector useInsetRect;
 	Selector inTop, inLeft, inBottom, inRight;
+	Selector textTop, textLeft, textBottom, textRight;
 
 	Selector magnifier;
 #endif
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index e2fdedb..c238e2c 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -128,6 +128,17 @@ struct Window : public Port, public Common::Serializable {
 
 #ifdef ENABLE_SCI32
 /**
+ * Multiplies a rectangle by two ratios with default
+ * rounding. Modifies the rect directly.
+ */
+inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+	rect.left = (rect.left * ratioX).toInt();
+	rect.top = (rect.top * ratioY).toInt();
+	rect.right = (rect.right * ratioX).toInt();
+	rect.bottom = (rect.bottom * ratioY).toInt();
+}
+
+/**
  * Multiplies a number by a rational number, rounding up to
  * the nearest whole number.
  */
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index 56ce73e..e0fb56b 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -31,6 +31,7 @@
 #include "sci/graphics/cache.h"
 #include "sci/graphics/compare.h"
 #include "sci/graphics/font.h"
+#include "sci/graphics/frameout.h"
 #include "sci/graphics/screen.h"
 #include "sci/graphics/text32.h"
 
@@ -38,20 +39,220 @@ namespace Sci {
 
 #define BITMAP_HEADER_SIZE 46
 
-#define SCI_TEXT32_ALIGNMENT_RIGHT -1
-#define SCI_TEXT32_ALIGNMENT_CENTER 1
-#define SCI_TEXT32_ALIGNMENT_LEFT	0
+GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) :
+	_segMan(segMan),
+	_cache(fonts),
+	_screen(screen),
+	_scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+	_scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+	_bitmap(NULL_REG) {}
+
+void GfxText32::buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const {
+
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 0, width);
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 2, height);
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 4, (uint16)displaceX);
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 6, (uint16)displaceY);
+	bitmap[8] = skipColor;
+	bitmap[9] = 0;
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 10, 0);
+
+	if (useRemap) {
+		bitmap[10] |= 2;
+	}
+
+	WRITE_SCI11ENDIAN_UINT32(bitmap + 12, width * height);
+	WRITE_SCI11ENDIAN_UINT32(bitmap + 16, 0);
+
+	if (hunkPaletteOffset) {
+		WRITE_SCI11ENDIAN_UINT32(bitmap + 20, hunkPaletteOffset + BITMAP_HEADER_SIZE);
+	} else {
+		WRITE_SCI11ENDIAN_UINT32(bitmap + 20, 0);
+	}
+
+	WRITE_SCI11ENDIAN_UINT32(bitmap + 24, BITMAP_HEADER_SIZE);
+	WRITE_SCI11ENDIAN_UINT32(bitmap + 28, BITMAP_HEADER_SIZE);
+	WRITE_SCI11ENDIAN_UINT32(bitmap + 32, 0);
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 36, scaledWidth);
+	WRITE_SCI11ENDIAN_UINT16(bitmap + 38, scaledHeight);
+}
+
+int16 GfxText32::_defaultFontId = 0;
+
+reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling, reg_t *outBitmapObject) {
+
+	_field_22 = 0;
+	_borderColor = borderColor;
+	_text = text;
+	_textRect = rect;
+	_width = width;
+	_height = height;
+	_foreColor = foreColor;
+	_backColor = backColor;
+	_skipColor = skipColor;
+	_alignment = alignment;
+	_dimmed = dimmed;
+
+	if (fontId != _fontId) {
+		_fontId = fontId == -1 ? _defaultFontId : fontId;
+		_font = _cache->getFont(_fontId);
+	}
+
+	if (doScaling) {
+		int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+		int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+		Ratio scaleX(_scaledWidth, scriptWidth);
+		Ratio scaleY(_scaledHeight, scriptHeight);
+
+		_width = (_width * scaleX).toInt();
+		_height = (_height * scaleY).toInt();
+		mul(_textRect, scaleX, scaleY);
+	}
+
+	// _textRect represents where text is drawn inside the
+	// bitmap; clipRect is the entire bitmap
+	Common::Rect bitmapRect(_width, _height);
+
+	if (_textRect.intersects(bitmapRect)) {
+		_textRect.clip(bitmapRect);
+	} else {
+		_textRect = Common::Rect();
+	}
+
+	_bitmap = _segMan->allocateHunkEntry("FontBitmap()", _width * _height + BITMAP_HEADER_SIZE);
+
+	byte *bitmap = _segMan->getHunkPointer(_bitmap);
+	buildBitmapHeader(bitmap, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
+
+	erase(bitmapRect, false);
+
+	if (_borderColor > -1) {
+		drawFrame(bitmapRect, 1, _borderColor, false);
+	}
+
+	drawTextBox();
+
+	debug("Drawing a bitmap %dx%d, scaled %dx%d, border %d, font %d", width, height, _width, _height, _borderColor, _fontId);
 
-GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen)
-	: _segMan(segMan), _cache(fonts), _screen(screen) {
+	*outBitmapObject = _bitmap;
+	return _bitmap;
 }
 
-GfxText32::~GfxText32() {
+reg_t GfxText32::createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1) {
+	warning("TODO: createTitledFontBitmap");
+	return NULL_REG;
+}
+
+void GfxText32::drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling) {
+	Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
+
+	byte *bitmap = _segMan->getHunkPointer(_bitmap);
+	byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
+
+	// NOTE: Not fully disassembled, but this should be right
+	// TODO: Implement variable frame size
+	assert(size == 1);
+	Buffer buffer(_width, _height, pixels);
+	buffer.frameRect(targetRect, color);
+}
+
+// TODO: This is not disassembled
+void GfxText32::drawTextBox() {
+	int16 charCount = 0;
+	uint16 curX = 0, curY = 0;
+	const char *txt = _text.c_str();
+	int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0;
+	uint16 start = 0;
+
+	// Calculate total text height
+	while (*txt) {
+		charCount = GetLongest(txt, _textRect.width(), _font);
+		if (charCount == 0)
+			break;
+
+		Width(txt, 0, (int16)strlen(txt), _fontId, textWidth, textHeight, true);
+
+		totalHeight += textHeight;
+		txt += charCount;
+		while (*txt == ' ') {
+			txt++; // skip over breaking spaces
+		}
+	}
+
+	txt = _text.c_str();
+
+	byte *pixels = _segMan->getHunkPointer(_bitmap);
+	pixels = pixels + READ_SCI11ENDIAN_UINT32(pixels + 28) + _width * _textRect.top + _textRect.left;
+
+	// Draw text in buffer
+	while (*txt) {
+		charCount = GetLongest(txt, _textRect.width(), _font);
+		if (charCount == 0)
+			break;
+		Width(txt, start, charCount, _fontId, textWidth, textHeight, true);
+
+		switch (_alignment) {
+			case kTextAlignRight:
+				offsetX = _textRect.width() - textWidth;
+				break;
+			case kTextAlignCenter:
+				// Center text both horizontally and vertically
+				offsetX = (_textRect.width() - textWidth) / 2;
+				offsetY = (_textRect.height() - totalHeight) / 2;
+				break;
+			case kTextAlignLeft:
+				offsetX = 0;
+				break;
+
+			default:
+				warning("Invalid alignment %d used in TextBox()", _alignment);
+		}
+
+		byte curChar;
+
+		for (int i = 0; i < charCount; i++) {
+			curChar = txt[i];
+
+			switch (curChar) {
+				case 0x0A:
+				case 0x0D:
+				case 0:
+					break;
+				case 0x7C:
+					warning("Code processing isn't implemented in SCI32");
+					break;
+				default:
+					_font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, _foreColor, _dimmed, pixels, _width, _height);
+					curX += _font->getCharWidth(curChar);
+					break;
+			}
+		}
+
+		curX = 0;
+		curY += _font->getHeight();
+		txt += charCount;
+		while (*txt == ' ') {
+			txt++; // skip over breaking spaces
+		}
+	}
+}
+
+void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
+	Common::Rect targetRect = doScaling ? rect : scaleRect(rect);
+
+	byte *bitmap = _segMan->getHunkPointer(_bitmap);
+	byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
+
+	// NOTE: There is an extra optimisation within the SCI code to
+	// do a single memset if the scaledRect is the same size as
+	// the bitmap, not implemented here.
+	Buffer buffer(_width, _height, pixels);
+	buffer.fillRect(targetRect, _backColor);
 }
 
 reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
 	return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk);
-
 }
 reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) {
 	reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text));
@@ -116,8 +317,16 @@ reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject
 	memset(bitmap, backColor, width * height);
 
 	// Save totalWidth, totalHeight
-	WRITE_LE_UINT16(memoryPtr, width);
-	WRITE_LE_UINT16(memoryPtr + 2, height);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr, width);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 2, height);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 4, 0);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 6, 0);
+	memoryPtr[8] = 0;
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 10, 0);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 20, BITMAP_HEADER_SIZE);
+	WRITE_SCI11ENDIAN_UINT32(memoryPtr + 28, 46);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 36, width);
+	WRITE_SCI11ENDIAN_UINT16(memoryPtr + 38, height);
 
 	int16 charCount = 0;
 	uint16 curX = 0, curY = 0;
@@ -149,15 +358,15 @@ reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject
 		Width(txt, start, charCount, fontId, textWidth, textHeight, true);
 
 		switch (alignment) {
-		case SCI_TEXT32_ALIGNMENT_RIGHT:
+		case kTextAlignRight:
 			offsetX = width - textWidth;
 			break;
-		case SCI_TEXT32_ALIGNMENT_CENTER:
+		case kTextAlignCenter:
 			// Center text both horizontally and vertically
 			offsetX = (width - textWidth) / 2;
 			offsetY = (height - totalHeight) / 2;
 			break;
-		case SCI_TEXT32_ALIGNMENT_LEFT:
+		case kTextAlignLeft:
 			offsetX = 0;
 			break;
 
@@ -347,9 +556,10 @@ int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId
 	int16 maxTextWidth = 0, textWidth;
 	int16 totalHeight = 0, textHeight;
 
-	// Adjust maxWidth if we're using an upscaled font
-	if (_screen->fontIsUpscaled())
-		maxWidth = maxWidth * _screen->getDisplayWidth() / _screen->getWidth();
+	int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+	maxWidth = maxWidth * _scaledWidth / scriptWidth;
 
 	rect.top = rect.left = 0;
 	GfxFont *font = _cache->getFont(fontId);
@@ -378,12 +588,8 @@ int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId
 		rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth);
 	}
 
-	// Adjust the width/height if we're using an upscaled font
-	// for the scripts
-	if (_screen->fontIsUpscaled()) {
-		rect.right = rect.right * _screen->getWidth() / _screen->getDisplayWidth();
-		rect.bottom = rect.bottom * _screen->getHeight() / _screen->getDisplayHeight();
-	}
+	rect.right = rect.right * scriptWidth / _scaledWidth;
+	rect.bottom = rect.bottom * scriptHeight / _scaledHeight;
 
 	return rect.right;
 }
diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h
index 7ba7df5..42804f5 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,15 +23,148 @@
 #ifndef SCI_GRAPHICS_TEXT32_H
 #define SCI_GRAPHICS_TEXT32_H
 
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+
 namespace Sci {
 
+enum TextAlign {
+	kTextAlignLeft   = 0,
+	kTextAlignCenter = 1,
+	kTextAlignRight  = -1
+};
+
 /**
- * Text32 class, handles text calculation and displaying of text for SCI2, SCI21 and SCI3 games
+ * This class handles text calculation and rendering for
+ * SCI32 games. The text calculation system in SCI32 is
+ * nearly the same as SCI16, which means this class behaves
+ * similarly. Notably, GfxText32 maintains drawing
+ * parameters across multiple calls.
  */
 class GfxText32 {
+private:
+	/**
+	 * The resource ID of the default font used by the game.
+	 *
+	 * @todo Check all SCI32 games to learn what their
+	 * default font is.
+	 */
+	static int16 _defaultFontId;
+
+	/**
+	 * The width and height of the currently active text
+	 * bitmap, in text-system coordinates.
+	 */
+	int16 _width, _height;
+
+	/**
+	 * The colour used to draw text.
+	 */
+	uint8 _foreColor;
+
+	/**
+	 * The background colour of the text box.
+	 */
+	uint8 _backColor;
+
+	/**
+	 * The transparent colour of the text box. Used when
+	 * compositing the bitmap onto the screen.
+	 */
+	uint8 _skipColor;
+
+	/**
+	 * The rect where the text is drawn within the bitmap.
+	 * This rect is clipped to the dimensions of the bitmap.
+	 */
+	Common::Rect _textRect;
+
+	/**
+	 * The text being drawn to the currently active text
+	 * bitmap.
+	 */
+	Common::String _text;
+
+	/**
+	 * The font being used to draw the text.
+	 */
+	GuiResourceId _fontId;
+
+	/**
+	 * The colour of the text box border.
+	 */
+	int16 _borderColor;
+
+	/**
+	 * TODO: Document
+	 */
+	bool _dimmed;
+
+	/**
+	 * The text alignment for the drawn text.
+	 */
+	TextAlign _alignment;
+
+	/**
+	 * The memory handle of the currently active bitmap.
+	 */
+	reg_t _bitmap;
+
+	/**
+	 * TODO: Document
+	 */
+	int _field_22;
+
+	/**
+	 * The currently active font resource used to write text
+	 * into the bitmap.
+	 *
+	 * @note SCI engine builds the font table directly
+	 * inside of FontMgr; we use GfxFont instead.
+	 */
+	GfxFont *_font;
+
+	// TODO: This is general for all CelObjMem and should be
+	// put in a single location, like maybe as a static
+	// method of CelObjMem?!
+	void buildBitmapHeader(byte *bitmap, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool useRemap) const;
+
+	void drawFrame(const Common::Rect &rect, const int size, const uint8 color, const bool doScaling);
+	void drawTextBox();
+	void erase(const Common::Rect &rect, const bool doScaling);
+
+	inline Common::Rect scaleRect(const Common::Rect &rect) {
+		Common::Rect scaledRect(rect);
+		int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+		int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+		Ratio scaleX(_scaledWidth, scriptWidth);
+		Ratio scaleY(_scaledHeight, scriptHeight);
+		mul(scaledRect, scaleX, scaleY);
+		return scaledRect;
+	}
+
 public:
 	GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen);
-	~GfxText32();
+
+	/**
+	 * The size of the x-dimension of the coordinate system
+	 * used by the text renderer.
+	 */
+	int16 _scaledWidth;
+
+	/**
+	 * The size of the y-dimension of the coordinate system
+	 * used by the text renderer.
+	 */
+	int16 _scaledHeight;
+
+	reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling, reg_t *outBitmapObject);
+
+	reg_t createTitledFontBitmap(CelInfo32 &celInfo, Common::Rect &rect, Common::String &text, int16 foreColor, int16 backColor, int font, int16 skipColor, int16 borderColor, bool dimmed, void *unknown1);
+
+#pragma mark -
+#pragma mark Old stuff
+
 	reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
 	reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG);
 	void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject);
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 9ef28b2..49eb31d 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -690,10 +690,10 @@ void SciEngine::initGraphics() {
 		_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
 		_gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);
 		_gfxPaint = _gfxPaint32;
-		_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
-		_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
 		_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
 		_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
+		_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
+		_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
 		_gfxFrameout->run();
 	} else {
 #endif
diff --git a/engines/sci/util.cpp b/engines/sci/util.cpp
index c72d3be..ccec41a 100644
--- a/engines/sci/util.cpp
+++ b/engines/sci/util.cpp
@@ -69,4 +69,13 @@ void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val) {
 		WRITE_LE_UINT16(ptr, val);
 }
 
+#ifdef ENABLE_SCI32
+void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val) {
+	if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1)
+		WRITE_BE_UINT32(ptr, val);
+	else
+		WRITE_LE_UINT32(ptr, val);
+}
+#endif
+
 } // End of namespace Sci
diff --git a/engines/sci/util.h b/engines/sci/util.h
index 3780309..b0fee51 100644
--- a/engines/sci/util.h
+++ b/engines/sci/util.h
@@ -37,6 +37,9 @@ void WRITE_SCIENDIAN_UINT16(void *ptr, uint16 val);
 uint16 READ_SCI11ENDIAN_UINT16(const void *ptr);
 uint32 READ_SCI11ENDIAN_UINT32(const void *ptr);
 void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val);
+#ifdef ENABLE_SCI32
+void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val);
+#endif
 
 // Wrappers for reading integer values in resources that are
 // LE in SCI1.1 Mac, but BE in SCI32 Mac


Commit: 2c0e64fdaf8f2ec2b28124d488be68f36cb25317
    https://github.com/scummvm/scummvm/commit/2c0e64fdaf8f2ec2b28124d488be68f36cb25317
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Add short-lived kSetFontRes kernel function

kSetFontRes didn't exist in SCI2, showed up in SCI2.1early,
then was replaced with kFont subop 1 in SCI2.1mid.

Changed paths:
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics32.cpp



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index b4a092a..fbd0b13 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -65,6 +65,7 @@ struct SciKernelMapSubEntry {
 #define SIG_SCI1            SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
 #define SIG_SCI11           SCI_VERSION_1_1, SCI_VERSION_1_1
 #define SIG_SINCE_SCI11     SCI_VERSION_1_1, SCI_VERSION_NONE
+#define SIG_SCI21EARLY_ONLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY
 #define SIG_SINCE_SCI21     SCI_VERSION_2_1_EARLY, SCI_VERSION_3
 #define SIG_UNTIL_SCI21MID  SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
 #define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3
@@ -670,7 +671,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(CelInfo),           SIG_EVERYWHERE,           "iiiiii",                NULL,            NULL },
 	{ MAP_CALL(SetLanguage),       SIG_EVERYWHERE,           "r",                     NULL,            NULL },
 	{ MAP_CALL(ScrollWindow),      SIG_EVERYWHERE,           "i(.*)",                 kScrollWindow_subops, NULL },
-	{ MAP_DUMMY(SetFontRes),       SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
+	{ MAP_CALL(SetFontRes),        SIG_SCI21EARLY_ONLY, SIGFOR_ALL, "ii",             NULL,            NULL },
 	{ MAP_CALL(Font),              SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL },
 	{ MAP_CALL(Bitmap),            SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_CALL(AddLine),           SIG_EVERYWHERE,           "oiiiiiiiii",            NULL,            NULL },
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index a83836b..cf53944 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -482,9 +482,10 @@ reg_t kFont(EngineState *s, int argc, reg_t *argv) {
 	return s->r_acc;
 }
 
-// TODO: Is this actually a thing??
 reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
-	return kStub(s, argc, argv);
+	g_sci->_gfxText32->_scaledWidth = argv[0].toUint16();
+	g_sci->_gfxText32->_scaledHeight = argv[1].toUint16();
+	return NULL_REG;
 }
 
 // TODO: Eventually, all of the kBitmap operations should be put


Commit: 775c39102a8999e7ea62313ce00b4b3f51780fc7
    https://github.com/scummvm/scummvm/commit/775c39102a8999e7ea62313ce00b4b3f51780fc7
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Implement templated drawing subroutines

Changed paths:
    engines/sci/graphics/celobj32.cpp
    engines/sci/graphics/celobj32.h



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 4b8d9af..e0f5668 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -97,6 +97,178 @@ void CelObj::deinit() {
 	_cache = nullptr;
 }
 
+#pragma mark -
+#pragma mark CelObj - Scalers
+
+template <bool FLIP, typename READER>
+struct SCALER_NoScale {
+	const byte *_row;
+	READER _reader;
+	const int16 _lastIndex;
+
+	SCALER_NoScale(const CelObj &celObj, const int16 maxWidth) :
+	_reader(celObj, maxWidth),
+	_lastIndex(maxWidth - 1) {}
+
+	inline void setSource(const int16 x, const int16 y) {
+		_row = _reader.getRow(y);
+
+		if (FLIP) {
+			_row += _lastIndex - x;
+		} else {
+			_row += x;
+		}
+	}
+
+	inline byte read() {
+		if (FLIP) {
+			return *_row--;
+		} else {
+			return *_row++;
+		}
+	}
+};
+
+template<bool FLIP, typename READER>
+struct SCALER_Scale {
+	const byte *_row;
+	READER _reader;
+	const CelScalerTable *_table;
+	int16 _x;
+	const uint16 _lastIndex;
+
+	SCALER_Scale(const CelObj &celObj, const int16 maxWidth, const Ratio scaleX, const Ratio scaleY) :
+	// The maximum width of the scaled object may not be as
+	// wide as the source data it requires if downscaling,
+	// so just always make the reader decompress an entire
+	// line of source data when scaling
+	_reader(celObj, celObj._width),
+	_lastIndex(maxWidth - 1),
+	_table(CelObj::_scaler->getScalerTable(scaleX, scaleY)) {}
+
+	inline void setSource(const int16 x, const int16 y) {
+		_row = _reader.getRow(_table->valuesY[y]);
+		if (FLIP) {
+			_x = _lastIndex - x;
+		} else {
+			_x = x;
+		}
+	}
+
+	inline byte read() {
+		if (FLIP) {
+			return _row[_table->valuesX[_x--]];
+		} else {
+			return _row[_table->valuesX[_x++]];
+		}
+	}
+};
+
+#pragma mark -
+#pragma mark CelObj - Resource readers
+
+struct READER_Uncompressed {
+private:
+	byte *_pixels;
+	const int16 _sourceWidth;
+	const int16 _maxWidth;
+
+public:
+	READER_Uncompressed(const CelObj &celObj, const int16 maxWidth) :
+	_sourceWidth(celObj._width),
+	_maxWidth(maxWidth) {
+		byte *resource = celObj.getResPointer();
+		_pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24);
+	}
+
+	inline const byte *getRow(const int16 y) const {
+		return _pixels + y * _sourceWidth;
+	}
+};
+
+struct READER_Compressed {
+private:
+	byte *_resource;
+	byte _buffer[1024];
+	uint32 _controlOffset;
+	uint32 _dataOffset;
+	uint32 _uncompressedDataOffset;
+	int16 _y;
+	const int16 _sourceHeight;
+	const uint8 _transparentColor;
+	const int16 _maxWidth;
+
+public:
+	READER_Compressed(const CelObj &celObj, const int16 maxWidth) :
+	_y(-1),
+	_maxWidth(maxWidth),
+	_resource(celObj.getResPointer()),
+	_sourceHeight(celObj._height),
+	_transparentColor(celObj._transparentColor) {
+		assert(_maxWidth <= celObj._width);
+
+		byte *celHeader = _resource + celObj._celHeaderOffset;
+		_dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+		_uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+		_controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+	}
+
+	inline const byte *getRow(const int16 y) {
+		if (y != _y) {
+			// compressed data segment for row
+			byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
+
+			// uncompressed data segment for row
+			byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
+
+			uint8 length;
+			for (int i = 0; i < _maxWidth; i += length) {
+				byte controlByte = *row++;
+				length = controlByte;
+
+				// Run-length encoded
+				if (controlByte & 0x80) {
+					length &= 0x3F;
+					assert(i + length < (int)sizeof(_buffer));
+
+					// Fill with skip color
+					if (controlByte & 0x40) {
+						memset(_buffer + i, _transparentColor, length);
+					// Next value is fill colour
+					} else {
+						memset(_buffer + i, *literal, length);
+						++literal;
+					}
+				// Uncompressed
+				} else {
+					assert(i + length < (int)sizeof(_buffer));
+					memcpy(_buffer + i, literal, length);
+					literal += length;
+				}
+			}
+			_y = y;
+		}
+
+		return _buffer;
+	}
+};
+
+#pragma mark -
+#pragma mark CelObj - Remappers
+
+struct MAPPER_NoMD {
+	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+		if (pixel != skipColor) {
+			*target = pixel;
+		}
+	}
+};
+struct MAPPER_NoMDNoSkip {
+	inline void draw(byte *target, const byte pixel, const uint8) const {
+		*target = pixel;
+	}
+};
+
 void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
 	const Buffer &priorityMap = g_sci->_gfxFrameout->getPriorityMap();
 	const Common::Point &scaledPosition = screenItem._scaledPosition;
@@ -105,7 +277,7 @@ void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Re
 
 	if (_remap) {
 		if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
-			const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+			const uint8 priority = MAX((int16)0, MIN((int16)255, screenItem._priority));
 
 			// NOTE: In the original engine code, there was a second branch for
 			// _remap here that would then call the following functions if _remap was false:
@@ -193,7 +365,7 @@ void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Re
 		}
 	} else {
 		if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
-			const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+			const uint8 priority = MAX((int16)0, MIN((int16)255, screenItem._priority));
 			if (scaleX.isOne() && scaleY.isOne()) {
 				if (_compressionType == kCelCompressionNone) {
 					if (_drawMirrored) {
@@ -309,57 +481,16 @@ void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Poin
 }
 
 uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
-	byte *resource = getResPointer();
-	byte *celHeader = resource + _celHeaderOffset;
-
 	if (mirrorX) {
 		x = _width - x - 1;
 	}
 
 	if (_compressionType == kCelCompressionNone) {
-		byte *pixels = resource + READ_SCI11ENDIAN_UINT32(celHeader + 24);
-		return pixels[y * _width + x];
+		READER_Uncompressed reader(*this, x + 1);
+		return reader.getRow(y)[x];
 	} else {
-		byte buffer[1024];
-
-		uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
-		uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
-		uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
-
-		// compressed data segment for row
-		byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
-
-		// uncompressed data segment for row
-		byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
-
-		uint8 length;
-		byte controlByte;
-		for (uint i = 0; i <= x; i += length) {
-			controlByte = *row++;
-			length = controlByte;
-
-			// Run-length encoded
-			if (controlByte & 0x80) {
-				length &= 0x3F;
-				assert(i + length < sizeof(buffer));
-
-				// Fill with skip color
-				if (controlByte & 0x40) {
-					memset(buffer + i, _transparentColor, length);
-				// Next value is fill colour
-				} else {
-					memset(buffer + i, *literal, length);
-					++literal;
-				}
-			// Uncompressed
-			} else {
-				assert(i + length < sizeof(buffer));
-				memcpy(buffer + i, literal, length);
-				literal += length;
-			}
-		}
-
-		return buffer[x];
+		READER_Compressed reader(*this, x + 1);
+		return reader.getRow(y)[x];
 	}
 }
 
@@ -425,6 +556,57 @@ void CelObj::putCopyInCache(const int cacheIndex) const {
 
 #pragma mark -
 #pragma mark CelObj - Drawing
+
+template <typename MAPPER, typename SCALER>
+struct RENDERER {
+	MAPPER &_mapper;
+	SCALER &_scaler;
+	const uint8 _skipColor;
+
+	RENDERER(MAPPER &mapper, SCALER &scaler, const uint8 skipColor) :
+	_mapper(mapper),
+	_scaler(scaler),
+	_skipColor(skipColor) {}
+
+	inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+		const int16 sourceX = targetRect.left - scaledPosition.x;
+		const int16 sourceY = targetRect.top - scaledPosition.y;
+
+		byte *targetPixel = (byte *)target.getPixels() + target.screenWidth * targetRect.top + targetRect.left;
+
+		const int16 skipStride = target.screenWidth - targetRect.width();
+		const int16 targetWidth = targetRect.width();
+		const int16 targetHeight = targetRect.height();
+		for (int y = 0; y < targetHeight; ++y) {
+			_scaler.setSource(sourceX, sourceY + y);
+
+			for (int x = 0; x < targetWidth; ++x) {
+				_mapper.draw(targetPixel++, _scaler.read(), _skipColor);
+			}
+
+			targetPixel += skipStride;
+		}
+	}
+};
+
+template <typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+
+	MAPPER mapper;
+	SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width());
+	RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+	renderer.draw(target, targetRect, scaledPosition);
+}
+
+template <typename MAPPER, typename SCALER>
+void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const {
+
+	MAPPER mapper;
+	SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaleX, scaleY);
+	RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor);
+	renderer.draw(target, targetRect, scaledPosition);
+}
+
 void dummyFill(Buffer &target, const Common::Rect &targetRect) {
 	target.fillRect(targetRect, 250);
 }
@@ -477,173 +659,42 @@ void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio
 	debug("scaleDrawUncompMap");
 	dummyFill(target, targetRect);
 }
-void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawHzFlipNoMD");
-	dummyFill(target, targetRect);
-}
-void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	const int sourceX = targetRect.left - scaledPosition.x;
-	const int sourceY = targetRect.top - scaledPosition.y;
-
-	byte *targetPixel = (byte *)target.getPixels() + (targetRect.top * target.screenWidth) + targetRect.left;
-
-	const int stride = target.screenWidth - targetRect.width();
 
-	byte *resource = getResPointer();
-	byte *celHeader = resource + _celHeaderOffset;
-
-	byte buffer[1024];
-
-	uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
-	uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
-	uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
-
-	for (int y = sourceY; y < sourceY + targetRect.height(); ++y) {
-		// compressed data segment for row
-		byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
-
-		// uncompressed data segment for row
-		byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
-
-		uint8 length;
-		byte controlByte;
-		for (int i = 0; i <= targetRect.width(); i += length) {
-			controlByte = *row++;
-			length = controlByte;
-
-			// Run-length encoded
-			if (controlByte & 0x80) {
-				length &= 0x3F;
-				assert(i + length < (int)sizeof(buffer));
-
-				// Fill with skip color
-				if (controlByte & 0x40) {
-					memset(buffer + i, _transparentColor, length);
-					// Next value is fill colour
-				} else {
-					memset(buffer + i, *literal, length);
-					++literal;
-				}
-				// Uncompressed
-			} else {
-				assert(i + length < (int)sizeof(buffer));
-				memcpy(buffer + i, literal, length);
-				literal += length;
-			}
-		}
-
-		for (int x = 0; x < targetRect.width(); ++x) {
-			byte pixel = buffer[sourceX + x];
-
-			if (pixel != _transparentColor) {
-				*targetPixel = pixel;
-			}
-
-			++targetPixel;
-		}
-
-		targetPixel += stride;
-	}
+void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	render<MAPPER_NoMD, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
 }
-void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	const int sourceX = targetRect.left - scaledPosition.x;
-	const int sourceY = targetRect.top - scaledPosition.y;
-
-	const int sourceStride = _width - targetRect.width();
-	const int targetStride = target.screenWidth - targetRect.width();
-	const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
-
-	byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
-	byte *targetPixel = (byte *)target.getPixels() + targetRect.top * target.screenWidth + targetRect.left;
-
-	for (int y = 0; y < targetRect.height(); ++y) {
-		for (int x = 0; x < targetRect.width(); ++x) {
-			byte pixel = *sourcePixel++;
 
-			if (pixel != _transparentColor) {
-				*targetPixel = pixel;
-			}
-
-			++targetPixel;
-		}
-
-		sourcePixel += sourceStride;
-		targetPixel += targetStride;
-	}
+void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	render<MAPPER_NoMD, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
 }
-void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	const int sourceX = targetRect.left - scaledPosition.x;
-	const int sourceY = targetRect.top - scaledPosition.y;
-	const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
-
-	byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
 
-	target.copyRectToSurface(sourcePixel, _width, targetRect.left, targetRect.top, targetRect.width(), targetRect.height());
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	render<MAPPER_NoMD, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
+}
 
+void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+	render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
 }
 void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawUncompHzFlipNoMD");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
 }
 void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawUncompHzFlipNoMDNoSkip");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
 }
+
 void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("scaleDrawNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
-	dummyFill(target, targetRect);
+	if (_drawMirrored) {
+		render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	} else {
+		render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("scaleDrawUncompNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
-	if (targetRect.isEmpty()) {
-		return;
-	}
-
-	const CelScalerTable *table = _scaler->getScalerTable(scaleX, scaleY);
-
-	int pixelX[1024];
-	int pixelY[1024];
-
-	bool use2xOptimisedDrawRoutine = false /* TODO: scaleX.getDenominator() * 2 == scaleX.getNumerator() */;
-
-	int16 sourceX = (scaledPosition.x * scaleX.getInverse()).toInt();
-	int16 sourceY = (scaledPosition.y * scaleY.getInverse()).toInt();
-
 	if (_drawMirrored) {
-		for (int x = targetRect.left; x < targetRect.right; ++x) {
-			pixelX[x] = _width - 1 - (table->valuesX[x] - sourceX);
-		}
+		render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
 	} else {
-		for (int x = targetRect.left; x < targetRect.right; ++x) {
-			pixelX[x] = table->valuesX[x] - sourceX;
-		}
-	}
-
-	for (int y = targetRect.top; y < targetRect.bottom; ++y) {
-		pixelY[y] = table->valuesY[y] - sourceY;
-	}
-
-	byte *sourcePixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
-
-	for (int y = targetRect.top; y < targetRect.bottom; ++y) {
-		byte *targetPixel = target.getAddress(targetRect.left, y);
-		byte *sourcePixel = sourcePixels + pixelY[y] * _width;
-		const int *sourcePixelIndex = pixelX + targetRect.left;
-
-		if (/* TODO */ use2xOptimisedDrawRoutine) {
-			// WriteUncompScaleLine2();
-		} else {
-			// start WriteUncompScaleLine
-			for (int x = targetRect.left; x < targetRect.right; ++x) {
-				byte value = sourcePixel[*sourcePixelIndex++];
-				if (value != _transparentColor) {
-					*targetPixel = value;
-				}
-				++targetPixel;
-			}
-			// end WriteUncompScaleLine
-		}
+		render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
 	}
 }
 
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index 8bda86e..2e1b425 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -205,9 +205,6 @@ class ScreenItem;
  * pixel buffer.
  */
 class CelObj {
-private:
-	static CelScaler *_scaler;
-
 protected:
 	/**
 	 * When true, this cel will be horizontally mirrored
@@ -219,6 +216,8 @@ protected:
 	bool _drawMirrored;
 
 public:
+	static CelScaler *_scaler;
+
 	/**
 	 * The basic identifying information for this cel. This
 	 * information effectively acts as a composite key for
@@ -380,6 +379,12 @@ public:
 #pragma mark -
 #pragma mark CelObj - Drawing
 private:
+	template <typename MAPPER, typename SCALER>
+	void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+
+	template <typename MAPPER, typename SCALER>
+	void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
 	void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
 	void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
 	void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;


Commit: bb8235063087f5e760b900c32aece5fec614598f
    https://github.com/scummvm/scummvm/commit/bb8235063087f5e760b900c32aece5fec614598f
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:02-06:00

Commit Message:
SCI: Fix broken LRU debugging

Changed paths:
    engines/sci/resource.cpp



diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index f357812..92d35ba 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -998,9 +998,9 @@ void ResourceManager::addToLRU(Resource *res) {
 	_LRU.push_front(res);
 	_memoryLRU += res->size;
 #if SCI_VERBOSE_RESMAN
-	debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total",
-	      getResourceTypeName(res->type), res->number, res->size,
-	      mgr->_memoryLRU);
+	debug("Adding %s (%d bytes) to lru control: %d bytes total",
+	      res->_id.toString().c_str(), res->size,
+	      _memoryLRU);
 #endif
 	res->_status = kResStatusEnqueued;
 }
@@ -1029,7 +1029,7 @@ void ResourceManager::freeOldResources() {
 		removeFromLRU(goner);
 		goner->unalloc();
 #ifdef SCI_VERBOSE_RESMAN
-		debug("resMan-debug: LRU: Freeing %s.%03d (%d bytes)", getResourceTypeName(goner->type), goner->number, goner->size);
+		debug("resMan-debug: LRU: Freeing %s (%d bytes)", goner->_id.toString().c_str(), goner->size);
 #endif
 	}
 }


Commit: 2f17ba2b0ab77ef939c21efa04f7aaafccbd0c37
    https://github.com/scummvm/scummvm/commit/2f17ba2b0ab77ef939c21efa04f7aaafccbd0c37
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: Increase LRU resource cache for SCI32 games

A single picture in SCI32 is often larger than the 256KiB limit,
meaning that the cache is useless for these games -- which is bad,
because the renderer works directly off raw resource data so it
must be decompressed and in-cache for rendering performance to be
acceptable.

Changed paths:
    engines/sci/resource.cpp
    engines/sci/resource.h



diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 92d35ba..86c5f52 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -865,6 +865,7 @@ ResourceManager::ResourceManager() {
 }
 
 void ResourceManager::init() {
+	_maxMemoryLRU = 256 * 1024; // 256KiB
 	_memoryLocked = 0;
 	_memoryLRU = 0;
 	_LRU.clear();
@@ -918,6 +919,14 @@ void ResourceManager::init() {
 
 	debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion()));
 
+	// Resources in SCI32 games are significantly larger than SCI16
+	// games and can cause immediate exhaustion of the LRU resource
+	// cache, leading to constant decompression of picture resources
+	// and making the renderer very slow.
+	if (getSciVersion() >= SCI_VERSION_2) {
+		_maxMemoryLRU = 2048 * 1024; // 2MiB
+	}
+
 	switch (_viewType) {
 	case kViewEga:
 		debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources");
@@ -1023,7 +1032,7 @@ void ResourceManager::printLRU() {
 }
 
 void ResourceManager::freeOldResources() {
-	while (MAX_MEMORY < _memoryLRU) {
+	while (_maxMemoryLRU < _memoryLRU) {
 		assert(!_LRU.empty());
 		Resource *goner = *_LRU.reverse_begin();
 		removeFromLRU(goner);
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index eb5b508..46ac2bb 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -426,9 +426,7 @@ protected:
 	// Note: maxMemory will not be interpreted as a hard limit, only as a restriction
 	// for resources which are not explicitly locked. However, a warning will be
 	// issued whenever this limit is exceeded.
-	enum {
-		MAX_MEMORY = 256 * 1024	// 256KB
-	};
+	int _maxMemoryLRU;
 
 	ViewType _viewType; // Used to determine if the game has EGA or VGA graphics
 	Common::List<ResourceSource *> _sources;


Commit: 3c9b93050643579a379249b29c36a74acf1fdaa0
    https://github.com/scummvm/scummvm/commit/3c9b93050643579a379249b29c36a74acf1fdaa0
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: Add comments to plane code

Changed paths:
    engines/sci/graphics/plane32.cpp



diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 548e7e9..297c6e3 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -377,6 +377,7 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
 		}
 	}
 
+	// Remove parts of eraselist/drawlist that are covered by other planes
 	breakEraseListByPlanes(eraseList, planeList);
 	breakDrawListByPlanes(drawList, planeList);
 


Commit: 03e3f2c68cef429983787ddd38e74718db1ee8d1
    https://github.com/scummvm/scummvm/commit/03e3f2c68cef429983787ddd38e74718db1ee8d1
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: Fix some rect off-by-ones

Changed paths:
    engines/sci/engine/kgraphics32.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/helpers.h
    engines/sci/graphics/plane32.cpp
    engines/sci/graphics/screen_item32.cpp



diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index cf53944..03cbd66 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -204,8 +204,8 @@ reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) {
 	Common::Rect rect(
 		readSelectorValue(segMan, object, SELECTOR(textLeft)),
 		readSelectorValue(segMan, object, SELECTOR(textTop)),
-		readSelectorValue(segMan, object, SELECTOR(textRight)),
-		readSelectorValue(segMan, object, SELECTOR(textBottom))
+		readSelectorValue(segMan, object, SELECTOR(textRight)) + 1,
+		readSelectorValue(segMan, object, SELECTOR(textBottom)) + 1
 	);
 
 	if (subop == 0) {
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index d748cc0..0bf5b4e 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -144,7 +144,7 @@ void GfxFrameout::run() {
 	// NOTE: This happens in SCI::InitPlane in the actual engine,
 	// and is a background fill plane to ensure hidden planes
 	// (planes with a priority of -1) are never drawn
-	Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth - 1, _currentBuffer.scriptHeight - 1));
+	Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight));
 	initPlane->_priority = 0;
 	_planes.add(initPlane);
 }
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index c238e2c..fbad120 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -142,13 +142,13 @@ inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common
  * Multiplies a number by a rational number, rounding up to
  * the nearest whole number.
  */
-inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
-	int num = (value + extra) * ratio.getNumerator();
+inline int mulru(const int value, const Common::Rational &ratio) {
+	int num = value * ratio.getNumerator();
 	int result = num / ratio.getDenominator();
 	if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
 		++result;
 	}
-	return result - extra;
+	return result;
 }
 
 /**
@@ -165,12 +165,19 @@ inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Co
  * Multiplies a point by two rational numbers for X and Y,
  * rounding up to the nearest whole number. Modifies the
  * rect directly.
+ *
+ * @note In SCI engine, the bottom-right corner of rects
+ * received an additional one pixel during the
+ * multiplication in order to round up to include the
+ * bottom-right corner. Since ScummVM rects do not include
+ * the bottom-right corner, doing this ends up making rects
+ * a pixel too wide/tall depending upon the remainder.
  */
-inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int brExtra = 0) {
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) {
 	rect.left = mulru(rect.left, ratioX);
 	rect.top = mulru(rect.top, ratioY);
-	rect.right = mulru(rect.right, ratioX, brExtra);
-	rect.bottom = mulru(rect.bottom, ratioY, brExtra);
+	rect.right = mulru(rect.right, ratioX);
+	rect.bottom = mulru(rect.bottom, ratioY);
 }
 
 struct Buffer : public Graphics::Surface {
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 297c6e3..33c01cf 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -78,8 +78,8 @@ _moved(0) {
 
 	_gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
 	_gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
-	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
-	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
 	convertGameRectToPlaneRect();
 
 	_back = readSelectorValue(segMan, object, SELECTOR(back));
@@ -136,7 +136,7 @@ void Plane::convertGameRectToPlaneRect() {
 	const Ratio ratioY = Ratio(screenHeight, scriptHeight);
 
 	_planeRect = _gameRect;
-	mulru(_planeRect, ratioX, ratioY, 1);
+	mulru(_planeRect, ratioX, ratioY);
 }
 
 void Plane::printDebugInfo(Console *con) const {
@@ -752,8 +752,8 @@ void Plane::update(const reg_t object) {
 	_vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
 	_gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
 	_gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
-	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
-	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+	_gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+	_gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
 	convertGameRectToPlaneRect();
 
 	_priority = readSelectorValue(segMan, object, SELECTOR(priority));
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index 300912f..ed9adcf 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -209,8 +209,8 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
 		_useInsetRect = true;
 		_insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
 		_insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
-		_insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
-		_insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+		_insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1;
+		_insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1;
 	} else {
 		_useInsetRect = false;
 	}
@@ -376,7 +376,7 @@ void ScreenItem::calcRects(const Plane &plane) {
 				Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
 				Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
 				mulru(_scaledPosition, celXRatio, celYRatio);
-				mulru(_screenItemRect, celXRatio, celYRatio, 1);
+				mulru(_screenItemRect, celXRatio, celYRatio);
 			}
 
 			_ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);


Commit: 766cf6cee7edbc3f6fa22a50381391725f70cabc
    https://github.com/scummvm/scummvm/commit/766cf6cee7edbc3f6fa22a50381391725f70cabc
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: Fix too-fast rendering

Now that the renderer is loading resources without spinning CPU time
on decompression every frame, it becomes apparent that kFrameOut is
spammed constantly by the interpreter and needs to be throttled to
ensure that transitions and fades work properly.

Changed paths:
    engines/sci/engine/kgraphics32.cpp



diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 03cbd66..f6b0074 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -127,6 +127,8 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
 reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
 	bool showBits = argc > 0 ? argv[0].toUint16() : true;
 	g_sci->_gfxFrameout->kernelFrameout(showBits);
+	s->speedThrottler(16);
+	s->_throttleTrigger = true;
 	return NULL_REG;
 }
 


Commit: 5a1fa5efa1459d7859432b21749587ef67f002d5
    https://github.com/scummvm/scummvm/commit/5a1fa5efa1459d7859432b21749587ef67f002d5
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: When rewriting lookup tables, also actually record what scale they are for

Changed paths:
    engines/sci/graphics/celobj32.cpp



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index e0f5668..07bac74 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -51,11 +51,13 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
 	if (table.scaleX != scaleX) {
 		assert(screenWidth <= ARRAYSIZE(table.valuesX));
 		buildLookupTable(table.valuesX, scaleX, screenWidth);
+		table.scaleX = scaleX;
 	}
 
 	if (table.scaleY != scaleY) {
 		assert(screenHeight <= ARRAYSIZE(table.valuesY));
 		buildLookupTable(table.valuesY, scaleY, screenHeight);
+		table.scaleY = scaleY;
 	}
 }
 


Commit: 3e820ee9b36b1fc2cf6516d400f1433d2238082f
    https://github.com/scummvm/scummvm/commit/3e820ee9b36b1fc2cf6516d400f1433d2238082f
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-02-18T13:18:03-06:00

Commit Message:
SCI: Fix bad positioning of relatively positioned pic cels

Changed paths:
    engines/sci/graphics/plane32.cpp



diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 33c01cf..38ce78d 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -183,7 +183,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p
 		screenItem->_priority = celObj->_priority;
 		screenItem->_fixPriority = true;
 		if (position != nullptr) {
-			screenItem->_position = *position;
+			screenItem->_position = *position + celObj->_relativePosition;
 		} else {
 			screenItem->_position = celObj->_relativePosition;
 		}






More information about the Scummvm-git-logs mailing list