DIRECTOR: Move to channel-based rendering

This commit is contained in:
Nathanael Gentry 2020-05-15 23:09:46 -04:00 committed by Eugene Sandulenko
parent 9fc6197c32
commit 5babbb30db
7 changed files with 769 additions and 715 deletions

View File

@ -62,7 +62,6 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) {
warning("processEvents: request to access frame %d of %d", sc->getCurrentFrame(), sc->_frames.size() - 1);
return;
}
Frame *currentFrame = sc->_frames[sc->getCurrentFrame()];
uint16 spriteId = 0;
Common::Point pos;
@ -82,7 +81,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) {
sc->_lastRollTime = sc->_lastEventTime;
if (_draggingSprite) {
Sprite *draggedSprite = currentFrame->_sprites[_draggingSpriteId];
Sprite *draggedSprite = sc->_sprites[_draggingSpriteId];
if (draggedSprite->_moveable) {
pos = g_system->getEventManager()->getMousePos();
Common::Point delta = pos - _draggingSpritePos;
@ -101,7 +100,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) {
// D3 doesn't have both mouse up and down.
// But we still want to know if the mouse is down for press effects.
spriteId = currentFrame->getSpriteIDFromPos(pos);
spriteId = sc->getSpriteIDFromPos(pos);
sc->_currentMouseDownSpriteId = spriteId;
sc->_mouseIsDown = true;
@ -111,7 +110,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) {
debugC(3, kDebugEvents, "event: Button Down @(%d, %d), sprite id: %d", pos.x, pos.y, spriteId);
_lingo->registerEvent(kEventMouseDown);
if (currentFrame->_sprites[spriteId]->_moveable)
if (sc->_sprites[spriteId]->_moveable)
g_director->setDraggedSprite(spriteId);
break;
@ -119,7 +118,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) {
case Common::EVENT_LBUTTONUP:
pos = g_system->getEventManager()->getMousePos();
spriteId = currentFrame->getSpriteIDFromPos(pos);
spriteId = sc->getSpriteIDFromPos(pos);
debugC(3, kDebugEvents, "event: Button Up @(%d, %d), sprite id: %d", pos.x, pos.y, spriteId);

View File

@ -23,13 +23,9 @@
#include "common/system.h"
#include "common/substream.h"
#include "graphics/macgui/macfontmanager.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/maceditabletext.h"
#include "graphics/primitives.h"
#include "director/director.h"
#include "director/cachedmactext.h"
#include "director/cast.h"
#include "director/frame.h"
#include "director/score.h"
@ -525,662 +521,4 @@ void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offse
}
void Frame::prepareFrame(Score *score, bool updateStageOnly) {
renderSprites(*score->_surface, false);
renderSprites(*score->_trailSurface, true);
if (!updateStageOnly) {
score->renderZoomBox();
_vm->_wm->draw();
if (_transType != 0)
// TODO Handle changing area case
playTransition(score);
if (_sound1 != 0 || _sound2 != 0) {
playSoundChannel();
}
if (_vm->getCurrentScore()->haveZoomBox())
score->_backSurface->copyFrom(*score->_surface);
}
g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height());
}
void Frame::playSoundChannel() {
debug(0, "STUB: playSoundChannel(), Sound1 %d Sound2 %d", _sound1, _sound2);
}
void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) {
for (uint16 i = 0; i <= _numChannels; i++) {
if (!_sprites[i]->_enabled)
continue;
if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail))
continue;
CastType castType = _sprites[i]->_castType;
if (castType == kCastTypeNull)
continue;
// this needs precedence to be hit first... D3 does something really tricky with cast IDs for shapes.
// I don't like this implementation 100% as the 'cast' above might not actually hit a member and be null?
debugC(1, kDebugImages, "Frame::renderSprites(): Channel: %d castType: %d", i, castType);
_sprites[i]->_currentBbox = _sprites[i]->_dirtyBbox;
if (castType == kCastShape) {
renderShape(surface, i);
} else if (castType == kCastText || castType == kCastRTE) {
renderText(surface, i, NULL);
} else if (castType == kCastButton) {
renderButton(surface, i);
} else {
if (!_sprites[i]->_cast || _sprites[i]->_cast->_type != kCastBitmap) {
warning("Frame::renderSprites(): No cast ID for sprite %d", i);
continue;
}
if (_sprites[i]->_cast->_surface == nullptr) {
warning("Frame::renderSprites(): No cast surface for sprite %d", i);
continue;
}
renderBitmap(surface, i);
}
}
}
void Frame::renderShape(Graphics::ManagedSurface &surface, uint16 spriteId) {
Sprite *sp = _sprites[spriteId];
InkType ink = sp->_ink;
byte spriteType = sp->_spriteType;
byte foreColor = sp->_foreColor;
byte backColor = sp->_backColor;
int lineSize = sp->_thickness & 0x3;
if (_vm->getVersion() >= 3 && spriteType == kCastMemberSprite) {
if (!sp->_cast) {
warning("Frame::renderShape(): kCastMemberSprite has no cast defined");
return;
}
switch (sp->_cast->_type) {
case kCastShape:
{
ShapeCast *sc = (ShapeCast *)sp->_cast;
switch (sc->_shapeType) {
case kShapeRectangle:
spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite;
break;
case kShapeRoundRect:
spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite;
break;
case kShapeOval:
spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite;
break;
case kShapeLine:
spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite;
break;
default:
break;
}
if (_vm->getVersion() > 3) {
foreColor = sc->_fgCol;
backColor = sc->_bgCol;
lineSize = sc->_lineThickness;
ink = sc->_ink;
}
// shapes should be rendered with transparency by default
if (ink == kInkTypeCopy) {
ink = kInkTypeTransparent;
}
}
break;
default:
warning("Frame::renderShape(): Unhandled cast type: %d", sp->_cast->_type);
break;
}
}
// for outlined shapes, line thickness of 1 means invisible.
lineSize -= 1;
Common::Rect shapeRect = sp->_currentBbox;
Graphics::ManagedSurface tmpSurface, maskSurface;
tmpSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
tmpSurface.clear(backColor);
maskSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
maskSurface.clear(0);
// Draw fill
Common::Rect fillRect((int)shapeRect.width(), (int)shapeRect.height());
Graphics::MacPlotData plotFill(&tmpSurface, &maskSurface, &_vm->getPatterns(), sp->getPattern(), -shapeRect.left, -shapeRect.top, 1, backColor);
switch (spriteType) {
case kRectangleSprite:
Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
break;
case kRoundedRectangleSprite:
Graphics::drawRoundRect(fillRect, 12, foreColor, true, Graphics::macDrawPixel, &plotFill);
break;
case kOvalSprite:
Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, foreColor, true, Graphics::macDrawPixel, &plotFill);
break;
case kCastMemberSprite: // Face kit D3
Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
break;
default:
break;
}
// Draw stroke
Common::Rect strokeRect(MAX((int)shapeRect.width() - lineSize, 0), MAX((int)shapeRect.height() - lineSize, 0));
Graphics::MacPlotData plotStroke(&tmpSurface, &maskSurface, &_vm->getPatterns(), 1, -shapeRect.left, -shapeRect.top, lineSize, backColor);
switch (spriteType) {
case kLineTopBottomSprite:
Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, Graphics::macDrawPixel, &plotStroke);
break;
case kLineBottomTopSprite:
Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, foreColor, Graphics::macDrawPixel, &plotStroke);
break;
case kRectangleSprite:
// fall through
case kOutlinedRectangleSprite: // this is actually a mouse-over shape? I don't think it's a real button.
Graphics::drawRect(strokeRect, foreColor, Graphics::macDrawPixel, &plotStroke);
//tmpSurface.fillRect(Common::Rect(shapeRect.width(), shapeRect.height()), (_vm->getCurrentScore()->_currentMouseDownSpriteId == spriteId ? 0 : 0xff));
break;
case kRoundedRectangleSprite:
// fall through
case kOutlinedRoundedRectangleSprite:
Graphics::drawRoundRect(strokeRect, 12, foreColor, false, Graphics::macDrawPixel, &plotStroke);
break;
case kOvalSprite:
// fall through
case kOutlinedOvalSprite:
Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, false, Graphics::macDrawPixel, &plotStroke);
break;
default:
break;
}
inkBasedBlit(surface, &maskSurface, tmpSurface, ink, shapeRect, spriteId);
}
void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) {
uint16 castId = _sprites[spriteId]->_castId;
// This may not be a button cast. It could be a textcast with the channel forcing it
// to be a checkbox or radio button!
Cast *member = _vm->getCastMember(castId);
if (!member) {
warning("renderButton: unknown cast id %d", castId);
} else if (member->_type != kCastButton) {
warning("renderButton: cast id %d not of type kCastButton", castId);
return;
}
ButtonCast *button = (ButtonCast *)member;
// Sometimes, at least in the D3 Workshop Examples, these buttons are just TextCast.
// If they are, then we just want to use the spriteType as the button type.
// If they are full-bown Cast members, then use the actual cast member type.
int buttonType = _sprites[spriteId]->_spriteType;
if (buttonType == kCastMemberSprite) {
switch (button->_buttonType) {
case kTypeCheckBox:
buttonType = kCheckboxSprite;
break;
case kTypeButton:
buttonType = kButtonSprite;
break;
case kTypeRadio:
buttonType = kRadioButtonSprite;
break;
}
}
bool invert = spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId;
// TODO: review all cases to confirm if we should use text height.
// height = textRect.height();
Common::Rect _rect = _sprites[spriteId]->_currentBbox;
int16 x = _rect.left;
int16 y = _rect.top;
Common::Rect textRect(0, 0, _rect.width(), _rect.height());
// WORKAROUND, HACK
// Because we're not drawing text with transparency
// We swap drawing depending on whether the button is
// inverted or not, to prevent destroying the border
if (!invert)
renderText(surface, spriteId, &textRect);
Graphics::MacPlotData plotStroke(&surface, nullptr, &_vm->getPatterns(), 1, 0, 0, 1, 0);
switch (buttonType) {
case kCheckboxSprite:
surface.frameRect(_rect, 0);
break;
case kButtonSprite: {
Graphics::MacPlotData pd(&surface, nullptr, &_vm->getMacWindowManager()->getPatterns(), Graphics::MacGUIConstants::kPatternSolid, 0, 0, 1, invert ? Graphics::kColorBlack : Graphics::kColorWhite);
Graphics::drawRoundRect(_rect, 4, 0, invert, Graphics::macDrawPixel, &pd);
}
break;
case kRadioButtonSprite:
Graphics::drawEllipse(x, y + 2, x + 11, y + 13, 0, false, Graphics::macDrawPixel, &plotStroke);
break;
default:
warning("renderButton: Unknown buttonType");
break;
}
if (invert)
renderText(surface, spriteId, &textRect);
}
void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteId, Common::Rect *textRect) {
TextCast *textCast = (TextCast*)_sprites[spriteId]->_cast;
if (textCast == nullptr) {
warning("Frame::renderText(): TextCast #%d is a nullptr", spriteId);
return;
}
Score *score = _vm->getCurrentScore();
Sprite *sprite = _sprites[spriteId];
Common::Rect bbox = sprite->_currentBbox;
int width = bbox.width();
int height = bbox.height();
int x = bbox.left;
int y = bbox.top;
if (_vm->getCurrentScore()->_fontMap.contains(textCast->_fontId)) {
// We need to make sure that the Shared Cast fonts have been loaded in?
// might need a mapping table here of our own.
// textCast->fontId = _vm->_wm->_fontMan->getFontIdByName(_vm->getCurrentScore()->_fontMap[textCast->fontId]);
}
if (width == 0 || height == 0) {
warning("Frame::renderText(): Requested to draw on an empty surface: %d x %d", width, height);
return;
}
if (sprite->_editable) {
if (!textCast->_widget) {
warning("Creating MacEditableText with '%s'", toPrintable(textCast->_ftext).c_str());
textCast->_widget = new Graphics::MacEditableText(score->_window, x, y, width, height, g_director->_wm, textCast->_ftext, new Graphics::MacFont(), 0, 255, width);
warning("Finished creating MacEditableText");
}
textCast->_widget->draw();
InkType ink = sprite->_ink;
if (spriteId == score->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
inkBasedBlit(surface, nullptr, textCast->_widget->getSurface()->rawSurface(), ink, Common::Rect(x, y, x + width, y + height), spriteId);
return;
}
debugC(3, kDebugText, "renderText: sprite: %d x: %d y: %d w: %d h: %d fontId: '%d' text: '%s'", spriteId, x, y, width, height, textCast->_fontId, Common::toPrintable(textCast->_ftext).c_str());
uint16 boxShadow = (uint16)textCast->_boxShadow;
uint16 borderSize = (uint16)textCast->_borderSize;
if (textRect != NULL)
borderSize = 0;
uint16 padding = (uint16)textCast->_gutterSize;
uint16 textShadow = (uint16)textCast->_textShadow;
//uint32 rectLeft = textCast->initialRect.left;
//uint32 rectTop = textCast->initialRect.top;
textCast->_cachedMacText->clip(width);
const Graphics::ManagedSurface *textSurface = textCast->_cachedMacText->getSurface();
if (!textSurface)
return;
height = textSurface->h;
if (textRect != NULL) {
// TODO: this offset could be due to incorrect fonts loaded!
textRect->bottom = height + textCast->_cachedMacText->getLineCount();
}
uint16 textX = 0, textY = 0;
if (textRect == NULL) {
if (borderSize > 0) {
if (_vm->getVersion() <= 3) {
height += (borderSize * 2);
textX += (borderSize + 2);
} else {
height += borderSize;
textX += (borderSize + 1);
}
textY += borderSize;
} else {
x += 1;
}
if (padding > 0) {
width += padding * 2;
height += padding;
textY += padding / 2;
}
if (textCast->_textAlign == kTextAlignRight)
textX -= 1;
if (textShadow > 0)
textX--;
} else {
x++;
if (width % 2 != 0)
x++;
if (sprite->_spriteType != kCastMemberSprite) {
y += 2;
switch (sprite->_spriteType) {
case kCheckboxSprite:
textX += 16;
break;
case kRadioButtonSprite:
textX += 17;
break;
default:
break;
}
} else {
ButtonType buttonType = ((ButtonCast*)textCast)->_buttonType;
switch (buttonType) {
case kTypeCheckBox:
width += 4;
textX += 16;
break;
case kTypeRadio:
width += 4;
textX += 17;
break;
case kTypeButton:
width += 4;
y += 2;
break;
default:
warning("Frame::renderText(): Expected button but got unexpected button type: %d", buttonType);
y += 2;
break;
}
}
}
switch (textCast->_textAlign) {
case kTextAlignLeft:
default:
break;
case kTextAlignCenter:
textX = (width / 2) - (textSurface->w / 2) + (padding / 2) + borderSize;
break;
case kTextAlignRight:
textX = width - (textSurface->w + 1) + (borderSize * 2) - (textShadow * 2) - (padding);
break;
}
Graphics::ManagedSurface textWithFeatures(width + (borderSize * 2) + boxShadow + textShadow, height + borderSize + boxShadow + textShadow);
textWithFeatures.fillRect(Common::Rect(textWithFeatures.w, textWithFeatures.h), score->getStageColor());
if (textRect == NULL && boxShadow > 0) {
textWithFeatures.fillRect(Common::Rect(boxShadow, boxShadow, textWithFeatures.w + boxShadow, textWithFeatures.h), 0);
}
if (textRect == NULL && borderSize != kSizeNone) {
for (int bb = 0; bb < borderSize; bb++) {
Common::Rect borderRect(bb, bb, textWithFeatures.w - bb - boxShadow - textShadow, textWithFeatures.h - bb - boxShadow - textShadow);
textWithFeatures.fillRect(borderRect, 0xff);
textWithFeatures.frameRect(borderRect, 0);
}
}
if (textShadow > 0)
textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX + textShadow, textY + textShadow), 0xff);
textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX, textY), 0xff);
InkType ink = sprite->_ink;
if (spriteId == score->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
inkBasedBlit(surface, nullptr, textWithFeatures, ink, Common::Rect(x, y, x + width, y + height), spriteId);
}
void Frame::renderBitmap(Graphics::ManagedSurface &surface, uint16 spriteId) {
InkType ink;
Sprite *sprite = _sprites[spriteId];
if (spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
else
ink = sprite->_ink;
BitmapCast *bc = (BitmapCast *)sprite->_cast;
Common::Rect drawRect = sprite->_currentBbox;
inkBasedBlit(surface, nullptr, *(bc->_surface), ink, drawRect, spriteId);
}
void Frame::inkBasedBlit(Graphics::ManagedSurface &targetSurface, const Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId) {
// drawRect could be bigger than the spriteSurface. Clip it
Common::Rect t(spriteSurface.w, spriteSurface.h);
t.moveTo(drawRect.left, drawRect.top);
drawRect.clip(t);
switch (ink) {
case kInkTypeCopy:
if (maskSurface)
targetSurface.transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), *maskSurface);
else
targetSurface.blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top));
break;
case kInkTypeTransparent:
// FIXME: is it always white (last entry in pallette)?
targetSurface.transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), _vm->getPaletteColorCount() - 1);
break;
case kInkTypeBackgndTrans:
drawBackgndTransSprite(targetSurface, spriteSurface, drawRect, spriteId);
break;
case kInkTypeMatte:
drawMatteSprite(targetSurface, spriteSurface, drawRect);
break;
case kInkTypeGhost:
drawGhostSprite(targetSurface, spriteSurface, drawRect);
break;
case kInkTypeReverse:
drawReverseSprite(targetSurface, spriteSurface, drawRect, spriteId);
break;
default:
warning("Frame::inkBasedBlit(): Unhandled ink type %d", ink);
targetSurface.blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top));
break;
}
}
void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId) {
byte skipColor = _sprites[spriteId]->_backColor;
Common::Rect srcRect(sprite.w, sprite.h);
if (!target.clip(srcRect, drawRect))
return; // Out of screen
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
for (int j = 0; j < srcRect.width(); j++) {
if (*src != skipColor)
*dst = *src;
src++;
dst++;
}
}
}
void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
Common::Rect srcRect(sprite.w, sprite.h);
if (!target.clip(srcRect, drawRect))
return; // Out of screen
uint8 skipColor = _vm->getPaletteColorCount() - 1;
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
for (int j = 0; j < srcRect.width(); j++) {
if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor))
*dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color
src++;
dst++;
}
}
}
void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId) {
Common::Rect srcRect(sprite.w, sprite.h);
if (!target.clip(srcRect, drawRect))
return; // Out of screen
uint8 skipColor = _vm->getPaletteColorCount() - 1;
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
byte srcColor = *src;
for (int j = 0; j < srcRect.width(); j++) {
if (_sprites[spriteId]->_cast->_type == kCastShape)
srcColor = 0x0;
else
srcColor = *src;
uint16 targetSprite = getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii));
if ((targetSprite != 0)) {
// TODO: This entire reverse colour attempt needs a lot more testing on
// a lot more colour depths.
if (srcColor != skipColor) {
if (_sprites[targetSprite]->_cast->_type != kCastBitmap) {
if (*dst == 0 || *dst == 255) {
*dst = _vm->transformColor(*dst);
} else if (srcColor == 255 || srcColor == 0) {
*dst = _vm->transformColor(*dst - 40);
} else {
*dst = _vm->transformColor(*src - 40);
}
} else {
if (*dst == 0 && _vm->getVersion() == 3 &&
((BitmapCast*)_sprites[spriteId]->_cast)->_bitsPerPixel > 1) {
*dst = _vm->transformColor(*src - 40);
} else {
*dst ^= _vm->transformColor(srcColor);
}
}
}
} else if (srcColor != skipColor) {
*dst = _vm->transformColor(srcColor);
}
src++;
dst++;
}
}
}
void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
Graphics::Surface tmp;
tmp.copyFrom(sprite);
Common::Rect srcRect(sprite.w, sprite.h);
if (!target.clip(srcRect, drawRect))
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 (_vm->getPalette()[color * 3 + 0] == 0xff &&
_vm->getPalette()[color * 3 + 1] == 0xff &&
_vm->getPalette()[color * 3 + 2] == 0xff) {
whiteColor = color;
break;
}
}
}
if (whiteColor == -1) {
debugC(1, kDebugImages, "Frame::drawMatteSprite(): No white color for Matte image");
for (int yy = 0; yy < srcRect.height(); yy++) {
const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy);
for (int xx = 0; xx < drawRect.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 < srcRect.height(); yy++) {
const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
const byte *mask = (const byte *)ff.getMask()->getBasePtr(srcRect.left, srcRect.top + yy);
byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy);
for (int xx = 0; xx < srcRect.width(); xx++, src++, dst++, mask++)
if (*mask == 0)
*dst = *src;
}
}
tmp.free();
}
uint16 Frame::getSpriteIDFromPos(Common::Point pos) {
for (int i = _sprites.size() - 1; i >= 0; i--)
if (_sprites[i]->_currentBbox.contains(pos))
return i;
return 0;
}
bool Frame::checkSpriteIntersection(uint16 spriteId, Common::Point pos) {
if (_sprites[spriteId]->_currentBbox.contains(pos))
return true;
return false;
}
Common::Rect *Frame::getSpriteRect(uint16 spriteId) {
return &_sprites[spriteId]->_currentBbox;
}
} // End of namespace Director

View File

@ -72,31 +72,19 @@ public:
~Frame();
void readChannels(Common::ReadStreamEndian *stream);
void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
void prepareFrame(Score *score, bool updateStageOnly = false);
uint16 getSpriteIDFromPos(Common::Point pos);
bool checkSpriteIntersection(uint16 spriteId, Common::Point pos);
Common::Rect *getSpriteRect(uint16 spriteId);
void executeImmediateScripts();
private:
void playTransition(Score *score);
void playSoundChannel();
void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail);
void renderText(Graphics::ManagedSurface &surface, uint16 spriteId, Common::Rect *textSize);
void renderShape(Graphics::ManagedSurface &surface, uint16 spriteId);
void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId);
void renderBitmap(Graphics::ManagedSurface &surface, uint16 spriteId);
private:
void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream);
void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
Image::ImageDecoder *getImageFrom(uint16 spriteId);
Common::String readTextStream(Common::SeekableSubReadStreamEndian *textStream, TextCast *textCast);
void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId);
void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId);
void inkBasedBlit(Graphics::ManagedSurface &targetSurface, const Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId);
public:
int _numChannels;

View File

@ -1715,21 +1715,21 @@ void LB::b_rollOver(int nargs) {
Datum res(0);
int arg = d.asInt();
if (!g_director->getCurrentScore()) {
Score *score = g_director->getCurrentScore();
if (!score) {
warning("b_rollOver: Reference to an empty score");
return;
}
Frame *frame = g_director->getCurrentScore()->_frames[g_director->getCurrentScore()->getCurrentFrame()];
if (arg >= (int32) frame->_sprites.size()) {
if (arg >= (int32) score->_sprites.size()) {
g_lingo->push(res);
return;
}
Common::Point pos = g_system->getEventManager()->getMousePos();
if (frame->checkSpriteIntersection(arg, pos))
if (score->checkSpriteIntersection(arg, pos))
res.u.i = 1; // TRUE
g_lingo->push(res);
@ -1775,9 +1775,8 @@ void LB::b_zoomBox(int nargs) {
Score *score = g_director->getCurrentScore();
uint16 curFrame = score->getCurrentFrame();
Frame *frame = score->_frames[curFrame];
Common::Rect *startRect = frame->getSpriteRect(startSprite);
Common::Rect *startRect = score->getSpriteRect(startSprite);
if (!startRect) {
warning("b_zoomBox: unknown start sprite #%d", startSprite);
return;
@ -1785,10 +1784,10 @@ void LB::b_zoomBox(int nargs) {
// Looks for endSprite in the current frame, otherwise
// Looks for endSprite in the next frame
Common::Rect *endRect = frame->getSpriteRect(endSprite);
Common::Rect *endRect = score->getSpriteRect(endSprite);
if (!endRect) {
if ((uint)curFrame + 1 < score->_frames.size())
score->_frames[curFrame + 1]->getSpriteRect(endSprite);
endRect = &score->_frames[curFrame + 1]->_sprites[endSprite]->_currentBbox;
}
if (!endRect) {
@ -1818,11 +1817,7 @@ void LB::b_updateStage(int nargs) {
return;
}
uint16 curFrame = score->getCurrentFrame();
Frame *frame = score->_frames[curFrame];
frame->prepareFrame(score, true);
g_director->processEvents(true);
score->renderFrame( score->getCurrentFrame(), false, true);
}

View File

@ -427,10 +427,9 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
{
Common::Point pos = g_system->getEventManager()->getMousePos();
Score *sc = _vm->getCurrentScore();
Frame *currentFrame = sc->_frames[sc->getCurrentFrame()];
uint16 spriteId = currentFrame->getSpriteIDFromPos(pos);
uint16 spriteId = sc->getSpriteIDFromPos(pos);
d.type = INT;
d.u.i = currentFrame->_sprites[spriteId]->_castId;
d.u.i = sc->_sprites[spriteId]->_castId;
if (d.u.i == 0)
d.u.i = -1;
}
@ -735,6 +734,10 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
break;
case kThePuppet:
sprite->_puppet = d.asInt();
if (!d.u.i) {
sprite->_currentPoint = sprite->_startPoint;
sprite->_dirtyBbox = sprite->_startBbox;
}
break;
case kTheStartTime:
sprite->_startTime = d.asInt();

View File

@ -31,6 +31,8 @@
#include "graphics/primitives.h"
#include "graphics/macgui/macfontmanager.h"
#include "graphics/macgui/macwindowmanager.h"
#include "graphics/macgui/maceditabletext.h"
#include "director/cachedmactext.h"
#include "image/bmp.h"
#include "director/director.h"
@ -69,7 +71,7 @@ const char *scriptType2str(ScriptType scr) {
Score::Score(DirectorEngine *vm) {
_vm = vm;
_surface = nullptr;
_trailSurface = nullptr;
_maskSurface = nullptr;
_backSurface = nullptr;
_backSurface2 = nullptr;
_lingo = _vm->getLingo();
@ -454,8 +456,8 @@ void Score::loadSpriteSounds(bool isSharedCast) {
Score::~Score() {
if (_trailSurface && _trailSurface->w)
_trailSurface->free();
if (_maskSurface && _maskSurface->w)
_maskSurface->free();
if (_backSurface && _backSurface->w)
_backSurface->free();
@ -465,7 +467,7 @@ Score::~Score() {
delete _backSurface;
delete _backSurface2;
delete _trailSurface;
delete _maskSurface;
if (_window)
_vm->_wm->removeWindow(_window);
@ -1706,14 +1708,16 @@ void Score::startLoop() {
_window->resize(_movieRect.width(), _movieRect.height());
_surface = _window->getWindowSurface();
_trailSurface = new Graphics::ManagedSurface;
_maskSurface = new Graphics::ManagedSurface;
_backSurface = new Graphics::ManagedSurface;
_backSurface2 = new Graphics::ManagedSurface;
_trailSurface->create(_movieRect.width(), _movieRect.height());
_maskSurface->create(_movieRect.width(), _movieRect.height());
_backSurface->create(_movieRect.width(), _movieRect.height());
_backSurface2->create(_movieRect.width(), _movieRect.height());
_sprites.resize(_frames[0]->_sprites.size());
if (_vm->_backSurface.w > 0) {
// Persist screen between the movies
// TODO: this is a workaround until the rendering pipeline is reworked
@ -1728,7 +1732,7 @@ void Score::startLoop() {
_vm->_wm->setScreen(_surface);
_trailSurface->clear(_stageColor);
_surface->clear(_stageColor);
_currentFrame = 0;
_stopPlay = false;
@ -1736,7 +1740,8 @@ void Score::startLoop() {
_lingo->processEvent(kEventStartMovie);
_frames[_currentFrame]->prepareFrame(this);
_sprites = _frames[_currentFrame]->_sprites;
renderFrame(_currentFrame, true);
while (!_stopPlay) {
if (_currentFrame >= _frames.size()) {
@ -1818,8 +1823,8 @@ void Score::update() {
_vm->_newMovieStarted = false;
_surface->clear(_stageColor);
_surface->copyFrom(*_trailSurface);
// _surface->clear(_stageColor);
// _surface->copyFrom(*_trailSurface);
_lingo->executeImmediateScripts(_frames[_currentFrame]);
@ -1830,7 +1835,7 @@ void Score::update() {
// TODO: Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame
}
_frames[_currentFrame]->prepareFrame(this);
renderFrame(_currentFrame);
// Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100)
// Enter and exit from previous frame (Director 4)
@ -1880,19 +1885,724 @@ void Score::update() {
_framesRan++;
}
void Score::renderFrame(uint16 frameId, bool forceUpdate, bool updateStageOnly) {
_maskSurface->clear(0);
Frame *currentFrame = _frames[frameId];
for (uint16 i = 0; i < _sprites.size(); i++) {
Sprite *currentSprite = _sprites[i];
Sprite *nextSprite;
if (currentSprite->_puppet)
nextSprite = currentSprite;
else
nextSprite = currentFrame->_sprites[i];
bool needsUpdate = (currentSprite->_currentBbox != nextSprite->_currentBbox || currentSprite->_currentBbox != currentSprite->_dirtyBbox);
if (needsUpdate || forceUpdate)
unrenderSprite(i);
_sprites[i] = nextSprite;
}
for (uint i = 0; i < _sprites.size(); i++)
renderSprite(i);
if (!updateStageOnly) {
renderZoomBox();
_vm->_wm->draw();
if (currentFrame->_transType != 0)
// TODO Handle changing area case
currentFrame->playTransition(this);
if (currentFrame->_sound1 != 0 || currentFrame->_sound2 != 0) {
playSoundChannel(frameId);
}
if (_vm->getCurrentScore()->haveZoomBox())
_backSurface->copyFrom(*_surface);
}
g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->getBounds().width(), _surface->getBounds().height());
}
void Score::unrenderSprite(uint16 spriteId) {
Sprite *currentSprite = _sprites[spriteId];
if (!currentSprite->_trails) {
_maskSurface->fillRect(currentSprite->_currentBbox, 1);
_surface->fillRect(currentSprite->_currentBbox, _stageColor);
}
currentSprite->_currentBbox = currentSprite->_dirtyBbox;
}
void Score::renderSprite(uint16 id) {
Sprite *sprite = _sprites[id];
if (!sprite)
return;
CastType castType = sprite->_castType;
_maskSurface->fillRect(sprite->_currentBbox, 1);
if (castType == kCastTypeNull)
return;
debugC(1, kDebugImages, "Score::renderFrame(): channel: %d, castType: %d", id, castType);
// this needs precedence to be hit first... D3 does something really tricky
// with cast IDs for shapes. I don't like this implementation 100% as the
// 'cast' above might not actually hit a member and be null?
if (castType == kCastShape) {
renderShape(id);
} else if (castType == kCastText || castType == kCastRTE) {
renderText(id, NULL);
} else if (castType == kCastButton) {
renderButton(id);
} else {
if (!sprite->_cast || sprite->_cast->_type != kCastBitmap) {
warning("Score::renderFrame(): No cast ID for sprite %d", id);
return;
}
if (sprite->_cast->_surface == nullptr) {
warning("Score::renderFrame(): No cast surface for sprite %d", id);
return;
}
renderBitmap(id);
}
}
void Score::renderShape(uint16 spriteId) {
Sprite *sp = _sprites[spriteId];
InkType ink = sp->_ink;
byte spriteType = sp->_spriteType;
byte foreColor = sp->_foreColor;
byte backColor = sp->_backColor;
int lineSize = sp->_thickness & 0x3;
if (_vm->getVersion() >= 3 && spriteType == kCastMemberSprite) {
if (!sp->_cast) {
warning("Frame::renderShape(): kCastMemberSprite has no cast defined");
return;
}
switch (sp->_cast->_type) {
case kCastShape:
{
ShapeCast *sc = (ShapeCast *)sp->_cast;
switch (sc->_shapeType) {
case kShapeRectangle:
spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite;
break;
case kShapeRoundRect:
spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite;
break;
case kShapeOval:
spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite;
break;
case kShapeLine:
spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite;
break;
default:
break;
}
if (_vm->getVersion() > 3) {
foreColor = sc->_fgCol;
backColor = sc->_bgCol;
lineSize = sc->_lineThickness;
ink = sc->_ink;
}
// shapes should be rendered with transparency by default
if (ink == kInkTypeCopy) {
ink = kInkTypeTransparent;
}
}
break;
default:
warning("Frame::renderShape(): Unhandled cast type: %d", sp->_cast->_type);
break;
}
}
// for outlined shapes, line thickness of 1 means invisible.
lineSize -= 1;
Common::Rect shapeRect = sp->_currentBbox;
Graphics::ManagedSurface tmpSurface, maskSurface;
tmpSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
tmpSurface.clear(backColor);
maskSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8());
maskSurface.clear(0);
// Draw fill
Common::Rect fillRect((int)shapeRect.width(), (int)shapeRect.height());
Graphics::MacPlotData plotFill(&tmpSurface, &maskSurface, &_vm->getPatterns(), sp->getPattern(), -shapeRect.left, -shapeRect.top, 1, backColor);
switch (spriteType) {
case kRectangleSprite:
Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
break;
case kRoundedRectangleSprite:
Graphics::drawRoundRect(fillRect, 12, foreColor, true, Graphics::macDrawPixel, &plotFill);
break;
case kOvalSprite:
Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, foreColor, true, Graphics::macDrawPixel, &plotFill);
break;
case kCastMemberSprite: // Face kit D3
Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill);
break;
default:
break;
}
// Draw stroke
Common::Rect strokeRect(MAX((int)shapeRect.width() - lineSize, 0), MAX((int)shapeRect.height() - lineSize, 0));
Graphics::MacPlotData plotStroke(&tmpSurface, &maskSurface, &_vm->getPatterns(), 1, -shapeRect.left, -shapeRect.top, lineSize, backColor);
switch (spriteType) {
case kLineTopBottomSprite:
Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, Graphics::macDrawPixel, &plotStroke);
break;
case kLineBottomTopSprite:
Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, foreColor, Graphics::macDrawPixel, &plotStroke);
break;
case kRectangleSprite:
// fall through
case kOutlinedRectangleSprite: // this is actually a mouse-over shape? I don't think it's a real button.
Graphics::drawRect(strokeRect, foreColor, Graphics::macDrawPixel, &plotStroke);
//tmpSurface.fillRect(Common::Rect(shapeRect.width(), shapeRect.height()), (_vm->getCurrentScore()->_currentMouseDownSpriteId == spriteId ? 0 : 0xff));
break;
case kRoundedRectangleSprite:
// fall through
case kOutlinedRoundedRectangleSprite:
Graphics::drawRoundRect(strokeRect, 12, foreColor, false, Graphics::macDrawPixel, &plotStroke);
break;
case kOvalSprite:
// fall through
case kOutlinedOvalSprite:
Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, false, Graphics::macDrawPixel, &plotStroke);
break;
default:
break;
}
inkBasedBlit(&maskSurface, tmpSurface, ink, shapeRect, spriteId);
}
void Score::renderButton(uint16 spriteId) {
uint16 castId = _sprites[spriteId]->_castId;
// This may not be a button cast. It could be a textcast with the channel forcing it
// to be a checkbox or radio button!
Cast *member = _vm->getCastMember(castId);
if (!member) {
warning("renderButton: unknown cast id %d", castId);
} else if (member->_type != kCastButton) {
warning("renderButton: cast id %d not of type kCastButton", castId);
return;
}
ButtonCast *button = (ButtonCast *)member;
// Sometimes, at least in the D3 Workshop Examples, these buttons are just TextCast.
// If they are, then we just want to use the spriteType as the button type.
// If they are full-bown Cast members, then use the actual cast member type.
int buttonType = _sprites[spriteId]->_spriteType;
if (buttonType == kCastMemberSprite) {
switch (button->_buttonType) {
case kTypeCheckBox:
buttonType = kCheckboxSprite;
break;
case kTypeButton:
buttonType = kButtonSprite;
break;
case kTypeRadio:
buttonType = kRadioButtonSprite;
break;
}
}
bool invert = spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId;
// TODO: review all cases to confirm if we should use text height.
// height = textRect.height();
Common::Rect _rect = _sprites[spriteId]->_currentBbox;
int16 x = _rect.left;
int16 y = _rect.top;
Common::Rect textRect(0, 0, _rect.width(), _rect.height());
// WORKAROUND, HACK
// Because we're not drawing text with transparency
// We swap drawing depending on whether the button is
// inverted or not, to prevent destroying the border
if (!invert)
renderText(spriteId, &textRect);
Graphics::MacPlotData plotStroke(_surface, nullptr, &_vm->getPatterns(), 1, 0, 0, 1, 0);
switch (buttonType) {
case kCheckboxSprite:
_surface->frameRect(_rect, 0);
break;
case kButtonSprite: {
Graphics::MacPlotData pd(_surface, nullptr, &_vm->getMacWindowManager()->getPatterns(), Graphics::MacGUIConstants::kPatternSolid, 0, 0, 1, invert ? Graphics::kColorBlack : Graphics::kColorWhite);
Graphics::drawRoundRect(_rect, 4, 0, invert, Graphics::macDrawPixel, &pd);
}
break;
case kRadioButtonSprite:
Graphics::drawEllipse(x, y + 2, x + 11, y + 13, 0, false, Graphics::macDrawPixel, &plotStroke);
break;
default:
warning("renderButton: Unknown buttonType");
break;
}
if (invert)
renderText(spriteId, &textRect);
}
void Score::renderText(uint16 spriteId, Common::Rect *textRect) {
TextCast *textCast = (TextCast*)_sprites[spriteId]->_cast;
if (textCast == nullptr) {
warning("Frame::renderText(): TextCast #%d is a nullptr", spriteId);
return;
}
Score *score = _vm->getCurrentScore();
Sprite *sprite = _sprites[spriteId];
Common::Rect bbox = sprite->_currentBbox;
int width = bbox.width();
int height = bbox.height();
int x = bbox.left;
int y = bbox.top;
if (_vm->getCurrentScore()->_fontMap.contains(textCast->_fontId)) {
// We need to make sure that the Shared Cast fonts have been loaded in?
// might need a mapping table here of our own.
// textCast->fontId = _vm->_wm->_fontMan->getFontIdByName(_vm->getCurrentScore()->_fontMap[textCast->fontId]);
}
if (width == 0 || height == 0) {
warning("Frame::renderText(): Requested to draw on an empty surface: %d x %d", width, height);
return;
}
if (sprite->_editable) {
if (!textCast->_widget) {
warning("Creating MacEditableText with '%s'", toPrintable(textCast->_ftext).c_str());
textCast->_widget = new Graphics::MacEditableText(score->_window, x, y, width, height, g_director->_wm, textCast->_ftext, new Graphics::MacFont(), 0, 255, width);
warning("Finished creating MacEditableText");
}
textCast->_widget->draw();
InkType ink = sprite->_ink;
if (spriteId == score->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
inkBasedBlit(nullptr, textCast->_widget->getSurface()->rawSurface(), ink, Common::Rect(x, y, x + width, y + height), spriteId);
return;
}
debugC(3, kDebugText, "renderText: sprite: %d x: %d y: %d w: %d h: %d fontId: '%d' text: '%s'", spriteId, x, y, width, height, textCast->_fontId, Common::toPrintable(textCast->_ftext).c_str());
uint16 boxShadow = (uint16)textCast->_boxShadow;
uint16 borderSize = (uint16)textCast->_borderSize;
if (textRect != NULL)
borderSize = 0;
uint16 padding = (uint16)textCast->_gutterSize;
uint16 textShadow = (uint16)textCast->_textShadow;
//uint32 rectLeft = textCast->initialRect.left;
//uint32 rectTop = textCast->initialRect.top;
textCast->_cachedMacText->clip(width);
const Graphics::ManagedSurface *textSurface = textCast->_cachedMacText->getSurface();
if (!textSurface)
return;
height = textSurface->h;
if (textRect != NULL) {
// TODO: this offset could be due to incorrect fonts loaded!
textRect->bottom = height + textCast->_cachedMacText->getLineCount();
}
uint16 textX = 0, textY = 0;
if (textRect == NULL) {
if (borderSize > 0) {
if (_vm->getVersion() <= 3) {
height += (borderSize * 2);
textX += (borderSize + 2);
} else {
height += borderSize;
textX += (borderSize + 1);
}
textY += borderSize;
} else {
x += 1;
}
if (padding > 0) {
width += padding * 2;
height += padding;
textY += padding / 2;
}
if (textCast->_textAlign == kTextAlignRight)
textX -= 1;
if (textShadow > 0)
textX--;
} else {
x++;
if (width % 2 != 0)
x++;
if (sprite->_spriteType != kCastMemberSprite) {
y += 2;
switch (sprite->_spriteType) {
case kCheckboxSprite:
textX += 16;
break;
case kRadioButtonSprite:
textX += 17;
break;
default:
break;
}
} else {
ButtonType buttonType = ((ButtonCast*)textCast)->_buttonType;
switch (buttonType) {
case kTypeCheckBox:
width += 4;
textX += 16;
break;
case kTypeRadio:
width += 4;
textX += 17;
break;
case kTypeButton:
width += 4;
y += 2;
break;
default:
warning("Frame::renderText(): Expected button but got unexpected button type: %d", buttonType);
y += 2;
break;
}
}
}
switch (textCast->_textAlign) {
case kTextAlignLeft:
default:
break;
case kTextAlignCenter:
textX = (width / 2) - (textSurface->w / 2) + (padding / 2) + borderSize;
break;
case kTextAlignRight:
textX = width - (textSurface->w + 1) + (borderSize * 2) - (textShadow * 2) - (padding);
break;
}
Graphics::ManagedSurface textWithFeatures(width + (borderSize * 2) + boxShadow + textShadow, height + borderSize + boxShadow + textShadow);
textWithFeatures.fillRect(Common::Rect(textWithFeatures.w, textWithFeatures.h), score->getStageColor());
if (textRect == NULL && boxShadow > 0) {
textWithFeatures.fillRect(Common::Rect(boxShadow, boxShadow, textWithFeatures.w + boxShadow, textWithFeatures.h), 0);
}
if (textRect == NULL && borderSize != kSizeNone) {
for (int bb = 0; bb < borderSize; bb++) {
Common::Rect borderRect(bb, bb, textWithFeatures.w - bb - boxShadow - textShadow, textWithFeatures.h - bb - boxShadow - textShadow);
textWithFeatures.fillRect(borderRect, 0xff);
textWithFeatures.frameRect(borderRect, 0);
}
}
if (textShadow > 0)
textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX + textShadow, textY + textShadow), 0xff);
textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX, textY), 0xff);
InkType ink = sprite->_ink;
if (spriteId == score->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
inkBasedBlit(nullptr, textWithFeatures, ink, Common::Rect(x, y, x + width, y + height), spriteId);
}
void Score::renderBitmap(uint16 spriteId) {
InkType ink;
Sprite *sprite = _sprites[spriteId];
if (spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId)
ink = kInkTypeReverse;
else
ink = sprite->_ink;
BitmapCast *bc = (BitmapCast *)sprite->_cast;
Common::Rect drawRect = sprite->_currentBbox;
inkBasedBlit(nullptr, *(bc->_surface), ink, drawRect, spriteId);
}
void Score::inkBasedBlit(Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId) {
// drawRect could be bigger than the spriteSurface. Clip it
Common::Rect t(spriteSurface.w, spriteSurface.h);
t.moveTo(drawRect.left, drawRect.top);
bool nullMask = false;
// combine the given mask with the maskSurface
if (!maskSurface) {
nullMask = true;
maskSurface = new Graphics::ManagedSurface;
maskSurface->create(spriteSurface.w, spriteSurface.h, Graphics::PixelFormat::createFormatCLUT8());
maskSurface->clear(0);
}
maskSurface->blitFrom(*_maskSurface, drawRect, Common::Point(0, 0));
drawRect.clip(t);
switch (ink) {
case kInkTypeCopy:
if (maskSurface)
_surface->transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), *maskSurface);
else
_surface->blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top));
break;
case kInkTypeTransparent:
// FIXME: is it always white (last entry in pallette)?
_surface->transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), _vm->getPaletteColorCount() - 1);
break;
case kInkTypeBackgndTrans:
drawBackgndTransSprite(spriteSurface, drawRect, spriteId);
break;
case kInkTypeMatte:
drawMatteSprite(spriteSurface, drawRect);
break;
case kInkTypeGhost:
drawGhostSprite(spriteSurface, drawRect);
break;
case kInkTypeReverse:
drawReverseSprite(spriteSurface, drawRect, spriteId);
break;
default:
warning("Frame::inkBasedBlit(): Unhandled ink type %d", ink);
_surface->blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top));
break;
}
if (nullMask)
delete maskSurface;
}
void Score::drawBackgndTransSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId) {
byte skipColor = _sprites[spriteId]->_backColor;
Common::Rect srcRect(sprite.w, sprite.h);
if (!_surface->clip(srcRect, drawRect))
return; // Out of screen
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii);
for (int j = 0; j < srcRect.width(); j++) {
if (*src != skipColor)
*dst = *src;
src++;
dst++;
}
}
}
void Score::drawGhostSprite(const Graphics::Surface &sprite, Common::Rect &drawRect) {
Common::Rect srcRect(sprite.w, sprite.h);
if (!_surface->clip(srcRect, drawRect))
return; // Out of screen
uint8 skipColor = _vm->getPaletteColorCount() - 1;
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii);
for (int j = 0; j < srcRect.width(); j++) {
if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor))
*dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color
src++;
dst++;
}
}
}
void Score::drawReverseSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId) {
Common::Rect srcRect(sprite.w, sprite.h);
if (!_surface->clip(srcRect, drawRect))
return; // Out of screen
uint8 skipColor = _vm->getPaletteColorCount() - 1;
for (int ii = 0; ii < srcRect.height(); ii++) {
const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii);
byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii);
byte srcColor = *src;
for (int j = 0; j < srcRect.width(); j++) {
if (_sprites[spriteId]->_cast->_type == kCastShape)
srcColor = 0x0;
else
srcColor = *src;
uint16 targetSprite = getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii));
if ((targetSprite != 0)) {
// TODO: This entire reverse colour attempt needs a lot more testing on
// a lot more colour depths.
if (srcColor != skipColor) {
if (_sprites[targetSprite]->_cast->_type != kCastBitmap) {
if (*dst == 0 || *dst == 255) {
*dst = _vm->transformColor(*dst);
} else if (srcColor == 255 || srcColor == 0) {
*dst = _vm->transformColor(*dst - 40);
} else {
*dst = _vm->transformColor(*src - 40);
}
} else {
if (*dst == 0 && _vm->getVersion() == 3 &&
((BitmapCast*)_sprites[spriteId]->_cast)->_bitsPerPixel > 1) {
*dst = _vm->transformColor(*src - 40);
} else {
*dst ^= _vm->transformColor(srcColor);
}
}
}
} else if (srcColor != skipColor) {
*dst = _vm->transformColor(srcColor);
}
src++;
dst++;
}
}
}
void Score::drawMatteSprite(const Graphics::Surface &sprite, Common::Rect &drawRect) {
// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
Graphics::Surface tmp;
tmp.copyFrom(sprite);
Common::Rect srcRect(sprite.w, sprite.h);
if (!_surface->clip(srcRect, drawRect))
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 (_vm->getPalette()[color * 3 + 0] == 0xff &&
_vm->getPalette()[color * 3 + 1] == 0xff &&
_vm->getPalette()[color * 3 + 2] == 0xff) {
whiteColor = color;
break;
}
}
}
if (whiteColor == -1) {
debugC(1, kDebugImages, "Frame::drawMatteSprite(): No white color for Matte image");
for (int yy = 0; yy < srcRect.height(); yy++) {
const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + yy);
for (int xx = 0; xx < drawRect.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 < srcRect.height(); yy++) {
const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy);
const byte *mask = (const byte *)ff.getMask()->getBasePtr(srcRect.left, srcRect.top + yy);
byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + yy);
for (int xx = 0; xx < srcRect.width(); xx++, src++, dst++, mask++)
if (*mask == 0)
*dst = *src;
}
}
tmp.free();
}
uint16 Score::getSpriteIDFromPos(Common::Point pos) {
for (int i = _sprites.size() - 1; i >= 0; i--)
if (_sprites[i]->_currentBbox.contains(pos))
return i;
return 0;
}
bool Score::checkSpriteIntersection(uint16 spriteId, Common::Point pos) {
if (_sprites[spriteId]->_currentBbox.contains(pos))
return true;
return false;
}
Common::Rect *Score::getSpriteRect(uint16 spriteId) {
return &_sprites[spriteId]->_currentBbox;
}
Sprite *Score::getSpriteById(uint16 id) {
if (_currentFrame >= _frames.size() || id >= _frames[_currentFrame]->_sprites.size()) {
if (id >= _sprites.size()) {
warning("Score::getSpriteById(%d): out of bounds. frame: %d", id, _currentFrame);
return nullptr;
}
if (_frames[_currentFrame]->_sprites[id]) {
return _frames[_currentFrame]->_sprites[id];
if (_sprites[id]) {
return _sprites[id];
} else {
warning("Sprite on frame %d width id %d not found", _currentFrame, id);
return nullptr;
}
}
void Score::playSoundChannel(uint16 frameId) {
Frame *frame = _frames[frameId];
debug(0, "STUB: playSoundChannel(), Sound1 %d Sound2 %d", frame->_sound1, frame->_sound2);
}
void Score::addZoomBox(ZoomBox *box) {
_zoomBoxes.push_back(box);
}

View File

@ -102,6 +102,10 @@ public:
int getCurrentLabelNumber();
int getNextLabelNumber(int referenceFrame);
uint16 getSpriteIDFromPos(Common::Point pos);
bool checkSpriteIntersection(uint16 spriteId, Common::Point pos);
Common::Rect *getSpriteRect(uint16 spriteId);
void addZoomBox(ZoomBox *box);
void renderZoomBox(bool redraw = false);
bool haveZoomBox() { return !_zoomBoxes.empty(); }
@ -109,9 +113,25 @@ public:
int32 getStageColor() { return _stageColor; }
Cast *getCastMember(int castId);
void renderFrame(uint16 frameId, bool forceUpdate = false, bool updateStageOnly = false);
void renderSprite(uint16 id);
void unrenderSprite(uint16 spriteId);
private:
void update();
void renderText(uint16 spriteId, Common::Rect *textSize);
void renderShape(uint16 spriteId);
void renderButton(uint16 spriteId);
void renderBitmap(uint16 spriteId);
void inkBasedBlit(Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId);
void drawBackgndTransSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId);
void drawMatteSprite(const Graphics::Surface &sprite, Common::Rect &drawRect);
void drawGhostSprite(const Graphics::Surface &sprite, Common::Rect &drawRect);
void drawReverseSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId);
void playSoundChannel(uint16 frameId);
void readVersion(uint32 rid);
void loadPalette(Common::SeekableSubReadStreamEndian &stream);
void loadFrames(Common::SeekableSubReadStreamEndian &stream);
@ -128,6 +148,7 @@ private:
public:
Common::Array<Frame *> _frames;
Common::Array<Sprite *> _sprites;
Common::HashMap<uint16, CastInfo *> _castsInfo;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _castsNames;
Common::SortedArray<Label *> *_labels;
@ -136,7 +157,7 @@ public:
Common::HashMap<uint16, Common::String> _fontMap;
Common::Array<uint16> _castScriptIds;
Graphics::ManagedSurface *_surface;
Graphics::ManagedSurface *_trailSurface;
Graphics::ManagedSurface *_maskSurface;
Graphics::ManagedSurface *_backSurface;
Graphics::ManagedSurface *_backSurface2;
Graphics::Font *_font;