[Scummvm-git-logs] scummvm master -> aad959004fbf123a5baf7a94c4f9ae92c2eaaf54

npjg noreply at scummvm.org
Sat Aug 2 15:47:08 UTC 2025


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

Summary:
1b6e3ba916 MEDIASTATION: Create separate Document actor
aad959004f MEDIASTATION: Implement video display manager functions


Commit: 1b6e3ba916e23d313535c4b6e53326514c69d2f6
    https://github.com/scummvm/scummvm/commit/1b6e3ba916e23d313535c4b6e53326514c69d2f6
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-08-02T11:37:10-04:00

Commit Message:
MEDIASTATION: Create separate Document actor

To get this functionality out of the main engine object.

Changed paths:
  A engines/mediastation/assets/document.cpp
  A engines/mediastation/assets/document.h
    engines/mediastation/asset.h
    engines/mediastation/mediascript/codechunk.cpp
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h
    engines/mediastation/module.mk


diff --git a/engines/mediastation/asset.h b/engines/mediastation/asset.h
index d322aa2519e..036f6679983 100644
--- a/engines/mediastation/asset.h
+++ b/engines/mediastation/asset.h
@@ -43,6 +43,7 @@ enum AssetType {
 	kAssetTypeSprite = 0x000e, // SPR
 	kAssetTypeLKZazu = 0x000f,
 	kAssetTypeLKConstellations = 0x0010,
+	kAssetTypeDocument = 0x0011,
 	kAssetTypeImageSet = 0x001d,
 	kAssetTypeCursor = 0x000c, // CSR
 	kAssetTypePrinter = 0x0019, // PRT
diff --git a/engines/mediastation/assets/document.cpp b/engines/mediastation/assets/document.cpp
new file mode 100644
index 00000000000..897826e73a0
--- /dev/null
+++ b/engines/mediastation/assets/document.cpp
@@ -0,0 +1,54 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mediastation/mediastation.h"
+#include "mediastation/assets/document.h"
+
+namespace MediaStation {
+
+ScriptValue Document::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
+	ScriptValue returnValue;
+
+	switch (methodId) {
+	case kBranchToScreenMethod: {
+		assert(args.size() >= 1);
+		if (args.size() > 1) {
+			// TODO: Figure out what the rest of the args can be.
+			warning("branchToScreen got more than one arg");
+		}
+		uint32 contextId = args[0].asAssetId();
+		g_engine->scheduleScreenBranch(contextId);
+		return returnValue;
+	}
+
+	case kReleaseContextMethod: {
+		assert(args.size() == 1);
+		uint32 contextId = args[0].asAssetId();
+		g_engine->scheduleContextRelease(contextId);
+		return returnValue;
+	}
+
+	default:
+		return Asset::callMethod(methodId, args);
+	}
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/assets/document.h b/engines/mediastation/assets/document.h
new file mode 100644
index 00000000000..8129a10c4c4
--- /dev/null
+++ b/engines/mediastation/assets/document.h
@@ -0,0 +1,40 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_DOCUMENT_H
+#define MEDIASTATION_DOCUMENT_H
+
+#include "mediastation/asset.h"
+#include "mediastation/mediascript/scriptvalue.h"
+#include "mediastation/mediascript/scriptconstants.h"
+
+namespace MediaStation {
+
+class Document : public Asset {
+public:
+	Document() : Asset(kAssetTypeDocument) { _id = 1; };
+
+	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
+};
+
+} // End of namespace MediaStation
+
+#endif
\ No newline at end of file
diff --git a/engines/mediastation/mediascript/codechunk.cpp b/engines/mediastation/mediascript/codechunk.cpp
index 08ae09347ad..3806b7ab0a8 100644
--- a/engines/mediastation/mediascript/codechunk.cpp
+++ b/engines/mediastation/mediascript/codechunk.cpp
@@ -513,15 +513,7 @@ ScriptValue CodeChunk::evaluateMethodCall(BuiltInMethod method, uint paramCount)
 	ScriptValue returnValue;
 	switch (target.getType()) {
 	case kScriptValueTypeAssetId: {
-		if (target.asAssetId() == 1) {
-			// This is a "document" method that we need to handle specially.
-			// The document (@doc) accepts engine-level methods like changing the
-			// active screen.
-			// HACK: This is so we don't have to implement a separate document class
-			// just to house these methods. Rather, we just call in the engine.
-			returnValue = g_engine->callMethod(method, args);
-			return returnValue;
-		} else if (target.asAssetId() == 0) {
+		if (target.asAssetId() == 0) {
 			// It seems to be valid to call a method on a null asset ID, in
 			// which case nothing happens. Still issue warning for traceability.
 			warning("Attempt to call method on a null asset ID");
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index 649733a3e20..d14216fc5ab 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -28,6 +28,7 @@
 #include "mediastation/boot.h"
 #include "mediastation/context.h"
 #include "mediastation/asset.h"
+#include "mediastation/assets/document.h"
 #include "mediastation/assets/movie.h"
 #include "mediastation/assets/screen.h"
 #include "mediastation/assets/palette.h"
@@ -66,6 +67,9 @@ MediaStationEngine::~MediaStationEngine() {
 		delete it->_value;
 	}
 	_loadedContexts.clear();
+
+	// Delete the document actor. The rest are deleted from their contexts.
+	delete _assets[0];
 }
 
 Asset *MediaStationEngine::getAssetById(uint assetId) {
@@ -149,6 +153,9 @@ Common::Error MediaStationEngine::run() {
 	}
 	_cursor->showCursor();
 
+	Document *document = new Document;
+	_assets.push_back(document);
+
 	if (ConfMan.hasKey("entry_context")) {
 		// For development purposes, we can choose to start at an arbitrary context
 		// in this title. This might not work in all cases.
@@ -389,31 +396,12 @@ void MediaStationEngine::registerAsset(Asset *assetToAdd) {
 	}
 }
 
-ScriptValue MediaStationEngine::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
-	ScriptValue returnValue;
-
-	switch (methodId) {
-	case kBranchToScreenMethod: {
-		assert(args.size() >= 1);
-		if (args.size() > 1) {
-			// TODO: Figure out what the rest of the args can be.
-			warning("MediaStationEngine::callMethod(): branchToScreen got more than one arg");
-		}
-		uint32 contextId = args[0].asAssetId();
-		_requestedScreenBranchId = contextId;
-		return returnValue;
-	}
-
-	case kReleaseContextMethod: {
-		assert(args.size() == 1);
-		uint32 contextId = args[0].asAssetId();
-		_requestedContextReleaseId.push_back(contextId);
-		return returnValue;
-	}
+void MediaStationEngine::scheduleScreenBranch(uint screenId) {
+	_requestedScreenBranchId = screenId;
+}
 
-	default:
-		error("MediaStationEngine::callMethod(): Got unimplemented method ID %s (%d)", builtInMethodToStr(methodId), static_cast<uint>(methodId));
-	}
+void MediaStationEngine::scheduleContextRelease(uint contextId) {
+	_requestedContextReleaseId.push_back(contextId);
 }
 
 void MediaStationEngine::doBranchToScreen() {
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index 85e83ba43cc..8189da60b5e 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -82,13 +82,14 @@ public:
 
 	void setPalette(Asset *palette);
 	void registerAsset(Asset *assetToAdd);
+	void scheduleScreenBranch(uint screenId);
+	void scheduleContextRelease(uint contextId);
 
 	Asset *getAssetById(uint assetId);
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
 	ScriptValue *getVariable(uint variableId);
 
-	ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args);
 	ScriptValue callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args);
 	Common::RandomSource _randomSource;
 
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
index abd4880133c..a32a467c3f3 100644
--- a/engines/mediastation/module.mk
+++ b/engines/mediastation/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/mediastation
 MODULE_OBJS = \
 	asset.o \
 	assets/canvas.o \
+	assets/document.o \
 	assets/font.o \
 	assets/hotspot.o \
 	assets/image.o \


Commit: aad959004fbf123a5baf7a94c4f9ae92c2eaaf54
    https://github.com/scummvm/scummvm/commit/aad959004fbf123a5baf7a94c4f9ae92c2eaaf54
Author: Nathanael Gentry (nathanael.gentrydb8 at gmail.com)
Date: 2025-08-02T11:46:00-04:00

Commit Message:
MEDIASTATION: Implement video display manager functions

Both blitting and basic transitions should now be working properly.
More advanced transitions from later in the engine's development
will be reversed separately.

Changed paths:
  A engines/mediastation/dissolvepatterns.h
  A engines/mediastation/graphics.cpp
  A engines/mediastation/graphics.h
  R engines/mediastation/transitions.cpp
    engines/mediastation/asset.cpp
    engines/mediastation/asset.h
    engines/mediastation/assets/canvas.cpp
    engines/mediastation/assets/canvas.h
    engines/mediastation/assets/document.cpp
    engines/mediastation/assets/document.h
    engines/mediastation/assets/image.cpp
    engines/mediastation/assets/image.h
    engines/mediastation/assets/movie.cpp
    engines/mediastation/assets/movie.h
    engines/mediastation/assets/palette.cpp
    engines/mediastation/assets/palette.h
    engines/mediastation/assets/path.cpp
    engines/mediastation/assets/sprite.cpp
    engines/mediastation/assets/sprite.h
    engines/mediastation/assets/text.cpp
    engines/mediastation/assets/text.h
    engines/mediastation/bitmap.cpp
    engines/mediastation/bitmap.h
    engines/mediastation/context.cpp
    engines/mediastation/datafile.h
    engines/mediastation/debugchannels.h
    engines/mediastation/mediastation.cpp
    engines/mediastation/mediastation.h
    engines/mediastation/module.mk


diff --git a/engines/mediastation/asset.cpp b/engines/mediastation/asset.cpp
index d2be6c294fb..ab92d7b3b22 100644
--- a/engines/mediastation/asset.cpp
+++ b/engines/mediastation/asset.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "common/util.h"
+
 #include "mediastation/asset.h"
 #include "mediastation/debugchannels.h"
 #include "mediastation/mediascript/scriptconstants.h"
@@ -199,6 +201,13 @@ ScriptValue SpatialEntity::callMethod(BuiltInMethod methodId, Common::Array<Scri
 		returnValue.setToFloat(_zIndex);
 		break;
 
+	case kSetDissolveFactorMethod: {
+		assert(args.size() == 1);
+		double dissolveFactor = args[0].asFloat();
+		setDissolveFactor(dissolveFactor);
+		break;
+	}
+
 	case kIsVisibleMethod:
 		assert(args.empty());
 		returnValue.setToBool(isVisible());
@@ -216,6 +225,10 @@ void SpatialEntity::readParameter(Chunk &chunk, AssetHeaderSectionType paramType
 		_boundingBox = chunk.readTypedRect();
 		break;
 
+	case kAssetHeaderDissolveFactor:
+		_dissolveFactor = chunk.readTypedDouble();
+		break;
+
 	case kAssetHeaderZIndex:
 		_zIndex = chunk.readTypedGraphicUnit();
 		break;
@@ -284,8 +297,16 @@ void SpatialEntity::setZIndex(int zIndex) {
 	invalidateLocalZIndex();
 }
 
+void SpatialEntity::setDissolveFactor(double dissolveFactor) {
+	CLIP(dissolveFactor, 0.0, 1.0);
+	if (dissolveFactor != _dissolveFactor) {
+		_dissolveFactor = dissolveFactor;
+		invalidateLocalBounds();
+	}
+}
+
 void SpatialEntity::invalidateLocalBounds() {
-	g_engine->_dirtyRects.push_back(_boundingBox);
+	g_engine->addDirtyRect(getBbox());
 }
 
 void SpatialEntity::invalidateLocalZIndex() {
diff --git a/engines/mediastation/asset.h b/engines/mediastation/asset.h
index 036f6679983..cbd934e147c 100644
--- a/engines/mediastation/asset.h
+++ b/engines/mediastation/asset.h
@@ -168,20 +168,19 @@ class SpatialEntity : public Asset {
 public:
 	SpatialEntity(AssetType type) : Asset(type) {};
 
-	virtual void redraw(Common::Rect &rect) { return; }
+	virtual void draw(const Common::Array<Common::Rect> &dirtyRegion) { return; }
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
+	virtual void readParameter(Chunk &chunk, AssetHeaderSectionType paramType) override;
 
 	virtual bool isSpatialActor() const override { return true; }
 	virtual bool isVisible() const { return _isVisible; }
-
-	virtual void readParameter(Chunk &chunk, AssetHeaderSectionType paramType) override;
-
-	Common::Rect getBbox() const { return _boundingBox; }
+	virtual Common::Rect getBbox() const { return _boundingBox; }
 	int zIndex() const { return _zIndex; }
 
 protected:
 	uint _stageId = 0;
 	int _zIndex = 0;
+	double _dissolveFactor = 0.0;
 	Common::Rect _boundingBox;
 	bool _isVisible = false;
 	bool _hasTransparency = false;
@@ -192,6 +191,7 @@ protected:
 	void setBounds(const Common::Rect &bounds);
 	void setZIndex(int zIndex);
 
+	virtual void setDissolveFactor(double dissolveFactor);
 	virtual void invalidateLocalBounds();
 	virtual void invalidateLocalZIndex();
 };
diff --git a/engines/mediastation/assets/canvas.cpp b/engines/mediastation/assets/canvas.cpp
index cb9ead43def..d5096f6f075 100644
--- a/engines/mediastation/assets/canvas.cpp
+++ b/engines/mediastation/assets/canvas.cpp
@@ -29,10 +29,6 @@ void Canvas::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 		_isVisible = static_cast<bool>(chunk.readTypedByte());
 		break;
 
-	case kAssetHeaderDissolveFactor:
-		_dissolveFactor = chunk.readTypedDouble();
-		break;
-
 	default:
 		SpatialEntity::readParameter(chunk, paramType);
 	}
diff --git a/engines/mediastation/assets/canvas.h b/engines/mediastation/assets/canvas.h
index 76e121d092a..4210d95ac58 100644
--- a/engines/mediastation/assets/canvas.h
+++ b/engines/mediastation/assets/canvas.h
@@ -34,9 +34,6 @@ public:
 
 	virtual void readParameter(Chunk &chunk, AssetHeaderSectionType paramType) override;
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
-
-private:
-	double _dissolveFactor = 0.0;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/document.cpp b/engines/mediastation/assets/document.cpp
index 897826e73a0..8c09e95138c 100644
--- a/engines/mediastation/assets/document.cpp
+++ b/engines/mediastation/assets/document.cpp
@@ -28,16 +28,9 @@ ScriptValue Document::callMethod(BuiltInMethod methodId, Common::Array<ScriptVal
 	ScriptValue returnValue;
 
 	switch (methodId) {
-	case kBranchToScreenMethod: {
-		assert(args.size() >= 1);
-		if (args.size() > 1) {
-			// TODO: Figure out what the rest of the args can be.
-			warning("branchToScreen got more than one arg");
-		}
-		uint32 contextId = args[0].asAssetId();
-		g_engine->scheduleScreenBranch(contextId);
+	case kBranchToScreenMethod:
+		processBranch(args);
 		return returnValue;
-	}
 
 	case kReleaseContextMethod: {
 		assert(args.size() == 1);
@@ -51,4 +44,16 @@ ScriptValue Document::callMethod(BuiltInMethod methodId, Common::Array<ScriptVal
 	}
 }
 
+void Document::processBranch(Common::Array<ScriptValue> &args) {
+	assert(args.size() >= 1);
+	uint contextId = args[0].asAssetId();
+	if (args.size() > 1) {
+		bool disableUpdates = static_cast<bool>(args[1].asParamToken());
+		if (disableUpdates)
+			warning("processBranch: disableUpdates parameter not handled yet");
+	}
+
+	g_engine->scheduleScreenBranch(contextId);
+}
+
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/document.h b/engines/mediastation/assets/document.h
index 8129a10c4c4..dadb1e8b0ef 100644
--- a/engines/mediastation/assets/document.h
+++ b/engines/mediastation/assets/document.h
@@ -33,6 +33,9 @@ public:
 	Document() : Asset(kAssetTypeDocument) { _id = 1; };
 
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
+
+private:
+	void processBranch(Common::Array<ScriptValue> &args);
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/image.cpp b/engines/mediastation/assets/image.cpp
index a3b1fb91e7a..8b4a029de3c 100644
--- a/engines/mediastation/assets/image.cpp
+++ b/engines/mediastation/assets/image.cpp
@@ -48,10 +48,6 @@ void Image::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 		_loadType = chunk.readTypedByte();
 		break;
 
-	case kAssetHeaderDissolveFactor:
-		_dissolveFactor = chunk.readTypedDouble();
-		break;
-
 	case kAssetHeaderX:
 		_xOffset = chunk.readTypedUint16();
 		break;
@@ -80,48 +76,36 @@ ScriptValue Image::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 		return returnValue;
 	}
 
-	case kSetDissolveFactorMethod: {
-		warning("STUB: setDissolveFactor");
-		assert(args.size() == 1);
-		_dissolveFactor = args[0].asFloat();
-		return returnValue;
-	}
-
 	default:
 		return SpatialEntity::callMethod(methodId, args);
 	}
 }
 
-void Image::redraw(Common::Rect &rect) {
-	if (!_isVisible) {
-		return;
+void Image::draw(const Common::Array<Common::Rect> &dirtyRegion) {
+	if (_isVisible) {
+		Common::Point origin = getBbox().origin();
+		g_engine->getDisplayManager()->imageBlit(origin, _bitmap, _dissolveFactor, dirtyRegion);
 	}
+}
 
-	Common::Point leftTop = getLeftTop();
-	Common::Rect bbox(leftTop, _bitmap->width(), _bitmap->height());
-	Common::Rect areaToRedraw = bbox.findIntersectingRect(rect);
-	if (!areaToRedraw.isEmpty()) {
-		Common::Point originOnScreen(areaToRedraw.left, areaToRedraw.top);
-		areaToRedraw.translate(-leftTop.x, -leftTop.y);
-		areaToRedraw.clip(Common::Rect(0, 0, _bitmap->width(), _bitmap->height()));
-		g_engine->_screen->simpleBlitFrom(_bitmap->_surface, areaToRedraw, originOnScreen);
-	}
+void Image::invalidateLocalBounds() {
+	g_engine->addDirtyRect(getBbox());
 }
 
 void Image::spatialShow() {
 	_isVisible = true;
-	Common::Rect bbox(getLeftTop(), _bitmap->width(), _bitmap->height());
-	g_engine->_dirtyRects.push_back(bbox);
+	invalidateLocalBounds();
 }
 
 void Image::spatialHide() {
 	_isVisible = false;
-	Common::Rect bbox(getLeftTop(), _bitmap->width(), _bitmap->height());
-	g_engine->_dirtyRects.push_back(bbox);
+	invalidateLocalBounds();
 }
 
-Common::Point Image::getLeftTop() {
-	return Common::Point(_xOffset + _boundingBox.left, _yOffset + _boundingBox.top);
+Common::Rect Image::getBbox() const {
+	Common::Point origin(_xOffset + _boundingBox.left, _yOffset + _boundingBox.top);
+	Common::Rect bbox(origin, _bitmap->width(), _bitmap->height());
+	return bbox;
 }
 
 void Image::readChunk(Chunk &chunk) {
diff --git a/engines/mediastation/assets/image.h b/engines/mediastation/assets/image.h
index a729504c0be..23c708f32af 100644
--- a/engines/mediastation/assets/image.h
+++ b/engines/mediastation/assets/image.h
@@ -40,20 +40,19 @@ public:
 	virtual void readChunk(Chunk &chunk) override;
 	virtual void readParameter(Chunk &chunk, AssetHeaderSectionType paramType) override;
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
-	virtual void redraw(Common::Rect &rect) override;
+	virtual void draw(const Common::Array<Common::Rect> &dirtyRegion) override;
+	virtual void invalidateLocalBounds() override;
+	virtual Common::Rect getBbox() const override;
 
 private:
 	Bitmap *_bitmap = nullptr;
 	uint _loadType = 0;
-	double _dissolveFactor = 0.0;
 	int _xOffset = 0;
 	int _yOffset = 0;
 
 	// Script method implementations.
 	void spatialShow();
 	void spatialHide();
-
-	Common::Point getLeftTop();
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/assets/movie.cpp b/engines/mediastation/assets/movie.cpp
index d71928ba0db..e515d694c93 100644
--- a/engines/mediastation/assets/movie.cpp
+++ b/engines/mediastation/assets/movie.cpp
@@ -43,7 +43,7 @@ MovieFrame::MovieFrame(Chunk &chunk) {
 		unk4 = chunk.readTypedUint16();
 		index = chunk.readTypedUint16();
 	} else {
-		blitType = chunk.readTypedUint16();
+		blitType = static_cast<MovieBlitType>(chunk.readTypedUint16());
 		startInMilliseconds = chunk.readTypedUint32();
 		endInMilliseconds = chunk.readTypedUint32();
 		// These are unsigned in the data files but ScummVM expects signed.
@@ -119,10 +119,6 @@ void Movie::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 		_isVisible = static_cast<bool>(chunk.readTypedByte());
 		break;
 
-	case kAssetHeaderDissolveFactor:
-		_dissolveFactor = chunk.readTypedDouble();
-		break;
-
 	case kAssetHeaderMovieAudioChunkReference:
 		_audioChunkReference = chunk.readTypedChunkReference();
 		break;
@@ -190,13 +186,6 @@ ScriptValue Movie::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 		return returnValue;
 	}
 
-	case kSetDissolveFactorMethod: {
-		warning("STUB: setDissolveFactor");
-		assert(args.size() == 1);
-		_dissolveFactor = args[0].asFloat();
-		return returnValue;
-	}
-
 	default:
 		return SpatialEntity::callMethod(methodId, args);
 	}
@@ -318,15 +307,28 @@ void Movie::updateFrameState() {
 	}
 }
 
-void Movie::redraw(Common::Rect &rect) {
+void Movie::draw(const Common::Array<Common::Rect> &dirtyRegion) {
 	for (MovieFrame *frame : _framesOnScreen) {
 		Common::Rect bbox = getFrameBoundingBox(frame);
-		Common::Rect areaToRedraw = bbox.findIntersectingRect(rect);
-		if (!areaToRedraw.isEmpty()) {
-			Common::Point originOnScreen(areaToRedraw.left, areaToRedraw.top);
-			areaToRedraw.translate(-frame->leftTop.x - _boundingBox.left, -frame->leftTop.y - _boundingBox.top);
-			areaToRedraw.clip(Common::Rect(0, 0, frame->image->width(), frame->image->height()));
-			g_engine->_screen->simpleBlitFrom(frame->image->_surface, areaToRedraw, originOnScreen);
+
+		switch (frame->blitType) {
+		case kUncompressedMovieBlit:
+			g_engine->getDisplayManager()->imageBlit(bbox.origin(), frame->image, _dissolveFactor, dirtyRegion);
+			break;
+
+		case kUncompressedDeltaMovieBlit:
+			g_engine->getDisplayManager()->imageDeltaBlit(bbox.origin(), frame->diffBetweenKeyframeAndFrame, frame->image, frame->keyframeImage, _dissolveFactor, dirtyRegion);
+			break;
+
+		case kCompressedDeltaMovieBlit:
+			if (frame->keyframeImage->isCompressed()) {
+				decompressIntoAuxImage(frame);
+			}
+			g_engine->getDisplayManager()->imageDeltaBlit(bbox.origin(), frame->diffBetweenKeyframeAndFrame, frame->image, frame->keyframeImage, _dissolveFactor, dirtyRegion);
+			break;
+
+		default:
+			error("Got unknown movie frame blit type: %d", frame->blitType);
 		}
 	}
 }
@@ -437,7 +439,17 @@ void Movie::readSubfile(Subfile &subfile, Chunk &chunk) {
 }
 
 void Movie::invalidateRect(const Common::Rect &rect) {
-	g_engine->_dirtyRects.push_back(rect);
+	g_engine->addDirtyRect(rect);
+}
+
+void Movie::decompressIntoAuxImage(MovieFrame *frame) {
+	const Common::Point origin(0, 0);
+	Common::Rect test = Common::Rect(frame->keyframeImage->width(), frame->keyframeImage->height());
+	Common::Array<Common::Rect> allDirty(1);
+	allDirty.push_back(test);
+	frame->keyframeImage->_image.create(frame->keyframeImage->width(), frame->keyframeImage->height(), Graphics::PixelFormat::createFormatCLUT8());
+	frame->keyframeImage->_image.setTransparentColor(0);
+	g_engine->getDisplayManager()->imageBlit(origin, frame->keyframeImage, 1.0, allDirty, &frame->keyframeImage->_image);
 }
 
 void Movie::readImageData(Chunk &chunk) {
diff --git a/engines/mediastation/assets/movie.h b/engines/mediastation/assets/movie.h
index 896a0311836..d82e0e310bb 100644
--- a/engines/mediastation/assets/movie.h
+++ b/engines/mediastation/assets/movie.h
@@ -32,6 +32,13 @@
 
 namespace MediaStation {
 
+enum MovieBlitType {
+	kInvalidMovieBlit = 0,
+	kUncompressedMovieBlit = 1,
+	kUncompressedDeltaMovieBlit = 2,
+	kCompressedDeltaMovieBlit = 3,
+};
+
 class MovieFrameHeader : public BitmapHeader {
 public:
 	MovieFrameHeader(Chunk &chunk);
@@ -66,7 +73,7 @@ struct MovieFrame {
 	uint endInMilliseconds = 0;
 	Common::Point leftTop;
 	Common::Point diffBetweenKeyframeAndFrame;
-	uint blitType = 0;
+	MovieBlitType blitType = kInvalidMovieBlit;
 	int16 zIndex = 0;
 	uint keyframeIndex = 0;
 	bool keepAfterEnd = false;
@@ -87,7 +94,7 @@ public:
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
 	virtual void process() override;
 
-	virtual void redraw(Common::Rect &rect) override;
+	virtual void draw(const Common::Array<Common::Rect> &dirtyRegion) override;
 
 	virtual bool isVisible() const override { return _isVisible; }
 
@@ -100,7 +107,6 @@ private:
 	uint _fullTime = 0;
 
 	uint _loadType = 0;
-	double _dissolveFactor = 0.0;
 	bool _isPlaying = false;
 	bool _hasStill = false;
 
@@ -117,6 +123,7 @@ private:
 	void setVisibility(bool visibility);
 	void updateFrameState();
 	void invalidateRect(const Common::Rect &rect);
+	void decompressIntoAuxImage(MovieFrame *frame);
 
 	void readImageData(Chunk &chunk);
 	void readFrameData(Chunk &chunk);
diff --git a/engines/mediastation/assets/palette.cpp b/engines/mediastation/assets/palette.cpp
index a82d4536e5b..1159686d224 100644
--- a/engines/mediastation/assets/palette.cpp
+++ b/engines/mediastation/assets/palette.cpp
@@ -33,11 +33,9 @@ Palette::~Palette() {
 void Palette::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 	switch (paramType) {
 	case kAssetHeaderPalette: {
-		const uint PALETTE_ENTRIES = 256;
-		const uint PALETTE_BYTES = PALETTE_ENTRIES * 3;
-		byte *buffer = new byte[PALETTE_BYTES];
-		chunk.read(buffer, PALETTE_BYTES);
-		_palette = new Graphics::Palette(buffer, PALETTE_ENTRIES, DisposeAfterUse::YES);
+		byte *buffer = new byte[Graphics::PALETTE_SIZE];
+		chunk.read(buffer, Graphics::PALETTE_SIZE);
+		_palette = new Graphics::Palette(buffer, Graphics::PALETTE_COUNT, DisposeAfterUse::YES);
 		break;
 	}
 
diff --git a/engines/mediastation/assets/palette.h b/engines/mediastation/assets/palette.h
index b54dc21fe8b..5345e24facd 100644
--- a/engines/mediastation/assets/palette.h
+++ b/engines/mediastation/assets/palette.h
@@ -22,6 +22,8 @@
 #ifndef MEDIASTATION_PALETTE_H
 #define MEDIASTATION_PALETTE_H
 
+#include "graphics/palette.h"
+
 #include "mediastation/asset.h"
 #include "mediastation/mediascript/scriptvalue.h"
 #include "mediastation/mediascript/scriptconstants.h"
diff --git a/engines/mediastation/assets/path.cpp b/engines/mediastation/assets/path.cpp
index e487eb6b0ed..6fdf2861491 100644
--- a/engines/mediastation/assets/path.cpp
+++ b/engines/mediastation/assets/path.cpp
@@ -70,7 +70,7 @@ ScriptValue Path::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue>
 
 	case kSetDurationMethod: {
 		assert(args.size() == 1);
-		uint durationInMilliseconds = static_cast<uint>(args[0].asFloat() * 1000);
+		uint durationInMilliseconds = static_cast<uint>(args[0].asTime() * 1000);
 		setDuration(durationInMilliseconds);
 		return returnValue;
 	}
diff --git a/engines/mediastation/assets/sprite.cpp b/engines/mediastation/assets/sprite.cpp
index 45cf88de556..6554d4c5047 100644
--- a/engines/mediastation/assets/sprite.cpp
+++ b/engines/mediastation/assets/sprite.cpp
@@ -77,10 +77,6 @@ void Sprite::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 		_chunkReference = chunk.readTypedChunkReference();
 		break;
 
-	case kAssetHeaderDissolveFactor:
-		_dissolveFactor = chunk.readTypedDouble();
-		break;
-
 	case kAssetHeaderFrameRate:
 		_frameRate = static_cast<uint32>(chunk.readTypedDouble());
 		break;
@@ -142,13 +138,6 @@ ScriptValue Sprite::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue
 		return returnValue;
 	}
 
-	case kSetDissolveFactorMethod: {
-		warning("STUB: setDissolveFactor");
-		assert(args.size() == 1);
-		_dissolveFactor = args[0].asFloat();
-		return returnValue;
-	}
-
 	case kTimePlayMethod: {
 		assert(args.empty());
 		play();
@@ -373,20 +362,12 @@ void Sprite::postMovieEndEventIfNecessary() {
 	runEventHandlerIfExists(kSpriteMovieEndEvent, value);
 }
 
-void Sprite::redraw(Common::Rect &rect) {
+void Sprite::draw(const Common::Array<Common::Rect> &dirtyRegion) {
 	SpriteFrame *activeFrame = _frames[_currentFrameIndex];
-	if (activeFrame == nullptr || !_isVisible) {
-		return;
-	}
-
-	Common::Rect bbox = activeFrame->boundingBox();
-	bbox.translate(_boundingBox.left, _boundingBox.top);
-	Common::Rect areaToRedraw = bbox.findIntersectingRect(rect);
-	if (!areaToRedraw.isEmpty()) {
-		Common::Point originOnScreen(areaToRedraw.left, areaToRedraw.top);
-		areaToRedraw.translate(-activeFrame->left() - _boundingBox.left, -activeFrame->top() - _boundingBox.top);
-		areaToRedraw.clip(Common::Rect(0, 0, activeFrame->width(), activeFrame->height()));
-		g_engine->_screen->simpleBlitFrom(activeFrame->_surface, areaToRedraw, originOnScreen);
+	if (_isVisible) {
+		Common::Rect frameBbox = activeFrame->boundingBox();
+		frameBbox.translate(_boundingBox.left, _boundingBox.top);
+		g_engine->getDisplayManager()->imageBlit(frameBbox.origin(), activeFrame, _dissolveFactor, dirtyRegion);
 	}
 }
 
diff --git a/engines/mediastation/assets/sprite.h b/engines/mediastation/assets/sprite.h
index 15d55001c8c..4c89bcc83d6 100644
--- a/engines/mediastation/assets/sprite.h
+++ b/engines/mediastation/assets/sprite.h
@@ -72,7 +72,7 @@ public:
 	~Sprite();
 
 	virtual void process() override;
-	virtual void redraw(Common::Rect &rect) override;
+	virtual void draw(const Common::Array<Common::Rect> &dirtyRegion) override;
 
 	virtual void readParameter(Chunk &chunk, AssetHeaderSectionType paramType) override;
 	virtual ScriptValue callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) override;
@@ -83,7 +83,6 @@ public:
 
 private:
 	static const uint DEFAULT_CLIP_ID = 1200;
-	double _dissolveFactor = 0.0;
 	uint _loadType = 0;
 	uint _frameRate = 0;
 	uint _frameCount = 0;
diff --git a/engines/mediastation/assets/text.cpp b/engines/mediastation/assets/text.cpp
index 154c3b78205..935897fbedb 100644
--- a/engines/mediastation/assets/text.cpp
+++ b/engines/mediastation/assets/text.cpp
@@ -65,10 +65,6 @@ void Text::readParameter(Chunk &chunk, AssetHeaderSectionType paramType) {
 		break;
 	}
 
-	case kAssetHeaderDissolveFactor:
-		_dissolveFactor = chunk.readTypedDouble();
-		break;
-
 	default:
 		SpatialEntity::readParameter(chunk, paramType);
 	}
diff --git a/engines/mediastation/assets/text.h b/engines/mediastation/assets/text.h
index 0e186f82220..98d057dc17e 100644
--- a/engines/mediastation/assets/text.h
+++ b/engines/mediastation/assets/text.h
@@ -58,7 +58,6 @@ public:
 private:
 	bool _editable = false;
 	uint _loadType = 0;
-	double _dissolveFactor = 0.0;
 	bool _isVisible = false;
 	Common::String _text;
 	uint _maxTextLength = 0;
diff --git a/engines/mediastation/bitmap.cpp b/engines/mediastation/bitmap.cpp
index c0251d3a5a8..1143c2e5e0d 100644
--- a/engines/mediastation/bitmap.cpp
+++ b/engines/mediastation/bitmap.cpp
@@ -26,189 +26,43 @@ namespace MediaStation {
 
 BitmapHeader::BitmapHeader(Chunk &chunk) {
 	uint headerSizeInBytes = chunk.readTypedUint16();
-	debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): headerSize = 0x%x", headerSizeInBytes);
 	_dimensions = chunk.readTypedGraphicSize();
 	_compressionType = static_cast<BitmapCompressionType>(chunk.readTypedUint16());
-	debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): _compressionType = 0x%x", static_cast<uint>(_compressionType));
-	// TODO: Figure out what this is.
-	// This has something to do with the width of the bitmap but is always
-	// a few pixels off from the width. And in rare cases it seems to be
-	// the true width!
-	unk2 = chunk.readTypedUint16();
-}
-
-bool BitmapHeader::isCompressed() {
-	return (_compressionType != kUncompressedBitmap1) && (_compressionType != kUncompressedBitmap2);
+	_stride = chunk.readTypedUint16();
+	debugC(5, kDebugLoading, "BitmapHeader::BitmapHeader(): headerSize: %d, _compressionType = 0x%x, _stride = %d",
+		headerSizeInBytes, static_cast<uint>(_compressionType), _stride);
 }
 
 Bitmap::Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader) : _bitmapHeader(bitmapHeader) {
-	// The header must be constructed beforehand.
-	int16 width = _bitmapHeader->_dimensions.x;
-	int16 height = _bitmapHeader->_dimensions.y;
-	_surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
-	_surface.setTransparentColor(0);
-	uint8 *pixels = (uint8 *)_surface.getPixels();
-	if (_bitmapHeader->isCompressed()) {
-		// DECOMPRESS THE IMAGE.
-		debugC(5, kDebugLoading, "Bitmap::Bitmap(): Decompressing bitmap (@0x%llx)", static_cast<long long int>(chunk.pos()));
-		decompress(chunk);
-		debugC(5, kDebugLoading, "Bitmap::Bitmap(): Finished decompressing bitmap (@0x%llx) [%d remaining bytes]", static_cast<long long int>(chunk.pos()), chunk.bytesRemaining());
-		// TODO: Make sure there is nothing important in here. They are likely
-		// just zeroes.
-		chunk.skip(chunk.bytesRemaining());
-	} else {
-		// READ THE UNCOMPRESSED IMAGE DIRECTLY.
-		// TODO: Understand why we need to ignore these 2 bytes.
-		chunk.skip(2);
-		chunk.read(pixels, chunk.bytesRemaining());
+	if (stride() < width()) {
+		warning("Bitmap: Got stride less than width");
+	}
+
+	_unk1 = chunk.readUint16LE();
+	if (chunk.bytesRemaining() > 0) {
+		if (isCompressed()) {
+			_compressedStream = chunk.readStream(chunk.bytesRemaining());
+		} else {
+			_image.create(width(), height(), Graphics::PixelFormat::createFormatCLUT8());
+			if (getCompressionType() == kUncompressedTransparentBitmap)
+				_image.setTransparentColor(0);
+			byte *pixels = static_cast<byte *>(_image.getPixels());
+			chunk.read(pixels, chunk.bytesRemaining());
+		}
 	}
 }
 
 Bitmap::~Bitmap() {
 	delete _bitmapHeader;
 	_bitmapHeader = nullptr;
-}
-
-int16 Bitmap::width() {
-	return _bitmapHeader->_dimensions.x;
-}
 
-int16 Bitmap::height() {
-	return _bitmapHeader->_dimensions.y;
+	delete _compressedStream;
+	_compressedStream = nullptr;
 }
 
-void Bitmap::decompress(Chunk &chunk) {
-	// MAKE SURE WE READ PAST THE FIRST 2 BYTES.
-	uint unk1 = chunk.readByte();
-	uint unk2 = chunk.readByte();
-	if ((unk1 == 0) && (unk2 == 0)) {
-		if (chunk.bytesRemaining() == 0) {
-			// Sometimes there are compressed images that actually have no
-			// contents! If we've hit this case, exit the decompression now.
-			return;
-		}
-	} else {
-		chunk.seek(chunk.pos() - 2);
-	}
-
-	// GET THE DECOMPRESSED PIXELS BUFFER.
-	// Media Station has 8 bits per pixel, so the decompression buffer is
-	// simple.
-	char *decompressedImage = static_cast<char *>(_surface.getPixels());
-
-	// DECOMPRESS THE RLE-COMPRESSED BITMAP STREAM.
-	// TODO: Comemnted out becuase transparency runs not supported yet,
-	// and there were compiler warnings about these variables not being used.
-	// bool transparencyRunEverRead = false;
-	// size_t transparencyRunTopYCoordinate = 0;
-	// size_t transparencyRunLeftXCoordinate = 0;
-	bool imageFullyRead = false;
-	int16 currentYCoordinate = 0;
-	while (currentYCoordinate < height()) {
-		int16 currentXCoordinate = 0;
-		bool readingTransparencyRun = false;
-		while (true) {
-			byte operation = chunk.readByte();
-			if (operation == 0x00) {
-				// ENTER CONTROL MODE.
-				operation = chunk.readByte();
-				if (operation == 0x00) {
-					// MARK THE END OF THE LINE.
-					// Also check if the image is finished being read.
-					if (chunk.bytesRemaining() == 0) {
-						imageFullyRead = true;
-					}
-					break;
-				} else if (operation == 0x01) {
-					// MARK THE END OF THE IMAGE.
-					// TODO: When is this actually used?
-					imageFullyRead = true;
-					break;
-				} else if (operation == 0x02) {
-					// MARK THE START OF A KEYFRAME TRANSPARENCY REGION.
-					// Until a color index other than 0x00 (usually white) is read on this line,
-					// all pixels on this line will be marked transparent.
-					// If no transparency regions are present in this image, all 0x00 color indices are treated
-					// as transparent. Otherwise, only the 0x00 color indices within transparency regions
-					// are considered transparent. Only intraframes (frames that are not keyframes) have been
-					// observed to have transparency regions, and these intraframes have them so the keyframe
-					// can extend outside the boundary of the intraframe and
-					// still be removed.
-					//
-					// TODO: Comemnted out becuase transparency runs not
-					// supported yet, and there were compiler warnings about
-					// these variables being set but not used.
-					// readingTransparencyRun = true;
-					// transparencyRunTopYCoordinate = currentYCoordinate;
-					// transparencyRunLeftXCoordinate = currentXCoordinate;
-					// transparencyRunEverRead = true;
-				} else if (operation == 0x03) {
-					// ADJUST THE PIXEL POSITION.
-					// This permits jumping to a different part of the same row without
-					// needing a run of pixels in between. But the actual data consumed
-					// seems to actually be higher this way, as you need the control byte
-					// first.
-					// So to skip 10 pixels using this approach, you would encode 00 03 0a 00.
-					// But to "skip" 10 pixels by encoding them as blank (0xff), you would encode 0a ff.
-					// What gives? I'm not sure.
-					byte x_change = chunk.readByte();
-					currentXCoordinate += x_change;
-					byte y_change = chunk.readByte();
-					currentYCoordinate += y_change;
-				} else if (operation >= 0x04) {
-					// READ A RUN OF UNCOMPRESSED PIXELS.
-					size_t yOffset = currentYCoordinate * width();
-					size_t runStartingOffset = yOffset + currentXCoordinate;
-					char *runStartingPointer = decompressedImage + runStartingOffset;
-					byte runLength = operation;
-					// TODO: Is there a better way to do this than just copying?
-					char *uncompressedPixels = new char[runLength];
-					chunk.read(uncompressedPixels, runLength);
-					memcpy(runStartingPointer, uncompressedPixels, runLength);
-					delete[] uncompressedPixels;
-
-					currentXCoordinate += operation;
-					if (chunk.pos() % 2 == 1) {
-						chunk.readByte();
-					}
-				}
-			} else {
-				// READ A RUN OF LENGTH ENCODED PIXELS.
-				size_t yOffset = currentYCoordinate * width();
-				size_t runStartingOffset = yOffset + currentXCoordinate;
-				char *runStartingPointer = decompressedImage + runStartingOffset;
-				byte colorIndexToRepeat = chunk.readByte();
-				byte repetitionCount = operation;
-				memset(runStartingPointer, colorIndexToRepeat, repetitionCount);
-				currentXCoordinate += repetitionCount;
-
-				if (readingTransparencyRun) {
-					// TODO: This code is comemnted out becuase the engine
-					// doesn't support the keyframes/transparency regions on
-					// movies yet. However, only some movies have this to start with.
-
-					// GET THE TRANSPARENCY RUN STARTING OFFSET.
-					// size_t transparencyRunYOffset = transparencyRunTopYCoordinate * width();
-					// size_t transparencyRunStartOffset = transparencyRunYOffset + transparencyRunLeftXCoordinate;
-					// size_t transparencyRunEndingOffset = yOffset + currentXCoordinate;
-					// size_t transparency_run_length = transparencyRunEndingOffset - transparencyRunStartOffset;
-					// char *transparencyRunSrcPointer = keyframe_image + runStartingOffset;
-					// char *transparencyRunDestPointer = decompressedImage + runStartingOffset;
-
-					// COPY THE TRANSPARENT AREA FROM THE KEYFRAME.
-					// The "interior" of transparency regions is always encoded by a single run of
-					// pixels, usually 0x00 (white).
-					// memcpy(transparencyRunDestPointer, transparencyRunSrcPointer, transparency_run_length);
-					readingTransparencyRun = false;
-				}
-			}
-		}
-
-		currentYCoordinate++;
-		if (imageFullyRead) {
-			break;
-		}
-	}
+bool Bitmap::isCompressed() const {
+	return (getCompressionType() != kUncompressedBitmap) && \
+		(getCompressionType() != kUncompressedTransparentBitmap);
 }
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/bitmap.h b/engines/mediastation/bitmap.h
index 2c92728fcc9..16660887cfa 100644
--- a/engines/mediastation/bitmap.h
+++ b/engines/mediastation/bitmap.h
@@ -31,36 +31,39 @@
 namespace MediaStation {
 
 enum BitmapCompressionType {
-	kUncompressedBitmap1 = 0,
-	kRleCompressedBitmap = 1,
-	kUnk1CompressedBitmap = 6,
-	kUncompressedBitmap2 = 7,
+	kUncompressedBitmap = 0,
+	kRle8BitmapCompression = 1,
+	kCccBitmapCompression = 5,
+	kCccTransparentBitmapCompression = 6,
+	kUncompressedTransparentBitmap = 7,
 };
 
 class BitmapHeader {
 public:
 	BitmapHeader(Chunk &chunk);
 
-	bool isCompressed();
-
 	Common::Point _dimensions;
-	BitmapCompressionType _compressionType;
-	uint unk2;
+	BitmapCompressionType _compressionType = kUncompressedBitmap;
+	int16 _stride = 0;
 };
 
 class Bitmap {
 public:
-	BitmapHeader *_bitmapHeader = nullptr;
-
 	Bitmap(Chunk &chunk, BitmapHeader *bitmapHeader);
 	virtual ~Bitmap();
 
-	int16 width();
-	int16 height();
-	Graphics::ManagedSurface _surface;
+	bool isCompressed() const;
+	BitmapCompressionType getCompressionType() const { return _bitmapHeader->_compressionType; }
+	int16 width() const { return _bitmapHeader->_dimensions.x; }
+	int16 height() const { return _bitmapHeader->_dimensions.y; }
+	int16 stride() const { return _bitmapHeader->_stride; }
+
+	Common::SeekableReadStream *_compressedStream = nullptr;
+	Graphics::ManagedSurface _image;
 
 private:
-	void decompress(Chunk &chunk);
+	BitmapHeader *_bitmapHeader = nullptr;
+	uint _unk1 = 0;
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/context.cpp b/engines/mediastation/context.cpp
index 02756b834ee..e9b4a64edcc 100644
--- a/engines/mediastation/context.cpp
+++ b/engines/mediastation/context.cpp
@@ -364,14 +364,12 @@ bool Context::readHeaderSection(Chunk &chunk) {
 		if (_palette != nullptr) {
 			error("Context::readHeaderSection(): Got multiple palettes (@0x%llx)", static_cast<long long int>(chunk.pos()));
 		}
-		// TODO: Avoid the copying here!
-		const uint PALETTE_ENTRIES = 256;
-		const uint PALETTE_BYTES = PALETTE_ENTRIES * 3;
-		byte *buffer = new byte[PALETTE_BYTES];
-		chunk.read(buffer, PALETTE_BYTES);
-		_palette = new Graphics::Palette(buffer, PALETTE_ENTRIES);
-		delete[] buffer;
+
+		byte *buffer = new byte[Graphics::PALETTE_SIZE];
+		chunk.read(buffer, Graphics::PALETTE_SIZE);
+		_palette = new Graphics::Palette(buffer, Graphics::PALETTE_COUNT, DisposeAfterUse::YES);
 		debugC(5, kDebugLoading, "Context::readHeaderSection(): Read palette");
+
 		// This is likely just an ending flag that we expect to be zero.
 		uint endingFlag = chunk.readTypedUint16();
 		if (endingFlag != 0) {
diff --git a/engines/mediastation/datafile.h b/engines/mediastation/datafile.h
index 5a1b47e9519..ac656fea9a6 100644
--- a/engines/mediastation/datafile.h
+++ b/engines/mediastation/datafile.h
@@ -95,9 +95,6 @@ public:
 	VersionInfo readTypedVersion();
 	uint32 readTypedChunkReference();
 	Polygon readTypedPolygon();
-	// PALETTE:
-	// u.palette = new unsigned char[0x300];
-	// chunk.read(u.palette, 0x300);
 
 private:
 	void readAndVerifyType(DatumType type);
diff --git a/engines/mediastation/debugchannels.h b/engines/mediastation/debugchannels.h
index 4c77531dd95..3d2878464cc 100644
--- a/engines/mediastation/debugchannels.h
+++ b/engines/mediastation/debugchannels.h
@@ -36,7 +36,7 @@ enum DebugChannels {
 	kDebugScan,
 	kDebugScript,
 	kDebugEvents,
-	kDebugLoading
+	kDebugLoading,
 };
 
 } // End of namespace MediaStation
diff --git a/engines/mediastation/dissolvepatterns.h b/engines/mediastation/dissolvepatterns.h
new file mode 100644
index 00000000000..d7f87b83897
--- /dev/null
+++ b/engines/mediastation/dissolvepatterns.h
@@ -0,0 +1,763 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_DISSOLVE_PATTERNS_H
+#define MEDIASTATION_DISSOLVE_PATTERNS_H
+
+namespace MediaStation {
+
+static const byte DISSOLVE_PATTERN_00[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_01[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_02[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_03[] = {
+	1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_04[] = {
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_05[] = {
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_06[] = {
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_07[] = {
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_08[] = {
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_09[] = {
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_0a[] = {
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_0b[] = {
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_0c[] = {
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_0d[] = {
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_0e[] = {
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_0f[] = {
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_10[] = {
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
+	1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
+	1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
+};
+
+static const byte DISSOLVE_PATTERN_11[] = {
+	1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1,
+};
+
+static const byte DISSOLVE_PATTERN_12[] = {
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_13[] = {
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_14[] = {
+	1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_15[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_16[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_17[] = {
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+	0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const byte DISSOLVE_PATTERN_18[] = {
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+	1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+};
+
+struct DissolvePattern {
+	uint16 widthHeight;
+	uint16 threshold;
+	const byte *data;
+};
+
+static const byte DISSOLVE_PATTERN_COUNT = 0x19;
+static const DissolvePattern DISSOLVE_PATTERNS[] = {
+	{0x19, 1, DISSOLVE_PATTERN_00},
+	{0x19, 1, DISSOLVE_PATTERN_01},
+	{0x19, 1, DISSOLVE_PATTERN_02},
+	{0x19, 1, DISSOLVE_PATTERN_03},
+	{0x19, 1, DISSOLVE_PATTERN_04},
+	{0x19, 1, DISSOLVE_PATTERN_05},
+	{0x19, 1, DISSOLVE_PATTERN_06},
+	{0x19, 1, DISSOLVE_PATTERN_07},
+	{0x19, 1, DISSOLVE_PATTERN_08},
+	{0x19, 1, DISSOLVE_PATTERN_09},
+	{0x19, 1, DISSOLVE_PATTERN_0a},
+	{0x19, 1, DISSOLVE_PATTERN_0b},
+	{0x19, 0, DISSOLVE_PATTERN_0c},
+	{0x19, 0, DISSOLVE_PATTERN_0d},
+	{0x19, 0, DISSOLVE_PATTERN_0e},
+	{0x19, 0, DISSOLVE_PATTERN_0f},
+	{0x19, 0, DISSOLVE_PATTERN_10},
+	{0x19, 0, DISSOLVE_PATTERN_11},
+	{0x19, 0, DISSOLVE_PATTERN_12},
+	{0x19, 0, DISSOLVE_PATTERN_13},
+	{0x19, 0, DISSOLVE_PATTERN_14},
+	{0x19, 0, DISSOLVE_PATTERN_15},
+	{0x19, 0, DISSOLVE_PATTERN_16},
+	{0x19, 0, DISSOLVE_PATTERN_17},
+	{0x18, 1, DISSOLVE_PATTERN_18},
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/graphics.cpp b/engines/mediastation/graphics.cpp
new file mode 100644
index 00000000000..83518727fe0
--- /dev/null
+++ b/engines/mediastation/graphics.cpp
@@ -0,0 +1,905 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+#include "common/util.h"
+#include "engines/util.h"
+
+#include "mediastation/assets/palette.h"
+#include "mediastation/bitmap.h"
+#include "mediastation/debugchannels.h"
+#include "mediastation/dissolvepatterns.h"
+#include "mediastation/graphics.h"
+#include "mediastation/mediastation.h"
+
+namespace MediaStation {
+
+VideoDisplayManager::VideoDisplayManager(MediaStationEngine *vm) : _vm(vm) {
+	initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
+	_screen = new Graphics::Screen();
+}
+
+VideoDisplayManager::~VideoDisplayManager() {
+	delete _screen;
+	_screen = nullptr;
+	_vm = nullptr;
+}
+
+void VideoDisplayManager::effectTransition(Common::Array<ScriptValue> &args) {
+	if (args.empty()) {
+		warning("effectTransition: Script args cannot be empty");
+		return;
+	}
+
+	TransitionType transitionType = static_cast<TransitionType>(args[0].asParamToken());
+	switch (transitionType) {
+	case kTransitionFadeToBlack:
+		fadeToBlack(args);
+		break;
+
+	case kTransitionFadeToPalette:
+		fadeToRegisteredPalette(args);
+		break;
+
+	case kTransitionSetToPalette:
+		setToRegisteredPalette(args);
+		break;
+
+	case kTransitionSetToBlack:
+		setToBlack(args);
+		break;
+
+	case kTransitionFadeToColor:
+		fadeToColor(args);
+		break;
+
+	case kTransitionSetToColor:
+		setToColor(args);
+		break;
+
+	case kTransitionSetToPercentOfPalette:
+		setToPercentOfPalette(args);
+		break;
+
+	case kTransitionFadeToPaletteObject:
+		fadeToPaletteObject(args);
+		break;
+
+	case kTransitionSetToPaletteObject:
+		setToPaletteObject(args);
+		break;
+
+	case kTransitionSetToPercentOfPaletteObject:
+		setToPercentOfPaletteObject(args);
+		break;
+
+	case kTransitionColorShiftCurrentPalette:
+		colorShiftCurrentPalette(args);
+		break;
+
+	case kTransitionCircleOut:
+		circleOut(args);
+		break;
+
+	default:
+		warning("effectTransition: Got unknown transition type %d", static_cast<uint>(transitionType));
+	}
+}
+
+void VideoDisplayManager::doTransitionOnSync() {
+	if (!_scheduledTransitionOnSync.empty()) {
+		effectTransition(_scheduledTransitionOnSync);
+		_scheduledTransitionOnSync.clear();
+	}
+}
+
+void VideoDisplayManager::fadeToBlack(Common::Array<ScriptValue> &args) {
+	double fadeTime = DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 2) {
+		fadeTime = args[1].asTime();
+	}
+	if (args.size() >= 4) {
+		startIndex = static_cast<uint>(args[2].asFloat());
+		colorCount = static_cast<uint>(args[3].asFloat());
+	}
+
+	_fadeToColor(0, 0, 0, fadeTime, startIndex, colorCount);
+}
+
+void VideoDisplayManager::fadeToRegisteredPalette(Common::Array<ScriptValue> &args) {
+	double fadeTime = DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 2) {
+		fadeTime = args[1].asTime();
+	}
+	if (args.size() >= 4) {
+		startIndex = static_cast<uint>(args[2].asFloat());
+		colorCount = static_cast<uint>(args[3].asFloat());
+	}
+
+	_fadeToRegisteredPalette(fadeTime, startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToRegisteredPalette(Common::Array<ScriptValue> &args) {
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 3) {
+		startIndex = static_cast<uint>(args[1].asFloat());
+		colorCount = static_cast<uint>(args[2].asFloat());
+	}
+
+	_setToRegisteredPalette(startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToBlack(Common::Array<ScriptValue> &args) {
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 3) {
+		startIndex = static_cast<uint>(args[1].asFloat());
+		colorCount = static_cast<uint>(args[2].asFloat());
+	}
+
+	_setToColor(0, 0, 0, startIndex, colorCount);
+}
+
+void VideoDisplayManager::fadeToColor(Common::Array<ScriptValue> &args) {
+	byte r = 0, g = 0, b = 0;
+	double fadeTime = DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 5) {
+		r = static_cast<byte>(args[1].asFloat());
+		g = static_cast<byte>(args[2].asFloat());
+		b = static_cast<byte>(args[3].asFloat());
+		fadeTime = args[4].asTime();
+	}
+	if (args.size() >= 7) {
+		fadeTime = args[5].asTime();
+		startIndex = static_cast<uint>(args[6].asFloat());
+		colorCount = static_cast<uint>(args[7].asFloat());
+	}
+
+	_fadeToColor(r, g, b, fadeTime, startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToColor(Common::Array<ScriptValue> &args) {
+	if (args.size() < 6) {
+		error("setToColor: Too few script args");
+	}
+
+	byte r = static_cast<byte>(args[1].asFloat());
+	byte g = static_cast<byte>(args[2].asFloat());
+	byte b = static_cast<byte>(args[3].asFloat());
+	uint startIndex = static_cast<uint>(args[4].asFloat());
+	uint colorCount = static_cast<uint>(args[5].asFloat());
+
+	_setToColor(r, g, b, startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToPercentOfPalette(Common::Array<ScriptValue> &args) {
+	if (args.size() < 7) {
+		error("setToPercentOfPalette: Too few script args");
+	}
+
+	double percent = args[1].asFloat();
+	byte r = static_cast<byte>(args[2].asFloat());
+	byte g = static_cast<byte>(args[3].asFloat());
+	byte b = static_cast<byte>(args[4].asFloat());
+	uint startIndex = static_cast<uint>(args[5].asFloat());
+	uint colorCount = static_cast<uint>(args[6].asFloat());
+
+	_setPercentToColor(percent, r, g, b, startIndex, colorCount);
+}
+
+void VideoDisplayManager::fadeToPaletteObject(Common::Array<ScriptValue> &args) {
+	uint paletteId = 0;
+	double fadeTime = DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 2) {
+		paletteId = args[1].asAssetId();
+	} else {
+		warning("fadeToPaletteObject: Too few script args");
+		return;
+	}
+	if (args.size() >= 3) {
+		fadeTime = args[2].asFloat();
+	}
+	if (args.size() >= 5) {
+		startIndex = static_cast<uint>(args[3].asFloat());
+		colorCount = static_cast<uint>(args[4].asFloat());
+	}
+
+	_fadeToPaletteObject(paletteId, fadeTime, startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToPaletteObject(Common::Array<ScriptValue> &args) {
+	uint paletteId = 0;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 2) {
+		paletteId = args[1].asAssetId();
+	} else {
+		warning("fadeToPaletteObject: Too few script args");
+		return;
+	}
+	if (args.size() >= 4) {
+		startIndex = static_cast<uint>(args[2].asFloat());
+		colorCount = static_cast<uint>(args[3].asFloat());
+	}
+
+	_setToPaletteObject(paletteId, startIndex, colorCount);
+}
+
+void VideoDisplayManager::setToPercentOfPaletteObject(Common::Array<ScriptValue> &args) {
+	uint paletteId = 0;
+	double percent = 0.0;
+	uint startIndex = DEFAULT_PALETTE_TRANSITION_START_INDEX;
+	uint colorCount = DEFAULT_PALETTE_TRANSITION_COLOR_COUNT;
+
+	if (args.size() >= 3) {
+		percent = args[1].asFloat();
+		paletteId = args[2].asAssetId();
+	} else {
+		error("fadeToPaletteObject: Too few script args");
+		return;
+	}
+	if (args.size() >= 5) {
+		startIndex = static_cast<uint>(args[3].asFloat());
+		colorCount = static_cast<uint>(args[4].asFloat());
+	}
+
+	_setPercentToPaletteObject(percent, paletteId, startIndex, colorCount);
+}
+
+void VideoDisplayManager::colorShiftCurrentPalette(Common::Array<ScriptValue> &args) {
+	if (args.size() < 4) {
+		warning("colorShiftCurrentPalette: Too few script args");
+		return;
+	}
+
+	uint shift = static_cast<uint>(args[1].asFloat());
+	uint startIndex = static_cast<uint>(args[2].asFloat());
+	uint colorCount = static_cast<uint>(args[3].asFloat());
+
+	_colorShiftCurrentPalette(startIndex, shift, colorCount);
+}
+
+void VideoDisplayManager::circleOut(Common::Array<ScriptValue> &args) {
+	warning("STUB: circleOut");
+}
+
+void VideoDisplayManager::_setPalette(Graphics::Palette &palette, uint startIndex, uint colorCount) {
+	// We can't use the Palette::set method directly because it assumes the
+	// data we want to copy is at the start of the data pointer, but in this
+	// case we want to copy some range not necessarily right at the start. Thus,
+	// we need to calculate some pointers manually.
+	_limitColorRange(startIndex, colorCount);
+	uint startOffset = 3 * startIndex;
+	const byte *startPointer = palette.data() + startOffset;
+	_screen->setPalette(startPointer, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setPaletteToColor(Graphics::Palette &targetPalette, byte r, byte g, byte b) {
+	for (uint colorIndex = 0; colorIndex < Graphics::PALETTE_COUNT; colorIndex++) {
+		targetPalette.set(colorIndex, r, g, b);
+	}
+}
+
+uint VideoDisplayManager::_limitColorRange(uint &startIndex, uint &colorCount) {
+	CLIP<uint>(startIndex, 0, Graphics::PALETTE_COUNT - 1);
+	uint endColorIndex = startIndex + colorCount;
+	CLIP<uint>(endColorIndex, 0, Graphics::PALETTE_COUNT);
+	colorCount = endColorIndex - startIndex;
+	return endColorIndex;
+}
+
+byte VideoDisplayManager::_interpolateColorComponent(byte source, byte target, double progress) {
+	// The original scaled to 1024 to convert to an integer, but we will just
+	// do floating-point interpolation.
+	double result = source + ((target - source) * progress);
+	return static_cast<byte>(CLIP<uint>(result, 0, Graphics::PALETTE_COUNT));
+}
+
+void VideoDisplayManager::_fadeToColor(byte r, byte g, byte b, double fadeTime, uint startIndex, uint colorCount) {
+	// Create a temporary palette that is all one color.
+	Graphics::Palette tempPalette(Graphics::PALETTE_COUNT);
+	_setPaletteToColor(tempPalette, r, g, b);
+	_fadeToPalette(fadeTime, tempPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setToColor(byte r, byte g, byte b, uint startIndex, uint colorCount) {
+	Graphics::Palette tempPalette = _screen->getPalette();
+	uint endIndex = _limitColorRange(startIndex, colorCount);
+	for (uint colorIndex = startIndex; colorIndex < endIndex; colorIndex++) {
+		tempPalette.set(colorIndex, r, g, b);
+	}
+
+	_setPalette(tempPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setPercentToColor(double percent, byte r, byte g, byte b, uint startIndex, uint colorCount) {
+	// Create a temporary palette that is all one color.
+	Graphics::Palette tempPalette(Graphics::PALETTE_COUNT);
+	_setPaletteToColor(tempPalette, r, g, b);
+
+	_setToPercentPalette(percent, *_registeredPalette, tempPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setToPercentPalette(double percent, Graphics::Palette &currentPalette, Graphics::Palette &targetPalette, uint startIndex, uint colorCount) {
+	if (percent < 0.0 || percent > 1.0) {
+		warning("_setToPercentPalette: Got invalid palette percent value %f", percent);
+		percent = CLIP<double>(percent, 0.0, 1.0);
+	}
+
+	uint endIndex = _limitColorRange(startIndex, colorCount);
+
+	Graphics::Palette blendedPalette = currentPalette;
+	for (uint colorIndex = startIndex; colorIndex < endIndex; colorIndex++) {
+		byte redSource, greenSource, blueSource, redTarget, greenTarget, blueTarget;
+		currentPalette.get(colorIndex, redSource, greenSource, blueSource);
+		targetPalette.get(colorIndex, redTarget, greenTarget, blueTarget);
+
+		byte newRed = _interpolateColorComponent(redSource, redTarget, percent);
+		byte newGreen = _interpolateColorComponent(greenSource, greenTarget, percent);
+		byte newBlue = _interpolateColorComponent(blueSource, blueTarget, percent);
+
+		blendedPalette.set(colorIndex, newRed, newGreen, newBlue);
+	}
+
+	_setPalette(blendedPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_fadeToPalette(double fadeTime, Graphics::Palette &targetPalette, uint startIndex, uint colorCount) {
+	if (fadeTime <= 0.0) {
+		// Set the fade time to something reasonable so we can continue.
+		warning("_fadeToPalette: Got invalid fade time %f", fadeTime);
+		fadeTime = 0.1;
+	}
+
+	// Gamma correction can be set via a script function, but I haven't seen
+	// that be set to anything other than 1 (the default), so it's not
+	// implemented for now.
+	Graphics::Palette currentPalette = _screen->getPalette();
+	Graphics::Palette intermediatePalette(Graphics::PALETTE_COUNT);
+	uint endIndex = _limitColorRange(startIndex, colorCount);
+	uint fadeTimeMillis = static_cast<uint>(fadeTime * 1000);
+	uint startTimeMillis = g_system->getMillis();
+	uint endTimeMillis = startTimeMillis + fadeTimeMillis;
+
+	while (g_system->getMillis() < endTimeMillis) {
+		uint currentTimeMillis = g_system->getMillis();
+		double progress = MIN<double>(static_cast<double>(currentTimeMillis - startTimeMillis) / fadeTimeMillis, 1.0);
+
+		for (uint colorIndex = startIndex; colorIndex < endIndex; colorIndex++) {
+			byte sourceR, sourceG, sourceB, targetR, targetG, targetB;
+
+			currentPalette.get(colorIndex, sourceR, sourceG, sourceB);
+			targetPalette.get(colorIndex, targetR, targetG, targetB);
+			byte newR = _interpolateColorComponent(sourceR, targetR, progress);
+			byte newG = _interpolateColorComponent(sourceG, targetG, progress);
+			byte newB = _interpolateColorComponent(sourceB, targetB, progress);
+
+			intermediatePalette.set(colorIndex, newR, newG, newB);
+		}
+
+		_setPalette(intermediatePalette, startIndex, colorCount);
+		g_system->updateScreen();
+		g_system->delayMillis(5);
+	}
+
+	// Ensure we end with exactly the target palette
+	_setPalette(targetPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_fadeToRegisteredPalette(double fadeTime, uint startIndex, uint colorCount) {
+	_fadeToPalette(fadeTime, *_registeredPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setToRegisteredPalette(uint startIndex, uint colorCount) {
+	_setPalette(*_registeredPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_colorShiftCurrentPalette(uint startIndex, uint shiftAmount, uint colorCount) {
+	uint endIndex = _limitColorRange(startIndex, colorCount);
+	Graphics::Palette currentPalette = _screen->getPalette();
+	Graphics::Palette shiftedPalette = currentPalette;
+
+	for (uint i = startIndex; i < endIndex; i++) {
+		// Calculate target index with wraparound.
+		// We convert to a zero-based index, wrap that index to stay in bounds,
+		// and then convert back to the actual palette index range.
+		uint targetIndex = ((i + shiftAmount - startIndex) % colorCount) + startIndex;
+
+		byte r, g, b;
+		currentPalette.get(i, r, g, b);
+		shiftedPalette.set(targetIndex, r, g, b);
+	}
+
+	_setPalette(shiftedPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_fadeToPaletteObject(uint paletteId, double fadeTime, uint startIndex, uint colorCount) {
+	Asset *asset = _vm->getAssetById(paletteId);
+	if (asset == nullptr) {
+		error("Got null target palette");
+	} else if (asset->type() != kAssetTypePalette) {
+		error("Asset %d is not a palette", paletteId);
+	}
+
+	Graphics::Palette *palette = static_cast<Palette *>(asset)->_palette;
+	_fadeToPalette(fadeTime, *palette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setToPaletteObject(uint paletteId, uint startIndex, uint colorCount) {
+	Asset *asset = _vm->getAssetById(paletteId);
+	if (asset == nullptr) {
+		error("Got null target palette");
+	} else if (asset->type() != kAssetTypePalette) {
+		error("Asset %d is not a palette", paletteId);
+	}
+
+	Graphics::Palette *palette = static_cast<Palette *>(asset)->_palette;
+	_setPalette(*palette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::_setPercentToPaletteObject(double percent, uint paletteId, uint startIndex, uint colorCount) {
+	Asset *asset = _vm->getAssetById(paletteId);
+	if (asset == nullptr) {
+		error("Got null target palette");
+	} else if (asset->type() != kAssetTypePalette) {
+		error("Asset %d is not a palette", paletteId);
+	}
+
+	Graphics::Palette *targetPalette = static_cast<Palette *>(asset)->_palette;
+	_setToPercentPalette(percent, *_registeredPalette, *targetPalette, startIndex, colorCount);
+}
+
+void VideoDisplayManager::imageBlit(
+	const Common::Point &destinationPoint,
+	const Bitmap *sourceImage,
+	const double dissolveFactor,
+	const Common::Array<Common::Rect> &dirtyRegion,
+	Graphics::ManagedSurface *targetImage) {
+
+	byte blitFlags = kClipEnabled;
+	switch (sourceImage->getCompressionType()) {
+	case kUncompressedBitmap:
+		break;
+
+	case kRle8BitmapCompression:
+		blitFlags |= kRle8Blit;
+		break;
+
+	case kCccBitmapCompression:
+		blitFlags |= kCccBlit;
+		break;
+
+	case kCccTransparentBitmapCompression:
+		blitFlags |= kCccTransparentBlit;
+		break;
+
+	case kUncompressedTransparentBitmap:
+		blitFlags |= kUncompressedTransparentBlit;
+		break;
+
+	default:
+		error("imageBlit: Got unknown bitmap compression type %d",
+			static_cast<uint>(sourceImage->getCompressionType()));
+	}
+
+	if (dissolveFactor > 1.0 || dissolveFactor < 0.0) {
+		warning("imageBlit: Got out-of-range dissolve factor: %f", dissolveFactor);
+		CLIP(dissolveFactor, 0.0, 1.0);
+	} else if (dissolveFactor == 0.0) {
+		// If the image is fully transparent, there is nothing to draw, so we can return now.
+		return;
+	} else if (dissolveFactor != 1.0) {
+		blitFlags |= kPartialDissolve;
+	}
+	uint integralDissolveFactor = static_cast<uint>(dissolveFactor * 100 + 0.5);
+
+	if (targetImage == nullptr) {
+		targetImage = _screen;
+	}
+
+	// In the disasm, this whole function has complex blit flag logic
+	// throughout, including a lot of switch cases in the statement below
+	// that were unreachable with the flags actually available, as defined above.
+	// The apparently unreachable switch cases are excluded from the statement below.
+	switch (blitFlags) {
+	case kClipEnabled:
+	case kUncompressedTransparentBlit | kClipEnabled:
+		// The original had two different methods for transparent versus
+		// non-transparent blitting, but we will just use simpleBlitFrom in both
+		// cases. It will pick the better method if there is no transparent
+		// color set.
+		blitRectsClip(targetImage, destinationPoint, sourceImage->_image, dirtyRegion);
+		break;
+
+	case kRle8Blit | kClipEnabled:
+		rleBlitRectsClip(targetImage, destinationPoint, sourceImage, dirtyRegion);
+		break;
+
+	case kCccBlit | kClipEnabled:
+	case kCccTransparentBlit | kClipEnabled:
+		// CCC blitting is unimplemented for now because few, if any, titles actually use it.
+		error("imageBlit: CCC blitting not implemented yet");
+		break;
+
+	case kPartialDissolve | kClipEnabled:
+	case kPartialDissolve | kUncompressedTransparentBlit | kClipEnabled:
+	case kPartialDissolve | kRle8Blit | kClipEnabled:
+		// The original had a separate function (rleDissolveBlitRectsClip) for decompressing the RLE and
+		// applying the dissolve at the same time that we are decompressing, but I thought that was too
+		// complex. Instead, we just check the compression type in the same
+		// method and decompress beforehand if necessary.
+		dissolveBlitRectsClip(targetImage, destinationPoint, sourceImage, dirtyRegion, integralDissolveFactor);
+		break;
+
+	default:
+		error("imageBlit: Got invalid blit mode: 0x%x", blitFlags);
+	}
+}
+
+void VideoDisplayManager::blitRectsClip(
+	Graphics::ManagedSurface *dest,
+	const Common::Point &destLocation,
+	const Graphics::ManagedSurface &source,
+	const Common::Array<Common::Rect> &dirtyRegion) {
+
+	for (const Common::Rect &dirtyRect : dirtyRegion) {
+		Common::Rect destRect(destLocation, source.w, source.h);
+		Common::Rect areaToRedraw = dirtyRect.findIntersectingRect(destRect);
+
+		if (!areaToRedraw.isEmpty()) {
+			// Calculate source coordinates (relative to source image).
+			Common::Point originOnScreen(areaToRedraw.origin());
+			areaToRedraw.translate(-destLocation.x, -destLocation.y);
+			dest->simpleBlitFrom(source, areaToRedraw, originOnScreen);
+		}
+	}
+}
+
+void VideoDisplayManager::rleBlitRectsClip(
+	Graphics::ManagedSurface *dest,
+	const Common::Point &destLocation,
+	const Bitmap *source,
+	const Common::Array<Common::Rect> &dirtyRegion) {
+
+	Graphics::ManagedSurface surface = decompressRle8Bitmap(source);
+	Common::Rect destRect(destLocation, source->width(), source->height());
+	for (const Common::Rect &dirtyRect : dirtyRegion) {
+		Common::Rect areaToRedraw = dirtyRect.findIntersectingRect(destRect);
+
+		if (!areaToRedraw.isEmpty()) {
+			// Calculate source coordinates (relative to source image).
+			Common::Point originOnScreen(areaToRedraw.origin());
+			areaToRedraw.translate(-destLocation.x, -destLocation.y);
+			dest->simpleBlitFrom(surface, areaToRedraw, originOnScreen);
+		}
+	}
+}
+
+void VideoDisplayManager::dissolveBlitRectsClip(
+	Graphics::ManagedSurface *dest,
+	const Common::Point &destPos,
+	const Bitmap *source,
+	const Common::Array<Common::Rect> &dirtyRegion,
+	const uint integralDissolveFactor) {
+
+	byte dissolveIndex = DISSOLVE_PATTERN_COUNT;
+	if (integralDissolveFactor != 50) {
+		dissolveIndex = ((integralDissolveFactor + 2) / 4) - 1;
+		CLIP<byte>(dissolveIndex, 0, (DISSOLVE_PATTERN_COUNT - 1));
+	}
+
+	Common::Rect destRect(Common::Rect(destPos, source->width(), source->height()));
+	for (const Common::Rect &dirtyRect : dirtyRegion) {
+		Common::Rect areaToRedraw = dirtyRect.findIntersectingRect(destRect);
+		if (!areaToRedraw.isEmpty()) {
+			// Calculate source coordinates (relative to source image).
+			Common::Point originOnScreen(areaToRedraw.origin());
+			areaToRedraw.translate(-destPos.x, -destPos.y);
+			dissolveBlit1Rect(dest, areaToRedraw, originOnScreen, source, dirtyRect, DISSOLVE_PATTERNS[dissolveIndex]);
+		}
+	}
+}
+
+void VideoDisplayManager::dissolveBlit1Rect(
+	Graphics::ManagedSurface *dest,
+	const Common::Rect &areaToRedraw,
+	const Common::Point &originOnScreen,
+	const Bitmap *source,
+	const Common::Rect &dirtyRegion,
+	const DissolvePattern &pattern) {
+
+	Graphics::ManagedSurface sourceSurface;
+	const Graphics::ManagedSurface *srcPtr = nullptr;
+	switch (source->getCompressionType()) {
+	case kRle8BitmapCompression:
+		sourceSurface = decompressRle8Bitmap(source);
+		srcPtr = &sourceSurface;
+		break;
+
+	case kUncompressedBitmap:
+	case kUncompressedTransparentBitmap:
+		srcPtr = &source->_image;
+		break;
+
+	default:
+		error("dissolveBlit1Rect: Unsupported compression type for dissolve blit: %d",
+			static_cast<uint>(source->getCompressionType()));
+	}
+
+	Common::Point patternStartPos(originOnScreen.x % pattern.widthHeight, originOnScreen.y % pattern.widthHeight);
+	Common::Point currentPatternPos;
+	for (int y = 0; y < areaToRedraw.height(); y++) {
+		currentPatternPos.y = (patternStartPos.y + y) % pattern.widthHeight;
+
+		for (int x = 0; x < areaToRedraw.width(); x++) {
+			currentPatternPos.x = (patternStartPos.x + x) % pattern.widthHeight;
+			uint patternIndex = currentPatternPos.y * pattern.widthHeight + currentPatternPos.x;
+
+			bool shouldDrawPixel = pattern.data[patternIndex] == pattern.threshold;
+			if (shouldDrawPixel) {
+				// Even if the pattern indicates we should draw here, only
+				// copy non-transparent source pixels (value != 0 for transparent bitmaps).
+				Common::Point sourcePos(areaToRedraw.left + x, areaToRedraw.top + y);
+
+				bool sourceXInBounds = sourcePos.x >= 0 && sourcePos.x < srcPtr->w;
+				bool sourceYInBounds = sourcePos.y >= 0 && sourcePos.y < srcPtr->h;
+				if (sourceXInBounds && sourceYInBounds) {
+					byte sourcePixel = srcPtr->getPixel(sourcePos.x, sourcePos.y);
+					if (sourcePixel != 0) {
+						Common::Point destPos(originOnScreen.x + x, originOnScreen.y + y);
+						bool destXInBounds = destPos.x >= 0 && destPos.x < dest->w;
+						bool destYInBounds = destPos.y >= 0 && destPos.y < dest->h;
+						if (destXInBounds && destYInBounds) {
+							dest->setPixel(destPos.x, destPos.y, sourcePixel);
+						} else {
+							warning("dissolveBlit1Rect: Dest out of bounds");
+						}
+					}
+				} else {
+					warning("dissolveBlit1Rect: Source out of bounds");
+				}
+			}
+		}
+	}
+}
+
+void VideoDisplayManager::imageDeltaBlit(
+	const Common::Point &deltaFramePos,
+	const Common::Point &keyFrameOffset,
+	const Bitmap *deltaFrame,
+	const Bitmap *keyFrame,
+	const double dissolveFactor,
+	const Common::Array<Common::Rect> &dirtyRegion) {
+
+	if (deltaFrame->getCompressionType() != kRle8BitmapCompression) {
+		error("imageDeltaBlit: Unsupported delta frame compression type for delta blit: %d",
+			static_cast<uint>(keyFrame->getCompressionType()));
+	} else if (dissolveFactor != 1.0) {
+		warning("imageDeltaBlit: Delta blit does not support dissolving");
+	}
+
+	switch (keyFrame->getCompressionType()) {
+	case kUncompressedBitmap:
+	case kUncompressedTransparentBitmap:
+		deltaRleBlitRectsClip(_screen, deltaFramePos, deltaFrame, keyFrame, dirtyRegion);
+		break;
+
+	case kRle8BitmapCompression:
+		fullDeltaRleBlitRectsClip(_screen, deltaFramePos, keyFrameOffset, deltaFrame, keyFrame, dirtyRegion);
+		break;
+
+	default:
+		error("imageDeltaBlit: Unsupported keyframe image type for delta blit: %d",
+			static_cast<uint>(deltaFrame->getCompressionType()));
+	}
+}
+
+void VideoDisplayManager::fullDeltaRleBlitRectsClip(
+	Graphics::ManagedSurface *destinationImage,
+	const Common::Point &deltaFramePos,
+	const Common::Point &keyFrameOffset,
+	const Bitmap *deltaFrame,
+	const Bitmap *keyFrame,
+	const Common::Array<Common::Rect> &dirtyRegion) {
+
+	Graphics::ManagedSurface surface = decompressRle8Bitmap(deltaFrame, &keyFrame->_image, &keyFrameOffset);
+	for (const Common::Rect &dirtyRect : dirtyRegion) {
+		// The original has a fullDeltaRleBlit1Rect function, but given that we do
+		// the delta application when we decompress the keyframe above, we really
+		// don't need a separate function for this.
+		Common::Rect destRect(deltaFramePos, deltaFrame->width(), deltaFrame->height());
+		Common::Rect areaToRedraw = dirtyRect.findIntersectingRect(destRect);
+
+		if (!areaToRedraw.isEmpty()) {
+			// Calculate source coordinates (relative to source image).
+			Common::Point originOnScreen(areaToRedraw.origin());
+			areaToRedraw.translate(-deltaFramePos.x, -deltaFramePos.y);
+			destinationImage->simpleBlitFrom(surface, areaToRedraw, originOnScreen);
+		}
+	}
+}
+
+void VideoDisplayManager::deltaRleBlitRectsClip(
+	Graphics::ManagedSurface *destinationImage,
+	const Common::Point &deltaFramePos,
+	const Bitmap *deltaFrame,
+	const Bitmap *keyFrame,
+	const Common::Array<Common::Rect> &dirtyRegion) {
+
+	Common::Rect deltaFrameBounds = Common::Rect(deltaFramePos, deltaFrame->width(), deltaFrame->height());
+	for (const Common::Rect &dirtyRect : dirtyRegion) {
+		if (deltaFrameBounds.intersects(dirtyRect)) {
+			deltaRleBlit1Rect(destinationImage, deltaFramePos, deltaFrame, keyFrame, dirtyRect);
+		}
+	}
+}
+
+void VideoDisplayManager::deltaRleBlit1Rect(
+	Graphics::ManagedSurface *destinationImage,
+	const Common::Point &destinationPoint,
+	const Bitmap *deltaFrame,
+	const Bitmap *keyFrame,
+	const Common::Rect &dirtyRect) {
+
+	// This is a very complex function that attempts to decompress the keyframe
+	// and delta frame at the same time, assuming the keyframe is also
+	// compressed. However, real titles don't seem to use it, instead
+	// decompressing the keyframe separately and then passng it in.
+	// So this is left unimplemented until it's actually needed.
+	warning("STUB: deltaRleBlit1Rect");
+}
+
+Graphics::ManagedSurface VideoDisplayManager::decompressRle8Bitmap(
+	const Bitmap *source,
+	const Graphics::ManagedSurface *keyFrame,
+	const Common::Point *keyFrameOffset) {
+
+	// Create a surface to hold the decompressed bitmap.
+	Graphics::ManagedSurface dest;
+	dest.create(source->width(), source->height(), Graphics::PixelFormat::createFormatCLUT8());
+	dest.setTransparentColor(0);
+	int destSizeInBytes = source->width() * source->height();
+
+	Common::SeekableReadStream *chunk = source->_compressedStream;
+	chunk->seek(0);
+
+	bool imageFullyRead = false;
+	Common::Point sourcePos;
+	while (sourcePos.y < source->height()) {
+		sourcePos.x = 0;
+		while (true) {
+			if (sourcePos.y >= source->height()) {
+				break;
+			}
+
+			byte operation = chunk->readByte();
+			if (operation == 0x00) {
+				operation = chunk->readByte();
+				if (operation == 0x00) {
+					// Mark the end of the line.
+					// Also check if the image is finished being read.
+					if (chunk->eos()) {
+						imageFullyRead = true;
+					}
+					break;
+
+				} else if (operation == 0x01) {
+					// Mark the end of the image.
+					imageFullyRead = true;
+					break;
+
+				} else if (operation == 0x02) {
+					// Copy from the keyframe region.
+					assert((keyFrame != nullptr) && (keyFrameOffset != nullptr));
+					byte xToCopy = chunk->readByte();
+					byte yToCopy = chunk->readByte();
+
+					// If we requested to copy multiple lines, do that first.
+					for (int lineOffset = 0; lineOffset < yToCopy; lineOffset++) {
+						Common::Point keyFramePos = sourcePos - *keyFrameOffset + Common::Point(0, lineOffset);
+						Common::Point destPos = sourcePos + Common::Point(0, lineOffset);
+
+						bool sourceXInBounds = (keyFramePos.x >= 0) && (keyFramePos.x + xToCopy <= keyFrame->w);
+						bool sourceYInBounds = (keyFramePos.y >= 0) && (keyFramePos.y < keyFrame->h);
+						bool destInBounds = (destPos.y * dest.w) + (destPos.x + xToCopy) <= destSizeInBytes;
+						if (sourceXInBounds && sourceYInBounds && destInBounds) {
+							const byte *srcPtr = static_cast<const byte *>(keyFrame->getBasePtr(keyFramePos.x, keyFramePos.y));
+							byte *destPtr = static_cast<byte *>(dest.getBasePtr(destPos.x, destPos.y));
+							memcpy(destPtr, srcPtr, xToCopy);
+						} else {
+							warning("decompressRle8Bitmap: Keyframe copy (multi-line) exceeds bounds");
+						}
+					}
+
+					// Then copy the pixels in the same line.
+					Common::Point keyFramePos = sourcePos - *keyFrameOffset;
+					bool sourceXInBounds = (keyFramePos.x >= 0) && (keyFramePos.x + xToCopy <= keyFrame->w);
+					bool sourceYInBounds = (keyFramePos.y >= 0) && (keyFramePos.y < keyFrame->h);
+					bool destInBounds = (sourcePos.y * dest.w) + (sourcePos.x + xToCopy) <= destSizeInBytes;
+					if (sourceXInBounds && sourceYInBounds && destInBounds) {
+						const byte *srcPtr = static_cast<const byte *>(keyFrame->getBasePtr(keyFramePos.x, keyFramePos.y));
+						byte *destPtr = static_cast<byte *>(dest.getBasePtr(sourcePos.x, sourcePos.y));
+						memcpy(destPtr, srcPtr, xToCopy);
+					} else {
+						warning("decompressRle8Bitmap: Keyframe copy (same line) exceeds bounds");
+					}
+
+					sourcePos += Common::Point(xToCopy, yToCopy);
+
+				} else if (operation == 0x03) {
+					// Adjust the pixel position.
+					sourcePos.x += chunk->readByte();
+					sourcePos.y += chunk->readByte();
+
+				} else if (operation >= 0x04) {
+					// Read a run of uncompressed pixels.
+					// The bounds check is structured this way because the run can extend across scanlines.
+					byte runLength = operation;
+					uint maxAllowedRun = destSizeInBytes - (sourcePos.y * dest.w + sourcePos.x);
+					CLIP<uint>(runLength, 0, maxAllowedRun);
+
+					byte *destPtr = static_cast<byte *>(dest.getBasePtr(sourcePos.x, sourcePos.y));
+					chunk->read(destPtr, runLength);
+					if (chunk->pos() % 2 == 1) {
+						chunk->readByte();
+					}
+
+					sourcePos.x += runLength;
+				}
+			} else {
+				// Read a run of length encoded pixels.
+				byte colorIndexToRepeat = chunk->readByte();
+				byte repetitionCount = operation;
+				uint maxAllowedCount = destSizeInBytes - (sourcePos.y * dest.w + sourcePos.x);
+				CLIP<uint>(repetitionCount, 0, maxAllowedCount);
+
+				byte *destPtr = static_cast<byte *>(dest.getBasePtr(sourcePos.x, sourcePos.y));
+				memset(destPtr, colorIndexToRepeat, repetitionCount);
+				sourcePos.x += repetitionCount;
+			}
+		}
+
+		sourcePos.y++;
+		if (imageFullyRead) {
+			break;
+		}
+	}
+
+	return dest;
+}
+
+} // End of namespace MediaStation
diff --git a/engines/mediastation/graphics.h b/engines/mediastation/graphics.h
new file mode 100644
index 00000000000..25466a14c25
--- /dev/null
+++ b/engines/mediastation/graphics.h
@@ -0,0 +1,190 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MEDIASTATION_GRAPHICS_H
+#define MEDIASTATION_GRAPHICS_H
+
+#include "common/rect.h"
+#include "common/array.h"
+#include "graphics/managed_surface.h"
+#include "graphics/screen.h"
+
+#include "mediastation/mediascript/scriptvalue.h"
+
+namespace MediaStation {
+
+class MediaStationEngine;
+struct DissolvePattern;
+class Bitmap;
+
+enum BlitMode {
+	kUncompressedBlit = 0x00,
+	kRle8Blit = 0x01,
+	kClipEnabled = 0x04,
+	kUncompressedTransparentBlit = 0x08,
+	kPartialDissolve = 0x10,
+	kFullDissolve = 0x20,
+	kCccBlit = 0x40,
+	kCccTransparentBlit = 0x80
+};
+
+enum TransitionType {
+	kTransitionFadeToBlack = 300,
+	kTransitionFadeToPalette = 301,
+	kTransitionSetToPalette = 302,
+	kTransitionSetToBlack = 303,
+	kTransitionFadeToColor = 304,
+	kTransitionSetToColor = 305,
+	kTransitionSetToPercentOfPalette = 306,
+	kTransitionFadeToPaletteObject = 307,
+	kTransitionSetToPaletteObject = 308,
+	kTransitionSetToPercentOfPaletteObject = 309,
+	kTransitionColorShiftCurrentPalette = 310,
+	kTransitionCircleOut = 328
+};
+
+class VideoDisplayManager {
+public:
+	VideoDisplayManager(MediaStationEngine *vm);
+	~VideoDisplayManager();
+
+	void updateScreen() { _screen->update(); }
+	Graphics::Palette *getRegisteredPalette() { return _registeredPalette; }
+	void setRegisteredPalette(Graphics::Palette *palette) { _registeredPalette = palette; }
+
+	void imageBlit(
+		const Common::Point &destinationPoint,
+		const Bitmap *image,
+		const double dissolveFactor,
+		const Common::Array<Common::Rect> &dirtyRegion,
+		Graphics::ManagedSurface *destinationImage = nullptr);
+
+	void imageDeltaBlit(
+		const Common::Point &deltaFramePos,
+		const Common::Point &keyFrameOffset,
+		const Bitmap *deltaFrame,
+		const Bitmap *keyFrame,
+		const double dissolveFactor,
+		const Common::Array<Common::Rect> &dirtyRegion);
+
+	void effectTransition(Common::Array<ScriptValue> &args);
+	void setTransitionOnSync(Common::Array<ScriptValue> &args) { _scheduledTransitionOnSync = args; }
+	void doTransitionOnSync();
+
+private:
+	static const uint SCREEN_WIDTH = 640;
+	static const uint SCREEN_HEIGHT = 480;
+
+	MediaStationEngine *_vm = nullptr;
+	Graphics::Screen *_screen = nullptr;
+	Graphics::Palette *_registeredPalette = nullptr;
+	Common::Array<ScriptValue> _scheduledTransitionOnSync;
+
+	// Blitting methods.
+	// blitRectsClip encompasses the functionality of both opaqueBlitRectsClip
+	// and transparentBlitRectsClip in the disasm.
+	void blitRectsClip(
+		Graphics::ManagedSurface *dest,
+		const Common::Point &destLocation,
+		const Graphics::ManagedSurface &source,
+		const Common::Array<Common::Rect> &dirtyRegion);
+	void rleBlitRectsClip(
+		Graphics::ManagedSurface *dest,
+		const Common::Point &destLocation,
+		const Bitmap *source,
+		const Common::Array<Common::Rect> &dirtyRegion);
+	Graphics::ManagedSurface decompressRle8Bitmap(
+		const Bitmap *source,
+		const Graphics::ManagedSurface *keyFrame = nullptr,
+		const Common::Point *keyFrameOffset = nullptr);
+	void dissolveBlitRectsClip(
+		Graphics::ManagedSurface *dest,
+		const Common::Point &destPos,
+		const Bitmap *source,
+		const Common::Array<Common::Rect> &dirtyRegion,
+		const uint dissolveFactor);
+	void dissolveBlit1Rect(
+		Graphics::ManagedSurface *dest,
+		const Common::Rect &areaToRedraw,
+		const Common::Point &originOnScreen,
+		const Bitmap *source,
+		const Common::Rect &dirtyRegion,
+		const DissolvePattern &pattern);
+	void fullDeltaRleBlitRectsClip(
+		Graphics::ManagedSurface *destinationImage,
+		const Common::Point &deltaFramePos,
+		const Common::Point &keyFrameOffset,
+		const Bitmap *deltaFrame,
+		const Bitmap *keyFrame,
+		const Common::Array<Common::Rect> &dirtyRegion);
+	void deltaRleBlitRectsClip(
+		Graphics::ManagedSurface *destinationImage,
+		const Common::Point &deltaFramePos,
+		const Bitmap *deltaFrame,
+		const Bitmap *keyFrame,
+		const Common::Array<Common::Rect> &dirtyRegion);
+	void deltaRleBlit1Rect(
+		Graphics::ManagedSurface *destinationImage,
+		const Common::Point &destinationPoint,
+		const Bitmap *sourceImage,
+		const Bitmap *deltaImage,
+		const Common::Rect &dirtyRect);
+
+	// Transition methods.
+	const double DEFAULT_FADE_TRANSITION_TIME_IN_SECONDS = 0.5;
+	const byte DEFAULT_PALETTE_TRANSITION_START_INDEX = 0x01;
+	const byte DEFAULT_PALETTE_TRANSITION_COLOR_COUNT = 0xFE;
+	void fadeToBlack(Common::Array<ScriptValue> &args);
+	void fadeToRegisteredPalette(Common::Array<ScriptValue> &args);
+	void setToRegisteredPalette(Common::Array<ScriptValue> &args);
+	void setToBlack(Common::Array<ScriptValue> &args);
+	void fadeToColor(Common::Array<ScriptValue> &args);
+	void setToColor(Common::Array<ScriptValue> &args);
+	void setToPercentOfPalette(Common::Array<ScriptValue> &args);
+	void fadeToPaletteObject(Common::Array<ScriptValue> &args);
+	void setToPaletteObject(Common::Array<ScriptValue> &args);
+	void setToPercentOfPaletteObject(Common::Array<ScriptValue> &args);
+	void colorShiftCurrentPalette(Common::Array<ScriptValue> &args);
+	void circleOut(Common::Array<ScriptValue> &args);
+
+	void _setPalette(Graphics::Palette &palette, uint startIndex, uint colorCount);
+	void _setPaletteToColor(Graphics::Palette &targetPalette, byte r, byte g, byte b);
+	uint _limitColorRange(uint &startIndex, uint &colorCount);
+	byte _interpolateColorComponent(byte source, byte target, double progress);
+
+	void _fadeToColor(byte r, byte g, byte b, double fadeTime, uint startIndex, uint colorCount);
+	void _setToColor(byte r, byte g, byte b, uint startIndex, uint colorCount);
+	void _setPercentToColor(double percent, byte r, byte g, byte b, uint startIndex, uint colorCount);
+
+	void _fadeToPalette(double fadeTime, Graphics::Palette &targetPalette, uint startIndex, uint colorCount);
+	void _setToPercentPalette(double percent, Graphics::Palette &currentPalette, Graphics::Palette &targetPalette,
+		uint startIndex, uint colorCount);
+	void _fadeToRegisteredPalette(double fadeTime, uint startIndex, uint colorCount);
+	void _setToRegisteredPalette(uint startIndex, uint colorCount);
+	void _colorShiftCurrentPalette(uint startIndex, uint shiftAmount, uint colorCount);
+	void _fadeToPaletteObject(uint paletteId, double fadeTime, uint startIndex, uint colorCount);
+	void _setToPaletteObject(uint paletteId, uint startIndex, uint colorCount);
+	void _setPercentToPaletteObject(double percent, uint paletteId, uint startIndex, uint colorCount);
+};
+
+} // End of namespace MediaStation
+
+#endif
diff --git a/engines/mediastation/mediastation.cpp b/engines/mediastation/mediastation.cpp
index d14216fc5ab..9fa02af597e 100644
--- a/engines/mediastation/mediastation.cpp
+++ b/engines/mediastation/mediastation.cpp
@@ -20,7 +20,6 @@
  */
 
 #include "common/config-manager.h"
-#include "engines/util.h"
 
 #include "mediastation/mediastation.h"
 #include "mediastation/debugchannels.h"
@@ -54,8 +53,8 @@ MediaStationEngine::MediaStationEngine(OSystem *syst, const ADGameDescription *g
 }
 
 MediaStationEngine::~MediaStationEngine() {
-	delete _screen;
-	_screen = nullptr;
+	delete _displayManager;
+	_displayManager = nullptr;
 
 	delete _cursor;
 	_cursor = nullptr;
@@ -136,10 +135,7 @@ bool MediaStationEngine::isFirstGenerationEngine() {
 }
 
 Common::Error MediaStationEngine::run() {
-	initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
-	_screen = new Graphics::Screen();
-	// TODO: Determine if all titles blank the screen to 0xff.
-	_screen->fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0xff);
+	_displayManager = new VideoDisplayManager(this);
 
 	Common::Path bootStmFilepath = Common::Path("BOOT.STM");
 	_boot = new Boot(bootStmFilepath);
@@ -180,25 +176,23 @@ Common::Error MediaStationEngine::run() {
 			_requestedContextReleaseId.clear();
 		}
 
+		if (_requestedScreenBranchId != 0) {
+			doBranchToScreen();
+			_requestedScreenBranchId = 0;
+		}
+
 		debugC(5, kDebugGraphics, "***** START SCREEN UPDATE ***");
 		for (Asset *asset : _assets) {
 			asset->process();
 
-			if (_requestedScreenBranchId != 0) {
-				doBranchToScreen();
-				_requestedScreenBranchId = 0;
-				break;
-			}
-
 			if (_needsHotspotRefresh) {
 				refreshActiveHotspot();
 				_needsHotspotRefresh = false;
 			}
 		}
-		redraw();
+		draw();
 		debugC(5, kDebugGraphics, "***** END SCREEN UPDATE ***");
 
-		_screen->update();
 		g_system->delayMillis(10);
 	}
 
@@ -296,33 +290,21 @@ void MediaStationEngine::refreshActiveHotspot() {
 	}
 }
 
-void MediaStationEngine::redraw() {
-	if (_dirtyRects.empty()) {
-		return;
-	}
-
-	for (Common::Rect dirtyRect : _dirtyRects) {
+void MediaStationEngine::draw() {
+	if (!_dirtyRects.empty()) {
 		for (Asset *asset : _spatialEntities) {
-			if (!asset->isSpatialActor()) {
-				continue;
-			}
-
-			SpatialEntity *entity = static_cast<SpatialEntity *>(asset);
-			if (!entity->isVisible()) {
-				continue;
-			}
-
-			Common::Rect bbox = entity->getBbox();
-			if (!bbox.isEmpty()) {
-				if (dirtyRect.intersects(bbox)) {
-					entity->redraw(dirtyRect);
+			if (asset->isSpatialActor()) {
+				SpatialEntity *entity = static_cast<SpatialEntity *>(asset);
+				if (entity->isVisible()) {
+					entity->draw(_dirtyRects);
 				}
 			}
 		}
-	}
 
-	_screen->update();
-	_dirtyRects.clear();
+		_dirtyRects.clear();
+	}
+	_displayManager->updateScreen();
+	_displayManager->doTransitionOnSync();
 }
 
 Context *MediaStationEngine::loadContext(uint32 contextId) {
@@ -363,28 +345,10 @@ Context *MediaStationEngine::loadContext(uint32 contextId) {
 	}
 	Context *context = new Context(entryCxtFilepath);
 
-	// Some contexts have a built-in palette that becomes active when the
-	// context is loaded, and some rely on scripts to set
-	// the palette later.
-	if (context->_palette != nullptr) {
-		_screen->setPalette(*context->_palette);
-	}
-
 	_loadedContexts.setVal(contextId, context);
 	return context;
 }
 
-void MediaStationEngine::setPalette(Asset *asset) {
-	if (asset == nullptr) {
-		error("Requested palette not found");
-	} else if (asset->type() != kAssetTypePalette) {
-		error("Requested palette %d is not a palette", asset->id());
-	} else {
-		Palette *paletteAsset = static_cast<Palette *>(asset);
-		_screen->setPalette(*paletteAsset->_palette);
-	}
-}
-
 void MediaStationEngine::registerAsset(Asset *assetToAdd) {
 	if (getAssetById(assetToAdd->id())) {
 		error("Asset with ID 0x%d was already defined in this title", assetToAdd->id());
@@ -410,13 +374,12 @@ void MediaStationEngine::doBranchToScreen() {
 		releaseContext(_currentContext->_screenAsset->id());
 	}
 
-	Context *context = loadContext(_requestedScreenBranchId);
-	_currentContext = context;
-	_dirtyRects.push_back(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT));
+	_currentContext = loadContext(_requestedScreenBranchId);
 	_currentHotspot = nullptr;
+	_displayManager->setRegisteredPalette(_currentContext->_palette);
 
-	if (context->_screenAsset != nullptr) {
-		context->_screenAsset->runEventHandlerIfExists(kEntryEvent);
+	if (_currentContext->_screenAsset != nullptr) {
+		_currentContext->_screenAsset->runEventHandlerIfExists(kEntryEvent);
 	}
 
 	_requestedScreenBranchId = 0;
@@ -488,11 +451,12 @@ ScriptValue MediaStationEngine::callBuiltInFunction(BuiltInFunction function, Co
 
 	switch (function) {
 	case kEffectTransitionFunction:
-	case kEffectTransitionOnSyncFunction: {
-		// TODO: effectTransitionOnSync should be split out into its own function.
-		effectTransition(args);
+		_displayManager->effectTransition(args);
+		return returnValue;
+
+	case kEffectTransitionOnSyncFunction:
+		_displayManager->setTransitionOnSync(args);
 		return returnValue;
-	}
 
 	case kDrawingFunction: {
 		// Not entirely sure what this function does, but it seems like a way to
diff --git a/engines/mediastation/mediastation.h b/engines/mediastation/mediastation.h
index 8189da60b5e..609abb783c4 100644
--- a/engines/mediastation/mediastation.h
+++ b/engines/mediastation/mediastation.h
@@ -34,7 +34,6 @@
 #include "common/util.h"
 #include "engines/engine.h"
 #include "engines/savestate.h"
-#include "graphics/screen.h"
 
 #include "mediastation/detection.h"
 #include "mediastation/datafile.h"
@@ -42,11 +41,13 @@
 #include "mediastation/context.h"
 #include "mediastation/asset.h"
 #include "mediastation/cursors.h"
+#include "mediastation/graphics.h"
 
 namespace MediaStation {
 
 struct MediaStationGameDescription;
 class Hotspot;
+class Bitmap;
 
 // Most Media Station titles follow this file structure from the root directory
 // of the CD-ROM:
@@ -78,9 +79,9 @@ public:
 	bool isFirstGenerationEngine();
 	void processEvents();
 	void refreshActiveHotspot();
-	void redraw();
+	void addDirtyRect(const Common::Rect &rect) { _dirtyRects.push_back(rect); }
+	void draw();
 
-	void setPalette(Asset *palette);
 	void registerAsset(Asset *assetToAdd);
 	void scheduleScreenBranch(uint screenId);
 	void scheduleContextRelease(uint contextId);
@@ -89,21 +90,16 @@ public:
 	Asset *getAssetByChunkReference(uint chunkReference);
 	Function *getFunctionById(uint functionId);
 	ScriptValue *getVariable(uint variableId);
+	VideoDisplayManager *getDisplayManager() { return _displayManager; }
 
 	ScriptValue callBuiltInFunction(BuiltInFunction function, Common::Array<ScriptValue> &args);
 	Common::RandomSource _randomSource;
 
-	Graphics::Screen *_screen = nullptr;
 	Context *_currentContext = nullptr;
 
 	Common::Point _mousePos;
-	Common::Array<Common::Rect> _dirtyRects;
 	bool _needsHotspotRefresh = false;
 
-	// All Media Station titles run at 640x480.
-	const uint16 SCREEN_WIDTH = 640;
-	const uint16 SCREEN_HEIGHT = 480;
-
 protected:
 	Common::Error run() override;
 
@@ -111,28 +107,28 @@ private:
 	Common::Event _event;
 	Common::FSNode _gameDataDir;
 	const ADGameDescription *_gameDescription;
+	Common::Array<Common::Rect> _dirtyRects;
 
 	// In Media Station, only the cursors are stored in the executable; everything
 	// else is in the Context (*.CXT) data files.
 	CursorManager *_cursor;
 	void setCursor(uint id);
 
+	VideoDisplayManager *_displayManager = nullptr;
+
 	Boot *_boot = nullptr;
 	Common::Array<Asset *> _assets;
 	Common::SortedArray<SpatialEntity *, const SpatialEntity *> _spatialEntities;
 	Common::HashMap<uint, Context *> _loadedContexts;
 	Asset *_currentHotspot = nullptr;
-
 	uint _requestedScreenBranchId = 0;
 	Common::Array<uint> _requestedContextReleaseId;
-	void doBranchToScreen();
 
+	void doBranchToScreen();
 	Context *loadContext(uint32 contextId);
 	void releaseContext(uint32 contextId);
 	Asset *findAssetToAcceptMouseEvents();
 
-	void effectTransition(Common::Array<ScriptValue> &args);
-
 	static int compareAssetByZIndex(const SpatialEntity *a, const SpatialEntity *b);
 };
 
diff --git a/engines/mediastation/module.mk b/engines/mediastation/module.mk
index a32a467c3f3..b86556b3dcc 100644
--- a/engines/mediastation/module.mk
+++ b/engines/mediastation/module.mk
@@ -21,6 +21,7 @@ MODULE_OBJS = \
 	context.o \
 	cursors.o \
 	datafile.o \
+	graphics.o \
 	mediascript/codechunk.o \
 	mediascript/collection.o \
 	mediascript/eventhandler.o \
@@ -28,8 +29,7 @@ MODULE_OBJS = \
 	mediascript/scriptconstants.o \
 	mediascript/scriptvalue.o \
 	mediastation.o \
-	metaengine.o \
-	transitions.o
+	metaengine.o
 
 # This module can be built as a plugin
 ifeq ($(ENABLE_MEDIASTATION), DYNAMIC_PLUGIN)
diff --git a/engines/mediastation/transitions.cpp b/engines/mediastation/transitions.cpp
deleted file mode 100644
index 14b23e267ef..00000000000
--- a/engines/mediastation/transitions.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* ScummVM - Graphic Adventure Engine
- *
- * ScummVM is the legal property of its developers, whose names
- * are too numerous to list here. Please refer to the COPYRIGHT
- * file distributed with this source distribution.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "mediastation/mediastation.h"
-
-namespace MediaStation {
-
-enum TransitionType {
-	kTransitionFadeToBlack = 300,
-	kTransitionFadeToPalette = 301,
-	kTransitionSetToPalette = 302,
-	kTransitionSetToBlack = 303,
-	kTransitionFadeToColor = 304,
-	kTransitionSetToColor = 305,
-	kTransitionSetToPercentOfPalette = 306,
-	kTransitionFadeToPaletteObject = 307,
-	kTransitionSetToPaletteObject = 308,
-	kTransitionSetToPercentOfPaletteObject = 309,
-	kTransitionCircleOut = 328
-};
-
-void MediaStationEngine::effectTransition(Common::Array<ScriptValue> &args) {
-	TransitionType transitionType = static_cast<TransitionType>(args[0].asParamToken());
-	switch (transitionType) {
-	case kTransitionFadeToBlack:
-	case kTransitionSetToBlack: {
-		// TODO: Implement transition.
-		warning("MediaStationEngine::effectTransition(): Fading/setting to black not implemented");
-		break;
-	}
-
-	case kTransitionFadeToPalette:
-	case kTransitionSetToPalette: {
-		// TODO: Implement transition by getting palette out of current context.
-		warning("MediaStationEngine::effectTransition(): Fading/setting to palette not implemented, changing palette immediately");
-		break;
-	}
-
-	case kTransitionFadeToColor:
-	case kTransitionSetToColor: {
-		// TODO: Implement transitions.
-		warning("MediaStationEngine::effectTransition(): Fading/setting to color not implemented");
-		break;
-	}
-
-	case kTransitionFadeToPaletteObject: {
-		// TODO: Implement transition.
-		warning("MediaStationEngine::effectTransition(): Fading to palette object not implemented, changing palette immediately");
-		Asset *asset = g_engine->getAssetById(args[1].asAssetId());
-		g_engine->setPalette(asset);
-		break;
-	}
-
-	case kTransitionSetToPaletteObject: {
-		Asset *asset = g_engine->getAssetById(args[1].asAssetId());
-		g_engine->setPalette(asset);
-		break;
-	}
-
-	case kTransitionSetToPercentOfPaletteObject: {
-		double percentComplete = args[1].asFloat();
-
-		// TODO: Implement percent of palette transition.
-		warning("MediaStationEngine::effectTransition(): Setting to %f%% of palette not implemented, changing palette immediately", percentComplete);
-		Asset *asset = g_engine->getAssetById(args[2].asAssetId());
-		g_engine->setPalette(asset);
-		break;
-	}
-
-	case kTransitionCircleOut: {
-		warning("MediaStationEngine::effectTransition(): Circle out transition not implemented");
-		break;
-	}
-
-	default:
-		warning("MediaStationEngine::effectTransition(): Got unknown transition type %d", static_cast<uint>(transitionType));
-	}
-}
-
-} // End of namespace MediaStation
\ No newline at end of file




More information about the Scummvm-git-logs mailing list