GRAPHICS: Add support for pixels with masks and inverted pixels

This commit is contained in:
elasota 2023-02-05 17:42:03 -05:00 committed by Eugene Sandulenko
parent 8adcc29df9
commit 3db67a33ac
36 changed files with 449 additions and 91 deletions

View file

@ -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) {}

View file

@ -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:

View file

@ -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;
}

View file

@ -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,
};
/**

View file

@ -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) {
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;
#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,6 +1653,7 @@ void OpenGLGraphicsManager::updateCursorPalette() {
_cursor->setPalette(0, 256, _gamePalette);
}
if (_cursorUseKey)
_cursor->setColorKey(_cursorKeyColor);
}
@ -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.

View file

@ -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.
*/

View file

@ -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();
}

View file

@ -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 {

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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,

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -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. */

View file

@ -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) {

View file

@ -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();
};

View file

@ -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() {