[Scummvm-git-logs] scummvm master -> 325674ace7774333b191327cba40bd3878d37374

sev- noreply at scummvm.org
Sun Feb 19 22:51:17 UTC 2023


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

Summary:
3db67a33ac GRAPHICS: Add support for pixels with masks and inverted pixels
866e459992 TESTBED: Add tests for masked and inverted cursors.
76d84718e2 GRAPHICS: Corrected semantics for MacOS cursors
0a32f23bd6 BACKENDS: ANDROID: Update log message for setMouseCursor
607e49756c BACKENDS: IOS7: Enable log message for unsupported cursor masks
774066714a BACKENDS: N64: Fix wrong error message
6af0bd2a95 COMMON: Correct cursor mask comments.
d7303bd006 GRAPHICS: Delete duplicated (and incorrect) parameter description.
325674ace7 GRAPHICS: Add masked cursor support to SurfaceSdl, fix tests


Commit: 3db67a33aca701bae81d2b68da6c2455656bccc8
    https://github.com/scummvm/scummvm/commit/3db67a33aca701bae81d2b68da6c2455656bccc8
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
GRAPHICS: Add support for pixels with masks and inverted pixels

Changed paths:
    backends/graphics/graphics.h
    backends/graphics/null/null-graphics.h
    backends/graphics/opengl/framebuffer.cpp
    backends/graphics/opengl/framebuffer.h
    backends/graphics/opengl/opengl-graphics.cpp
    backends/graphics/opengl/opengl-graphics.h
    backends/graphics/opengl/texture.cpp
    backends/graphics/opengl/texture.h
    backends/graphics/surfacesdl/surfacesdl-graphics.cpp
    backends/graphics/surfacesdl/surfacesdl-graphics.h
    backends/graphics3d/android/android-graphics3d.cpp
    backends/graphics3d/android/android-graphics3d.h
    backends/graphics3d/openglsdl/openglsdl-graphics3d.h
    backends/modular-backend.cpp
    backends/modular-backend.h
    backends/platform/3ds/osystem-graphics.cpp
    backends/platform/3ds/osystem.h
    backends/platform/dc/dc.h
    backends/platform/dc/display.cpp
    backends/platform/ds/ds-graphics.cpp
    backends/platform/ds/osystem_ds.h
    backends/platform/ios7/ios7_osys_main.h
    backends/platform/ios7/ios7_osys_video.mm
    backends/platform/iphone/osys_main.h
    backends/platform/iphone/osys_video.mm
    backends/platform/n64/osys_n64.h
    backends/platform/n64/osys_n64_base.cpp
    backends/platform/psp/osys_psp.cpp
    backends/platform/psp/osys_psp.h
    backends/platform/wii/osystem.h
    backends/platform/wii/osystem_gfx.cpp
    common/system.h
    graphics/cursor.h
    graphics/cursorman.cpp
    graphics/cursorman.h
    graphics/wincursor.cpp


diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h
index 417ac7d192c..553bc26effc 100644
--- a/backends/graphics/graphics.h
+++ b/backends/graphics/graphics.h
@@ -99,7 +99,7 @@ public:
 
 	virtual bool showMouse(bool visible) = 0;
 	virtual void warpMouse(int x, int y) = 0;
-	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) = 0;
+	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = nullptr, const byte *mask = nullptr) = 0;
 	virtual void setCursorPalette(const byte *colors, uint start, uint num) = 0;
 
 	virtual void displayMessageOnOSD(const Common::U32String &msg) {}
diff --git a/backends/graphics/null/null-graphics.h b/backends/graphics/null/null-graphics.h
index ef6fc4227bd..60437e23238 100644
--- a/backends/graphics/null/null-graphics.h
+++ b/backends/graphics/null/null-graphics.h
@@ -83,7 +83,7 @@ public:
 
 	bool showMouse(bool visible) override { return !visible; }
 	void warpMouse(int x, int y) override {}
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override {}
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL) override {}
 	void setCursorPalette(const byte *colors, uint start, uint num) override {}
 
 private:
diff --git a/backends/graphics/opengl/framebuffer.cpp b/backends/graphics/opengl/framebuffer.cpp
index 59096fffd97..e3be0ed990b 100644
--- a/backends/graphics/opengl/framebuffer.cpp
+++ b/backends/graphics/opengl/framebuffer.cpp
@@ -119,6 +119,14 @@ void Framebuffer::applyBlendState() {
 			GL_CALL(glEnable(GL_BLEND));
 			GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 			break;
+		case kBlendModeAdditive:
+			GL_CALL(glEnable(GL_BLEND));
+			GL_CALL(glBlendFunc(GL_ONE, GL_ONE));
+			break;
+		case kBlendModeMaskAlphaAndInvertByColor:
+			GL_CALL(glEnable(GL_BLEND));
+			GL_CALL(glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA));
+			break;
 		default:
 			break;
 	}
diff --git a/backends/graphics/opengl/framebuffer.h b/backends/graphics/opengl/framebuffer.h
index 8e7f57013be..3d3b7909bf8 100644
--- a/backends/graphics/opengl/framebuffer.h
+++ b/backends/graphics/opengl/framebuffer.h
@@ -59,7 +59,18 @@ public:
 		 * Requires the image data being drawn to have its color values pre-multipled
 		 * with the alpha value.
 		 */
-		kBlendModePremultipliedTransparency
+		kBlendModePremultipliedTransparency,
+
+		/**
+		 * Newly drawn pixels add to the destination value.
+		 */
+		kBlendModeAdditive,
+
+		/**
+		 * Newly drawn pixels mask out existing pixels based on the alpha value and
+		 * add inversions of the pixels based on the color.
+		 */
+		kBlendModeMaskAlphaAndInvertByColor,
 	};
 
 	/**
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index 3128b18644c..82ed23fa41a 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -73,10 +73,10 @@ OpenGLGraphicsManager::OpenGLGraphicsManager()
 	  _pipeline(nullptr), _stretchMode(STRETCH_FIT),
 	  _defaultFormat(), _defaultFormatAlpha(),
 	  _gameScreen(nullptr), _overlay(nullptr),
-	  _cursor(nullptr),
+	  _cursor(nullptr), _cursorMask(nullptr),
 	  _cursorHotspotX(0), _cursorHotspotY(0),
 	  _cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0),
-	  _cursorKeyColor(0), _cursorDontScale(false), _cursorPaletteEnabled(false), _shakeOffsetScaled()
+	  _cursorKeyColor(0), _cursorUseKey(true), _cursorDontScale(false), _cursorPaletteEnabled(false), _shakeOffsetScaled()
 #if !USE_FORCED_GLES
 	  , _libretroPipeline(nullptr)
 #endif
@@ -96,6 +96,7 @@ OpenGLGraphicsManager::~OpenGLGraphicsManager() {
 	delete _gameScreen;
 	delete _overlay;
 	delete _cursor;
+	delete _cursorMask;
 #ifdef USE_OSD
 	delete _osdMessageSurface;
 	delete _osdIconSurface;
@@ -112,6 +113,8 @@ bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) const {
 	case OSystem::kFeatureCursorPalette:
 	case OSystem::kFeatureFilteringMode:
 	case OSystem::kFeatureStretchMode:
+	case OSystem::kFeatureCursorMask:
+	case OSystem::kFeatureCursorMaskInvert:
 #ifdef USE_SCALERS
 	case OSystem::kFeatureScalers:
 #endif
@@ -588,6 +591,41 @@ void OpenGLGraphicsManager::fillScreen(uint32 col) {
 	_gameScreen->fill(col);
 }
 
+void OpenGLGraphicsManager::renderCursor() {
+	/*
+	Windows and Mac cursor XOR works by drawing the cursor to the screen with the formula (Destination AND Mask XOR Color)
+
+	OpenGL does not have an XOR blend mode though.  Full inversions can be accomplished by using blend modes with
+	ONE_MINUS_DST_COLOR but the problem is how to do that in a way that handles linear filtering properly.
+
+	To avoid color fringing, we need to produce an output of 3 separately-modulated inputs: The framebuffer modulated by
+	(1 - inversion)*(1 - alpha), the inverted framebuffer modulated by inversion*(1 - alpha), and the cursor colors modulated by alpha.
+	The last part is additive and not framebuffer dependent so it can just be a separate draw call.  The first two are the problem
+	because we can't use the unmodified framebuffer value twice if we do it in two separate draw calls, and if we do it in a single
+	draw call, we can only supply one RGB input even though the inversion mask should be RGB.
+
+	If we only allow grayscale inversions though, then we can put inversion*(1 - alpha) in the RGB channel and
+	(1 - inversion)*(1 - alpha) in the alpha channel and use and use ((1-dstColor)*src+(1-srcAlpha)*dest) blend formula to do
+	the inversion and opacity mask at once.  We use 1-srcAlpha instead of srcAlpha so zero-fill is transparent.
+	*/
+	if (_cursorMask) {
+		_backBuffer.enableBlend(Framebuffer::kBlendModeMaskAlphaAndInvertByColor);
+
+		_pipeline->drawTexture(_cursorMask->getGLTexture(),
+							   _cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
+							   _cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
+							   _cursorWidthScaled, _cursorHeightScaled);
+
+		_backBuffer.enableBlend(Framebuffer::kBlendModeAdditive);
+	} else
+		_backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency);
+
+	_pipeline->drawTexture(_cursor->getGLTexture(),
+						   _cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
+						   _cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
+						   _cursorWidthScaled, _cursorHeightScaled);
+}
+
 void OpenGLGraphicsManager::updateScreen() {
 	if (!_gameScreen) {
 		return;
@@ -616,7 +654,7 @@ void OpenGLGraphicsManager::updateScreen() {
 	    && !(_libretroPipeline && _libretroPipeline->isAnimated())
 #endif
 	    && !(_overlayVisible && _overlay->isDirty())
-	    && !(_cursorVisible && _cursor && _cursor->isDirty())
+	    && !(_cursorVisible && ((_cursor && _cursor->isDirty()) || (_cursorMask && _cursorMask->isDirty())))
 #ifdef USE_OSD
 	    && !_osdMessageSurface && !_osdIconSurface
 #endif
@@ -629,6 +667,9 @@ void OpenGLGraphicsManager::updateScreen() {
 	if (_cursorVisible && _cursor) {
 		_cursor->updateGLTexture();
 	}
+	if (_cursorVisible && _cursorMask) {
+		_cursorMask->updateGLTexture();
+	}
 	_overlay->updateGLTexture();
 
 #if !USE_FORCED_GLES
@@ -665,12 +706,7 @@ void OpenGLGraphicsManager::updateScreen() {
 		// This has the disadvantage of having overlay (subtitles) drawn above it
 		// but the cursor will look nicer
 		if (!_overlayInGUI && drawCursor) {
-			_backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency);
-
-			_pipeline->drawTexture(_cursor->getGLTexture(),
-			                         _cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
-			                         _cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
-			                         _cursorWidthScaled, _cursorHeightScaled);
+			renderCursor();
 			drawCursor = false;
 
 			// Everything we need to clip has been clipped
@@ -691,14 +727,8 @@ void OpenGLGraphicsManager::updateScreen() {
 	}
 
 	// Fourth step: Draw the cursor if we didn't before.
-	if (drawCursor) {
-		_backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency);
-
-		_pipeline->drawTexture(_cursor->getGLTexture(),
-		                         _cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
-		                         _cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
-		                         _cursorWidthScaled, _cursorHeightScaled);
-	}
+	if (drawCursor)
+		renderCursor();
 
 	if (!_overlayVisible) {
 		_backBuffer.enableScissorTest(false);
@@ -819,12 +849,12 @@ namespace {
 template<typename SrcColor, typename DstColor>
 void multiplyColorWithAlpha(const byte *src, byte *dst, const uint w, const uint h,
 							const Graphics::PixelFormat &srcFmt, const Graphics::PixelFormat &dstFmt,
-							const uint srcPitch, const uint dstPitch, const SrcColor keyColor) {
+							const uint srcPitch, const uint dstPitch, const SrcColor keyColor, bool useKeyColor) {
 	for (uint y = 0; y < h; ++y) {
 		for (uint x = 0; x < w; ++x) {
 			const uint32 color = *(const SrcColor *)src;
 
-			if (color == keyColor) {
+			if (useKeyColor && color == keyColor) {
 				*(DstColor *)dst = 0;
 			} else {
 				byte a, r, g, b;
@@ -849,9 +879,12 @@ void multiplyColorWithAlpha(const byte *src, byte *dst, const uint w, const uint
 }
 } // End of anonymous namespace
 
-void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
 
-	_cursorKeyColor = keycolor;
+void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
+	_cursorUseKey = (mask == nullptr);
+	if (_cursorUseKey)
+		_cursorKeyColor = keycolor;
+
 	_cursorHotspotX = hotspotX;
 	_cursorHotspotY = hotspotY;
 	_cursorDontScale = dontScale;
@@ -859,10 +892,13 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 	if (!w || !h) {
 		delete _cursor;
 		_cursor = nullptr;
+		delete _cursorMask;
+		_cursorMask = nullptr;
 		return;
 	}
 
 	Graphics::PixelFormat inputFormat;
+	Graphics::PixelFormat maskFormat;
 #ifdef USE_RGB_COLOR
 	if (format) {
 		inputFormat = *format;
@@ -873,18 +909,20 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 	inputFormat = Graphics::PixelFormat::createFormatCLUT8();
 #endif
 
-	// In case the color format has changed we will need to create the texture.
-	if (!_cursor || _cursor->getFormat() != inputFormat) {
-		delete _cursor;
-		_cursor = nullptr;
-
 #ifdef USE_SCALERS
-		bool wantScaler = (_currentState.scaleFactor > 1) && !dontScale
-		                && _scalerPlugins[_currentState.scalerIndex]->get<ScalerPluginObject>().canDrawCursor();
+	bool wantScaler = (_currentState.scaleFactor > 1) && !dontScale && _scalerPlugins[_currentState.scalerIndex]->get<ScalerPluginObject>().canDrawCursor();
 #else
-		bool wantScaler = false;
+	bool wantScaler = false;
 #endif
 
+	bool wantMask = (mask != nullptr);
+	bool haveMask = (_cursorMask != nullptr);
+
+	// In case the color format has changed we will need to create the texture.
+	if (!_cursor || _cursor->getFormat() != inputFormat || haveMask != wantMask) {
+		delete _cursor;
+		_cursor = nullptr;
+
 		GLenum glIntFormat, glFormat, glType;
 
 		Graphics::PixelFormat textureFormat;
@@ -899,9 +937,11 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 		} else {
 			textureFormat = _defaultFormatAlpha;
 		}
-		_cursor = createSurface(textureFormat, true, wantScaler);
+		_cursor = createSurface(textureFormat, true, wantScaler, wantMask);
 		assert(_cursor);
+
 		updateLinearFiltering();
+
 #ifdef USE_SCALERS
 		if (wantScaler) {
 			_cursor->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
@@ -909,18 +949,40 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 #endif
 	}
 
+	if (mask) {
+		if (!_cursorMask) {
+			maskFormat = _defaultFormatAlpha;
+			_cursorMask = createSurface(maskFormat, true, wantScaler);
+			assert(_cursorMask);
+
+			updateLinearFiltering();
+
+#ifdef USE_SCALERS
+			if (wantScaler) {
+				_cursorMask->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
+			}
+#endif
+		}
+	} else {
+		delete _cursorMask;
+		_cursorMask = nullptr;
+	}
+
 	Common::Point topLeftCoord(0, 0);
+	Common::Point cursorSurfaceSize(w, h);
 
 	// If the cursor is scalable, add a 1-texel transparent border.
 	// This ensures that linear filtering falloff from the edge pixels has room to completely fade out instead of
 	// being cut off at half-way.  Could use border clamp too, but GLES2 doesn't support that.
 	if (!_cursorDontScale) {
 		topLeftCoord = Common::Point(1, 1);
-		_cursor->allocate(w + 2, h + 2);
-	} else {
-		_cursor->allocate(w, h);
+		cursorSurfaceSize += Common::Point(2, 2);
 	}
 
+	_cursor->allocate(cursorSurfaceSize.x, cursorSurfaceSize.y);
+	if (_cursorMask)
+		_cursorMask->allocate(cursorSurfaceSize.x, cursorSurfaceSize.y);
+
 	_cursorHotspotX += topLeftCoord.x;
 	_cursorHotspotY += topLeftCoord.y;
 
@@ -930,6 +992,24 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 		if (!_cursorDontScale)
 			_cursor->fill(keycolor);
 		_cursor->copyRectToTexture(topLeftCoord.x, topLeftCoord.y, w, h, buf, w * inputFormat.bytesPerPixel);
+
+		if (mask) {
+			// Construct a mask of opaque pixels
+			Common::Array<byte> maskBytes;
+			maskBytes.resize(cursorSurfaceSize.x * cursorSurfaceSize.y, 0);
+
+			for (uint y = 0; y < h; y++) {
+				for (uint x = 0; x < w; x++) {
+					// The cursor pixels must be masked out for anything except opaque
+					if (mask[y * w + x] == kCursorMaskOpaque)
+						maskBytes[(y + topLeftCoord.y) * cursorSurfaceSize.x + topLeftCoord.x + x] = 1;
+				}
+			}
+
+			_cursor->setMask(&maskBytes[0]);
+		} else {
+			_cursor->setMask(nullptr);
+		}
 	} else {
 		// Otherwise it is a bit more ugly because we have to handle a key
 		// color properly.
@@ -952,18 +1032,32 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 		if (dst->format.bytesPerPixel == 2) {
 			if (inputFormat.bytesPerPixel == 2) {
 				multiplyColorWithAlpha<uint16, uint16>((const byte *)buf, topLeftPixelPtr, w, h,
-				                                       inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
+													   inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
 			} else if (inputFormat.bytesPerPixel == 4) {
 				multiplyColorWithAlpha<uint32, uint16>((const byte *)buf, topLeftPixelPtr, w, h,
-				                                       inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
+													   inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
 			}
-		} else {
+		} else if (dst->format.bytesPerPixel == 4) {
 			if (inputFormat.bytesPerPixel == 2) {
 				multiplyColorWithAlpha<uint16, uint32>((const byte *)buf, topLeftPixelPtr, w, h,
-				                                       inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
+													   inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
 			} else if (inputFormat.bytesPerPixel == 4) {
 				multiplyColorWithAlpha<uint32, uint32>((const byte *)buf, topLeftPixelPtr, w, h,
-				                                       inputFormat, dst->format, srcPitch, dst->pitch, keycolor);
+													   inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
+			}
+		}
+
+		// Replace all non-opaque pixels with black pixels
+		if (mask) {
+			Graphics::Surface *cursorSurface = _cursor->getSurface();
+
+			for (uint x = 0; x < w; x++) {
+				for (uint y = 0; y < h; y++) {
+					uint8 maskByte = mask[y * w + x];
+
+					if (maskByte != kCursorMaskOpaque)
+						cursorSurface->setPixel(x + topLeftCoord.x, y + topLeftCoord.y, 0);
+				}
 			}
 		}
 
@@ -971,6 +1065,54 @@ void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int
 		_cursor->flagDirty();
 	}
 
+	if (_cursorMask && mask) {
+		// Generate the multiply+invert texture.
+		// We're generating this for a blend mode where source factor is ONE_MINUS_DST_COLOR and dest factor is ONE_MINUS_SRC_ALPHA
+		// In other words, positive RGB channel values will add inverted destination pixels, positive alpha values will modulate
+		// RGB+Alpha = Inverted   Alpha Only = Black   0 = No change
+
+		Graphics::Surface *cursorSurface = _cursor->getSurface();
+		Graphics::Surface *maskSurface = _cursorMask->getSurface();
+		maskFormat = _cursorMask->getFormat();
+
+		const Graphics::PixelFormat cursorFormat = cursorSurface->format;
+
+		bool haveXorPixels = false;
+
+		_cursorMask->fill(0);
+		for (uint x = 0; x < w; x++) {
+			for (uint y = 0; y < h; y++) {
+				// See the description of renderCursor for an explanation of why this works the way it does.
+
+				uint8 maskOpacity = 0xff;
+
+				if (inputFormat.bytesPerPixel != 1) {
+					uint32 cursorPixel = cursorSurface->getPixel(x + topLeftCoord.x, y + topLeftCoord.y);
+
+					uint8 r, g, b;
+					cursorFormat.colorToARGB(cursorPixel, maskOpacity, r, g, b);
+				}
+
+				uint8 maskInversionAdd = 0;
+
+				uint8 maskByte = mask[y * w + x];
+				if (maskByte == kCursorMaskTransparent)
+					maskOpacity = 0;
+
+				if (maskByte == kCursorMaskInvert) {
+					maskOpacity = 0xff;
+					maskInversionAdd = 0xff;
+					haveXorPixels = true;
+				}
+
+				uint32 encodedMaskPixel = maskFormat.ARGBToColor(maskOpacity, maskInversionAdd, maskInversionAdd, maskInversionAdd);
+				maskSurface->setPixel(x + topLeftCoord.x, y + topLeftCoord.y, encodedMaskPixel);
+			}
+		}
+
+		_cursorMask->flagDirty();
+	}
+
 	// In case we actually use a palette set that up properly.
 	if (inputFormat.bytesPerPixel == 1) {
 		updateCursorPalette();
@@ -1307,7 +1449,7 @@ void OpenGLGraphicsManager::notifyContextDestroy() {
 	OpenGLContext.reset();
 }
 
-Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha, bool wantScaler) {
+Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha, bool wantScaler, bool wantMask) {
 	GLenum glIntFormat, glFormat, glType;
 
 #ifdef USE_SCALERS
@@ -1327,7 +1469,7 @@ Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &forma
 
 	if (format.bytesPerPixel == 1) {
 #if !USE_FORCED_GLES
-		if (TextureCLUT8GPU::isSupportedByContext()) {
+		if (TextureCLUT8GPU::isSupportedByContext() && !wantMask) {
 			return new TextureCLUT8GPU();
 		}
 #endif
@@ -1511,7 +1653,8 @@ void OpenGLGraphicsManager::updateCursorPalette() {
 		_cursor->setPalette(0, 256, _gamePalette);
 	}
 
-	_cursor->setColorKey(_cursorKeyColor);
+	if (_cursorUseKey)
+		_cursor->setColorKey(_cursorKeyColor);
 }
 
 void OpenGLGraphicsManager::recalculateCursorScaling() {
@@ -1557,6 +1700,10 @@ void OpenGLGraphicsManager::updateLinearFiltering() {
 		_cursor->enableLinearFiltering(_currentState.filtering);
 	}
 
+	if (_cursorMask) {
+		_cursorMask->enableLinearFiltering(_currentState.filtering);
+	}
+
 	// The overlay UI should also obey the filtering choice (managed via the Filter Graphics checkbox in Graphics Tab).
 	// Thus, when overlay filtering is disabled, scaling in OPENGL is done with GL_NEAREST (nearest neighbor scaling).
 	// It may look crude, but it should be crispier and it's left to user choice to enable filtering.
diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h
index 4cd3c1f45f7..47b6c6a62df 100644
--- a/backends/graphics/opengl/opengl-graphics.h
+++ b/backends/graphics/opengl/opengl-graphics.h
@@ -119,7 +119,7 @@ public:
 	void clearOverlay() override;
 	void grabOverlay(Graphics::Surface &surface) const override;
 
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) override;
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) override;
 	void setCursorPalette(const byte *colors, uint start, uint num) override;
 
 	void displayMessageOnOSD(const Common::U32String &msg) override;
@@ -130,6 +130,8 @@ public:
 	void grabPalette(byte *colors, uint start, uint num) const override;
 
 protected:
+	void renderCursor();
+
 	/**
 	 * Whether an GLES or GLES2 context is active.
 	 */
@@ -168,7 +170,7 @@ protected:
 	 * @param wantScaler Whether or not a software scaler should be used.
 	 * @return A pointer to the surface or nullptr on failure.
 	 */
-	Surface *createSurface(const Graphics::PixelFormat &format, bool wantAlpha = false, bool wantScaler = false);
+	Surface *createSurface(const Graphics::PixelFormat &format, bool wantAlpha = false, bool wantScaler = false, bool wantMask = false);
 
 	//
 	// Transaction support
@@ -374,6 +376,11 @@ protected:
 	 */
 	Surface *_cursor;
 
+	/**
+	 * The rendering surface for the opacity and inversion mask (if any)
+	 */
+	Surface *_cursorMask;
+
 	/**
 	 * The X offset for the cursor hotspot in unscaled game coordinates.
 	 */
@@ -417,6 +424,11 @@ protected:
 	 */
 	uint32 _cursorKeyColor;
 
+	/**
+	 * If true, use key color.
+	 */
+	bool _cursorUseKey;
+
 	/**
 	 * Whether no cursor scaling should be applied.
 	 */
diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp
index 8a267ef47ea..f1adfa9079f 100644
--- a/backends/graphics/opengl/texture.cpp
+++ b/backends/graphics/opengl/texture.cpp
@@ -377,7 +377,8 @@ FakeTexture::FakeTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType, con
 	: Texture(glIntFormat, glFormat, glType, format),
 	  _fakeFormat(fakeFormat),
 	  _rgbData(),
-	  _palette(nullptr) {
+	  _palette(nullptr),
+	  _mask(nullptr) {
 	if (_fakeFormat.isCLUT8()) {
 		_palette = new uint32[256];
 		memset(_palette, 0, sizeof(uint32));
@@ -386,6 +387,7 @@ FakeTexture::FakeTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType, con
 
 FakeTexture::~FakeTexture() {
 	delete[] _palette;
+	delete[] _mask;
 	_palette = nullptr;
 	_rgbData.free();
 }
@@ -402,6 +404,22 @@ void FakeTexture::allocate(uint width, uint height) {
 	_rgbData.create(width, height, getFormat());
 }
 
+void FakeTexture::setMask(const byte *mask) {
+	if (mask) {
+		const uint numPixels = _rgbData.w * _rgbData.h;
+
+		if (!_mask)
+			_mask = new byte[numPixels];
+
+		memcpy(_mask, mask, numPixels);
+	} else {
+		delete[] _mask;
+		_mask = nullptr;
+	}
+
+	flagDirty();
+}
+
 void FakeTexture::setColorKey(uint colorKey) {
 	if (!_palette)
 		return;
@@ -446,6 +464,32 @@ void FakeTexture::updateGLTexture() {
 		Graphics::crossBlit(dst, src, outSurf->pitch, _rgbData.pitch, dirtyArea.width(), dirtyArea.height(), outSurf->format, _rgbData.format);
 	}
 
+	if (_mask) {
+		uint maskPitch = _rgbData.w;
+		uint dirtyWidth = dirtyArea.width();
+		byte destBPP = outSurf->format.bytesPerPixel;
+
+		const byte *maskRowStart = (_mask + dirtyArea.top * maskPitch + dirtyArea.left);
+		byte *dstRowStart = dst;
+
+		for (uint y = dirtyArea.top; y < static_cast<uint>(dirtyArea.bottom); y++) {
+			if (destBPP == 2) {
+				for (uint x = 0; x < dirtyWidth; x++) {
+					if (!maskRowStart[x])
+						reinterpret_cast<uint16 *>(dstRowStart)[x] = 0;
+				}
+			} else if (destBPP == 4) {
+				for (uint x = 0; x < dirtyWidth; x++) {
+					if (!maskRowStart[x])
+						reinterpret_cast<uint32 *>(dstRowStart)[x] = 0;
+				}
+			}
+
+			dstRowStart += outSurf->pitch;
+			maskRowStart += maskPitch;
+		}
+	}
+
 	// Do generic handling of updating the texture.
 	Texture::updateGLTexture();
 }
diff --git a/backends/graphics/opengl/texture.h b/backends/graphics/opengl/texture.h
index 3ba19d0c1fd..815f9515220 100644
--- a/backends/graphics/opengl/texture.h
+++ b/backends/graphics/opengl/texture.h
@@ -193,6 +193,13 @@ public:
 	 */
 	virtual void allocate(uint width, uint height) = 0;
 
+	/**
+	 * Assign a mask to the surface, where a byte value of 0 is black with 0 alpha and 1 is the normal color.
+	 *
+	 * @param mask   The mask data.
+	 */
+	virtual void setMask(const byte *mask) {}
+
 	/**
 	 * Copy image data to the surface.
 	 *
@@ -320,6 +327,7 @@ public:
 	~FakeTexture() override;
 
 	void allocate(uint width, uint height) override;
+	void setMask(const byte *mask) override;
 
 	Graphics::PixelFormat getFormat() const override { return _fakeFormat; }
 
@@ -336,6 +344,7 @@ protected:
 	Graphics::Surface _rgbData;
 	Graphics::PixelFormat _fakeFormat;
 	uint32 *_palette;
+	uint8 *_mask;
 };
 
 class TextureRGB555 : public FakeTexture {
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index 8f4d90be67f..a1a8f649786 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -1948,9 +1948,12 @@ void SurfaceSdlGraphicsManager::copyRectToOverlay(const void *buf, int pitch, in
 #pragma mark --- Mouse ---
 #pragma mark -
 
-void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keyColor, bool dontScale, const Graphics::PixelFormat *format) {
+void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keyColor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	bool formatChanged = false;
 
+	if (mask)
+		warning("SurfaceSdlGraphicsManager::setMouseCursor: Masks are not supported");
+
 	if (format) {
 #ifndef USE_RGB_COLOR
 		assert(format->bytesPerPixel == 1);
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h
index 5eb1e6c1f75..8078c8eb089 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.h
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h
@@ -124,7 +124,7 @@ public:
 	int16 getOverlayHeight() const override { return _videoMode.overlayHeight; }
 	int16 getOverlayWidth() const override { return _videoMode.overlayWidth; }
 
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override;
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL) override;
 	void setCursorPalette(const byte *colors, uint start, uint num) override;
 
 #ifdef USE_OSD
diff --git a/backends/graphics3d/android/android-graphics3d.cpp b/backends/graphics3d/android/android-graphics3d.cpp
index 89c2b26cd8e..dd22a908e43 100644
--- a/backends/graphics3d/android/android-graphics3d.cpp
+++ b/backends/graphics3d/android/android-graphics3d.cpp
@@ -759,10 +759,13 @@ void AndroidGraphics3dManager::updateCursorScaling() {
 void AndroidGraphics3dManager::setMouseCursor(const void *buf, uint w, uint h,
         int hotspotX, int hotspotY,
         uint32 keycolor, bool dontScale,
-        const Graphics::PixelFormat *format) {
+        const Graphics::PixelFormat *format, const byte *mask) {
 	ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY,
 	      keycolor, dontScale, format);
 
+	if (mask)
+		warning("AndroidGraphics3dManager::setMouseCursor: Masks are not supported");
+
 	GLTHREADCHECK;
 
 #ifdef USE_RGB_COLOR
diff --git a/backends/graphics3d/android/android-graphics3d.h b/backends/graphics3d/android/android-graphics3d.h
index 99d30046ff1..37241e1df2a 100644
--- a/backends/graphics3d/android/android-graphics3d.h
+++ b/backends/graphics3d/android/android-graphics3d.h
@@ -109,7 +109,7 @@ public:
 	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
 	                            int hotspotY, uint32 keycolor,
 	                            bool dontScale,
-	                            const Graphics::PixelFormat *format) override;
+	                            const Graphics::PixelFormat *format, const byte *mask) override;
 	virtual void setCursorPalette(const byte *colors, uint start, uint num) override;
 
 	float getHiDPIScreenFactor() const override;
diff --git a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
index d8b19cde173..2100d2620f1 100644
--- a/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
+++ b/backends/graphics3d/openglsdl/openglsdl-graphics3d.h
@@ -103,7 +103,7 @@ public:
 
 	// GraphicsManager API - Mouse
 	bool showMouse(bool visible) override;
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override {}
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL) override {}
 	void setCursorPalette(const byte *colors, uint start, uint num) override {}
 
 	// SdlGraphicsManager API
diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp
index 1e406c8a5ac..dfab11a165c 100644
--- a/backends/modular-backend.cpp
+++ b/backends/modular-backend.cpp
@@ -260,8 +260,8 @@ void ModularGraphicsBackend::warpMouse(int x, int y) {
 	_graphicsManager->warpMouse(x, y);
 }
 
-void ModularGraphicsBackend::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
-	_graphicsManager->setMouseCursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format);
+void ModularGraphicsBackend::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
+	_graphicsManager->setMouseCursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format, mask);
 }
 
 void ModularGraphicsBackend::setCursorPalette(const byte *colors, uint start, uint num) {
diff --git a/backends/modular-backend.h b/backends/modular-backend.h
index d22e2cc9ac8..71713a81e2e 100644
--- a/backends/modular-backend.h
+++ b/backends/modular-backend.h
@@ -115,7 +115,7 @@ public:
 
 	bool showMouse(bool visible) override final;
 	void warpMouse(int x, int y) override final;
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override final;
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL) override final;
 	void setCursorPalette(const byte *colors, uint start, uint num) override final;
 	bool lockMouse(bool lock) override final;
 
diff --git a/backends/platform/3ds/osystem-graphics.cpp b/backends/platform/3ds/osystem-graphics.cpp
index 7a187229248..3094f93ef07 100644
--- a/backends/platform/3ds/osystem-graphics.cpp
+++ b/backends/platform/3ds/osystem-graphics.cpp
@@ -767,13 +767,16 @@ void OSystem_3DS::setCursorDelta(float deltaX, float deltaY) {
 void OSystem_3DS::setMouseCursor(const void *buf, uint w, uint h,
 								 int hotspotX, int hotspotY,
 								 uint32 keycolor, bool dontScale,
-								 const Graphics::PixelFormat *format) {
+								 const Graphics::PixelFormat *format, const byte *mask) {
 	_cursorScalable = !dontScale;
 	_cursorHotspotX = hotspotX;
 	_cursorHotspotY = hotspotY;
 	_cursorKeyColor = keycolor;
 	_pfCursor = !format ? Graphics::PixelFormat::createFormatCLUT8() : *format;
 
+	if (mask)
+		warning("OSystem_3DS::setMouseCursor: Masks are not supported");
+
 	if (w != (uint)_cursor.w || h != (uint)_cursor.h || _cursor.format != _pfCursor) {
 		_cursor.create(w, h, _pfCursor);
 		_cursorTexture.create(w, h, &DEFAULT_MODE);
diff --git a/backends/platform/3ds/osystem.h b/backends/platform/3ds/osystem.h
index 6ca25f3cc2c..fac4d88e729 100644
--- a/backends/platform/3ds/osystem.h
+++ b/backends/platform/3ds/osystem.h
@@ -170,7 +170,7 @@ public:
 	void warpMouse(int x, int y);
 	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
 	                    int hotspotY, uint32 keycolor, bool dontScale = false,
-	                    const Graphics::PixelFormat *format = NULL);
+	                    const Graphics::PixelFormat *format = NULL, const byte *mask = NULL);
 	void setCursorPalette(const byte *colors, uint start, uint num);
 
 	// Transform point from touchscreen coords into gamescreen coords
diff --git a/backends/platform/dc/dc.h b/backends/platform/dc/dc.h
index d7f91e3faef..a8b4a0ce3dd 100644
--- a/backends/platform/dc/dc.h
+++ b/backends/platform/dc/dc.h
@@ -126,7 +126,7 @@ public:
   void warpMouse(int x, int y);
 
   // Set the bitmap that's used when drawing the cursor.
-  void setMouseCursor(const void *buf, uint w, uint h, int hotspot_x, int hotspot_y, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
+  void setMouseCursor(const void *buf, uint w, uint h, int hotspot_x, int hotspot_y, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask);
 
   // Replace the specified range of cursor the palette with new colors.
   void setCursorPalette(const byte *colors, uint start, uint num);
diff --git a/backends/platform/dc/display.cpp b/backends/platform/dc/display.cpp
index 1a18f522e56..e4dfc2b90de 100644
--- a/backends/platform/dc/display.cpp
+++ b/backends/platform/dc/display.cpp
@@ -295,8 +295,10 @@ void OSystem_Dreamcast::warpMouse(int x, int y)
 
 void OSystem_Dreamcast::setMouseCursor(const void *buf, uint w, uint h,
 				       int hotspot_x, int hotspot_y,
-				       uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format)
-{
+				       uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
+	if (mask)
+		warning("OSystem_Dreamcast::setMouseCursor: Masks are not supported");
+
   _ms_cur_w = w;
   _ms_cur_h = h;
 
diff --git a/backends/platform/ds/ds-graphics.cpp b/backends/platform/ds/ds-graphics.cpp
index b875e749538..c2a09944f21 100644
--- a/backends/platform/ds/ds-graphics.cpp
+++ b/backends/platform/ds/ds-graphics.cpp
@@ -549,10 +549,13 @@ void OSystem_DS::warpMouse(int x, int y) {
 		_cursorPos = _screen->scaledToReal(x, y);
 }
 
-void OSystem_DS::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void OSystem_DS::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	if (!buf || w == 0 || h == 0)
 		return;
 
+	if (mask)
+		warning("OSystem_DS::setMouseCursor: Masks are not supported");
+
 	Graphics::PixelFormat actualFormat = format ? *format : _pfCLUT8;
 	if (_cursor.w != (int16)w || _cursor.h != (int16)h || _cursor.format != actualFormat)
 		_cursor.create(w, h, actualFormat);
diff --git a/backends/platform/ds/osystem_ds.h b/backends/platform/ds/osystem_ds.h
index 113d76bb9f4..9430789a72b 100644
--- a/backends/platform/ds/osystem_ds.h
+++ b/backends/platform/ds/osystem_ds.h
@@ -126,7 +126,7 @@ public:
 	virtual bool showMouse(bool visible);
 
 	virtual void warpMouse(int x, int y);
-	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
+	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask);
 
 	virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
 
diff --git a/backends/platform/ios7/ios7_osys_main.h b/backends/platform/ios7/ios7_osys_main.h
index 55f42a886c7..da284f49be6 100644
--- a/backends/platform/ios7/ios7_osys_main.h
+++ b/backends/platform/ios7/ios7_osys_main.h
@@ -177,7 +177,7 @@ public:
 	bool showMouse(bool visible) override;
 
 	void warpMouse(int x, int y) override;
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 255, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override;
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 255, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL) override;
 	void setCursorPalette(const byte *colors, uint start, uint num) override;
 
 	bool pollEvent(Common::Event &event) override;
diff --git a/backends/platform/ios7/ios7_osys_video.mm b/backends/platform/ios7/ios7_osys_video.mm
index e4a3e5b0ce5..ab0e4d5637e 100644
--- a/backends/platform/ios7/ios7_osys_video.mm
+++ b/backends/platform/ios7/ios7_osys_video.mm
@@ -502,9 +502,12 @@ void OSystem_iOS7::dirtyFullOverlayScreen() {
 	}
 }
 
-void OSystem_iOS7::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void OSystem_iOS7::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	//printf("setMouseCursor(%p, %u, %u, %i, %i, %u, %d, %p)\n", (const void *)buf, w, h, hotspotX, hotspotY, keycolor, dontScale, (const void *)format);
 
+	//if (mask)
+	//	printf("OSystem_iOS7::setMouseCursor: Masks are not supported");
+
 	const Graphics::PixelFormat pixelFormat = format ? *format : Graphics::PixelFormat::createFormatCLUT8();
 #if 0
 	printf("bytesPerPixel: %u RGBAlosses: %u,%u,%u,%u RGBAshifts: %u,%u,%u,%u\n", pixelFormat.bytesPerPixel,
diff --git a/backends/platform/iphone/osys_main.h b/backends/platform/iphone/osys_main.h
index 3b5a3291492..965baaf4c4c 100644
--- a/backends/platform/iphone/osys_main.h
+++ b/backends/platform/iphone/osys_main.h
@@ -158,7 +158,7 @@ public:
 	virtual bool showMouse(bool visible);
 
 	virtual void warpMouse(int x, int y);
-	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 255, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
+	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 255, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL);
 	virtual void setCursorPalette(const byte *colors, uint start, uint num);
 
 	virtual bool pollEvent(Common::Event &event);
diff --git a/backends/platform/iphone/osys_video.mm b/backends/platform/iphone/osys_video.mm
index 795755453a5..5bb567fc511 100644
--- a/backends/platform/iphone/osys_video.mm
+++ b/backends/platform/iphone/osys_video.mm
@@ -400,7 +400,7 @@ void OSystem_IPHONE::dirtyFullOverlayScreen() {
 	}
 }
 
-void OSystem_IPHONE::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void OSystem_IPHONE::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	//printf("setMouseCursor(%p, %u, %u, %i, %i, %u, %d, %p)\n", (const void *)buf, w, h, hotspotX, hotspotY, keycolor, dontScale, (const void *)format);
 
 	const Graphics::PixelFormat pixelFormat = format ? *format : Graphics::PixelFormat::createFormatCLUT8();
diff --git a/backends/platform/n64/osys_n64.h b/backends/platform/n64/osys_n64.h
index f391f60daec..b05c1cf2135 100644
--- a/backends/platform/n64/osys_n64.h
+++ b/backends/platform/n64/osys_n64.h
@@ -180,7 +180,7 @@ public:
 	virtual bool showMouse(bool visible);
 
 	virtual void warpMouse(int x, int y);
-	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
+	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask);
 	virtual void setCursorPalette(const byte *colors, uint start, uint num);
 
 	virtual bool pollEvent(Common::Event &event);
diff --git a/backends/platform/n64/osys_n64_base.cpp b/backends/platform/n64/osys_n64_base.cpp
index 4642edd09c1..f7906f4bb31 100644
--- a/backends/platform/n64/osys_n64_base.cpp
+++ b/backends/platform/n64/osys_n64_base.cpp
@@ -766,9 +766,12 @@ void OSystem_N64::warpMouse(int x, int y) {
 	_dirtyOffscreen = true;
 }
 
-void OSystem_N64::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void OSystem_N64::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	if (!w || !h) return;
 
+	if (mask)
+		warning("OSystem_DS::setMouseCursor: Masks are not supported");
+
 	_mouseHotspotX = hotspotX;
 	_mouseHotspotY = hotspotY;
 
diff --git a/backends/platform/psp/osys_psp.cpp b/backends/platform/psp/osys_psp.cpp
index c4a8103714f..e6324fe063c 100644
--- a/backends/platform/psp/osys_psp.cpp
+++ b/backends/platform/psp/osys_psp.cpp
@@ -305,8 +305,12 @@ void OSystem_PSP::warpMouse(int x, int y) {
 	_cursor.setXY(x, y);
 }
 
-void OSystem_PSP::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void OSystem_PSP::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	DEBUG_ENTER_FUNC();
+
+	if (mask)
+		PSP_DEBUG_PRINT("OSystem_PSP::setMouseCursor: Masks are not supported");
+
 	_displayManager.waitUntilRenderFinished();
 	_pendingUpdate = false;
 
diff --git a/backends/platform/psp/osys_psp.h b/backends/platform/psp/osys_psp.h
index 6054bf92654..3287420c4bc 100644
--- a/backends/platform/psp/osys_psp.h
+++ b/backends/platform/psp/osys_psp.h
@@ -114,7 +114,7 @@ public:
 	// Mouse related
 	bool showMouse(bool visible);
 	void warpMouse(int x, int y);
-	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
+	void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask);
 
 	// Events and input
 	bool pollEvent(Common::Event &event);
diff --git a/backends/platform/wii/osystem.h b/backends/platform/wii/osystem.h
index d28c20f45b4..def660562b2 100644
--- a/backends/platform/wii/osystem.h
+++ b/backends/platform/wii/osystem.h
@@ -191,7 +191,7 @@ public:
 	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
 								int hotspotY, uint32 keycolor,
 								bool dontScale,
-								const Graphics::PixelFormat *format) override;
+								const Graphics::PixelFormat *format, const byte *mask) override;
 
 	bool pollEvent(Common::Event &event) override;
 	uint32 getMillis(bool skipRecord = false) override;
diff --git a/backends/platform/wii/osystem_gfx.cpp b/backends/platform/wii/osystem_gfx.cpp
index dab644218ca..490bc8a0c03 100644
--- a/backends/platform/wii/osystem_gfx.cpp
+++ b/backends/platform/wii/osystem_gfx.cpp
@@ -657,7 +657,11 @@ void OSystem_Wii::warpMouse(int x, int y) {
 void OSystem_Wii::setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
 									int hotspotY, uint32 keycolor,
 									bool dontScale,
-									const Graphics::PixelFormat *format) {
+									const Graphics::PixelFormat *format, const byte *mask) {
+
+	if (mask)
+		printf("OSystem_Wii::setMouseCursor: Masks are not supported\n");
+
 	gfx_tex_format_t tex_format = GFX_TF_PALETTE_RGB5A3;
 	uint tw, th;
 	uint32 oldKeycolor = _mouseKeyColor;
diff --git a/common/system.h b/common/system.h
index 1d99f746c2b..2c39d9d1dc1 100644
--- a/common/system.h
+++ b/common/system.h
@@ -120,6 +120,25 @@ enum Type {
 
 } // End of namespace LogMessageType
 
+/**
+* Pixel mask modes for cursor graphics.
+*/
+enum CursorMaskValue {
+	// Overlapped pixel is unchanged
+	kCursorMaskTransparent = 0,
+
+	// Overlapped pixel is replaced with the cursor pixel.
+	kCursorMaskOpaque = 1,
+
+	/** Fully inverts the overlapped pixel regardless of the cursor color data.
+	 *  Backend must support kFeatureCursorMaskInvert for this mode. */
+	kCursorMaskInvert = 2,
+
+	/** Inverts the overlapped pixel based on the cursor's color value.
+	  * Backend must support kFeatureCursorMaskInvertUsingColor for this mode. */
+	kCursorMaskInvertUsingColor = 3,
+};
+
 /**
  * Interface for ScummVM backends.
  *
@@ -386,6 +405,24 @@ public:
 		 */
 		kFeatureCursorPalette,
 
+		/**
+		 * Backends supporting this feature allow specifying a mask for a
+		 * cursor instead of a key color.
+		 */
+		kFeatureCursorMask,
+
+		/**
+		 * Backends supporting this feature allow cursor masks to use mode kCursorMaskInvert in mask values,
+		 * which inverts the destination pixel.
+		 */
+		kFeatureCursorMaskInvert,
+
+		/**
+		 * Backends supporting this feature allow cursor masks to use mode kCursorMaskInvertUsingColor in the mask values,
+		 * which inverts the destination pixel based on the color value of the cursor.
+		 */
+		kFeatureCursorMaskInvertUsingColor,
+
 		/**
 		 * A backend has this feature if its overlay pixel format has an alpha
 		 * channel which offers at least 3-4 bits of accuracy (as opposed to
@@ -1312,11 +1349,13 @@ public:
 	 * @param keycolor  Transparency color value. This should not exceed the maximum color value of the specified format.
 	 *                  In case it does, the behavior is undefined. The backend might just error out or simply ignore the
 	 *                  value. (The SDL backend will just assert to prevent abuse of this).
+	 *                  This parameter does nothing if a mask is provided.
 	 * @param dontScale Whether the cursor should never be scaled. An exception is high ppi displays, where the cursor
 	 *                  might be too small to notice otherwise, these are allowed to scale the cursor anyway.
 	 * @param format    Pointer to the pixel format that the cursor graphic uses (0 means CLUT8).
+	 * @param mask      A mask containing values from the CursorMaskValue enum for each cursor pixel.
 	 */
-	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = nullptr) = 0;
+	virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = nullptr, const byte *mask = nullptr) = 0;
 
 	/**
 	 * Replace the specified range of cursor palette with new colors.
diff --git a/graphics/cursor.h b/graphics/cursor.h
index 5686d0539ab..e2911c774d1 100644
--- a/graphics/cursor.h
+++ b/graphics/cursor.h
@@ -58,6 +58,9 @@ public:
 	/** Return the cursor's surface. */
 	virtual const byte *getSurface() const = 0;
 
+	/** Return the cursor's mask, if it has one. */
+	virtual const byte *getMask() const { return nullptr; }
+
 	/** Return the cursor's palette in RGB format. */
 	virtual const byte *getPalette() const = 0;
 	/** Return the starting index of the palette. */
diff --git a/graphics/cursorman.cpp b/graphics/cursorman.cpp
index d9eff1bf4fc..3853132595c 100644
--- a/graphics/cursorman.cpp
+++ b/graphics/cursorman.cpp
@@ -58,13 +58,16 @@ bool CursorManager::showMouse(bool visible) {
 	return g_system->showMouse(visible);
 }
 
-void CursorManager::pushCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
-	Cursor *cur = new Cursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format);
+void CursorManager::pushCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
+	if (!g_system->hasFeature(OSystem::kFeatureCursorMask))
+		mask = nullptr;
+
+	Cursor *cur = new Cursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format, mask);
 
 	cur->_visible = isVisible();
 	_cursorStack.push(cur);
 
-	g_system->setMouseCursor(cur->_data, w, h, hotspotX, hotspotY, keycolor, dontScale, format);
+	g_system->setMouseCursor(cur->_data, w, h, hotspotX, hotspotY, keycolor, dontScale, format, mask);
 }
 
 void CursorManager::popCursor() {
@@ -76,7 +79,7 @@ void CursorManager::popCursor() {
 
 	if (!_cursorStack.empty()) {
 		cur = _cursorStack.top();
-		g_system->setMouseCursor(cur->_data, cur->_width, cur->_height, cur->_hotspotX, cur->_hotspotY, cur->_keycolor, cur->_dontScale, &cur->_format);
+		g_system->setMouseCursor(cur->_data, cur->_width, cur->_height, cur->_hotspotX, cur->_hotspotY, cur->_keycolor, cur->_dontScale, &cur->_format, cur->_mask);
 	} else {
 		g_system->setMouseCursor(nullptr, 0, 0, 0, 0, 0);
 	}
@@ -102,10 +105,12 @@ void CursorManager::popAllCursors() {
 	g_system->showMouse(isVisible());
 }
 
-void CursorManager::replaceCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+void CursorManager::replaceCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
+	if (!g_system->hasFeature(OSystem::kFeatureCursorMask))
+		mask = nullptr;
 
 	if (_cursorStack.empty()) {
-		pushCursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format);
+		pushCursor(buf, w, h, hotspotX, hotspotY, keycolor, dontScale, format, mask);
 		return;
 	}
 
@@ -130,6 +135,14 @@ void CursorManager::replaceCursor(const void *buf, uint w, uint h, int hotspotX,
 	if (buf && cur->_data)
 		memcpy(cur->_data, buf, size);
 
+	delete[] cur->_mask;
+	cur->_mask = nullptr;
+
+	if (mask) {
+		cur->_mask = new byte[w * h];
+		memcpy(cur->_mask, mask, w * h);
+	}
+
 	cur->_width = w;
 	cur->_height = h;
 	cur->_hotspotX = hotspotX;
@@ -143,12 +156,12 @@ void CursorManager::replaceCursor(const void *buf, uint w, uint h, int hotspotX,
 		cur->_format = Graphics::PixelFormat::createFormatCLUT8();
 #endif
 
-	g_system->setMouseCursor(cur->_data, w, h, hotspotX, hotspotY, keycolor, dontScale, format);
+	g_system->setMouseCursor(cur->_data, w, h, hotspotX, hotspotY, keycolor, dontScale, format, mask);
 }
 
 void CursorManager::replaceCursor(const Graphics::Cursor *cursor) {
 	replaceCursor(cursor->getSurface(), cursor->getWidth(), cursor->getHeight(), cursor->getHotspotX(),
-	              cursor->getHotspotY(), cursor->getKeyColor());
+				  cursor->getHotspotY(), cursor->getKeyColor(), false, nullptr, cursor->getMask());
 
 	if (cursor->getPalette())
 		replaceCursorPalette(cursor->getPalette(), cursor->getPaletteStartIndex(), cursor->getPaletteCount());
@@ -241,7 +254,7 @@ void CursorManager::lock(bool locked) {
 	_locked = locked;
 }
 
-CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
+CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 #ifdef USE_RGB_COLOR
 	if (!format)
 		_format = Graphics::PixelFormat::createFormatCLUT8();
@@ -258,6 +271,12 @@ CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, in
 	_data = new byte[_size];
 	if (data && _data)
 		memcpy(_data, data, _size);
+	if (mask) {
+		_mask = new byte[w * h];
+		if (_mask)
+			memcpy(_mask, mask, w * h);
+	} else
+		_mask = nullptr;
 	_width = w;
 	_height = h;
 	_hotspotX = hotspotX;
@@ -268,6 +287,7 @@ CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, in
 
 CursorManager::Cursor::~Cursor() {
 	delete[] _data;
+	delete[] _mask;
 }
 
 CursorManager::Palette::Palette(const byte *colors, uint start, uint num) {
diff --git a/graphics/cursorman.h b/graphics/cursorman.h
index de0fe2fbeb8..988c79dac64 100644
--- a/graphics/cursorman.h
+++ b/graphics/cursorman.h
@@ -68,22 +68,25 @@ public:
 	 * can be safely freed afterwards.
 	 *
 	 * @param buf		New cursor data.
+	 * @param mask		New cursor data.
 	 * @param w			Width.
 	 * @param h			Height.
 	 * @param hotspotX	Hotspot X coordinate.
 	 * @param hotspotY	Hotspot Y coordinate.
 	 * @param keycolor	Color value for the transparent color. This cannot exceed
 	 *                  the maximum color value as defined by format.
+	 *                  Does nothing if mask is set.
 	 * @param dontScale	Whether the cursor should never be scaled. An exception are high PPI displays, where the cursor
 	 *                  would be too small to notice otherwise. These are allowed to scale the cursor anyway.
 	 * @param format	Pointer to the pixel format that the cursor graphic uses.
 	 *					CLUT8 will be used if this is null or not specified.
+	 * @param mask      Optional pointer to cursor mask containing values from the CursorMaskValue enum.
 	 *
 	 * @note It is acceptable for the buffer to be a null pointer. It is sometimes
 	 *       useful to push a "dummy" cursor and modify it later. The
 	 *       cursor will be added to the stack, but not to the backend.
 	 */
-	void pushCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
+	void pushCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL, const byte *mask = NULL);
 
 	/**
 	 * Pop a cursor from the stack, and restore the previous one to the
@@ -100,18 +103,21 @@ public:
 	 * more optimized way of popping the old cursor before pushing the new one.
 	 *
 	 * @param buf		New cursor data.
+	 * @param mask		New cursor mask data.
 	 * @param w			Width.
 	 * @param h			Height.
 	 * @param hotspotX	Hotspot X coordinate.
 	 * @param hotspotY	Hotspot Y coordinate.
 	 * @param keycolor	Color value for the transparent color. This cannot exceed
 	 *                  the maximum color value as defined by format.
+	 *                  Does nothing if mask is set.
 	 * @param dontScale	Whether the cursor should never be scaled. An exception are high PPI displays, where the cursor
 	 *                  would be too small to notice otherwise. These are allowed to scale the cursor anyway.
 	 * @param format	Pointer to the pixel format that the cursor graphic uses,
 	 *					CLUT8 will be used if this is null or not specified.
+	 * @param mask      Optional pointer to cursor mask containing values from the CursorMaskValue enum.
 	 */
-	void replaceCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
+	void replaceCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = nullptr, const byte *mask = nullptr);
 
 	/**
 	 * Replace the current cursor on the stack.
@@ -211,6 +217,7 @@ private:
 
 	struct Cursor {
 		byte *_data;
+		byte *_mask;
 		bool _visible;
 		uint _width;
 		uint _height;
@@ -223,9 +230,9 @@ private:
 		uint _size;
 
 		// _format set to default by Graphics::PixelFormat default constructor
-		Cursor() : _data(0), _visible(false), _width(0), _height(0), _hotspotX(0), _hotspotY(0), _keycolor(0), _dontScale(false), _size(0) {}
+		Cursor() : _data(0), _mask(0), _visible(false), _width(0), _height(0), _hotspotX(0), _hotspotY(0), _keycolor(0), _dontScale(false), _size(0) {}
 
-		Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL);
+		Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask);
 		~Cursor();
 	};
 
diff --git a/graphics/wincursor.cpp b/graphics/wincursor.cpp
index 677ecf2a650..30ab3e67544 100644
--- a/graphics/wincursor.cpp
+++ b/graphics/wincursor.cpp
@@ -21,6 +21,7 @@
 
 #include "common/ptr.h"
 #include "common/stream.h"
+#include "common/system.h"
 #include "common/textconsole.h"
 
 #include "graphics/wincursor.h"
@@ -45,6 +46,7 @@ public:
 	byte getKeyColor() const override;
 
 	const byte *getSurface() const override { return _surface; }
+	const byte *getMask() const override { return _mask; }
 
 	const byte *getPalette() const override { return _palette; }
 	byte getPaletteStartIndex() const override { return 0; }
@@ -55,6 +57,7 @@ public:
 
 private:
 	byte *_surface;
+	byte *_mask;
 	byte _palette[256 * 3];
 
 	uint16 _width;    ///< The cursor's width.
@@ -72,7 +75,8 @@ WinCursor::WinCursor() {
 	_height   = 0;
 	_hotspotX = 0;
 	_hotspotY = 0;
-	_surface  = 0;
+	_surface  = nullptr;
+	_mask     = nullptr;
 	_keyColor = 0;
 	memset(_palette, 0, 256 * 3);
 }
@@ -104,6 +108,9 @@ byte WinCursor::getKeyColor() const {
 bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
 	clear();
 
+	const bool supportOpacity = g_system->hasFeature(OSystem::kFeatureCursorMask);
+	const bool supportInvert = g_system->hasFeature(OSystem::kFeatureCursorMaskInvert);
+
 	_hotspotX = stream.readUint16LE();
 	_hotspotY = stream.readUint16LE();
 
@@ -161,6 +168,8 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
 	// Parse the XOR map
 	const byte *src = initialSource;
 	_surface = new byte[_width * _height];
+	if (supportOpacity)
+		_mask = new byte[_width * _height];
 	byte *dest = _surface + _width * (_height - 1);
 	uint32 imagePitch = _width * bitsPerPixel / 8;
 
@@ -223,9 +232,29 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
 	src += andWidth * (_height - 1);
 
 	for (uint32 y = 0; y < _height; y++) {
-		for (uint32 x = 0; x < _width; x++)
-			if (src[x / 8] & (1 << (7 - x % 8)))
-				_surface[y * _width + x] = _keyColor;
+		for (uint32 x = 0; x < _width; x++) {
+			byte &surfaceByte = _surface[y * _width + x];
+			if (src[x / 8] & (1 << (7 - x % 8))) {
+				if (_mask) {
+					byte &maskByte = _mask[y * _width + x];
+					if (surfaceByte == 0) {
+						// Transparent
+						maskByte = 0;
+					} else {
+						// Inverted, if the backend supports invert then emit an inverted pixel, otherwise opaque
+						maskByte = supportInvert ? 2 : 1;
+					}
+				} else {
+					// Don't support mask or invert, leave this as opaque if it's XOR so it's visible
+					if (surfaceByte == 0)
+						surfaceByte = _keyColor;
+				}
+			} else {
+				// Opaque pixel
+				if (_mask)
+					_mask[y * _width + x] = 1;
+			}
+		}
 
 		src -= andWidth;
 	}
@@ -235,7 +264,8 @@ bool WinCursor::readFromStream(Common::SeekableReadStream &stream) {
 }
 
 void WinCursor::clear() {
-	delete[] _surface; _surface = 0;
+	delete[] _surface; _surface = nullptr;
+	delete[] _mask; _mask = nullptr;
 }
 
 WinCursorGroup::WinCursorGroup() {


Commit: 866e459992006d19b049666a616a447d1a46f4e3
    https://github.com/scummvm/scummvm/commit/866e459992006d19b049666a616a447d1a46f4e3
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
TESTBED: Add tests for masked and inverted cursors.

Changed paths:
    engines/testbed/graphics.cpp
    engines/testbed/graphics.h


diff --git a/engines/testbed/graphics.cpp b/engines/testbed/graphics.cpp
index 83aa2915cea..b66b3103a68 100644
--- a/engines/testbed/graphics.cpp
+++ b/engines/testbed/graphics.cpp
@@ -56,6 +56,7 @@ GFXTestSuite::GFXTestSuite() {
 
 	// Mouse Layer tests (Palettes and movements)
 	addTest("PalettizedCursors", &GFXtests::palettizedCursors);
+	addTest("MaskedCursors", &GFXtests::maskedCursors);
 	addTest("MouseMovements", &GFXtests::mouseMovements);
 	// FIXME: Scaled cursor crash with odd dimmensions
 	addTest("ScaledCursors", &GFXtests::scaledCursors);
@@ -721,6 +722,223 @@ TestExitStatus GFXtests::palettizedCursors() {
 	return passed;
 }
 
+/**
+ * Tests Masked cursors.
+ * Method: Create a cursor with a transparent, mask, inverted, and color-inverted areas, it should behave properly over colored areas. Once you click test terminates
+ */
+TestExitStatus GFXtests::maskedCursors() {
+
+	Testsuite::clearScreen();
+	Common::String info = "Masked cursors test.  If masked cursors are enabled, you should see a cursor with 4 sections:\n"
+						  "T for transparent, O for opaque, I for inverted, C for colorized inverted.\n"
+						  "If the I or C letters are absent, then that type of masking is unsupported";
+
+	if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+		Testsuite::logPrintf("Info! Skipping test : Masked Cursors\n");
+		return kTestSkipped;
+	}
+
+	TestExitStatus passed = kTestPassed;
+	bool isFeaturePresent = g_system->hasFeature(OSystem::kFeatureCursorMask);
+
+	g_system->delayMillis(1000);
+
+	if (isFeaturePresent) {
+		const byte cursorLeftColData[] = {
+			1, 1, 1,
+			0, 1, 0,
+			0, 1, 0,
+			0, 0, 0,
+
+			0, 1, 0,
+			1, 0, 1,
+			0, 1, 0,
+			0, 0, 0,
+
+			0, 1, 0,
+			0, 1, 0,
+			0, 1, 0,
+			0, 0, 0,
+
+			0, 1, 1,
+			1, 0, 0,
+			0, 1, 1,
+			0, 0, 0,
+		};
+
+		byte cursorData[16 * 16];
+		byte maskData[16 * 16];
+
+		for (int y = 0; y < 16; y++) {
+			for (int x = 0; x < 16; x++) {
+				// Fill cursor and mask with white transparent
+				cursorData[y * 16 + x] = 3;
+				maskData[y * 16 + x] = kCursorMaskTransparent;
+			}
+		}
+
+		for (int y = 12; y < 16; y++) {
+			for (int x = 5; x < 16; x++) {
+				// Fill color mask part with red
+				cursorData[y * 16 + x] = 2;
+			}
+		}
+
+		// Fill T O I C graphic
+		for (int y = 0; y < 16; y++)
+			for (int x = 0; x < 3; x++)
+				if (cursorLeftColData[y * 3 + x])
+					maskData[y * 16 + x + 1] = kCursorMaskOpaque;
+
+		// Fill middle column
+		for (int y = 0; y < 16; y++) {
+			for (int x = 0; x < 5; x++) {
+				maskData[y * 16 + x + 5] = kCursorMaskOpaque;
+			}
+		}
+
+		bool haveInverted = g_system->hasFeature(OSystem::kFeatureCursorMaskInvert);
+		bool haveInvertedColor = g_system->hasFeature(OSystem::kFeatureCursorMaskInvertUsingColor);
+
+		// Fill middle column
+		for (int y = 0; y < 16; y++) {
+			for (int x = 0; x < 4; x++) {
+				maskData[y * 16 + x + 5] = kCursorMaskOpaque;
+			}
+		}
+
+		for (int x = 0; x < 5; x++) {
+			for (int y = 0; y < 4; y++) {
+				maskData[(y + 0) * 16 + x + 11] = kCursorMaskTransparent;
+				maskData[(y + 4) * 16 + x + 11] = kCursorMaskOpaque;
+				maskData[(y + 8) * 16 + x + 11] = kCursorMaskInvert;
+				maskData[(y + 12) * 16 + x + 11] = kCursorMaskInvertUsingColor;
+			}
+		}
+
+		// Mask out unsupported types
+		if (!haveInverted) {
+			for (int y = 8; y < 12; y++)
+				for (int x = 0; x < 16; x++)
+					maskData[y * 16 + x] = kCursorMaskTransparent;
+		}
+
+		if (!haveInvertedColor) {
+			for (int y = 12; y < 16; y++)
+				for (int x = 0; x < 16; x++)
+					maskData[y * 16 + x] = kCursorMaskTransparent;
+		}
+
+		byte oldPalette[256 * 3];
+		g_system->getPaletteManager()->grabPalette(oldPalette, 0, 256);
+
+		byte newPalette[] = {0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255};
+		
+		g_system->getPaletteManager()->setPalette(newPalette, 0, 4);
+
+		CursorMan.replaceCursor(cursorData, 16, 16, 1, 1, 0, false, nullptr, maskData);
+		CursorMan.showMouse(true);
+		
+		bool waitingForClick = true;
+		while (waitingForClick) {
+			Common::Event event;
+			while (g_system->getEventManager()->pollEvent(event)) {
+				if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN) {
+					waitingForClick = false;
+				}
+			}
+
+			g_system->delayMillis(10);
+			g_system->updateScreen();
+		}
+
+		g_system->getPaletteManager()->setPalette(oldPalette, 0, 256);
+
+		if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'T' transparent?", "Yes", "No", kOptionLeft)) {
+			return kTestFailed;
+		}
+
+		if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'O' opaque?", "Yes", "No", kOptionLeft)) {
+			return kTestFailed;
+		}
+
+		if (!haveInverted) {
+			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'I' inverted?", "Yes", "No", kOptionLeft)) {
+				return kTestFailed;
+			}
+		}
+
+		if (!haveInvertedColor) {
+			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
+				return kTestFailed;
+			}
+		}
+
+#ifdef USE_RGB_COLOR
+		Common::String rgbInfo = "Try again with a cursor using RGB data?";
+
+		if (!Testsuite::handleInteractiveInput(rgbInfo, "OK", "Skip", kOptionRight)) {
+			g_system->delayMillis(500);
+
+			Graphics::PixelFormat rgbaFormat = Graphics::createPixelFormat<8888>();
+
+			uint32 rgbaCursorData[16 * 16];
+			for (uint i = 0; i < 16 * 16; i++) {
+				byte colorByte = cursorData[i];
+				byte r = newPalette[colorByte * 3 + 0];
+				byte g = newPalette[colorByte * 3 + 1];
+				byte b = newPalette[colorByte * 3 + 2];
+
+				rgbaCursorData[i] = rgbaFormat.ARGBToColor(255, r, g, b);
+			}
+
+			CursorMan.replaceCursor(rgbaCursorData, 16, 16, 1, 1, 0, false, &rgbaFormat, maskData);
+
+			waitingForClick = true;
+			while (waitingForClick) {
+				Common::Event event;
+				while (g_system->getEventManager()->pollEvent(event)) {
+					if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN) {
+						waitingForClick = false;
+					}
+				}
+
+				g_system->delayMillis(10);
+				g_system->updateScreen();
+			}
+
+			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'T' transparent?", "Yes", "No", kOptionLeft)) {
+				return kTestFailed;
+			}
+
+			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'O' opaque?", "Yes", "No", kOptionLeft)) {
+				return kTestFailed;
+			}
+
+			if (!haveInverted) {
+				if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'I' inverted?", "Yes", "No", kOptionLeft)) {
+					return kTestFailed;
+				}
+			}
+
+			if (!haveInvertedColor) {
+				if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
+					return kTestFailed;
+				}
+			}
+		}
+#endif
+
+		CursorMan.showMouse(false);
+	} else {
+		Testsuite::displayMessage("feature not supported");
+	}
+
+	g_system->delayMillis(500);
+
+	return passed;
+}
+
 /**
  * Tests automated mouse movements. "Warp" functionality provided by the backend.
  */
diff --git a/engines/testbed/graphics.h b/engines/testbed/graphics.h
index e46873f4ea6..d7b2525c589 100644
--- a/engines/testbed/graphics.h
+++ b/engines/testbed/graphics.h
@@ -45,6 +45,7 @@ TestExitStatus fullScreenMode();
 TestExitStatus filteringMode();
 TestExitStatus aspectRatio();
 TestExitStatus palettizedCursors();
+TestExitStatus maskedCursors();
 TestExitStatus mouseMovements();
 TestExitStatus copyRectToScreen();
 TestExitStatus iconifyWindow();


Commit: 76d84718e264a83d6b632b27d1908a42b94d7e5c
    https://github.com/scummvm/scummvm/commit/76d84718e264a83d6b632b27d1908a42b94d7e5c
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
GRAPHICS: Corrected semantics for MacOS cursors

Changed paths:
    common/system.h
    engines/testbed/graphics.cpp


diff --git a/common/system.h b/common/system.h
index 2c39d9d1dc1..76736c53236 100644
--- a/common/system.h
+++ b/common/system.h
@@ -134,9 +134,8 @@ enum CursorMaskValue {
 	 *  Backend must support kFeatureCursorMaskInvert for this mode. */
 	kCursorMaskInvert = 2,
 
-	/** Inverts the overlapped pixel based on the cursor's color value.
-	  * Backend must support kFeatureCursorMaskInvertUsingColor for this mode. */
-	kCursorMaskInvertUsingColor = 3,
+	/** kFeatureCursorMaskInvertUsingColor for this mode. */
+	kCursorMaskPaletteXorColorXnor = 3,
 };
 
 /**
@@ -418,10 +417,11 @@ public:
 		kFeatureCursorMaskInvert,
 
 		/**
-		 * Backends supporting this feature allow cursor masks to use mode kCursorMaskInvertUsingColor in the mask values,
-		 * which inverts the destination pixel based on the color value of the cursor.
+		 * Backends supporting this feature allow cursor masks to use mode kCursorMaskPaletteXorColorXnor in the mask values,
+		 * which uses (Color XOR Destination) for CLUT8 blending and (Color XNOR Destination) for RGB blending.  This is
+		 * equivalent to Classic MacOS behavior for pixel colors other than black and white.
 		 */
-		kFeatureCursorMaskInvertUsingColor,
+		kFeatureCursorMaskPaletteXorColorXnor,
 
 		/**
 		 * A backend has this feature if its overlay pixel format has an alpha
diff --git a/engines/testbed/graphics.cpp b/engines/testbed/graphics.cpp
index b66b3103a68..5c633fe688f 100644
--- a/engines/testbed/graphics.cpp
+++ b/engines/testbed/graphics.cpp
@@ -798,7 +798,7 @@ TestExitStatus GFXtests::maskedCursors() {
 		}
 
 		bool haveInverted = g_system->hasFeature(OSystem::kFeatureCursorMaskInvert);
-		bool haveInvertedColor = g_system->hasFeature(OSystem::kFeatureCursorMaskInvertUsingColor);
+		bool haveColorXorBlend = g_system->hasFeature(OSystem::kFeatureCursorMaskPaletteXorColorXnor);
 
 		// Fill middle column
 		for (int y = 0; y < 16; y++) {
@@ -812,7 +812,7 @@ TestExitStatus GFXtests::maskedCursors() {
 				maskData[(y + 0) * 16 + x + 11] = kCursorMaskTransparent;
 				maskData[(y + 4) * 16 + x + 11] = kCursorMaskOpaque;
 				maskData[(y + 8) * 16 + x + 11] = kCursorMaskInvert;
-				maskData[(y + 12) * 16 + x + 11] = kCursorMaskInvertUsingColor;
+				maskData[(y + 12) * 16 + x + 11] = kCursorMaskPaletteXorColorXnor;
 			}
 		}
 
@@ -823,7 +823,7 @@ TestExitStatus GFXtests::maskedCursors() {
 					maskData[y * 16 + x] = kCursorMaskTransparent;
 		}
 
-		if (!haveInvertedColor) {
+		if (!haveColorXorBlend) {
 			for (int y = 12; y < 16; y++)
 				for (int x = 0; x < 16; x++)
 					maskData[y * 16 + x] = kCursorMaskTransparent;
@@ -868,7 +868,7 @@ TestExitStatus GFXtests::maskedCursors() {
 			}
 		}
 
-		if (!haveInvertedColor) {
+		if (!haveColorXorBlend) {
 			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
 				return kTestFailed;
 			}
@@ -921,7 +921,7 @@ TestExitStatus GFXtests::maskedCursors() {
 				}
 			}
 
-			if (!haveInvertedColor) {
+			if (!haveColorXorBlend) {
 				if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
 					return kTestFailed;
 				}


Commit: 0a32f23bd6b80d8bf7c74fd629160a55c954cbbe
    https://github.com/scummvm/scummvm/commit/0a32f23bd6b80d8bf7c74fd629160a55c954cbbe
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
BACKENDS: ANDROID: Update log message for setMouseCursor

Changed paths:
    backends/graphics3d/android/android-graphics3d.cpp


diff --git a/backends/graphics3d/android/android-graphics3d.cpp b/backends/graphics3d/android/android-graphics3d.cpp
index dd22a908e43..3648fa9acc4 100644
--- a/backends/graphics3d/android/android-graphics3d.cpp
+++ b/backends/graphics3d/android/android-graphics3d.cpp
@@ -760,8 +760,8 @@ void AndroidGraphics3dManager::setMouseCursor(const void *buf, uint w, uint h,
         int hotspotX, int hotspotY,
         uint32 keycolor, bool dontScale,
         const Graphics::PixelFormat *format, const byte *mask) {
-	ENTER("%p, %u, %u, %d, %d, %u, %d, %p", buf, w, h, hotspotX, hotspotY,
-	      keycolor, dontScale, format);
+	ENTER("%p, %u, %u, %d, %d, %u, %d, %p, %p", buf, w, h, hotspotX, hotspotY,
+	      keycolor, dontScale, format, mask);
 
 	if (mask)
 		warning("AndroidGraphics3dManager::setMouseCursor: Masks are not supported");


Commit: 607e49756c12e120d0c73d6aa8b4db4b6e06e287
    https://github.com/scummvm/scummvm/commit/607e49756c12e120d0c73d6aa8b4db4b6e06e287
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
BACKENDS: IOS7: Enable log message for unsupported cursor masks

Changed paths:
    backends/platform/ios7/ios7_osys_video.mm


diff --git a/backends/platform/ios7/ios7_osys_video.mm b/backends/platform/ios7/ios7_osys_video.mm
index ab0e4d5637e..4e4d452b50d 100644
--- a/backends/platform/ios7/ios7_osys_video.mm
+++ b/backends/platform/ios7/ios7_osys_video.mm
@@ -505,8 +505,8 @@ void OSystem_iOS7::dirtyFullOverlayScreen() {
 void OSystem_iOS7::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	//printf("setMouseCursor(%p, %u, %u, %i, %i, %u, %d, %p)\n", (const void *)buf, w, h, hotspotX, hotspotY, keycolor, dontScale, (const void *)format);
 
-	//if (mask)
-	//	printf("OSystem_iOS7::setMouseCursor: Masks are not supported");
+	if (mask)
+		printf("OSystem_iOS7::setMouseCursor: Masks are not supported");
 
 	const Graphics::PixelFormat pixelFormat = format ? *format : Graphics::PixelFormat::createFormatCLUT8();
 #if 0


Commit: 774066714ac885ef881b67118405d82d55615c0f
    https://github.com/scummvm/scummvm/commit/774066714ac885ef881b67118405d82d55615c0f
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
BACKENDS: N64: Fix wrong error message

Changed paths:
    backends/platform/n64/osys_n64_base.cpp


diff --git a/backends/platform/n64/osys_n64_base.cpp b/backends/platform/n64/osys_n64_base.cpp
index f7906f4bb31..8cbf7cae9e3 100644
--- a/backends/platform/n64/osys_n64_base.cpp
+++ b/backends/platform/n64/osys_n64_base.cpp
@@ -770,7 +770,7 @@ void OSystem_N64::setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
 	if (!w || !h) return;
 
 	if (mask)
-		warning("OSystem_DS::setMouseCursor: Masks are not supported");
+		warning("OSystem_N64::setMouseCursor: Masks are not supported");
 
 	_mouseHotspotX = hotspotX;
 	_mouseHotspotY = hotspotY;


Commit: 6af0bd2a95ea3d2fd5dee84c3549d73a01e5d6fb
    https://github.com/scummvm/scummvm/commit/6af0bd2a95ea3d2fd5dee84c3549d73a01e5d6fb
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
COMMON: Correct cursor mask comments.

Changed paths:
    common/system.h


diff --git a/common/system.h b/common/system.h
index 76736c53236..79abf853c81 100644
--- a/common/system.h
+++ b/common/system.h
@@ -124,17 +124,20 @@ enum Type {
 * Pixel mask modes for cursor graphics.
 */
 enum CursorMaskValue {
-	// Overlapped pixel is unchanged
+	/** Overlapped pixel is unchanged */
 	kCursorMaskTransparent = 0,
 
-	// Overlapped pixel is replaced with the cursor pixel.
+	/** Overlapped pixel is replaced with the cursor pixel. */
 	kCursorMaskOpaque = 1,
 
 	/** Fully inverts the overlapped pixel regardless of the cursor color data.
 	 *  Backend must support kFeatureCursorMaskInvert for this mode. */
 	kCursorMaskInvert = 2,
 
-	/** kFeatureCursorMaskInvertUsingColor for this mode. */
+	/** Blends with mode (Destination AND Mask) XOR Color in palette modes, or
+	 *  (Destination AND Mask) XOR (NOT Color) in RGB modes, which is equivalent to
+	 *  Classic MacOS behavior for pixel colors other than black and white.
+	 *  Backend must support kFeatureCursorMaskPaletteXorColorXnor for this mode. */
 	kCursorMaskPaletteXorColorXnor = 3,
 };
 


Commit: d7303bd0065aaa94439c9dc1338d1932937f5294
    https://github.com/scummvm/scummvm/commit/d7303bd0065aaa94439c9dc1338d1932937f5294
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
GRAPHICS: Delete duplicated (and incorrect) parameter description.

Changed paths:
    graphics/cursorman.h


diff --git a/graphics/cursorman.h b/graphics/cursorman.h
index 988c79dac64..806ff61d3fe 100644
--- a/graphics/cursorman.h
+++ b/graphics/cursorman.h
@@ -68,7 +68,6 @@ public:
 	 * can be safely freed afterwards.
 	 *
 	 * @param buf		New cursor data.
-	 * @param mask		New cursor data.
 	 * @param w			Width.
 	 * @param h			Height.
 	 * @param hotspotX	Hotspot X coordinate.


Commit: 325674ace7774333b191327cba40bd3878d37374
    https://github.com/scummvm/scummvm/commit/325674ace7774333b191327cba40bd3878d37374
Author: elasota (ejlasota at gmail.com)
Date: 2023-02-19T23:51:09+01:00

Commit Message:
GRAPHICS: Add masked cursor support to SurfaceSdl, fix tests

Changed paths:
    backends/graphics/surfacesdl/surfacesdl-graphics.cpp
    engines/testbed/graphics.cpp


diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index a1a8f649786..fdb9076177e 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -137,8 +137,8 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou
 	_transactionMode(kTransactionNone),
 	_scalerPlugins(ScalerMan.getPlugins()), _scalerPlugin(nullptr), _scaler(nullptr),
 	_needRestoreAfterOverlay(false), _isInOverlayPalette(false), _isDoubleBuf(false), _prevForceRedraw(false), _numPrevDirtyRects(0),
-	_prevCursorNeedsRedraw(false)
-{
+	_prevCursorNeedsRedraw(false),
+	_mouseKeyColor(0) {
 
 	// allocate palette storage
 	_currentPalette = (SDL_Color *)calloc(sizeof(SDL_Color), 256);
@@ -220,7 +220,8 @@ bool SurfaceSdlGraphicsManager::hasFeature(OSystem::Feature f) const {
 		(f == OSystem::kFeatureVSync) ||
 #endif
 		(f == OSystem::kFeatureCursorPalette) ||
-		(f == OSystem::kFeatureIconifyWindow);
+		(f == OSystem::kFeatureIconifyWindow) ||
+		(f == OSystem::kFeatureCursorMask);
 }
 
 void SurfaceSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) {
@@ -1949,10 +1950,102 @@ void SurfaceSdlGraphicsManager::copyRectToOverlay(const void *buf, int pitch, in
 #pragma mark -
 
 void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keyColor, bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
-	bool formatChanged = false;
 
-	if (mask)
-		warning("SurfaceSdlGraphicsManager::setMouseCursor: Masks are not supported");
+	if (mask && (!format || format->bytesPerPixel == 1)) {
+		// 8-bit masked cursor, SurfaceSdl has no alpha mask support so we must convert this to color key
+		const byte *bufBytes = static_cast<const byte *>(buf);
+
+		uint numPixelUsingColor[256];
+		for (uint i = 0; i < 256; i++)
+			numPixelUsingColor[i] = 0;
+
+		uint numPixels = w * h;
+
+		for (uint i = 0; i < numPixels; i++) {
+			if (mask[i] == kCursorMaskOpaque)
+				numPixelUsingColor[bufBytes[i]]++;
+		}
+
+		uint bestColorNumPixels = 0xffffffffu;
+		uint bestKey = 0;
+		for (uint i = 0; i < 256; i++) {
+			if (numPixelUsingColor[i] < bestColorNumPixels) {
+				bestColorNumPixels = numPixelUsingColor[i];
+				bestKey = i;
+				if (bestColorNumPixels == 0)
+					break;
+			}
+		}
+
+		if (bestColorNumPixels != 0)
+			warning("SurfaceSdlGraphicsManager::setMouseCursor: A mask was specified for an 8-bit cursor but the cursor couldn't be converted to color key");
+
+		Common::Array<byte> maskedImage;
+		maskedImage.resize(w * h);
+		for (uint i = 0; i < numPixels; i++) {
+			if (mask[i] == kCursorMaskOpaque)
+				maskedImage[i] = bufBytes[i];
+			else
+				maskedImage[i] = static_cast<byte>(bestKey);
+		}
+
+		setMouseCursor(&maskedImage[0], w, h, hotspotX, hotspotY, bestKey, dontScale, format, nullptr);
+		return;
+	}
+
+#ifdef USE_RGB_COLOR
+	if (mask && format && format->bytesPerPixel > 1) {
+		const uint numPixels = w * h;
+		const uint inBPP = format->bytesPerPixel;
+
+		Graphics::PixelFormat formatWithAlpha = Graphics::createPixelFormat<8888>();
+
+		// Use the existing format if it already has alpha
+		if (format->aBits() > 0)
+			formatWithAlpha = *format;
+
+		const uint outBPP = format->bytesPerPixel;
+
+		Common::Array<byte> maskedImage;
+		maskedImage.resize(numPixels * outBPP);
+
+		uint32 inColor = 0;
+		byte *inColorPtr = reinterpret_cast<byte *>(&inColor);
+#ifdef SCUMM_BIG_ENDIAN
+		inColorPtr += 4 - inBPP;
+#endif
+
+		uint32 outColor = 0;
+		byte *outColorPtr = reinterpret_cast<byte *>(&outColor);
+#ifdef SCUMM_BIG_ENDIAN
+		outColorPtr += 4 - inBPP;
+#endif
+
+		for (uint i = 0; i < numPixels; i++) {
+			if (mask[i] != kCursorMaskOpaque)
+				outColor = 0;
+			else {
+				memcpy(inColorPtr, static_cast<const byte *>(buf) + i * inBPP, inBPP);
+
+				uint8 r = 0;
+				uint8 g = 0;
+				uint8 b = 0;
+				uint8 a = 0;
+				format->colorToARGB(inColor, a, r, g, b);
+				if (a == 0)
+					outColor = 0;
+				else
+					outColor = formatWithAlpha.ARGBToColor(a, r, g, b);
+			}
+			memcpy(&maskedImage[i * outBPP], outColorPtr, outBPP);
+		}
+
+		setMouseCursor(&maskedImage[0], w, h, hotspotX, hotspotY, 0, dontScale, &formatWithAlpha, nullptr);
+		return;
+	}
+#endif
+
+	bool formatChanged = false;
 
 	if (format) {
 #ifndef USE_RGB_COLOR
diff --git a/engines/testbed/graphics.cpp b/engines/testbed/graphics.cpp
index 5c633fe688f..d163c59a62d 100644
--- a/engines/testbed/graphics.cpp
+++ b/engines/testbed/graphics.cpp
@@ -740,6 +740,7 @@ TestExitStatus GFXtests::maskedCursors() {
 
 	TestExitStatus passed = kTestPassed;
 	bool isFeaturePresent = g_system->hasFeature(OSystem::kFeatureCursorMask);
+	bool haveCursorPalettes = g_system->hasFeature(OSystem::kFeatureCursorPalette);
 
 	g_system->delayMillis(1000);
 
@@ -836,6 +837,9 @@ TestExitStatus GFXtests::maskedCursors() {
 		
 		g_system->getPaletteManager()->setPalette(newPalette, 0, 4);
 
+		if (haveCursorPalettes)
+			g_system->setCursorPalette(newPalette, 0, 4);
+
 		CursorMan.replaceCursor(cursorData, 16, 16, 1, 1, 0, false, nullptr, maskData);
 		CursorMan.showMouse(true);
 		
@@ -862,13 +866,13 @@ TestExitStatus GFXtests::maskedCursors() {
 			return kTestFailed;
 		}
 
-		if (!haveInverted) {
+		if (haveInverted) {
 			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'I' inverted?", "Yes", "No", kOptionLeft)) {
 				return kTestFailed;
 			}
 		}
 
-		if (!haveColorXorBlend) {
+		if (haveColorXorBlend) {
 			if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
 				return kTestFailed;
 			}
@@ -915,13 +919,13 @@ TestExitStatus GFXtests::maskedCursors() {
 				return kTestFailed;
 			}
 
-			if (!haveInverted) {
+			if (haveInverted) {
 				if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'I' inverted?", "Yes", "No", kOptionLeft)) {
 					return kTestFailed;
 				}
 			}
 
-			if (!haveColorXorBlend) {
+			if (haveColorXorBlend) {
 				if (!Testsuite::handleInteractiveInput("Was the part of the cursor to the right of the 'C' inverted according to the color to the left of it?", "Yes", "No", kOptionLeft)) {
 					return kTestFailed;
 				}




More information about the Scummvm-git-logs mailing list