DIRECTOR: Lazily load ink masks

This is for mask and matte inks. Now, rather than a bitmap sprite requiring a
flood fill every time (for mask ink) the matte is created and cached when matte
ink is first requested on that cast. Mask ink does not require a new surface to
be created, but it uses the same new channel interface.
This commit is contained in:
Nathanael Gentry 2020-07-03 13:15:39 -04:00
parent 509871448d
commit a8b779ee0e
6 changed files with 106 additions and 76 deletions

View File

@ -45,6 +45,7 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::ReadStream
: CastMember(cast, castId) {
_type = kCastBitmap;
_img = nullptr;
_matte = nullptr;
_bytes = 0;
_pitch = 0;
_flags = 0;
@ -121,6 +122,9 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::ReadStream
BitmapCastMember::~BitmapCastMember() {
if (_img)
delete _img;
if (_matte)
delete _matte;
}
void BitmapCastMember::createWidget() {
@ -135,6 +139,61 @@ void BitmapCastMember::createWidget() {
_widget->getSurface()->blitFrom(*_img->getSurface());
}
void BitmapCastMember::createMatte() {
// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels
// are transparent
Graphics::Surface tmp;
tmp.create(_initialRect.width(), _initialRect.height(), Graphics::PixelFormat::createFormatCLUT8());
tmp.copyFrom(*_img->getSurface());
// Searching white color in the corners
int whiteColor = -1;
for (int y = 0; y < tmp.h; y++) {
for (int x = 0; x < tmp.w; x++) {
byte color = *(byte *)tmp.getBasePtr(x, y);
if (g_director->getPalette()[color * 3 + 0] == 0xff &&
g_director->getPalette()[color * 3 + 1] == 0xff &&
g_director->getPalette()[color * 3 + 2] == 0xff) {
whiteColor = color;
break;
}
}
}
if (whiteColor == -1) {
debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image");
} else {
delete _matte;
_matte = new Graphics::FloodFill(&tmp, whiteColor, 0, true);
for (int yy = 0; yy < tmp.h; yy++) {
_matte->addSeed(0, yy);
_matte->addSeed(tmp.w - 1, yy);
}
for (int xx = 0; xx < tmp.w; xx++) {
_matte->addSeed(xx, 0);
_matte->addSeed(xx, tmp.h - 1);
}
_matte->fillMask();
}
tmp.free();
}
Graphics::Surface *BitmapCastMember::getMatte() {
// Lazy loading of mattes
if (!_matte) {
createMatte();
}
return _matte ? _matte->getMask() : nullptr;
}
DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, Common::ReadStreamEndian &stream, uint16 version)
: CastMember(cast, castId) {
_type = kCastDigitalVideo;

View File

@ -29,6 +29,7 @@
namespace Graphics {
struct Surface;
struct FloodFill;
class MacText;
class MacWindowManager;
class MacButton;
@ -86,9 +87,13 @@ public:
BitmapCastMember(Cast *cast, uint16 castId, Common::ReadStreamEndian &stream, uint32 castTag, uint16 version);
~BitmapCastMember();
virtual void createWidget() override;
void createMatte();
Graphics::Surface *getMatte();
// virtual void setColors(int *fgcolor, int *bgcolor) override;
Image::ImageDecoder *_img;
Graphics::FloodFill *_matte;
uint16 _pitch;
uint16 _regX;

View File

@ -836,10 +836,11 @@ void inkDrawPixel(int x, int y, int color, void *data) {
if (*src == p->backColor)
break;
// fall through
case kInkTypeMatte:
case kInkTypeMask:
// Only unmasked pixels make it here, so copy them straight
case kInkTypeCopy:
*dst = *src;
case kInkTypeMask:
// TODO: Migrate from Stage to here
break;
case kInkTypeTransparent:
// FIXME: Is colour to ignore always white (last entry in pallette)?
@ -870,9 +871,6 @@ void inkDrawPixel(int x, int y, int color, void *data) {
if (*src != p->numColors - 1)
*dst = *dst | *src;
break;
case kInkTypeMatte:
// TODO: Migrate from Stage to here.
break;
// Arithmetic ink types
case kInkTypeBlend:
if (*src != p->numColors - 1)

View File

@ -68,6 +68,37 @@ Graphics::ManagedSurface *Channel::getSurface() {
}
}
const Graphics::Surface *Channel::getMask() {
switch (_sprite->_ink) {
case kInkTypeMatte:
// Mattes are only supported in bitmaps for now. Shapes don't need mattes,
// as they already have all non-enclosed white pixels transparent.
// Matte on text has a trivial enough effect to not worry about implementing.
if (_sprite->_cast && _sprite->_cast->_type == kCastBitmap) {
return ((BitmapCastMember *)_sprite->_cast)->getMatte();
} else {
return nullptr;
}
case kInkTypeMask: {
CastMember *member = g_director->getCurrentMovie()->getCastMember(_sprite->_castId + 1);
if (_sprite->_cast && member->_initialRect == _sprite->_cast->_initialRect) {
return &member->_widget->getSurface()->rawSurface();
} else {
warning("Channel::getMask(): Requested cast mask, but no matching mask was found");
return nullptr;
}
// Silence warning
break;
}
default:
return nullptr;
}
}
bool Channel::isDirty(Sprite *nextSprite) {
// When a sprite is puppeted setTheSprite ensures that the dirty flag here is
// set. Otherwise, we need to rerender when the position, bounding box, or

View File

@ -26,6 +26,7 @@
//#include "graphics/macgui/macwindowmanager.h"
namespace Graphics {
struct Surface;
class ManagedSurface;
class Font;
class MacWindow;
@ -112,10 +113,12 @@ struct Channel {
Channel(Sprite *sp);
bool isDirty(Sprite *nextSprite = nullptr);
Common::Rect getBbox();
Common::Point getPosition();
MacShape *getShape();
Graphics::ManagedSurface *getSurface();
const Graphics::Surface *getMask();
void setClean(Sprite *nextSprite, int spriteId);
void addDelta(Common::Point pos);

View File

@ -185,86 +185,20 @@ void Stage::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::Manag
return;
}
// Otherwise, we are drawing a cast type that does have a built-in surface, so
// blit from that.
// TODO: Work this ink type into inkDrawPixel.
if (sprite->_ink == kInkTypeMatte) {
drawMatteSprite(channel, srcRect, destRect, blitTo);
return;
}
// First, get any masks that might be needed.
const Graphics::Surface *mask = channel->getMask();
pd.srcPoint.y = MAX(abs(srcRect.top - destRect.top), 0);
for (int i = 0; i < destRect.height(); i++, pd.srcPoint.y++) {
pd.srcPoint.x = MAX(abs(srcRect.left - destRect.left), 0);
const byte *msk = mask ? (const byte *)mask->getBasePtr(pd.srcPoint.x, pd.srcPoint.y) : nullptr;
for (int j = 0; j < destRect.width(); j++, pd.srcPoint.x++)
inkDrawPixel(destRect.left + j, destRect.top + i, 0, &pd);
if (!mask || (msk && (sprite->_ink == kInkTypeMatte ? !(*msk++) : *msk++)))
inkDrawPixel(destRect.left + j, destRect.top + i, 0, &pd);
}
}
void Stage::drawMatteSprite(Channel *channel, Common::Rect &srcRect, Common::Rect &destRect, Graphics::ManagedSurface *blitTo) {
// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
Graphics::Surface tmp;
tmp.create(destRect.width(), destRect.height(), Graphics::PixelFormat::createFormatCLUT8());
tmp.copyFrom(channel->getSurface()->rawSurface());
if (!blitTo->clip(srcRect, destRect))
return; // Out of screen
// Searching white color in the corners
int whiteColor = -1;
for (int y = 0; y < tmp.h; y++) {
for (int x = 0; x < tmp.w; x++) {
byte color = *(byte *)tmp.getBasePtr(x, y);
if (g_director->getPalette()[color * 3 + 0] == 0xff &&
g_director->getPalette()[color * 3 + 1] == 0xff &&
g_director->getPalette()[color * 3 + 2] == 0xff) {
whiteColor = color;
break;
}
}
}
if (whiteColor == -1) {
debugC(1, kDebugImages, "Score::drawMatteSprite(): No white color for Matte image");
for (int yy = 0; yy < destRect.height(); yy++) {
const byte *src = (const byte *)channel->getSurface()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top + yy), 0));
byte *dst = (byte *)blitTo->getBasePtr(destRect.left, destRect.top + yy);
for (int xx = 0; xx < destRect.width(); xx++, src++, dst++)
*dst = *src;
}
} else {
Graphics::FloodFill ff(&tmp, whiteColor, 0, true);
for (int yy = 0; yy < tmp.h; yy++) {
ff.addSeed(0, yy);
ff.addSeed(tmp.w - 1, yy);
}
for (int xx = 0; xx < tmp.w; xx++) {
ff.addSeed(xx, 0);
ff.addSeed(xx, tmp.h - 1);
}
ff.fillMask();
for (int yy = 0; yy < destRect.height(); yy++) {
const byte *mask = (const byte *)ff.getMask()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top - yy), 0));
const byte *src = (const byte *)channel->getSurface()->getBasePtr(MAX(abs(srcRect.left - destRect.left), 0), MAX(abs(srcRect.top - destRect.top - yy), 0));
byte *dst = (byte *)blitTo->getBasePtr(destRect.left, destRect.top + yy);
for (int xx = 0; xx < destRect.width(); xx++, src++, dst++, mask++)
if (*mask == 0)
*dst = *src;
}
}
tmp.free();
}
Common::Point Stage::getMousePos() {
return g_system->getEventManager()->getMousePos() - Common::Point(_dims.left, _dims.top);
}