diff options
Diffstat (limited to 'engines/sword25/gfx')
53 files changed, 13963 insertions, 0 deletions
diff --git a/engines/sword25/gfx/animation.cpp b/engines/sword25/gfx/animation.cpp new file mode 100644 index 0000000000..0d3baae347 --- /dev/null +++ b/engines/sword25/gfx/animation.cpp @@ -0,0 +1,680 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/animation.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resmanager.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "ANIMATION" + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const Common::String &fileName) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + initializeAnimationResource(fileName); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const AnimationTemplate &templ) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + _animationTemplateHandle = AnimationTemplate::create(templ); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION, handle) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Objekt vom Stream laden. + _initSuccess = unpersist(reader); +} + +void Animation::initializeAnimationResource(const Common::String &fileName) { + // Die Resource wird für die gesamte Lebensdauer des Animations-Objektes gelockt. + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(fileName); + if (resourcePtr && resourcePtr->getType() == Resource::TYPE_ANIMATION) + _animationResourcePtr = static_cast<AnimationResource *>(resourcePtr); + else { + BS_LOG_ERRORLN("The resource \"%s\" could not be requested. The Animation can't be created.", fileName.c_str()); + return; + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen. + computeCurrentCharacteristics(); +} + +void Animation::initMembers() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + _running = false; + _finished = false; + _relX = 0; + _relY = 0; + _scaleFactorX = 1.0f; + _scaleFactorY = 1.0f; + _modulationColor = 0xffffffff; + _animationResourcePtr = 0; + _animationTemplateHandle = 0; + _framesLocked = false; +} + +Animation::~Animation() { + if (getAnimationDescription()) { + stop(); + getAnimationDescription()->unlock(); + } + + // Invoke the "delete" callback + if (_deleteCallback) + (_deleteCallback)(getHandle()); + +} + +void Animation::play() { + // If the animation was completed, then play it again from the start. + if (_finished) + stop(); + + _running = true; + lockAllFrames(); +} + +void Animation::pause() { + _running = false; + unlockAllFrames(); +} + +void Animation::stop() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + pause(); +} + +void Animation::setFrame(uint nr) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + + if (nr >= animationDescriptionPtr->getFrameCount()) { + BS_LOG_ERRORLN("Tried to set animation to illegal frame (%d). Value must be between 0 and %d.", + nr, animationDescriptionPtr->getFrameCount()); + return; + } + + _currentFrame = nr; + _currentFrameTime = 0; + computeCurrentCharacteristics(); + forceRefresh(); +} + +bool Animation::doRender() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + + // Bitmap des aktuellen Frames holen + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(_currentFrame).fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmapResource = static_cast<BitmapResource *>(pResource); + + // Framebufferobjekt holen + GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); + BS_ASSERT(pGfx); + + // Bitmap zeichnen + bool result; + if (isScalingAllowed() && (_width != pBitmapResource->getWidth() || _height != pBitmapResource->getHeight())) { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } else { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } + + // Resource freigeben + pBitmapResource->release(); + + return result; +} + +void Animation::frameNotification(int timeElapsed) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(timeElapsed >= 0); + + // Nur wenn die Animation läuft wird sie auch weiterbewegt + if (_running) { + // Gesamte vergangene Zeit bestimmen (inkl. Restzeit des aktuellen Frames) + _currentFrameTime += timeElapsed; + + // Anzahl an zu überpringenden Frames bestimmen + int skipFrames = animationDescriptionPtr->getMillisPerFrame() == 0 ? 0 : _currentFrameTime / animationDescriptionPtr->getMillisPerFrame(); + + // Neue Frame-Restzeit bestimmen + _currentFrameTime -= animationDescriptionPtr->getMillisPerFrame() * skipFrames; + + // Neuen Frame bestimmen (je nach aktuellener Abspielrichtung wird addiert oder subtrahiert) + int tmpCurFrame = _currentFrame; + switch (_direction) { + case FORWARD: + tmpCurFrame += skipFrames; + break; + + case BACKWARD: + tmpCurFrame -= skipFrames; + break; + + default: + BS_ASSERT(0); + } + + // Deal with overflows + if (tmpCurFrame < 0) { + // Loop-Point callback + if (_loopPointCallback && !(_loopPointCallback)(getHandle())) + _loopPointCallback = 0; + + // An underflow may only occur if the animation type is JOJO. + BS_ASSERT(animationDescriptionPtr->getAnimationType() == AT_JOJO); + tmpCurFrame = - tmpCurFrame; + _direction = FORWARD; + } else if (static_cast<uint>(tmpCurFrame) >= animationDescriptionPtr->getFrameCount()) { + // Loop-Point callback + if (_loopPointCallback && !(_loopPointCallback)(getHandle())) + _loopPointCallback = 0; + + switch (animationDescriptionPtr->getAnimationType()) { + case AT_ONESHOT: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - 1; + _finished = true; + pause(); + break; + + case AT_LOOP: + tmpCurFrame = tmpCurFrame % animationDescriptionPtr->getFrameCount(); + break; + + case AT_JOJO: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - (tmpCurFrame % animationDescriptionPtr->getFrameCount()) - 1; + _direction = BACKWARD; + break; + + default: + BS_ASSERT(0); + } + } + + if ((int)_currentFrame != tmpCurFrame) { + forceRefresh(); + + if (animationDescriptionPtr->getFrame(_currentFrame).action != "") { + // action callback + if (_actionCallback && !(_actionCallback)(getHandle())) + _actionCallback = 0; + } + } + + _currentFrame = static_cast<uint>(tmpCurFrame); + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen + computeCurrentCharacteristics(); + + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + BS_ASSERT(_currentFrameTime >= 0); +} + +void Animation::computeCurrentCharacteristics() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + // Größe des Bitmaps auf die Animation übertragen + _width = static_cast<int>(pBitmap->getWidth() * _scaleFactorX); + _height = static_cast<int>(pBitmap->getHeight() * _scaleFactorY); + + // Position anhand des Hotspots berechnen und setzen + int posX = _relX + computeXModifier(); + int posY = _relY + computeYModifier(); + + RenderObject::setPos(posX, posY); + + pBitmap->release(); +} + +bool Animation::lockAllFrames() { + if (!_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + if (!Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName)) { + BS_LOG_ERRORLN("Could not lock all animation frames."); + return false; + } + } + + _framesLocked = true; + } + + return true; +} + +bool Animation::unlockAllFrames() { + if (_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + Resource *pResource; + if (!(pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName))) { + BS_LOG_ERRORLN("Could not unlock all animation frames."); + return false; + } + + // Zwei mal freigeben um den Request von LockAllFrames() und den jetzigen Request aufzuheben + pResource->release(); + if (pResource->getLockCount()) + pResource->release(); + } + + _framesLocked = false; + } + + return true; +} + +Animation::ANIMATION_TYPES Animation::getAnimationType() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getAnimationType(); +} + +int Animation::getFPS() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFPS(); +} + +int Animation::getFrameCount() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrameCount(); +} + +bool Animation::isScalingAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isScalingAllowed(); +} + +bool Animation::isAlphaAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isAlphaAllowed(); +} + +bool Animation::isColorModulationAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isColorModulationAllowed(); +} + +void Animation::setPos(int relX, int relY) { + _relX = relX; + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setX(int relX) { + _relX = relX; + + computeCurrentCharacteristics(); +} + +void Animation::setY(int relY) { + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setAlpha(int alpha) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isAlphaAllowed()) { + BS_LOG_WARNINGLN("Tried to set alpha value on an animation that does not support alpha. Call was ignored."); + return; + } + + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setModulationColor(uint modulationColor) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isColorModulationAllowed()) { + BS_LOG_WARNINGLN("Tried to set modulation color on an animation that does not support color modulation. Call was ignored"); + return; + } + + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setScaleFactor(float scaleFactor) { + setScaleFactorX(scaleFactor); + setScaleFactorY(scaleFactor); +} + +void Animation::setScaleFactorX(float scaleFactorX) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set x scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorX != _scaleFactorX) { + _scaleFactorX = scaleFactorX; + if (_scaleFactorX <= 0.0f) + _scaleFactorX = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +void Animation::setScaleFactorY(float scaleFactorY) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set y scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorY != _scaleFactorY) { + _scaleFactorY = scaleFactorY; + if (_scaleFactorY <= 0.0f) + _scaleFactorY = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +const Common::String &Animation::getCurrentAction() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrame(_currentFrame).action; +} + +int Animation::getX() const { + return _relX; +} + +int Animation::getY() const { + return _relY; +} + +int Animation::getAbsoluteX() const { + return _absoluteX + (_relX - _x); +} + +int Animation::getAbsoluteY() const { + return _absoluteY + (_relY - _y); +} + +int Animation::computeXModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipV ? - static_cast<int>((pBitmap->getWidth() - 1 - curFrame.hotspotX) * _scaleFactorX) : + - static_cast<int>(curFrame.hotspotX * _scaleFactorX); + + pBitmap->release(); + + return result; +} + +int Animation::computeYModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipH ? - static_cast<int>((pBitmap->getHeight() - 1 - curFrame.hotspotY) * _scaleFactorY) : + - static_cast<int>(curFrame.hotspotY * _scaleFactorY); + + pBitmap->release(); + + return result; +} + +bool Animation::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + + writer.write(_relX); + writer.write(_relY); + writer.write(_scaleFactorX); + writer.write(_scaleFactorY); + writer.write(_modulationColor); + writer.write(_currentFrame); + writer.write(_currentFrameTime); + writer.write(_running); + writer.write(_finished); + writer.write(static_cast<uint>(_direction)); + + // Je nach Animationstyp entweder das Template oder die Ressource speichern. + if (_animationResourcePtr) { + uint marker = 0; + writer.write(marker); + writer.writeString(_animationResourcePtr->getFileName()); + } else if (_animationTemplateHandle) { + uint marker = 1; + writer.write(marker); + writer.write(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + //writer.write(_AnimationDescriptionPtr); + + writer.write(_framesLocked); + + // The following is only there to for compatibility with older saves + // resp. the original engine. + writer.write((uint)1); + writer.writeString("LuaLoopPointCB"); + writer.write(getHandle()); + writer.write((uint)1); + writer.writeString("LuaActionCB"); + writer.write(getHandle()); + writer.write((uint)1); + writer.writeString("LuaDeleteCB"); + writer.write(getHandle()); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +// ----------------------------------------------------------------------------- + +bool Animation::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + reader.read(_relX); + reader.read(_relY); + reader.read(_scaleFactorX); + reader.read(_scaleFactorY); + reader.read(_modulationColor); + reader.read(_currentFrame); + reader.read(_currentFrameTime); + reader.read(_running); + reader.read(_finished); + uint direction; + reader.read(direction); + _direction = static_cast<Direction>(direction); + + // Animationstyp einlesen. + uint marker; + reader.read(marker); + if (marker == 0) { + Common::String resourceFilename; + reader.readString(resourceFilename); + initializeAnimationResource(resourceFilename); + } else if (marker == 1) { + reader.read(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + reader.read(_framesLocked); + if (_framesLocked) + lockAllFrames(); + + + // The following is only there to for compatibility with older saves + // resp. the original engine. + uint callbackCount; + Common::String callbackFunctionName; + uint callbackData; + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaLoopPointCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaActionCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaDeleteCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // Set the callbacks + setCallbacks(); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +// ----------------------------------------------------------------------------- + +AnimationDescription *Animation::getAnimationDescription() const { + if (_animationResourcePtr) + return _animationResourcePtr; + else + return AnimationTemplateRegistry::instance().resolveHandle(_animationTemplateHandle); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animation.h b/engines/sword25/gfx/animation.h new file mode 100644 index 0000000000..72fe7e2de8 --- /dev/null +++ b/engines/sword25/gfx/animation.h @@ -0,0 +1,219 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATION_H +#define SWORD25_ANIMATION_H + +// Includes +#include "sword25/kernel/common.h" +#include "sword25/gfx/timedrenderobject.h" + +namespace Sword25 { + +// Forward declarations +class Kernel; +class AnimationResource; +class AnimationTemplate; +class AnimationDescription; +class InputPersistenceBlock; + +class Animation : public TimedRenderObject { + friend class RenderObject; + +private: + Animation(RenderObjectPtr<RenderObject> parentPtr, const Common::String &fileName); + Animation(RenderObjectPtr<RenderObject> parentPtr, const AnimationTemplate &template_); + Animation(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + enum ANIMATION_TYPES { + AT_ONESHOT, + AT_LOOP, + AT_JOJO + }; + + virtual ~Animation(); + + void play(); + void pause(); + void stop(); + void setFrame(uint nr); + + virtual void setPos(int x, int y); + virtual void setX(int x); + virtual void setY(int y); + + virtual int getX() const; + virtual int getY() const; + virtual int getAbsoluteX() const; + virtual int getAbsoluteY() const; + + /** + @brief Setzt den Alphawert der Animation. + @param Alpha der neue Alphawert der Animation (0 = keine Deckung, 255 = volle Deckung). + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + void setAlpha(int alpha); + + /** + @brief Setzt die Modulationfarbe der Animation. + @param Color eine 24-Bit Farbe, die die Modulationsfarbe der Animation festlegt. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + void setModulationColor(uint modulationColor); + + /** + @brief Setzt den Skalierungsfaktor der Animation. + @param ScaleFactor der Faktor um den die Animation in beide Richtungen gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactor(float scaleFactor); + + /** + @brief Setzt den Skalierungsfaktor der Animation auf der X-Achse. + @param ScaleFactor der Faktor um den die Animation in Richtungen der X-Achse gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorX(float scaleFactorX); + + /** + @brief Setzt den Skalierungsfaktor der Animation auf der Y-Achse. + @param ScaleFactor der Faktor um den die Animation in Richtungen der Y-Achse gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorY(float scaleFactorY); + + /** + @brief Gibt den Skalierungsfakter der Animation auf der X-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorX() const { + return _scaleFactorX; + } + + /** + @brief Gibt den Skalierungsfakter der Animation auf der Y-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorY() const { + return _scaleFactorY; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + + virtual void frameNotification(int timeElapsed); + + ANIMATION_TYPES getAnimationType() const; + int getFPS() const; + int getFrameCount() const; + bool isScalingAllowed() const; + bool isAlphaAllowed() const; + bool isColorModulationAllowed() const; + uint getCurrentFrame() const { + return _currentFrame; + } + const Common::String &getCurrentAction() const; + bool isRunning() const { + return _running; + } + + typedef bool (*ANIMATION_CALLBACK)(uint); + + void setCallbacks(); + +protected: + virtual bool doRender(); + +private: + enum Direction { + FORWARD, + BACKWARD + }; + + int _relX; + int _relY; + float _scaleFactorX; + float _scaleFactorY; + uint _modulationColor; + uint _currentFrame; + int _currentFrameTime; + bool _running; + bool _finished; + Direction _direction; + AnimationResource *_animationResourcePtr; + uint _animationTemplateHandle; + bool _framesLocked; + + ANIMATION_CALLBACK _loopPointCallback; + ANIMATION_CALLBACK _actionCallback; + ANIMATION_CALLBACK _deleteCallback; + + /** + @brief Lockt alle Frames. + @return Gibt false zurück, falls nicht alle Frames gelockt werden konnten. + */ + bool lockAllFrames(); + + /** + @brief Unlockt alle Frames. + @return Gibt false zurück, falls nicht alles Frames freigegeben werden konnten. + */ + bool unlockAllFrames(); + + /** + @brief Diese Methode aktualisiert die Parameter (Größe, Position) der Animation anhand des aktuellen Frames. + + Diese Methode muss bei jedem Framewechsel aufgerufen werden damit der RenderObject-Manager immer aktuelle Daten hat. + */ + void computeCurrentCharacteristics(); + + /** + @brief Berechnet den Abstand zwischen dem linken Rand und dem Hotspot auf X-Achse in der aktuellen Darstellung. + */ + int computeXModifier() const; + + /** + @brief Berechnet den Abstand zwischen dem linken Rand und dem Hotspot auf X-Achse in der aktuellen Darstellung. + */ + int computeYModifier() const; + + void initMembers(); + AnimationDescription *getAnimationDescription() const; + void initializeAnimationResource(const Common::String &fileName); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationdescription.cpp b/engines/sword25/gfx/animationdescription.cpp new file mode 100644 index 0000000000..68ba7b63a6 --- /dev/null +++ b/engines/sword25/gfx/animationdescription.cpp @@ -0,0 +1,65 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/animationdescription.h" + +namespace Sword25 { + +bool AnimationDescription::persist(OutputPersistenceBlock &writer) { + writer.write(static_cast<uint>(_animationType)); + writer.write(_FPS); + writer.write(_millisPerFrame); + writer.write(_scalingAllowed); + writer.write(_alphaAllowed); + writer.write(_colorModulationAllowed); + + return true; +} + +bool AnimationDescription::unpersist(InputPersistenceBlock &reader) { + uint animationType; + reader.read(animationType); + _animationType = static_cast<Animation::ANIMATION_TYPES>(animationType); + reader.read(_FPS); + reader.read(_millisPerFrame); + reader.read(_scalingAllowed); + reader.read(_alphaAllowed); + reader.read(_colorModulationAllowed); + + return reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationdescription.h b/engines/sword25/gfx/animationdescription.h new file mode 100644 index 0000000000..88cbb23503 --- /dev/null +++ b/engines/sword25/gfx/animationdescription.h @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONDESCRIPTION_H +#define SWORD25_ANIMATIONDESCRIPTION_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/animation.h" + +namespace Sword25 { + +class AnimationDescription : public Persistable { +protected: + AnimationDescription() : + _animationType(Animation::AT_LOOP), + _FPS(10), + _millisPerFrame(0), + _scalingAllowed(true), + _alphaAllowed(true), + _colorModulationAllowed(true) + {} + +public: + struct Frame { + // Die Hotspot-Angabe bezieht sich auf das ungeflippte Bild!! + int hotspotX; + int hotspotY; + bool flipV; + bool flipH; + Common::String fileName; + Common::String action; + }; + + virtual const Frame &getFrame(uint index) const = 0; + virtual uint getFrameCount() const = 0; + virtual void unlock() = 0; + + Animation::ANIMATION_TYPES getAnimationType() const { + return _animationType; + } + int getFPS() const { + return _FPS; + } + int getMillisPerFrame() const { + return _millisPerFrame; + } + bool isScalingAllowed() const { + return _scalingAllowed; + } + bool isAlphaAllowed() const { + return _alphaAllowed; + } + bool isColorModulationAllowed() const { + return _colorModulationAllowed; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + Animation::ANIMATION_TYPES _animationType; + int _FPS; + int _millisPerFrame; + bool _scalingAllowed; + bool _alphaAllowed; + bool _colorModulationAllowed; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationresource.cpp b/engines/sword25/gfx/animationresource.cpp new file mode 100644 index 0000000000..6e5f683a2e --- /dev/null +++ b/engines/sword25/gfx/animationresource.cpp @@ -0,0 +1,252 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/animationresource.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/bitmapresource.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "ANIMATIONRESOURCE" + +namespace { +const int DEFAULT_FPS = 10; +const int MIN_FPS = 1; +const int MAX_FPS = 200; +} + +AnimationResource::AnimationResource(const Common::String &filename) : + Resource(filename, Resource::TYPE_ANIMATION), + Common::XMLParser(), + _valid(false) { + // Get a pointer to the package manager + _pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(_pPackage); + + // Switch to the folder the specified Xml fiile is in + Common::String oldDirectory = _pPackage->getCurrentDirectory(); + if (getFileName().contains('/')) { + Common::String dir = Common::String(getFileName().c_str(), strrchr(getFileName().c_str(), '/')); + _pPackage->changeDirectory(dir); + } + + // Load the contents of the file + uint fileSize; + char *xmlData = _pPackage->getXmlFile(getFileName(), &fileSize); + if (!xmlData) { + BS_LOG_ERRORLN("Could not read \"%s\".", getFileName().c_str()); + return; + } + + // Parse the contents + if (!loadBuffer((const byte *)xmlData, fileSize)) + return; + + _valid = parse(); + close(); + free(xmlData); + + // Switch back to the previous folder + _pPackage->changeDirectory(oldDirectory); + + // Give an error message if there weren't any frames specified + if (_frames.empty()) { + BS_LOG_ERRORLN("\"%s\" does not have any frames.", getFileName().c_str()); + return; + } + + // Pre-cache all the frames + if (!precacheAllFrames()) { + BS_LOG_ERRORLN("Could not precache all frames of \"%s\".", getFileName().c_str()); + return; + } + + // Post processing to compute animation features + if (!computeFeatures()) { + BS_LOG_ERRORLN("Could not determine the features of \"%s\".", getFileName().c_str()); + return; + } + + _valid = true; +} + +bool AnimationResource::parseBooleanKey(Common::String s, bool &result) { + s.toLowercase(); + if (!strcmp(s.c_str(), "true")) + result = true; + else if (!strcmp(s.c_str(), "false")) + result = false; + else + return false; + return true; +} + +bool AnimationResource::parserCallback_animation(ParserNode *node) { + if (!parseIntegerKey(node->values["fps"], 1, &_FPS) || (_FPS < MIN_FPS) || (_FPS > MAX_FPS)) { + return parserError("Illegal or missing fps attribute in <animation> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_FPS); + } + + // Loop type value + const char *loopTypeString = node->values["type"].c_str(); + + if (strcmp(loopTypeString, "oneshot") == 0) { + _animationType = Animation::AT_ONESHOT; + } else if (strcmp(loopTypeString, "loop") == 0) { + _animationType = Animation::AT_LOOP; + } else if (strcmp(loopTypeString, "jojo") == 0) { + _animationType = Animation::AT_JOJO; + } else { + BS_LOG_WARNINGLN("Illegal type value (\"%s\") in <animation> tag in \"%s\". Assuming default (\"loop\").", + loopTypeString, getFileName().c_str()); + _animationType = Animation::AT_LOOP; + } + + // Calculate the milliseconds required per frame + // FIXME: Double check variable naming. Based on the constant, it may be microseconds + _millisPerFrame = 1000000 / _FPS; + + return true; +} + +bool AnimationResource::parserCallback_frame(ParserNode *node) { + Frame frame; + + const char *fileString = node->values["file"].c_str(); + if (!fileString) { + BS_LOG_ERRORLN("<frame> tag without file attribute occurred in \"%s\".", getFileName().c_str()); + return false; + } + frame.fileName = _pPackage->getAbsolutePath(fileString); + if (frame.fileName.empty()) { + BS_LOG_ERRORLN("Could not create absolute path for file specified in <frame> tag in \"%s\": \"%s\".", + getFileName().c_str(), fileString); + return false; + } + + const char *actionString = node->values["action"].c_str(); + if (actionString) + frame.action = actionString; + + const char *hotspotxString = node->values["hotspotx"].c_str(); + const char *hotspotyString = node->values["hotspoty"].c_str(); + if ((!hotspotxString && hotspotyString) || + (hotspotxString && !hotspotyString)) + BS_LOG_WARNINGLN("%s attribute occurred without %s attribute in <frame> tag in \"%s\". Assuming default (\"0\").", + hotspotxString ? "hotspotx" : "hotspoty", + !hotspotyString ? "hotspoty" : "hotspotx", + getFileName().c_str()); + + frame.hotspotX = 0; + if (hotspotxString && !parseIntegerKey(hotspotxString, 1, &frame.hotspotX)) + BS_LOG_WARNINGLN("Illegal hotspotx value (\"%s\") in frame tag in \"%s\". Assuming default (\"%s\").", + hotspotxString, getFileName().c_str(), frame.hotspotX); + + frame.hotspotY = 0; + if (hotspotyString && !parseIntegerKey(hotspotyString, 1, &frame.hotspotY)) + BS_LOG_WARNINGLN("Illegal hotspoty value (\"%s\") in frame tag in \"%s\". Assuming default (\"%s\").", + hotspotyString, getFileName().c_str(), frame.hotspotY); + + Common::String flipVString = node->values["flipv"]; + if (!flipVString.empty()) { + if (!parseBooleanKey(flipVString, frame.flipV)) { + BS_LOG_WARNINGLN("Illegal flipv value (\"%s\") in <frame> tag in \"%s\". Assuming default (\"false\").", + flipVString.c_str(), getFileName().c_str()); + frame.flipV = false; + } + } else + frame.flipV = false; + + Common::String flipHString = node->values["fliph"]; + if (!flipHString.empty()) { + if (!parseBooleanKey(flipVString, frame.flipV)) { + BS_LOG_WARNINGLN("Illegal fliph value (\"%s\") in <frame> tag in \"%s\". Assuming default (\"false\").", + flipHString.c_str(), getFileName().c_str()); + frame.flipH = false; + } + } else + frame.flipH = false; + + _frames.push_back(frame); + return true; +} + +AnimationResource::~AnimationResource() { +} + +bool AnimationResource::precacheAllFrames() const { + Common::Array<Frame>::const_iterator iter = _frames.begin(); + for (; iter != _frames.end(); ++iter) { + if (!Kernel::getInstance()->getResourceManager()->precacheResource((*iter).fileName)) { + BS_LOG_ERRORLN("Could not precache \"%s\".", (*iter).fileName.c_str()); + return false; + } + } + + return true; +} + +bool AnimationResource::computeFeatures() { + BS_ASSERT(_frames.size()); + + // Alle Features werden als vorhanden angenommen + _scalingAllowed = true; + _alphaAllowed = true; + _colorModulationAllowed = true; + + // Alle Frame durchgehen und alle Features deaktivieren, die auch nur von einem Frame nicht unterstützt werden. + Common::Array<Frame>::const_iterator iter = _frames.begin(); + for (; iter != _frames.end(); ++iter) { + BitmapResource *pBitmap; + if (!(pBitmap = static_cast<BitmapResource *>(Kernel::getInstance()->getResourceManager()->requestResource((*iter).fileName)))) { + BS_LOG_ERRORLN("Could not request \"%s\".", (*iter).fileName.c_str()); + return false; + } + + if (!pBitmap->isScalingAllowed()) + _scalingAllowed = false; + if (!pBitmap->isAlphaAllowed()) + _alphaAllowed = false; + if (!pBitmap->isColorModulationAllowed()) + _colorModulationAllowed = false; + + pBitmap->release(); + } + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationresource.h b/engines/sword25/gfx/animationresource.h new file mode 100644 index 0000000000..da07b55c3b --- /dev/null +++ b/engines/sword25/gfx/animationresource.h @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONRESOURCE_H +#define SWORD25_ANIMATIONRESOURCE_H + +#include "common/xmlparser.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" +#include "sword25/gfx/animationdescription.h" +#include "sword25/gfx/animation.h" + +namespace Sword25 { + +class Kernel; +class PackageManager; + +class AnimationResource : public Resource, public AnimationDescription, public Common::XMLParser { +public: + AnimationResource(const Common::String &filename); + virtual ~AnimationResource(); + + virtual const Frame &getFrame(uint index) const { + BS_ASSERT(index < _frames.size()); + return _frames[index]; + } + virtual uint getFrameCount() const { + return _frames.size(); + } + virtual void unlock() { + release(); + } + + Animation::ANIMATION_TYPES getAnimationType() const { + return _animationType; + } + int getFPS() const { + return _FPS; + } + int getMillisPerFrame() const { + return _millisPerFrame; + } + bool isScalingAllowed() const { + return _scalingAllowed; + } + bool isAlphaAllowed() const { + return _alphaAllowed; + } + bool isColorModulationAllowed() const { + return _colorModulationAllowed; + } + bool isValid() const { + return _valid; + } + +private: + bool _valid; + + Common::Array<Frame> _frames; + + PackageManager *_pPackage; + + + bool computeFeatures(); + bool precacheAllFrames() const; + + // Parser + CUSTOM_XML_PARSER(AnimationResource) { + XML_KEY(animation) + XML_PROP(fps, true) + XML_PROP(type, true) + + XML_KEY(frame) + XML_PROP(file, true) + XML_PROP(hotspotx, true) + XML_PROP(hotspoty, true) + XML_PROP(fliph, false) + XML_PROP(flipv, false) + KEY_END() + KEY_END() + } PARSER_END() + + bool parseBooleanKey(Common::String s, bool &result); + + // Parser callback methods + bool parserCallback_animation(ParserNode *node); + bool parserCallback_frame(ParserNode *node); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationtemplate.cpp b/engines/sword25/gfx/animationtemplate.cpp new file mode 100644 index 0000000000..4a060dbad9 --- /dev/null +++ b/engines/sword25/gfx/animationtemplate.cpp @@ -0,0 +1,243 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "ANIMATIONTEMPLATE" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resource.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" + +namespace Sword25 { + +uint AnimationTemplate::create(const Common::String &sourceAnimation) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(sourceAnimation); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +uint AnimationTemplate::create(const AnimationTemplate &other) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(other); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +uint AnimationTemplate::create(InputPersistenceBlock &reader, uint handle) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(reader, handle); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +AnimationTemplate::AnimationTemplate(const Common::String &sourceAnimation) { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this); + + _valid = false; + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt + _sourceAnimationPtr = requestSourceAnimation(sourceAnimation); + + // Erfolg signalisieren + _valid = (_sourceAnimationPtr != 0); +} + +AnimationTemplate::AnimationTemplate(const AnimationTemplate &other) : AnimationDescription() { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this); + + _valid = false; + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt. + if (!other._sourceAnimationPtr) + return; + _sourceAnimationPtr = requestSourceAnimation(other._sourceAnimationPtr->getFileName()); + + // Alle Member kopieren. + _animationType = other._animationType; + _FPS = other._FPS; + _millisPerFrame = other._millisPerFrame; + _scalingAllowed = other._scalingAllowed; + _alphaAllowed = other._alphaAllowed; + _colorModulationAllowed = other._colorModulationAllowed; + _frames = other._frames; + _sourceAnimationPtr = other._sourceAnimationPtr; + _valid = other._valid; + + _valid &= (_sourceAnimationPtr != 0); +} + +AnimationTemplate::AnimationTemplate(InputPersistenceBlock &reader, uint handle) { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this, handle); + + // Objekt laden. + _valid = unpersist(reader); +} + +AnimationResource *AnimationTemplate::requestSourceAnimation(const Common::String &sourceAnimation) const { + ResourceManager *RMPtr = Kernel::getInstance()->getResourceManager(); + Resource *resourcePtr; + if (NULL == (resourcePtr = RMPtr->requestResource(sourceAnimation)) || resourcePtr->getType() != Resource::TYPE_ANIMATION) { + BS_LOG_ERRORLN("The resource \"%s\" could not be requested or is has an invalid type. The animation template can't be created.", sourceAnimation.c_str()); + return 0; + } + return static_cast<AnimationResource *>(resourcePtr); +} + +AnimationTemplate::~AnimationTemplate() { + // Animations-Resource freigeben + if (_sourceAnimationPtr) { + _sourceAnimationPtr->release(); + } + + // Objekt deregistrieren + AnimationTemplateRegistry::instance().deregisterObject(this); +} + +void AnimationTemplate::addFrame(int index) { + if (validateSourceIndex(index)) { + _frames.push_back(_sourceAnimationPtr->getFrame(index)); + } +} + +void AnimationTemplate::setFrame(int destIndex, int srcIndex) { + if (validateDestIndex(destIndex) && validateSourceIndex(srcIndex)) { + _frames[destIndex] = _sourceAnimationPtr->getFrame(srcIndex); + } +} + +bool AnimationTemplate::validateSourceIndex(uint index) const { + if (index > _sourceAnimationPtr->getFrameCount()) { + BS_LOG_WARNINGLN("Tried to insert a frame (\"%d\") that does not exist in the source animation (\"%s\"). Ignoring call.", + index, _sourceAnimationPtr->getFileName().c_str()); + return false; + } else + return true; +} + +bool AnimationTemplate::validateDestIndex(uint index) const { + if (index > _frames.size()) { + BS_LOG_WARNINGLN("Tried to change a nonexistent frame (\"%d\") in a template animation. Ignoring call.", + index); + return false; + } else + return true; +} + +void AnimationTemplate::setFPS(int FPS) { + _FPS = FPS; + _millisPerFrame = 1000000 / _FPS; +} + +bool AnimationTemplate::persist(OutputPersistenceBlock &writer) { + bool Result = true; + + // Parent persistieren. + Result &= AnimationDescription::persist(writer); + + // Frameanzahl schreiben. + writer.write(_frames.size()); + + // Frames einzeln persistieren. + Common::Array<const Frame>::const_iterator Iter = _frames.begin(); + while (Iter != _frames.end()) { + writer.write(Iter->hotspotX); + writer.write(Iter->hotspotY); + writer.write(Iter->flipV); + writer.write(Iter->flipH); + writer.writeString(Iter->fileName); + writer.writeString(Iter->action); + ++Iter; + } + + // Restliche Member persistieren. + writer.writeString(_sourceAnimationPtr->getFileName()); + writer.write(_valid); + + return Result; +} + +bool AnimationTemplate::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Parent wieder herstellen. + result &= AnimationDescription::unpersist(reader); + + // Frameanzahl lesen. + uint frameCount; + reader.read(frameCount); + + // Frames einzeln wieder herstellen. + for (uint i = 0; i < frameCount; ++i) { + Frame frame; + reader.read(frame.hotspotX); + reader.read(frame.hotspotY); + reader.read(frame.flipV); + reader.read(frame.flipH); + reader.readString(frame.fileName); + reader.readString(frame.action); + + _frames.push_back(frame); + } + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt + Common::String sourceAnimation; + reader.readString(sourceAnimation); + _sourceAnimationPtr = requestSourceAnimation(sourceAnimation); + + reader.read(_valid); + + return _sourceAnimationPtr && reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationtemplate.h b/engines/sword25/gfx/animationtemplate.h new file mode 100644 index 0000000000..294f249f81 --- /dev/null +++ b/engines/sword25/gfx/animationtemplate.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATION_TEMPLATE_H +#define SWORD25_ANIMATION_TEMPLATE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/animationdescription.h" + +namespace Sword25 { + +class AnimationResource; + +class AnimationTemplate : public AnimationDescription { +public: + static uint create(const Common::String &sourceAnimation); + static uint create(const AnimationTemplate &other); + static uint create(InputPersistenceBlock &reader, uint handle); + AnimationTemplate *resolveHandle(uint handle) const; + +private: + AnimationTemplate(const Common::String &sourceAnimation); + AnimationTemplate(const AnimationTemplate &other); + AnimationTemplate(InputPersistenceBlock &reader, uint handle); + +public: + ~AnimationTemplate(); + + virtual const Frame &getFrame(uint index) const { + BS_ASSERT(index < _frames.size()); + return _frames[index]; + } + virtual uint getFrameCount() const { + return _frames.size(); + } + virtual void unlock() { + delete this; + } + + bool isValid() const { + return _valid; + } + + /** + @brief Fügt einen neuen Frame zur Animation hinzu. + + Der Frame wird an das Ende der Animation angehängt. + + @param Index der Index des Frames in der Quellanimation + */ + void addFrame(int index); + + /** + @brief Ändert einen bereits in der Animation vorhandenen Frame. + @param DestIndex der Index des Frames der überschrieben werden soll + @param SrcIndex der Index des einzufügenden Frames in der Quellanimation + */ + void setFrame(int destIndex, int srcIndex); + + /** + @brief Setzt den Animationstyp. + @param Type der Typ der Animation. Muss aus den enum BS_Animation::ANIMATION_TYPES sein. + */ + void setAnimationType(Animation::ANIMATION_TYPES type) { + _animationType = type; + } + + /** + @brief Setzt die Abspielgeschwindigkeit. + @param FPS die Abspielgeschwindigkeit in Frames pro Sekunde. + */ + void setFPS(int FPS); + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + Common::Array<Frame> _frames; + AnimationResource *_sourceAnimationPtr; + bool _valid; + + AnimationResource *requestSourceAnimation(const Common::String &sourceAnimation) const; + bool validateSourceIndex(uint index) const; + bool validateDestIndex(uint index) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationtemplateregistry.cpp b/engines/sword25/gfx/animationtemplateregistry.cpp new file mode 100644 index 0000000000..ac1d6096f4 --- /dev/null +++ b/engines/sword25/gfx/animationtemplateregistry.cpp @@ -0,0 +1,107 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "ANIMATIONTEMPLATEREGISTRY" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "sword25/gfx/animationtemplate.h" + +DECLARE_SINGLETON(Sword25::AnimationTemplateRegistry) + +namespace Sword25 { + +void AnimationTemplateRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void AnimationTemplateRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +bool AnimationTemplateRegistry::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // Das nächste zu vergebene Handle schreiben. + writer.write(_nextHandle); + + // Anzahl an BS_AnimationTemplates schreiben. + writer.write(_handle2PtrMap.size()); + + // Alle BS_AnimationTemplates persistieren. + HANDLE2PTR_MAP::const_iterator iter = _handle2PtrMap.begin(); + while (iter != _handle2PtrMap.end()) { + // Handle persistieren. + writer.write(iter->_key); + + // Objekt persistieren. + result &= iter->_value->persist(writer); + + ++iter; + } + + return result; +} + +// ----------------------------------------------------------------------------- + +bool AnimationTemplateRegistry::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Das nächste zu vergebene Handle wieder herstellen. + reader.read(_nextHandle); + + // Alle vorhandenen BS_AnimationTemplates zerstören. + while (!_handle2PtrMap.empty()) + delete _handle2PtrMap.begin()->_value; + + // Anzahl an BS_AnimationTemplates einlesen. + uint animationTemplateCount; + reader.read(animationTemplateCount); + + // Alle gespeicherten BS_AnimationTemplates wieder herstellen. + for (uint i = 0; i < animationTemplateCount; ++i) { + // Handle lesen. + uint handle; + reader.read(handle); + + // BS_AnimationTemplate wieder herstellen. + result &= (AnimationTemplate::create(reader, handle) != 0); + } + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationtemplateregistry.h b/engines/sword25/gfx/animationtemplateregistry.h new file mode 100644 index 0000000000..c5308bb124 --- /dev/null +++ b/engines/sword25/gfx/animationtemplateregistry.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONTEMPLATEREGISTRY_H +#define SWORD25_ANIMATIONTEMPLATEREGISTRY_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/kernel/objectregistry.h" + +#include "common/singleton.h" + +namespace Sword25 { + +class AnimationTemplate; + +class AnimationTemplateRegistry : + public ObjectRegistry<AnimationTemplate>, + public Persistable, + public Common::Singleton<AnimationTemplateRegistry> { +public: + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/bitmap.cpp b/engines/sword25/gfx/bitmap.cpp new file mode 100644 index 0000000000..b5412c8276 --- /dev/null +++ b/engines/sword25/gfx/bitmap.cpp @@ -0,0 +1,183 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/bitmap.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "BITMAP" + +Bitmap::Bitmap(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle) : + RenderObject(parentPtr, type, handle), + _modulationColor(0xffffffff), + _scaleFactorX(1.0f), + _scaleFactorY(1.0f), + _flipH(false), + _flipV(false) { +} + +Bitmap::~Bitmap() { +} + +void Bitmap::setAlpha(int alpha) { + if (!isAlphaAllowed()) { + BS_LOG_WARNINGLN("Tried to set alpha value on a bitmap that does not support alpha blending. Call was ignored."); + return; + } + + if (alpha < 0 || alpha > 255) { + int oldAlpha = alpha; + if (alpha < 0) + alpha = 0; + if (alpha > 255) + alpha = 255; + BS_LOG_WARNINGLN("Tried to set an invalid alpha value (%d) on a bitmap. Value was changed to %d.", oldAlpha, alpha); + + return; + } + + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Bitmap::setModulationColor(uint modulationColor) { + if (!isColorModulationAllowed()) { + BS_LOG_WARNINGLN("Tried to set modulation color of a bitmap that does not support color modulation. Call was ignored."); + return; + } + + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Bitmap::setScaleFactor(float scaleFactor) { + setScaleFactorX(scaleFactor); + setScaleFactorY(scaleFactor); +} + +void Bitmap::setScaleFactorX(float scaleFactorX) { + if (!isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap that does not support scaling. Call was ignored."); + return; + } + + if (scaleFactorX < 0) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap to a negative value. Call was ignored."); + return; + } + + if (scaleFactorX != _scaleFactorX) { + _scaleFactorX = scaleFactorX; + _width = static_cast<int>(_originalWidth * _scaleFactorX); + if (_scaleFactorX <= 0.0f) + _scaleFactorX = 0.001f; + if (_width <= 0) + _width = 1; + forceRefresh(); + } +} + +void Bitmap::setScaleFactorY(float scaleFactorY) { + if (!isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap that does not support scaling. Call was ignored."); + return; + } + + if (scaleFactorY < 0) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap to a negative value. Call was ignored."); + return; + } + + if (scaleFactorY != _scaleFactorY) { + _scaleFactorY = scaleFactorY; + _height = static_cast<int>(_originalHeight * scaleFactorY); + if (_scaleFactorY <= 0.0f) + _scaleFactorY = 0.001f; + if (_height <= 0) + _height = 1; + forceRefresh(); + } +} + +void Bitmap::setFlipH(bool flipH) { + _flipH = flipH; + forceRefresh(); +} + +void Bitmap::setFlipV(bool flipV) { + _flipV = flipV; + forceRefresh(); +} + +bool Bitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + writer.write(_flipH); + writer.write(_flipV); + writer.write(_scaleFactorX); + writer.write(_scaleFactorY); + writer.write(_modulationColor); + writer.write(_originalWidth); + writer.write(_originalHeight); + + return result; +} + +bool Bitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + reader.read(_flipH); + reader.read(_flipV); + reader.read(_scaleFactorX); + reader.read(_scaleFactorY); + reader.read(_modulationColor); + reader.read(_originalWidth); + reader.read(_originalHeight); + + forceRefresh(); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/bitmap.h b/engines/sword25/gfx/bitmap.h new file mode 100644 index 0000000000..741269c423 --- /dev/null +++ b/engines/sword25/gfx/bitmap.h @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_BITMAP_H +#define SWORD25_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Bitmap : public RenderObject { +protected: + Bitmap(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle = 0); + +public: + + virtual ~Bitmap(); + + /** + @brief Setzt den Alphawert des Bitmaps. + @param Alpha der neue Alphawert der Bitmaps (0 = keine Deckung, 255 = volle Deckung). + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + void setAlpha(int alpha); + + /** + @brief Setzt die Modulationfarbe der Bitmaps. + @param Color eine 24-Bit Farbe, die die Modulationsfarbe des Bitmaps festlegt. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + void setModulationColor(uint modulationColor); + + /** + @brief Setzt den Skalierungsfaktor des Bitmaps. + @param ScaleFactor der Faktor um den das Bitmap in beide Richtungen gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactor(float scaleFactor); + + /** + @brief Setzt den Skalierungsfaktor der Bitmap auf der X-Achse. + @param ScaleFactor der Faktor um den die Bitmap in Richtungen der X-Achse gestreckt werden soll. Dieser Wert muss positiv sein. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorX(float scaleFactorX); + + /** + @brief Setzt den Skalierungsfaktor der Bitmap auf der Y-Achse. + @param ScaleFactor der Faktor um den die Bitmap in Richtungen der Y-Achse gestreckt werden soll. Dieser Wert muss positiv sein. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorY(float scaleFactorY); + + /** + @brief Legt fest, ob das Bild an der X-Achse gespiegelt werden soll. + */ + void setFlipH(bool flipH); + + /** + @brief Legt fest, ob das Bild an der Y-Achse gespiegelt werden soll. + */ + void setFlipV(bool flipV); + + /** + @brief Gibt den aktuellen Alphawert des Bildes zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + int getAlpha() { + return _modulationColor >> 24; + } + + /** + @brief Gibt die aktuelle 24bit RGB Modulationsfarde des Bildes zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + int getModulationColor() { + return _modulationColor & 0x00ffffff; + } + + /** + @brief Gibt den Skalierungsfakter des Bitmaps auf der X-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorX() const { + return _scaleFactorX; + } + + /** + @brief Gibt den Skalierungsfakter des Bitmaps auf der Y-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorY() const { + return _scaleFactorY; + } + + /** + @brief Gibt zurück, ob das Bild an der X-Achse gespiegelt angezeigt wird. + */ + bool isFlipH() { + return _flipH; + } + + /** + @brief Gibt zurück, ob das Bild an der Y-Achse gespiegelt angezeigt wird. + */ + bool isFlipV() { + return _flipV; + } + + // ----------------------------------------------------------------------------- + // Die folgenden Methoden müssen alle BS_Bitmap-Klassen implementieren + // ----------------------------------------------------------------------------- + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + virtual uint getPixel(int x, int y) const = 0; + + /** + @brief Füllt den Inhalt des Bildes mit Pixeldaten. + @param Pixeldata ein Vector der die Pixeldaten enthält. Sie müssen in dem Farbformat des Bildes vorliegen und es müssen genügend Daten + vorhanden sein, um das ganze Bild zu füllen. + @param Offset der Offset in Byte im Pixeldata-Vector an dem sich der erste zu schreibende Pixel befindet.<br> + Der Standardwert ist 0. + @param Stride der Abstand in Byte zwischen dem Zeilenende und dem Beginn einer neuen Zeile im Pixeldata-Vector.<br> + Der Standardwert ist 0. + @return Gibt false zurück, falls der Aufruf fehlgeschlagen ist. + @remark Ein Aufruf dieser Methode ist nur erlaubt, wenn IsSetContentAllowed() true zurückgibt. + */ + virtual bool setContent(const byte *pixeldata, uint size, uint offset = 0, uint stride = 0) = 0; + + virtual bool isScalingAllowed() const = 0; + virtual bool isAlphaAllowed() const = 0; + virtual bool isColorModulationAllowed() const = 0; + virtual bool isSetContentAllowed() const = 0; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + bool _flipH; + bool _flipV; + float _scaleFactorX; + float _scaleFactorY; + uint _modulationColor; + int _originalWidth; + int _originalHeight; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/bitmapresource.cpp b/engines/sword25/gfx/bitmapresource.cpp new file mode 100644 index 0000000000..cf3c85e3ac --- /dev/null +++ b/engines/sword25/gfx/bitmapresource.cpp @@ -0,0 +1,62 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/bitmapresource.h" +#include "sword25/kernel/kernel.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "BITMAP" + +BitmapResource::BitmapResource(const Common::String &filename, Image *pImage) : + _valid(false), + _pImage(pImage), + Resource(filename, Resource::TYPE_BITMAP) { + _valid = _pImage != 0; +} + +BitmapResource::~BitmapResource() { + delete _pImage; +} + +uint BitmapResource::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _pImage->getWidth()); + BS_ASSERT(y >= 0 && y < _pImage->getHeight()); + + return _pImage->getPixel(x, y); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/bitmapresource.h b/engines/sword25/gfx/bitmapresource.h new file mode 100644 index 0000000000..37849f918e --- /dev/null +++ b/engines/sword25/gfx/bitmapresource.h @@ -0,0 +1,212 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_BITMAP_RESOURCE_H +#define SWORD25_BITMAP_RESOURCE_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" +#include "sword25/gfx/image/image.h" + +namespace Sword25 { + +class BitmapResource : public Resource { +public: + /** + @brief Die möglichen Flippingparameter für die Blit-Methode. + */ + enum FLIP_FLAGS { + /// Das Bild wird nicht gespiegelt. + FLIP_NONE = 0, + /// Das Bild wird an der horizontalen Achse gespiegelt. + FLIP_H = 1, + /// Das Bild wird an der vertikalen Achse gespiegelt. + FLIP_V = 2, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_HV = FLIP_H | FLIP_V, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_VH = FLIP_H | FLIP_V + }; + + BitmapResource(const Common::String &filename, Image *pImage); + virtual ~BitmapResource(); + + /** + @brief Gibt zurück, ob das Objekt einen gültigen Zustand hat. + */ + bool isValid() const { + return _valid; + } + + /** + @brief Gibt die Breite des Bitmaps zurück. + */ + int getWidth() const { + BS_ASSERT(_pImage); + return _pImage->getWidth(); + } + + /** + @brief Gibt die Höhe des Bitmaps zurück. + */ + int getHeight() const { + BS_ASSERT(_pImage); + return _pImage->getHeight(); + } + + /** + @brief Rendert das Bild in den Framebuffer. + @param PosX die Position auf der X-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param PosY die Position auf der Y-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param Flipping gibt an, wie das Bild gespiegelt werden soll.<br> + Der Standardwert ist BS_Image::FLIP_NONE (keine Spiegelung) + @param pSrcPartRect Pointer auf ein Common::Rect, welches den Ausschnitt des Quellbildes spezifiziert, der gerendert + werden soll oder NULL, falls das gesamte Bild gerendert werden soll.<br> + Dieser Ausschnitt bezieht sich auf das ungespiegelte und unskalierte Bild.<br> + Der Standardwert ist NULL. + @param Color ein ARGB Farbwert, der die Parameter für die Farbmodulation und fürs Alphablending festlegt.<br> + Die Alpha-Komponente der Farbe bestimmt den Alphablending Parameter (0 = keine Deckung, 255 = volle Deckung).<br> + Die Farbkomponenten geben die Farbe für die Farbmodulation an.<br> + Der Standardwert is BS_ARGB(255, 255, 255, 255) (volle Deckung, keine Farbmodulation). + Zum Erzeugen des Farbwertes können die Makros BS_RGB und BS_ARGB benutzt werden. + @param Width gibt die Ausgabebreite des Bildausschnittes an. + Falls diese von der Breite des Bildausschnittes abweicht wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @param Width gibt die Ausgabehöhe des Bildausschnittes an. + Falls diese von der Höhe des Bildauschnittes abweicht, wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark Er werden nicht alle Blitting-Operationen von allen BS_Image-Klassen unterstützt.<br> + Mehr Informationen gibt es in der Klassenbeschreibung von BS_Image und durch folgende Methoden: + - IsBlitTarget() + - IsScalingAllowed() + - IsFillingAllowed() + - IsAlphaAllowed() + - IsColorModulationAllowed() + */ + bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pSrcPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1) { + BS_ASSERT(_pImage); + return _pImage->blit(posX, posY, flipping, pSrcPartRect, color, width, height); + } + + /** + @brief Füllt einen Rechteckigen Bereich des Bildes mit einer Farbe. + @param pFillRect Pointer auf ein Common::Rect, welches den Ausschnitt des Bildes spezifiziert, der gefüllt + werden soll oder NULL, falls das gesamte Bild gefüllt werden soll.<br> + Der Standardwert ist NULL. + @param Color der 32 Bit Farbwert mit dem der Bildbereich gefüllt werden soll. + @remark Ein Aufruf dieser Methode ist nur gestattet, wenn IsFillingAllowed() true zurückgibt. + @remark Es ist möglich über die Methode transparente Rechtecke darzustellen, indem man eine Farbe mit einem Alphawert ungleich + 255 angibt. + @remark Unabhängig vom Farbformat des Bildes muss ein 32 Bit Farbwert angegeben werden. Zur Erzeugung, können die Makros + BS_RGB und BS_ARGB benutzt werden. + @remark Falls das Rechteck nicht völlig innerhalb des Bildschirms ist, wird es automatisch zurechtgestutzt. + */ + bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)) { + BS_ASSERT(_pImage); + return _pImage->fill(pFillRect, color); + } + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + uint getPixel(int x, int y) const; + + //@{ + /** @name Auskunfts-Methoden */ + + /** + @brief Überprüft, ob das BS_Image ein Zielbild für einen Blit-Aufruf sein kann. + @return Gibt false zurück, falls ein Blit-Aufruf mit diesem Objekt als Ziel nicht gestattet ist. + */ + bool isBlitTarget() { + BS_ASSERT(_pImage); + return _pImage->isBlitTarget(); + } + + /** + @brief Gibt true zurück, falls das BS_Image bei einem Aufruf von Blit() skaliert dargestellt werden kann. + */ + bool isScalingAllowed() { + BS_ASSERT(_pImage); + return _pImage->isScalingAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image mit einem Aufruf von Fill() gefüllt werden kann. + */ + bool isFillingAllowed() { + BS_ASSERT(_pImage); + return _pImage->isFillingAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit einem Alphawert dargestellt werden kann. + */ + bool isAlphaAllowed() { + BS_ASSERT(_pImage); + return _pImage->isAlphaAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit Farbmodulation dargestellt werden kann. + */ + bool isColorModulationAllowed() { + BS_ASSERT(_pImage); + return _pImage->isColorModulationAllowed(); + } + +private: + Image *_pImage; + bool _valid; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/dynamicbitmap.cpp b/engines/sword25/gfx/dynamicbitmap.cpp new file mode 100644 index 0000000000..612e370712 --- /dev/null +++ b/engines/sword25/gfx/dynamicbitmap.cpp @@ -0,0 +1,155 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/dynamicbitmap.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "DYNAMICBITMAP" + +DynamicBitmap::DynamicBitmap(RenderObjectPtr<RenderObject> parentPtr, uint width, uint height) : + Bitmap(parentPtr, TYPE_DYNAMICBITMAP) { + // Das BS_Bitmap konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) return; + + _initSuccess = createRenderedImage(width, height); +} + +DynamicBitmap::DynamicBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + Bitmap(parentPtr, TYPE_DYNAMICBITMAP, handle) { + _initSuccess = unpersist(reader); +} + +bool DynamicBitmap::createRenderedImage(uint width, uint height) { + // RenderedImage mit den gewünschten Maßen erstellen + bool result = false; + _image.reset(new RenderedImage(width, height, result)); + + _originalWidth = _width = width; + _originalHeight = _height = height; + + return result; +} + +DynamicBitmap::~DynamicBitmap() { +} + +uint DynamicBitmap::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + return _image->getPixel(x, y); +} + +bool DynamicBitmap::doRender() { + // Framebufferobjekt holen + GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); + BS_ASSERT(pGfx); + + // Bitmap zeichnen + bool result; + if (_scaleFactorX == 1.0f && _scaleFactorY == 1.0f) { + result = _image->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } else { + result = _image->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } + + return result; +} + +bool DynamicBitmap::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + return _image->setContent(pixeldata, size, offset, stride); +} + +bool DynamicBitmap::isScalingAllowed() const { + return _image->isScalingAllowed(); +} + +bool DynamicBitmap::isAlphaAllowed() const { + return _image->isAlphaAllowed(); +} + +bool DynamicBitmap::isColorModulationAllowed() const { + return _image->isColorModulationAllowed(); +} + +bool DynamicBitmap::isSetContentAllowed() const { + return true; +} + +bool DynamicBitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= Bitmap::persist(writer); + + // Bilddaten werden nicht gespeichert. Dies ist auch nicht weiter von bedeutung, da BS_DynamicBitmap nur vom Videoplayer benutzt wird. + // Während ein Video abläuft kann niemals gespeichert werden. BS_DynamicBitmap kann nur der Vollständigkeit halber persistiert werden. + BS_LOG_WARNINGLN("Persisting a BS_DynamicBitmap. Bitmap content is not persisted."); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool DynamicBitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= Bitmap::unpersist(reader); + + // Ein RenderedImage mit den gespeicherten Maßen erstellen. + result &= createRenderedImage(_width, _height); + + // Bilddaten werden nicht gespeichert (s.o.). + BS_LOG_WARNINGLN("Unpersisting a BS_DynamicBitmap. Bitmap contents are missing."); + + // Bild mit durchsichtigen Bilddaten initialisieren. + byte *transparentImageData = (byte *)calloc(_width * _height * 4, 1); + _image->setContent(transparentImageData, _width * _height); + free(transparentImageData); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/dynamicbitmap.h b/engines/sword25/gfx/dynamicbitmap.h new file mode 100644 index 0000000000..1737bdf5fc --- /dev/null +++ b/engines/sword25/gfx/dynamicbitmap.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_DYNAMIC_BITMAP_H +#define SWORD25_DYNAMIC_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "common/ptr.h" + +namespace Sword25 { + +class DynamicBitmap : public Bitmap { + friend class RenderObject; + +public: + virtual ~DynamicBitmap(); + + virtual uint getPixel(int x, int y) const; + + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + + virtual bool isScalingAllowed() const; + virtual bool isAlphaAllowed() const; + virtual bool isColorModulationAllowed() const; + virtual bool isSetContentAllowed() const; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + DynamicBitmap(RenderObjectPtr<RenderObject> parentPtr, uint width, uint height); + DynamicBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + + bool createRenderedImage(uint width, uint height); + + Common::ScopedPtr<RenderedImage> _image; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/fontresource.cpp b/engines/sword25/gfx/fontresource.cpp new file mode 100644 index 0000000000..dbb9c67fe5 --- /dev/null +++ b/engines/sword25/gfx/fontresource.cpp @@ -0,0 +1,138 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "FONTRESOURCE" + +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" + +#include "sword25/gfx/fontresource.h" + +namespace Sword25 { + +enum { + DEFAULT_LINEHEIGHT = 20, + DEFAULT_GAPWIDTH = 1 +}; + +FontResource::FontResource(Kernel *pKernel, const Common::String &fileName) : + _pKernel(pKernel), + _valid(false), + Resource(fileName, Resource::TYPE_FONT), + Common::XMLParser() { + + // Get a pointer to the package manager + BS_ASSERT(_pKernel); + PackageManager *pPackage = _pKernel->getPackage(); + BS_ASSERT(pPackage); + + // Load the contents of the file + uint fileSize; + char *xmlData = pPackage->getXmlFile(getFileName(), &fileSize); + if (!xmlData) { + BS_LOG_ERRORLN("Could not read \"%s\".", getFileName().c_str()); + return; + } + + // Parse the contents + if (!loadBuffer((const byte *)xmlData, fileSize)) + return; + + _valid = parse(); + close(); + free(xmlData); +} + +bool FontResource::parserCallback_font(ParserNode *node) { + // Get the attributes of the font + Common::String bitmapFilename = node->values["bitmap"]; + + if (!parseIntegerKey(node->values["lineheight"], 1, &_lineHeight)) { + BS_LOG_WARNINGLN("Illegal or missing lineheight attribute in <font> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_LINEHEIGHT); + _lineHeight = DEFAULT_LINEHEIGHT; + } + + if (!parseIntegerKey(node->values["gap"], 1, &_gapWidth)) { + BS_LOG_WARNINGLN("Illegal or missing gap attribute in <font> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_GAPWIDTH); + _gapWidth = DEFAULT_GAPWIDTH; + } + + // Get a reference to the package manager + BS_ASSERT(_pKernel); + PackageManager *pPackage = _pKernel->getPackage(); + BS_ASSERT(pPackage); + + // Get the full path and filename for the bitmap resource + _bitmapFileName = pPackage->getAbsolutePath(bitmapFilename); + if (_bitmapFileName == "") { + BS_LOG_ERRORLN("Image file \"%s\" was specified in <font> tag of \"%s\" but could not be found.", + _bitmapFileName.c_str(), getFileName().c_str()); + } + + // Pre-cache the resource + if (!_pKernel->getResourceManager()->precacheResource(_bitmapFileName)) { + BS_LOG_ERRORLN("Could not precache \"%s\".", _bitmapFileName.c_str()); + } + + return true; +} + +bool FontResource::parserCallback_character(ParserNode *node) { + // Get the attributes of the character + int charCode, top, left, right, bottom; + + if (!parseIntegerKey(node->values["code"], 1, &charCode) || (charCode < 0) || (charCode >= 256)) { + return parserError("Illegal or missing code attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + + if (!parseIntegerKey(node->values["top"], 1, &top) || (top < 0)) { + return parserError("Illegal or missing top attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["left"], 1, &left) || (left < 0)) { + return parserError("Illegal or missing left attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["right"], 1, &right) || (right < 0)) { + return parserError("Illegal or missing right attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["bottom"], 1, &bottom) || (bottom < 0)) { + return parserError("Illegal or missing bottom attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + + this->_characterRects[charCode] = Common::Rect(left, top, right, bottom); + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/fontresource.h b/engines/sword25/gfx/fontresource.h new file mode 100644 index 0000000000..19c44d0ade --- /dev/null +++ b/engines/sword25/gfx/fontresource.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_FONTRESOURCE_H +#define SWORD25_FONTRESOURCE_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/xmlparser.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" + +namespace Sword25 { + +class Kernel; + +class FontResource : public Resource, Common::XMLParser { +public: + /** + @brief Erzeugt eine neues Exemplar von BS_FontResource + @param pKernel ein Pointer auf den Kernel + @param FileName der Dateiname der zu ladenen Resource + @remark Wenn der Konstruktor erfolgreich ausgeführt werden konnte gibt die Methode IsValid true zurück. + */ + FontResource(Kernel *pKernel, const Common::String &fileName); + + /** + @brief Gibt true zurück, wenn das Objekt korrekt initialisiert wurde. + + Diese Methode kann dazu benutzt werden um festzustellen, ob der Konstruktor erfolgreich ausgeführt wurde. + */ + bool isValid() const { + return _valid; + } + + /** + @brief Gibt die Zeilenhöhe des Fonts in Pixeln zurück. + + Die Zeilenhöhe ist der Wert, der zur Y-Koordinate addiert wird, wenn ein Zeilenumbruch auftritt. + */ + int getLineHeight() const { + return _lineHeight; + } + + /** + @brief Gibt den Buchstabenabstand der Fonts in Pixeln zurück. + + Der Buchstabenabstand ist der Wert, der zwischen zwei Buchstaben freigelassen wird. + */ + int getGapWidth() const { + return _gapWidth; + } + + /** + @brief Gibt das Bounding-Rect eines Zeichens auf der Charactermap zurück. + @param Character der ASCII-Code des Zeichens + @return Das Bounding-Rect des übergebenen Zeichens auf der Charactermap. + */ + const Common::Rect &getCharacterRect(int character) const { + BS_ASSERT(character >= 0 && character < 256); + return _characterRects[character]; + } + + /** + @brief Gibt den Dateinamen der Charactermap zurück. + */ + const Common::String &getCharactermapFileName() const { + return _bitmapFileName; + } + +private: + Kernel *_pKernel; + bool _valid; + Common::String _bitmapFileName; + int _lineHeight; + int _gapWidth; + Common::Rect _characterRects[256]; + + // Parser + CUSTOM_XML_PARSER(FontResource) { + XML_KEY(font) + XML_PROP(bitmap, true) + XML_PROP(lineheight, false) + XML_PROP(gap, false) + + XML_KEY(character) + XML_PROP(code, true) + XML_PROP(left, true) + XML_PROP(top, true) + XML_PROP(right, true) + XML_PROP(bottom, true) + KEY_END() + KEY_END() + } PARSER_END() + + // Parser callback methods + bool parserCallback_font(ParserNode *node); + bool parserCallback_character(ParserNode *node); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/framecounter.cpp b/engines/sword25/gfx/framecounter.cpp new file mode 100644 index 0000000000..07415cc2dc --- /dev/null +++ b/engines/sword25/gfx/framecounter.cpp @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/system.h" +#include "sword25/gfx/framecounter.h" + +namespace Sword25 { + +Framecounter::Framecounter(int updateFrequency) : + _FPS(0), + _FPSCount(0), + _lastUpdateTime(-1) { + setUpdateFrequency(updateFrequency); +} + +void Framecounter::update() { + // Aktuellen Systemtimerstand auslesen + uint64 timer = g_system->getMillis() * 1000; + + // Falls m_LastUpdateTime == -1 ist, wird der Frame-Counter zum ersten Mal aufgerufen und der aktuelle Systemtimer als erster + // Messzeitpunkt genommen. + if (_lastUpdateTime == -1) + _lastUpdateTime = timer; + else { + // Die Anzahl der Frames im aktuellen Messzeitraum wird erhöht. + _FPSCount++; + + // Falls der Messzeitraum verstrichen ist, wird die durchschnittliche Framerate berechnet und ein neuer Messzeitraum begonnen. + if (timer - _lastUpdateTime >= _updateDelay) { + _FPS = static_cast<int>((1000000 * (uint64)_FPSCount) / (timer - _lastUpdateTime)); + _lastUpdateTime = timer; + _FPSCount = 0; + } + } +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/framecounter.h b/engines/sword25/gfx/framecounter.h new file mode 100644 index 0000000000..994950573f --- /dev/null +++ b/engines/sword25/gfx/framecounter.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_FRAMECOUNTER_H +#define SWORD25_FRAMECOUNTER_H + +// Includes +#include "sword25/kernel/common.h" + +namespace Sword25 { + +/** + * A simple class that implements a frame counter + */ +class Framecounter { +private: + + // TODO: This class should be rewritten based on Audio::Timestamp, + // which provides higher accuracy and avoids using 64 bit data types. + typedef unsigned long long uint64; + typedef signed long long int64; + + enum { + DEFAULT_UPDATE_FREQUENCY = 10 + }; + +public: + /** + * Creates a new BS_Framecounter object + * @param UpdateFrequency Specifies how often the frame counter should be updated in a sceond. + * The default value is 10. + */ + Framecounter(int updateFrequency = DEFAULT_UPDATE_FREQUENCY); + + /** + * Determines how often the frame counter should be updated in a second. + * @param UpdateFrequency Specifies how often the frame counter should be updated in a second. + */ + inline void setUpdateFrequency(int updateFrequency); + + /** + * This method must be called once per frame. + */ + void update(); + + /** + * Returns the current FPS value. + */ + int getFPS() const { + return _FPS; + } + +private: + int _FPS; + int _FPSCount; + int64 _lastUpdateTime; + uint64 _updateDelay; +}; + +// Inlines +void Framecounter::setUpdateFrequency(int updateFrequency) { + // Frequency in time (converted to microseconds) + _updateDelay = 1000000 / updateFrequency; +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/graphicengine.cpp b/engines/sword25/gfx/graphicengine.cpp new file mode 100644 index 0000000000..f629993abf --- /dev/null +++ b/engines/sword25/gfx/graphicengine.cpp @@ -0,0 +1,494 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "GRAPHICENGINE" + +#include "common/system.h" + +#include "sword25/gfx/bitmapresource.h" +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/fontresource.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/renderobjectmanager.h" +#include "sword25/gfx/screenshot.h" +#include "sword25/gfx/image/renderedimage.h" +#include "sword25/gfx/image/swimage.h" +#include "sword25/gfx/image/vectorimage.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" + + +#include "sword25/gfx/graphicengine.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lauxlib.h" +enum { + BIT_DEPTH = 32, + BACKBUFFER_COUNT = 1 +}; + + +namespace Sword25 { + +static const uint FRAMETIME_SAMPLE_COUNT = 5; // Anzahl der Framezeiten über die, die Framezeit gemittelt wird + +GraphicEngine::GraphicEngine(Kernel *pKernel) : + _width(0), + _height(0), + _bitDepth(0), + _windowed(0), + _lastTimeStamp((uint) -1), // max. BS_INT64 um beim ersten Aufruf von _UpdateLastFrameDuration() einen Reset zu erzwingen + _lastFrameDuration(0), + _timerActive(true), + _frameTimeSampleSlot(0), + _repaintedPixels(0), + _thumbnail(NULL), + ResourceService(pKernel) { + _frameTimeSamples.resize(FRAMETIME_SAMPLE_COUNT); + + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +GraphicEngine::~GraphicEngine() { + unregisterScriptBindings(); + _backSurface.free(); + _frameBuffer.free(); + delete _thumbnail; +} + +bool GraphicEngine::init(int width, int height, int bitDepth, int backbufferCount, bool isWindowed_) { + // Warnung ausgeben, wenn eine nicht unterstützte Bittiefe gewählt wurde. + if (bitDepth != BIT_DEPTH) { + BS_LOG_WARNINGLN("Can't use a bit depth of %d (not supported). Falling back to %d.", bitDepth, BIT_DEPTH); + _bitDepth = BIT_DEPTH; + } + + // Warnung ausgeben, wenn nicht genau ein Backbuffer gewählt wurde. + if (backbufferCount != BACKBUFFER_COUNT) { + BS_LOG_WARNINGLN("Can't use %d backbuffers (not supported). Falling back to %d.", backbufferCount, BACKBUFFER_COUNT); + backbufferCount = BACKBUFFER_COUNT; + } + + // Parameter in lokale Variablen kopieren + _width = width; + _height = height; + _bitDepth = bitDepth; + _windowed = isWindowed_; + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = _width; + _screenRect.bottom = _height; + + _backSurface.create(width, height, 4); + _frameBuffer.create(width, height, 4); + + // Standardmäßig ist Vsync an. + setVsync(true); + + // Layer-Manager initialisieren. + _renderObjectManagerPtr.reset(new RenderObjectManager(width, height, backbufferCount + 1)); + + // Hauptpanel erstellen + _mainPanelPtr = _renderObjectManagerPtr->getTreeRoot()->addPanel(width, height, BS_ARGB(0, 0, 0, 0)); + if (!_mainPanelPtr.isValid()) + return false; + _mainPanelPtr->setVisible(true); + + return true; +} + +bool GraphicEngine::startFrame(bool updateAll) { + // Berechnen, wie viel Zeit seit dem letzten Frame vergangen ist. + // Dieser Wert kann über GetLastFrameDuration() von Modulen abgefragt werden, die zeitabhängig arbeiten. + updateLastFrameDuration(); + + // Den Layer-Manager auf den nächsten Frame vorbereiten + _renderObjectManagerPtr->startFrame(); + + return true; +} + +bool GraphicEngine::endFrame() { + // Scene zeichnen + _renderObjectManagerPtr->render(); + + // FIXME: The frame buffer surface is only used as the base for creating thumbnails when saving the + // game, since the _backSurface is blanked. Currently I'm doing a slightly hacky check and only + // copying the back surface if line 50 (the first line after the interface area) is non-blank + if (READ_LE_UINT32((byte *)_backSurface.pixels + (_backSurface.pitch * 50)) & 0xffffff) { + // Make a copy of the current frame into the frame buffer + Common::copy((byte *)_backSurface.pixels, (byte *)_backSurface.pixels + + (_backSurface.pitch * _backSurface.h), (byte *)_frameBuffer.pixels); + } + + g_system->updateScreen(); + + // Debug-Lines zeichnen + if (!_debugLines.empty()) { +#if 0 + glEnable(GL_LINE_SMOOTH); + glBegin(GL_LINES); + + Common::Array<DebugLine>::const_iterator iter = m_DebugLines.begin(); + for (; iter != m_DebugLines.end(); ++iter) { + const uint &Color = (*iter).Color; + const BS_Vertex &Start = (*iter).Start; + const BS_Vertex &End = (*iter).End; + + glColor4ub((Color >> 16) & 0xff, (Color >> 8) & 0xff, Color & 0xff, Color >> 24); + glVertex2d(Start.X, Start.Y); + glVertex2d(End.X, End.Y); + } + + glEnd(); + glDisable(GL_LINE_SMOOTH); +#endif + + warning("STUB: Drawing debug lines"); + + _debugLines.clear(); + } + + // Framecounter aktualisieren + _FPSCounter.update(); + + return true; +} + +RenderObjectPtr<Panel> GraphicEngine::getMainPanel() { + return _mainPanelPtr; +} + +void GraphicEngine::setVsync(bool vsync) { + warning("STUB: SetVsync(%d)", vsync); +} + +bool GraphicEngine::getVsync() const { + warning("STUB: getVsync()"); + + return true; +} + +bool GraphicEngine::fill(const Common::Rect *fillRectPtr, uint color) { + Common::Rect rect(_width - 1, _height - 1); + + int ca = (color >> 24) & 0xff; + + if (ca == 0) + return true; + + int cr = (color >> 16) & 0xff; + int cg = (color >> 8) & 0xff; + int cb = (color >> 0) & 0xff; + + if (fillRectPtr) { + rect = *fillRectPtr; + } + + if (rect.width() > 0 && rect.height() > 0) { + if (ca == 0xff) { + _backSurface.fillRect(rect, color); + } else { + byte *outo = (byte *)_backSurface.getBasePtr(rect.left, rect.top); + byte *out; + + for (int i = rect.top; i < rect.bottom; i++) { + out = outo; + for (int j = rect.left; j < rect.right; j++) { + *out += (byte)(((cb - *out) * ca) >> 8); + out++; + *out += (byte)(((cg - *out) * ca) >> 8); + out++; + *out += (byte)(((cr - *out) * ca) >> 8); + out++; + *out = 255; + out++; + } + + outo += _backSurface.pitch; + } + } + + g_system->copyRectToScreen((byte *)_backSurface.getBasePtr(rect.left, rect.top), _backSurface.pitch, rect.left, rect.top, rect.width(), rect.height()); + } + + return true; +} + +Graphics::Surface *GraphicEngine::getScreenshot() { + return &_frameBuffer; +} + +// ----------------------------------------------------------------------------- +// RESOURCE MANAGING +// ----------------------------------------------------------------------------- + +Resource *GraphicEngine::loadResource(const Common::String &filename) { + BS_ASSERT(canLoadResource(filename)); + + // Load image for "software buffer" (FIXME: Whatever that means?) + if (filename.hasSuffix("_s.png")) { + bool result = false; + SWImage *pImage = new SWImage(filename, result); + if (!result) { + delete pImage; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + return 0; + } + + return pResource; + } + + // Load sprite image + if (filename.hasSuffix(".png") || filename.hasSuffix(".b25s")) { + bool result = false; + RenderedImage *pImage = new RenderedImage(filename, result); + if (!result) { + delete pImage; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + return 0; + } + + return pResource; + } + + + // Load vector graphics + if (filename.hasSuffix(".swf")) { + debug(2, "VectorImage: %s", filename.c_str()); + + // Pointer auf Package-Manager holen + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = static_cast<byte *>(pPackage->getFile(filename, &fileSize)))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return 0; + } + + bool result = false; + VectorImage *pImage = new VectorImage(pFileData, fileSize, result, filename); + if (!result) { + delete pImage; + delete[] pFileData; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + delete[] pFileData; + return 0; + } + + delete[] pFileData; + return pResource; + } + + // Load animation + if (filename.hasSuffix("_ani.xml")) { + AnimationResource *pResource = new AnimationResource(filename); + if (pResource->isValid()) + return pResource; + else { + delete pResource; + return 0; + } + } + + // Load font + if (filename.hasSuffix("_fnt.xml")) { + FontResource *pResource = new FontResource(Kernel::getInstance(), filename); + if (pResource->isValid()) + return pResource; + else { + delete pResource; + return 0; + } + } + + BS_LOG_ERRORLN("Service cannot load \"%s\".", filename.c_str()); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool GraphicEngine::canLoadResource(const Common::String &filename) { + return filename.hasSuffix(".png") || + filename.hasSuffix("_ani.xml") || + filename.hasSuffix("_fnt.xml") || + filename.hasSuffix(".swf") || + filename.hasSuffix(".b25s"); +} + + +// ----------------------------------------------------------------------------- +// DEBUGGING +// ----------------------------------------------------------------------------- + +void GraphicEngine::drawDebugLine(const Vertex &start, const Vertex &end, uint color) { + _debugLines.push_back(DebugLine(start, end, color)); +} + +void GraphicEngine::updateLastFrameDuration() { + // Record current time + const uint currentTime = Kernel::getInstance()->getMilliTicks(); + + // Compute the elapsed time since the last frame and prevent too big ( > 250 msecs) time jumps. + // These can occur when loading save states, during debugging or due to hardware inaccuracies. + _frameTimeSamples[_frameTimeSampleSlot] = static_cast<uint>(currentTime - _lastTimeStamp); + if (_frameTimeSamples[_frameTimeSampleSlot] > 250000) + _frameTimeSamples[_frameTimeSampleSlot] = 250000; + _frameTimeSampleSlot = (_frameTimeSampleSlot + 1) % FRAMETIME_SAMPLE_COUNT; + + // Compute the average frame duration over multiple frames to eliminate outliers. + Common::Array<uint>::const_iterator it = _frameTimeSamples.begin(); + uint sum = *it; + for (it++; it != _frameTimeSamples.end(); it++) + sum += *it; + _lastFrameDuration = sum * 1000 / FRAMETIME_SAMPLE_COUNT; + + // Update m_LastTimeStamp with the current frame's timestamp + _lastTimeStamp = currentTime; +} + +bool GraphicEngine::saveThumbnailScreenshot(const Common::String &filename) { + // Note: In ScumMVM, rather than saivng the thumbnail to a file, we store it in memory + // until needed when creating savegame files + delete _thumbnail; + _thumbnail = Screenshot::createThumbnail(&_frameBuffer); + return true; +} + +void GraphicEngine::ARGBColorToLuaColor(lua_State *L, uint color) { + lua_Number components[4] = { + (color >> 16) & 0xff, // Rot + (color >> 8) & 0xff, // Grün + color & 0xff, // Blau + color >> 24, // Alpha + }; + + lua_newtable(L); + + for (uint i = 1; i <= 4; i++) { + lua_pushnumber(L, i); + lua_pushnumber(L, components[i - 1]); + lua_settable(L, -3); + } +} + +uint GraphicEngine::luaColorToARGBColor(lua_State *L, int stackIndex) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Sicherstellen, dass wir wirklich eine Tabelle betrachten + luaL_checktype(L, stackIndex, LUA_TTABLE); + // Größe der Tabelle auslesen + uint n = luaL_getn(L, stackIndex); + // RGB oder RGBA Farben werden unterstützt und sonst keine + if (n != 3 && n != 4) + luaL_argcheck(L, 0, stackIndex, "at least 3 of the 4 color components have to be specified"); + + // Red color component reading + lua_rawgeti(L, stackIndex, 1); + uint red = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || red >= 256) + luaL_argcheck(L, 0, stackIndex, "red color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Green color component reading + lua_rawgeti(L, stackIndex, 2); + uint green = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || green >= 256) + luaL_argcheck(L, 0, stackIndex, "green color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Blue color component reading + lua_rawgeti(L, stackIndex, 3); + uint blue = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || blue >= 256) + luaL_argcheck(L, 0, stackIndex, "blue color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Alpha color component reading + uint alpha = 0xff; + if (n == 4) { + lua_rawgeti(L, stackIndex, 4); + alpha = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || alpha >= 256) + luaL_argcheck(L, 0, stackIndex, "alpha color component must be an integer between 0 and 255"); + lua_pop(L, 1); + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return (alpha << 24) | (red << 16) | (green << 8) | blue; +} + +bool GraphicEngine::persist(OutputPersistenceBlock &writer) { + writer.write(_timerActive); + + bool result = _renderObjectManagerPtr->persist(writer); + + return result; +} + +bool GraphicEngine::unpersist(InputPersistenceBlock &reader) { + reader.read(_timerActive); + _renderObjectManagerPtr->unpersist(reader); + + return reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/graphicengine.h b/engines/sword25/gfx/graphicengine.h new file mode 100644 index 0000000000..e1ae0d2d62 --- /dev/null +++ b/engines/sword25/gfx/graphicengine.h @@ -0,0 +1,392 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * GraphicEngine + * ---------------- + * This the graphics engine interface. + * + * Autor: Malte Thiesen + */ + +#ifndef SWORD25_GRAPHICENGINE_H +#define SWORD25_GRAPHICENGINE_H + +// Includes +#include "common/array.h" +#include "common/rect.h" +#include "common/ptr.h" +#include "common/str.h" +#include "graphics/surface.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resservice.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/framecounter.h" +#include "sword25/gfx/renderobjectptr.h" +#include "sword25/math/vertex.h" + +namespace Sword25 { + +class Kernel; +class Image; +class Panel; +class Screenshot; +class RenderObjectManager; + +typedef uint BS_COLOR; + +#define BS_RGB(R,G,B) (0xFF000000 | ((R) << 16) | ((G) << 8) | (B)) +#define BS_ARGB(A,R,G,B) (((A) << 24) | ((R) << 16) | ((G) << 8) | (B)) + +/** + * This is the graphics engine. Unlike the original code, this is not + * an interface that needs to be subclassed, but rather already contains + * all required functionality. + */ +class GraphicEngine : public ResourceService, public Persistable { +public: + // Enums + // ----- + + // Colour formats + // + /** + * The colour format used by the engine + */ + enum COLOR_FORMATS { + /// Undefined/unknown colour format + CF_UNKNOWN = 0, + /** + * 24-bit colour format (R8G8B8) + */ + CF_RGB24, + /** + * 32-bit colour format (A8R8G8B8) (little endian) + */ + CF_ARGB32, + /** + 32-bit colour format (A8B8G8R8) (little endian) + */ + CF_ABGR32 + }; + + // Constructor + // ----------- + GraphicEngine(Kernel *pKernel); + ~GraphicEngine(); + + // Interface + // --------- + + /** + * Initialises the graphics engine and sets the screen mode. Returns + * true if initialisation failed. + * @note This method should be called immediately after the + * initialisation of all services. + * + * @param Height The height of the output buffer in pixels. The default value is 600 + * @param BitDepth The bit depth of the desired output buffer in bits. The default value is 16 + * @param BackbufferCount The number of back buffers to be created. The default value is 2 + * @param Windowed Indicates whether the engine is to run in windowed mode. + */ + bool init(int width = 800, int height = 600, int bitDepth = 16, int backbufferCount = 2, bool windowed = false); + + /** + * Begins rendering a new frame. + * Notes: This method must be called at the beginning of the main loop, before any rendering methods are used. + * Notes: Implementations of this method must call _UpdateLastFrameDuration() + * @param UpdateAll Specifies whether the renderer should redraw everything on the next frame. + * This feature can be useful if the renderer with Dirty Rectangles works, but sometimes the client may + */ + bool startFrame(bool updateAll = false); + + /** + * Ends the rendering of a frame and draws it on the screen. + * + * This method must be at the end of the main loop. After this call, no further Render method may be called. + * This should only be called once for a given previous call to #StartFrame. + */ + bool endFrame(); + + // Debug methods + + /** + * Draws a line in the frame buffer + * + * This method must be called between calls to StartFrame() and EndFrame(), and is intended only for debugging + * purposes. The line will only appear for a single frame. If the line is to be shown permanently, it must be + * called for every frame. + * @param Start The starting point of the line + * @param End The ending point of the line + * @param Color The colour of the line. The default is BS_RGB (255,255,255) (White) + */ + void drawDebugLine(const Vertex &start, const Vertex &end, uint color = BS_RGB(255, 255, 255)); + + /** + * Creates a thumbnail with the dimensions of 200x125. This will not include the top and bottom of the screen.. + * the interface boards the the image as a 16th of it's original size. + * Notes: This method should only be called after a call to EndFrame(), and before the next call to StartFrame(). + * The frame buffer must have a resolution of 800x600. + * @param Filename The filename for the screenshot + */ + bool saveThumbnailScreenshot(const Common::String &filename); + + /** + * Reads the current contents of the frame buffer + * Notes: This method is for creating screenshots. It is not very optimised. It should only be called + * after a call to EndFrame(), and before the next call to StartFrame(). + * @param Width Returns the width of the frame buffer + * @param Height Returns the height of the frame buffer + * @param Data Returns the raw data of the frame buffer as an array of 32-bit colour values. + */ + Graphics::Surface *getScreenshot(); + + + RenderObjectPtr<Panel> getMainPanel(); + + /** + * Specifies the time (in microseconds) since the last frame has passed + */ + int getLastFrameDurationMicro() const { + if (!_timerActive) + return 0; + return _lastFrameDuration; + } + + /** + * Specifies the time (in microseconds) the previous frame took + */ + float getLastFrameDuration() const { + if (!_timerActive) + return 0; + return static_cast<float>(_lastFrameDuration) / 1000000.0f; + } + + void stopMainTimer() { + _timerActive = false; + } + + void resumeMainTimer() { + _timerActive = true; + } + + float getSecondaryFrameDuration() const { + return static_cast<float>(_lastFrameDuration) / 1000000.0f; + } + + // Accessor methods + + /** + * Returns the width of the output buffer in pixels + */ + int getDisplayWidth() const { + return _width; + } + + /** + * Returns the height of the output buffer in pixels + */ + int getDisplayHeight() const { + return _height; + } + + /** + * Returns the bounding box of the output buffer: (0, 0, Width, Height) + */ + Common::Rect &getDisplayRect() { + return _screenRect; + } + + /** + * Returns the bit depth of the output buffer + */ + int getBitDepth() { + return _bitDepth; + } + + /** + * Determines whether the frame buffer change is to be synchronised with Vsync. This is turned on by default. + * Notes: In windowed mode, this setting has no effect. + * @param Vsync Indicates whether the frame buffer changes are to be synchronised with Vsync. + */ + void setVsync(bool vsync); + + /** + * Returns true if V-Sync is on. + * Notes: In windowed mode, this setting has no effect. + */ + bool getVsync() const; + + /** + * Returns true if the engine is running in Windowed mode. + */ + bool isWindowed() { + return _windowed; + } + + /** + * Fills a rectangular area of the frame buffer with a colour. + * Notes: It is possible to create transparent rectangles by passing a colour with an Alpha value of 255. + * @param FillRectPtr Pointer to a Common::Rect, which specifies the section of the frame buffer to be filled. + * If the rectangle falls partly off-screen, then it is automatically trimmed. + * If a NULL value is passed, then the entire image is to be filled. + * @param Color The 32-bit colour with which the area is to be filled. The default is BS_RGB(0, 0, 0) (black) + * @note FIf the rectangle is not completely inside the screen, it is automatically clipped. + */ + bool fill(const Common::Rect *fillRectPtr = 0, uint color = BS_RGB(0, 0, 0)); + + // Debugging Methods + + int getFPSCount() const { + return _FPSCounter.getFPS(); + } + int getRepaintedPixels() const { + return _repaintedPixels; + } + + Graphics::Surface _backSurface; + Graphics::Surface *getSurface() { return &_backSurface; } + + Graphics::Surface _frameBuffer; + Graphics::Surface *getFrameBuffer() { return &_frameBuffer; } + + Common::MemoryReadStream *_thumbnail; + Common::MemoryReadStream *getThumbnail() { return _thumbnail; } + + // Access methods + + /** + * Returns the size of a pixel entry in bytes for a particular colour format + * @param ColorFormat The desired colour format. The parameter must be of type COLOR_FORMATS + * @return Returns the size of a pixel in bytes. If the colour format is unknown, -1 is returned. + */ + static int getPixelSize(GraphicEngine::COLOR_FORMATS colorFormat) { + switch (colorFormat) { + case GraphicEngine::CF_ARGB32: + return 4; + default: + return -1; + } + } + + /** + * Calculates the length of an image line in bytes, depending on a given colour format. + * @param ColorFormat The colour format + * @param Width The width of the line in pixels + * @return Reflects the length of the line in bytes. If the colour format is + * unknown, -1 is returned + */ + static int calcPitch(GraphicEngine::COLOR_FORMATS colorFormat, int width) { + switch (colorFormat) { + case GraphicEngine::CF_ARGB32: + return width * 4; + + default: + BS_ASSERT(false); + } + + return -1; + } + + // Resource-Managing Methods + // -------------------------- + virtual Resource *loadResource(const Common::String &fileName); + virtual bool canLoadResource(const Common::String &fileName); + + // Persistence Methods + // ------------------- + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + + static void ARGBColorToLuaColor(lua_State *L, uint color); + static uint luaColorToARGBColor(lua_State *L, int stackIndex); + +protected: + + // Display Variables + // ----------------- + int _width; + int _height; + Common::Rect _screenRect; + int _bitDepth; + bool _windowed; + + // Debugging Variables + // ------------------- + Framecounter _FPSCounter; + + uint _repaintedPixels; + + /** + * Calculates the time since the last frame beginning has passed. + */ + void updateLastFrameDuration(); + +private: + bool registerScriptBindings(); + void unregisterScriptBindings(); + + // LastFrameDuration Variables + // --------------------------- + uint _lastTimeStamp; + uint _lastFrameDuration; + bool _timerActive; + Common::Array<uint> _frameTimeSamples; + uint _frameTimeSampleSlot; + +private: + byte *_backBuffer; + + RenderObjectPtr<Panel> _mainPanelPtr; + + Common::ScopedPtr<RenderObjectManager> _renderObjectManagerPtr; + + struct DebugLine { + DebugLine(const Vertex &start, const Vertex &end, uint color) : + _start(start), + _end(end), + _color(color) {} + DebugLine() {} + + Vertex _start; + Vertex _end; + uint _color; + }; + + Common::Array<DebugLine> _debugLines; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/graphicengine_script.cpp b/engines/sword25/gfx/graphicengine_script.cpp new file mode 100644 index 0000000000..0814a23871 --- /dev/null +++ b/engines/sword25/gfx/graphicengine_script.cpp @@ -0,0 +1,1305 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" +#include "sword25/script/luacallback.h" +#include "sword25/math/vertex.h" + +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/renderobject.h" +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/animation.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/text.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "GRAPHICENGINE" + +static bool animationDeleteCallback(uint Data); +static bool animationActionCallback(uint Data); +static bool animationLoopPointCallback(uint Data); + +namespace { +class ActionCallback : public LuaCallback { +public: + ActionCallback(lua_State *L) : LuaCallback(L) {} + + Common::String Action; + +protected: + virtual int PreFunctionInvokation(lua_State *L) { + lua_pushstring(L, Action.c_str()); + return 1; + } +}; + +static LuaCallback *loopPointCallbackPtr = 0; // FIXME: should be turned into GraphicEngine member var +static ActionCallback *actionCallbackPtr = 0; // FIXME: should be turned into GraphicEngine member var +} + +// Die Strings werden als #defines definiert um Stringkomposition zur Compilezeit zu ermöglichen. +#define RENDEROBJECT_CLASS_NAME "Gfx.RenderObject" +#define BITMAP_CLASS_NAME "Gfx.Bitmap" +#define PANEL_CLASS_NAME "Gfx.Panel" +#define TEXT_CLASS_NAME "Gfx.Text" +#define ANIMATION_CLASS_NAME "Gfx.Animation" +#define ANIMATION_TEMPLATE_CLASS_NAME "Gfx.AnimationTemplate" +static const char *GFX_LIBRARY_NAME = "Gfx"; + +// Wie luaL_checkudata, nur ohne dass kein Fehler erzeugt wird. +static void *my_checkudata(lua_State *L, int ud, const char *tname) { + int top = lua_gettop(L); + + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + // lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + LuaBindhelper::getMetatable(L, tname); + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_settop(L, top); + return p; + } + } + } + + lua_settop(L, top); + return NULL; +} + +static void newUintUserData(lua_State *L, uint value) { + void *userData = lua_newuserdata(L, sizeof(value)); + memcpy(userData, &value, sizeof(value)); +} + +static AnimationTemplate *checkAnimationTemplate(lua_State *L, int idx = 1) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.AnimationTemplate + uint animationTemplateHandle; + if ((animationTemplateHandle = *reinterpret_cast<uint *>(my_checkudata(L, idx, ANIMATION_TEMPLATE_CLASS_NAME))) != 0) { + AnimationTemplate *animationTemplatePtr = AnimationTemplateRegistry::instance().resolveHandle(animationTemplateHandle); + if (!animationTemplatePtr) + luaL_error(L, "The animation template with the handle %d does no longer exist.", animationTemplateHandle); + return animationTemplatePtr; + } else { + luaL_argcheck(L, 0, idx, "'" ANIMATION_TEMPLATE_CLASS_NAME "' expected"); + return 0; + } +} + + +static int newAnimationTemplate(lua_State *L) { + uint animationTemplateHandle = AnimationTemplate::create(luaL_checkstring(L, 1)); + AnimationTemplate *animationTemplatePtr = AnimationTemplateRegistry::instance().resolveHandle(animationTemplateHandle); + if (animationTemplatePtr && animationTemplatePtr->isValid()) { + newUintUserData(L, animationTemplateHandle); + //luaL_getmetatable(L, ANIMATION_TEMPLATE_CLASS_NAME); + LuaBindhelper::getMetatable(L, ANIMATION_TEMPLATE_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int at_addFrame(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->addFrame(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int at_setFrame(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->setFrame(static_cast<int>(luaL_checknumber(L, 2)), static_cast<int>(luaL_checknumber(L, 3))); + return 0; +} + +static bool animationTypeStringToNumber(const char *typeString, Animation::ANIMATION_TYPES &result) { + if (strcmp(typeString, "jojo") == 0) { + result = Animation::AT_JOJO; + return true; + } else if (strcmp(typeString, "loop") == 0) { + result = Animation::AT_LOOP; + return true; + } else if (strcmp(typeString, "oneshot") == 0) { + result = Animation::AT_ONESHOT; + return true; + } else + return false; +} + +static int at_setAnimationType(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + Animation::ANIMATION_TYPES animationType; + if (animationTypeStringToNumber(luaL_checkstring(L, 2), animationType)) { + pAT->setAnimationType(animationType); + } else { + luaL_argcheck(L, 0, 2, "Invalid animation type"); + } + + return 0; +} + +static int at_setFPS(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->setFPS(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int at_finalize(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + delete pAT; + return 0; +} + +static const luaL_reg ANIMATION_TEMPLATE_METHODS[] = { + {"AddFrame", at_addFrame}, + {"SetFrame", at_setFrame}, + {"SetAnimationType", at_setAnimationType}, + {"SetFPS", at_setFPS}, + {"__gc", at_finalize}, + {0, 0} +}; + +static GraphicEngine *getGE() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + GraphicEngine *pGE = pKernel->getGfx(); + BS_ASSERT(pGE); + return pGE; +} + +static int init(lua_State *L) { + GraphicEngine *pGE = getGE(); + + switch (lua_gettop(L)) { + case 0: + lua_pushbooleancpp(L, pGE->init()); + break; + case 1: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)))); + break; + case 2: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)))); + break; + case 3: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)))); + break; + case 4: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), static_cast<int>(luaL_checknumber(L, 4)))); + break; + default: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), static_cast<int>(luaL_checknumber(L, 4)), + lua_tobooleancpp(L, 5))); + } + + +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Main-Panel zum Gfx-Modul hinzufügen + RenderObjectPtr<Panel> mainPanelPtr(getGE()->getMainPanel()); + BS_ASSERT(mainPanelPtr.isValid()); + + lua_pushstring(L, GFX_LIBRARY_NAME); + lua_gettable(L, LUA_GLOBALSINDEX); + BS_ASSERT(!lua_isnil(L, -1)); + + newUintUserData(L, mainPanelPtr->getHandle()); + BS_ASSERT(!lua_isnil(L, -1)); + // luaL_getmetatable(L, PANEL_CLASS_NAME); + LuaBindhelper::getMetatable(L, PANEL_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + + lua_pushstring(L, "MainPanel"); + lua_insert(L, -2); + lua_settable(L, -3); + + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return 1; +} + +static int startFrame(lua_State *L) { + GraphicEngine *pGE = getGE(); + + if (lua_gettop(L) == 0) + lua_pushbooleancpp(L, pGE->startFrame()); + else + lua_pushbooleancpp(L, pGE->startFrame(lua_tobooleancpp(L, 1))); + + return 1; +} + +static int endFrame(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->endFrame()); + + return 1; +} + +static int drawDebugLine(lua_State *L) { + GraphicEngine *pGE = getGE(); + + Vertex start; + Vertex end; + Vertex::luaVertexToVertex(L, 1, start); + Vertex::luaVertexToVertex(L, 2, end); + pGE->drawDebugLine(start, end, GraphicEngine::luaColorToARGBColor(L, 3)); + + return 0; +} + +static int getDisplayWidth(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getDisplayWidth()); + + return 1; +} + +static int getDisplayHeight(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getDisplayHeight()); + + return 1; +} + +static int getBitDepth(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getBitDepth()); + + return 1; +} + +static int setVsync(lua_State *L) { + GraphicEngine *pGE = getGE(); + + pGE->setVsync(lua_tobooleancpp(L, 1)); + + return 0; +} + +static int isVsync(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->getVsync()); + + return 1; +} + +static int isWindowed(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->isWindowed()); + + return 1; +} + +static int getFPSCount(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getFPSCount()); + + return 1; +} + +static int getLastFrameDuration(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getLastFrameDuration()); + + return 1; +} + +static int stopMainTimer(lua_State *L) { + GraphicEngine *pGE = getGE(); + pGE->stopMainTimer(); + return 0; +} + +static int resumeMainTimer(lua_State *L) { + GraphicEngine *pGE = getGE(); + pGE->resumeMainTimer(); + return 0; +} + +static int getSecondaryFrameDuration(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getSecondaryFrameDuration()); + + return 1; +} + +static int saveScreenshot(lua_State *L) { + // This is used by system/debug.lua only. We do not implement this; support + // for taking screenshots is a backend feature. + lua_pushbooleancpp(L, false); + + return 1; +} + +static int saveThumbnailScreenshot(lua_State *L) { + GraphicEngine *pGE = getGE(); + lua_pushbooleancpp(L, pGE->saveThumbnailScreenshot(luaL_checkstring(L, 1))); + return 1; +} + +static int getRepaintedPixels(lua_State *L) { + GraphicEngine *pGE = getGE(); + lua_pushnumber(L, static_cast<lua_Number>(pGE->getRepaintedPixels())); + return 1; +} + +static const luaL_reg GFX_FUNCTIONS[] = { + {"Init", init}, + {"StartFrame", startFrame}, + {"EndFrame", endFrame}, + {"DrawDebugLine", drawDebugLine}, + {"SetVsync", setVsync}, + {"GetDisplayWidth", getDisplayWidth}, + {"GetDisplayHeight", getDisplayHeight}, + {"GetBitDepth", getBitDepth}, + {"IsVsync", isVsync}, + {"IsWindowed", isWindowed}, + {"GetFPSCount", getFPSCount}, + {"GetLastFrameDuration", getLastFrameDuration}, + {"StopMainTimer", stopMainTimer}, + {"ResumeMainTimer", resumeMainTimer}, + {"GetSecondaryFrameDuration", getSecondaryFrameDuration}, + {"SaveScreenshot", saveScreenshot}, + {"NewAnimationTemplate", newAnimationTemplate}, + {"GetRepaintedPixels", getRepaintedPixels}, + {"SaveThumbnailScreenshot", saveThumbnailScreenshot}, + {0, 0} +}; + +static RenderObjectPtr<RenderObject> checkRenderObject(lua_State *L, bool errorIfRemoved = true) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable einer Klasse haben, die von Gfx.RenderObject "erbt". + uint *userDataPtr; + if ((userDataPtr = (uint *) my_checkudata(L, 1, BITMAP_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, ANIMATION_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, PANEL_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, TEXT_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr; + else { + if (errorIfRemoved) + luaL_error(L, "The renderobject with the handle %d does no longer exist.", *userDataPtr); + } + } else { + luaL_argcheck(L, 0, 1, "'" RENDEROBJECT_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<RenderObject>(); +} + +static int ro_setPos(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + Vertex pos; + Vertex::luaVertexToVertex(L, 2, pos); + roPtr->setPos(pos.x, pos.y); + return 0; +} + +static int ro_setX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setX(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setY(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setZ(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setZ(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setVisible(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setVisible(lua_tobooleancpp(L, 2)); + return 0; +} + +static int ro_getX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getX()); + + return 1; +} + +static int ro_getY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getY()); + + return 1; +} + +static int ro_getZ(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getZ()); + + return 1; +} + +static int ro_getAbsoluteX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getAbsoluteX()); + + return 1; +} + +static int ro_getAbsoluteY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getAbsoluteY()); + + return 1; +} + +static int ro_getWidth(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getWidth()); + + return 1; +} + +static int ro_getHeight(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getHeight()); + + return 1; +} + +static int ro_isVisible(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushbooleancpp(L, roPtr->isVisible()); + + return 1; +} + +static int ro_addPanel(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + RenderObjectPtr<Panel> panelPtr = roPtr->addPanel(static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), + GraphicEngine::luaColorToARGBColor(L, 4)); + if (panelPtr.isValid()) { + newUintUserData(L, panelPtr->getHandle()); + // luaL_getmetatable(L, PANEL_CLASS_NAME); + LuaBindhelper::getMetatable(L, PANEL_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addBitmap(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + RenderObjectPtr<Bitmap> bitmaPtr = roPtr->addBitmap(luaL_checkstring(L, 2)); + if (bitmaPtr.isValid()) { + newUintUserData(L, bitmaPtr->getHandle()); + // luaL_getmetatable(L, BITMAP_CLASS_NAME); + LuaBindhelper::getMetatable(L, BITMAP_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addText(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + + RenderObjectPtr<Text> textPtr; + if (lua_gettop(L) >= 3) + textPtr = roPtr->addText(luaL_checkstring(L, 2), luaL_checkstring(L, 3)); + else + textPtr = roPtr->addText(luaL_checkstring(L, 2)); + + if (textPtr.isValid()) { + newUintUserData(L, textPtr->getHandle()); + // luaL_getmetatable(L, TEXT_CLASS_NAME); + LuaBindhelper::getMetatable(L, TEXT_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addAnimation(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + + RenderObjectPtr<Animation> animationPtr; + if (lua_type(L, 2) == LUA_TUSERDATA) + animationPtr = roPtr->addAnimation(*checkAnimationTemplate(L, 2)); + else + animationPtr = roPtr->addAnimation(luaL_checkstring(L, 2)); + + if (animationPtr.isValid()) { + newUintUserData(L, animationPtr->getHandle()); + // luaL_getmetatable(L, ANIMATION_CLASS_NAME); + LuaBindhelper::getMetatable(L, ANIMATION_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + + // Alle Animationscallbacks registrieren. + animationPtr->setCallbacks(); + } else + lua_pushnil(L); + + return 1; +} + +void Animation::setCallbacks() { + _actionCallback = animationActionCallback; + _loopPointCallback = animationLoopPointCallback; + _deleteCallback = animationDeleteCallback; +} + +static const luaL_reg RENDEROBJECT_METHODS[] = { + {"AddAnimation", ro_addAnimation}, + {"AddText", ro_addText}, + {"AddBitmap", ro_addBitmap}, + {"AddPanel", ro_addPanel}, + {"SetPos", ro_setPos}, + {"SetX", ro_setX}, + {"SetY", ro_setY}, + {"SetZ", ro_setZ}, + {"SetVisible", ro_setVisible}, + {"GetX", ro_getX}, + {"GetY", ro_getY}, + {"GetZ", ro_getZ}, + {"GetAbsoluteX", ro_getAbsoluteX}, + {"GetAbsoluteY", ro_getAbsoluteY}, + {"GetWidth", ro_getWidth}, + {"GetHeight", ro_getHeight}, + {"IsVisible", ro_isVisible}, + {0, 0} +}; + +static RenderObjectPtr<Panel> checkPanel(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Panel + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, PANEL_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) { + return roPtr->toPanel(); + } else + luaL_error(L, "The panel with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" PANEL_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Panel>(); +} + +static int p_getColor(lua_State *L) { + RenderObjectPtr<Panel> PanelPtr = checkPanel(L); + BS_ASSERT(PanelPtr.isValid()); + GraphicEngine::ARGBColorToLuaColor(L, PanelPtr->getColor()); + + return 1; +} + +static int p_setColor(lua_State *L) { + RenderObjectPtr<Panel> PanelPtr = checkPanel(L); + BS_ASSERT(PanelPtr.isValid()); + PanelPtr->setColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int p_remove(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr.erase(); + return 0; +} + +static const luaL_reg PANEL_METHODS[] = { + {"GetColor", p_getColor}, + {"SetColor", p_setColor}, + {"Remove", p_remove}, + {0, 0} +}; + +static RenderObjectPtr<Bitmap> checkBitmap(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Bitmap + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, BITMAP_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) { + return roPtr->toBitmap(); + } else + luaL_error(L, "The bitmap with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" BITMAP_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Bitmap>(); +} + +static int b_setAlpha(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setAlpha(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setTintColor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setModulationColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int b_setScaleFactor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactor(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setScaleFactorX(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactorX(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setScaleFactorY(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactorY(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setFlipH(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setFlipH(lua_tobooleancpp(L, 2)); + return 0; +} + +static int b_setFlipV(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setFlipV(lua_tobooleancpp(L, 2)); + return 0; +} + +static int b_getAlpha(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getAlpha()); + return 1; +} + +static int b_getTintColor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + GraphicEngine::ARGBColorToLuaColor(L, bitmapPtr->getModulationColor()); + return 1; +} + +static int b_getScaleFactorX(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getScaleFactorX()); + return 1; +} + +static int b_getScaleFactorY(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getScaleFactorY()); + return 1; +} + +static int b_isFlipH(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isFlipH()); + return 1; +} + +static int b_isFlipV(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isFlipV()); + return 1; +} + +static int b_getPixel(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + Vertex Pos; + Vertex::luaVertexToVertex(L, 2, Pos); + GraphicEngine::ARGBColorToLuaColor(L, bitmapPtr->getPixel(Pos.x, Pos.y)); + return 1; +} + +static int b_isScalingAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isScalingAllowed()); + return 1; +} + +static int b_isAlphaAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isAlphaAllowed()); + return 1; +} + +static int b_isTintingAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isColorModulationAllowed()); + return 1; +} + +static int b_remove(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr.erase(); + return 0; +} + +static const luaL_reg BITMAP_METHODS[] = { + {"SetAlpha", b_setAlpha}, + {"SetTintColor", b_setTintColor}, + {"SetScaleFactor", b_setScaleFactor}, + {"SetScaleFactorX", b_setScaleFactorX}, + {"SetScaleFactorY", b_setScaleFactorY}, + {"SetFlipH", b_setFlipH}, + {"SetFlipV", b_setFlipV}, + {"GetAlpha", b_getAlpha}, + {"GetTintColor", b_getTintColor}, + {"GetScaleFactorX", b_getScaleFactorX}, + {"GetScaleFactorY", b_getScaleFactorY}, + {"IsFlipH", b_isFlipH}, + {"IsFlipV", b_isFlipV}, + {"GetPixel", b_getPixel}, + {"IsScalingAllowed", b_isScalingAllowed}, + {"IsAlphaAllowed", b_isAlphaAllowed}, + {"IsTintingAllowed", b_isTintingAllowed}, + {"Remove", b_remove}, + {0, 0} +}; + +static RenderObjectPtr<Animation> checkAnimation(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Animation + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, ANIMATION_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr->toAnimation(); + else { + luaL_error(L, "The animation with the handle %d does no longer exist.", *userDataPtr); + } + } else { + luaL_argcheck(L, 0, 1, "'" ANIMATION_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Animation>(); +} + +static int a_play(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->play(); + return 0; +} + +static int a_pause(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->pause(); + return 0; +} + +static int a_stop(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->stop(); + return 0; +} + +static int a_setFrame(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setFrame(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setAlpha(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setAlpha(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setTintColor(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setModulationColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int a_setScaleFactor(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactor(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setScaleFactorX(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactorX(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setScaleFactorY(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactorY(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_getScaleFactorX(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getScaleFactorX()); + return 1; +} + +static int a_getScaleFactorY(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getScaleFactorY()); + return 1; +} + +static int a_getAnimationType(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + switch (animationPtr->getAnimationType()) { + case Animation::AT_JOJO: + lua_pushstring(L, "jojo"); + break; + case Animation::AT_LOOP: + lua_pushstring(L, "loop"); + break; + case Animation::AT_ONESHOT: + lua_pushstring(L, "oneshot"); + break; + default: + BS_ASSERT(false); + } + return 1; +} + +static int a_getFPS(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getFPS()); + return 1; +} + +static int a_getFrameCount(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getFrameCount()); + return 1; +} + +static int a_isScalingAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isScalingAllowed()); + return 1; +} + +static int a_isAlphaAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isAlphaAllowed()); + return 1; +} + +static int a_isTintingAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isColorModulationAllowed()); + return 1; +} + +static int a_getCurrentFrame(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getCurrentFrame()); + return 1; +} + +static int a_getCurrentAction(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushstring(L, animationPtr->getCurrentAction().c_str()); + return 1; +} + +static int a_isPlaying(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isRunning()); + return 1; +} + +static bool animationLoopPointCallback(uint handle) { + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + loopPointCallbackPtr->invokeCallbackFunctions(L, handle); + + return true; +} + +static int a_registerLoopPointCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + loopPointCallbackPtr->registerCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static int a_unregisterLoopPointCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + loopPointCallbackPtr->unregisterCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static bool animationActionCallback(uint Handle) { + RenderObjectPtr<Animation> animationPtr(Handle); + if (animationPtr.isValid()) { + actionCallbackPtr->Action = animationPtr->getCurrentAction(); + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + actionCallbackPtr->invokeCallbackFunctions(L, animationPtr->getHandle()); + } + + return true; +} + +static int a_registerActionCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + actionCallbackPtr->registerCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static int a_unregisterActionCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + actionCallbackPtr->unregisterCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static bool animationDeleteCallback(uint Handle) { + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + loopPointCallbackPtr->removeAllObjectCallbacks(L, Handle); + + return true; +} + +static int a_remove(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr.erase(); + return 0; +} + +static const luaL_reg ANIMATION_METHODS[] = { + {"Play", a_play}, + {"Pause", a_pause}, + {"Stop", a_stop}, + {"SetFrame", a_setFrame}, + {"SetAlpha", a_setAlpha}, + {"SetTintColor", a_setTintColor}, + {"SetScaleFactor", a_setScaleFactor}, + {"SetScaleFactorX", a_setScaleFactorX}, + {"SetScaleFactorY", a_setScaleFactorY}, + {"GetScaleFactorX", a_getScaleFactorX}, + {"GetScaleFactorY", a_getScaleFactorY}, + {"GetAnimationType", a_getAnimationType}, + {"GetFPS", a_getFPS}, + {"GetFrameCount", a_getFrameCount}, + {"IsScalingAllowed", a_isScalingAllowed}, + {"IsAlphaAllowed", a_isAlphaAllowed}, + {"IsTintingAllowed", a_isTintingAllowed}, + {"GetCurrentFrame", a_getCurrentFrame}, + {"GetCurrentAction", a_getCurrentAction}, + {"IsPlaying", a_isPlaying}, + {"RegisterLoopPointCallback", a_registerLoopPointCallback}, + {"UnregisterLoopPointCallback", a_unregisterLoopPointCallback}, + {"RegisterActionCallback", a_registerActionCallback}, + {"UnregisterActionCallback", a_unregisterActionCallback}, + {"Remove", a_remove}, + {0, 0} +}; + +static RenderObjectPtr<Text> checkText(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Text + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, TEXT_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr->toText(); + else + luaL_error(L, "The text with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" TEXT_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Text>(); +} + +static int t_setFont(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setFont(luaL_checkstring(L, 2)); + return 0; +} + +static int t_setText(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setText(luaL_checkstring(L, 2)); + return 0; +} + +static int t_setAlpha(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAlpha(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int t_setColor(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int t_setAutoWrap(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAutoWrap(lua_tobooleancpp(L, 2)); + return 0; +} + +static int t_setAutoWrapThreshold(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAutoWrapThreshold(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int t_getText(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushstring(L, textPtr->getText().c_str()); + return 1; +} + +static int t_getFont(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushstring(L, textPtr->getFont().c_str()); + return 1; +} + +static int t_getAlpha(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getAlpha()); + return 1; +} + +static int t_getColor(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getColor()); + return 1; +} + +static int t_isAutoWrap(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushbooleancpp(L, textPtr->isAutoWrapActive()); + return 1; +} + +static int t_getAutoWrapThreshold(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getAutoWrapThreshold()); + return 1; +} + +static int t_remove(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr.erase(); + return 0; +} + +static const luaL_reg TEXT_METHODS[] = { + {"SetFont", t_setFont}, + {"SetText", t_setText}, + {"SetAlpha", t_setAlpha}, + {"SetColor", t_setColor}, + {"SetAutoWrap", t_setAutoWrap}, + {"SetAutoWrapThreshold", t_setAutoWrapThreshold}, + {"GetText", t_getText}, + {"GetFont", t_getFont}, + {"GetAlpha", t_getAlpha}, + {"GetColor", t_getColor}, + {"IsAutoWrap", t_isAutoWrap}, + {"GetAutoWrapThreshold", t_getAutoWrapThreshold}, + {"Remove", t_remove}, + {0, 0} +}; + +bool GraphicEngine::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addMethodsToClass(L, BITMAP_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, PANEL_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, TEXT_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + + if (!LuaBindhelper::addMethodsToClass(L, PANEL_CLASS_NAME, PANEL_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, BITMAP_CLASS_NAME, BITMAP_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, TEXT_CLASS_NAME, TEXT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_CLASS_NAME, ANIMATION_METHODS)) return false; + + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_TEMPLATE_CLASS_NAME, ANIMATION_TEMPLATE_METHODS)) return false; + + if (!LuaBindhelper::addFunctionsToLib(L, GFX_LIBRARY_NAME, GFX_FUNCTIONS)) return false; + + assert(loopPointCallbackPtr == 0); + loopPointCallbackPtr = new LuaCallback(L); + + assert(actionCallbackPtr == 0); + actionCallbackPtr = new ActionCallback(L); + + return true; +} + +void GraphicEngine::unregisterScriptBindings() { + delete loopPointCallbackPtr; + loopPointCallbackPtr = 0; + + delete actionCallbackPtr; + actionCallbackPtr = 0; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/art.cpp b/engines/sword25/gfx/image/art.cpp new file mode 100644 index 0000000000..064ca333e7 --- /dev/null +++ b/engines/sword25/gfx/image/art.cpp @@ -0,0 +1,2653 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* Various utility functions RLL finds useful. */ + +#include "sword25/gfx/image/art.h" + +namespace Sword25 { + +/** + * art_die: Print the error message to stderr and exit with a return code of 1. + * @fmt: The printf-style format for the error message. + * + * Used for dealing with severe errors. + **/ +void art_die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(1); +} + +/** + * art_warn: Print the warning message to stderr. + * @fmt: The printf-style format for the warning message. + * + * Used for generating warnings. + **/ +void art_warn(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +/** + * art_svp_free: Free an #ArtSVP structure. + * @svp: #ArtSVP to free. + * + * Frees an #ArtSVP structure and all the segments in it. + **/ +void art_svp_free(ArtSVP *svp) { + int n_segs = svp->n_segs; + int i; + + for (i = 0; i < n_segs; i++) + free(svp->segs[i].points); + free(svp); +} + +#define EPSILON 0 + +/** + * art_svp_seg_compare: Compare two segments of an svp. + * @seg1: First segment to compare. + * @seg2: Second segment to compare. + * + * Compares two segments of an svp. Return 1 if @seg2 is below or to the + * right of @seg1, -1 otherwise. + **/ +int art_svp_seg_compare(const void *s1, const void *s2) { + const ArtSVPSeg *seg1 = (const ArtSVPSeg *)s1; + const ArtSVPSeg *seg2 = (const ArtSVPSeg *)s2; + + if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1; + else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1; + else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1; + else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1; + else if ((seg1->points[1].x - seg1->points[0].x) * + (seg2->points[1].y - seg2->points[0].y) - + (seg1->points[1].y - seg1->points[0].y) * + (seg2->points[1].x - seg2->points[0].x) > 0) return 1; + else return -1; +} + +/** + * art_vpath_add_point: Add point to vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @code: The pathcode for the new point. + * @x: The X coordinate of the new point. + * @y: The Y coordinate of the new point. + * + * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath + * and *@pn_points_max as necessary. *@pn_points is incremented. + * + * This routine always adds the point after all points already in the + * vpath. Thus, it should be called in the order the points are + * desired. + **/ +void art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y) { + int i; + + i = (*pn_points)++; + if (i == *pn_points_max) + art_expand(*p_vpath, ArtVpath, *pn_points_max); + (*p_vpath)[i].code = code; + (*p_vpath)[i].x = x; + (*p_vpath)[i].y = y; +} + +/* Sort vector paths into sorted vector paths */ + +/* reverse a list of points in place */ +static void reverse_points(ArtPoint *points, int n_points) { + int i; + ArtPoint tmp_p; + + for (i = 0; i < (n_points >> 1); i++) { + tmp_p = points[i]; + points[i] = points[n_points - (i + 1)]; + points[n_points - (i + 1)] = tmp_p; + } +} + +/** + * art_svp_from_vpath: Convert a vpath to a sorted vector path. + * @vpath: #ArtVPath to convert. + * + * Converts a vector path into sorted vector path form. The svp form is + * more efficient for rendering and other vector operations. + * + * Basically, the implementation is to traverse the vector path, + * generating a new segment for each "run" of points in the vector + * path with monotonically increasing Y values. All the resulting + * values are then sorted. + * + * Note: I'm not sure that the sorting rule is correct with respect + * to numerical stability issues. + * + * Return value: Resulting sorted vector path. + **/ +ArtSVP *art_svp_from_vpath(ArtVpath *vpath) { + int n_segs, n_segs_max; + ArtSVP *svp; + int dir; + int new_dir; + int i; + ArtPoint *points; + int n_points, n_points_max; + double x, y; + double x_min, x_max; + + n_segs = 0; + n_segs_max = 16; + svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + + dir = 0; + n_points = 0; + n_points_max = 0; + points = NULL; + i = 0; + + x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant, + but it makes gcc -Wall -ansi -pedantic happier */ + x_min = x_max = 0; /* same */ + + while (vpath[i].code != ART_END) { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) { + if (points != NULL && n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + points = NULL; + } + + if (points == NULL) { + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + } + + n_points = 1; + points[0].x = x = vpath[i].x; + points[0].y = y = vpath[i].y; + x_min = x; + x_max = x; + dir = 0; + } else { /* must be LINETO */ + new_dir = (vpath[i].y > y || + (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1; + if (dir && dir != new_dir) { + /* new segment */ + x = points[n_points - 1].x; + y = points[n_points - 1].y; + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + + n_points = 1; + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + points[0].x = x; + points[0].y = y; + x_min = x; + x_max = x; + } + + if (points != NULL) { + if (n_points == n_points_max) + art_expand(points, ArtPoint, n_points_max); + points[n_points].x = x = vpath[i].x; + points[n_points].y = y = vpath[i].y; + if (x < x_min) x_min = x; + else if (x > x_max) x_max = x; + n_points++; + } + dir = new_dir; + } + i++; + } + + if (points != NULL) { + if (n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + } else + free(points); + } + + svp->n_segs = n_segs; + + qsort(&svp->segs, n_segs, sizeof(ArtSVPSeg), art_svp_seg_compare); + + return svp; +} + + +/* Basic constructors and operations for bezier paths */ + +#define RENDER_LEVEL 4 +#define RENDER_SIZE (1 << (RENDER_LEVEL)) + +/** + * art_vpath_render_bez: Render a bezier segment into the vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @x0: X coordinate of starting bezier point. + * @y0: Y coordinate of starting bezier point. + * @x1: X coordinate of first bezier control point. + * @y1: Y coordinate of first bezier control point. + * @x2: X coordinate of second bezier control point. + * @y2: Y coordinate of second bezier control point. + * @x3: X coordinate of ending bezier point. + * @y3: Y coordinate of ending bezier point. + * @flatness: Flatness control. + * + * Renders a bezier segment into the vector path, reallocating and + * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is + * incremented by the number of vector points added. + * + * This step includes (@x0, @y0) but not (@x3, @y3). + * + * The @flatness argument guides the amount of subdivision. The Adobe + * PostScript reference manual defines flatness as the maximum + * deviation between the any point on the vpath approximation and the + * corresponding point on the "true" curve, and we follow this + * definition here. A value of 0.25 should ensure high quality for aa + * rendering. +**/ +static void art_vpath_render_bez(ArtVpath **p_vpath, int *pn, int *pn_max, + double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + double flatness) { + double x3_0, y3_0; + double z3_0_dot; + double z1_dot, z2_dot; + double z1_perp, z2_perp; + double max_perp_sq; + + double x_m, y_m; + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + /* It's possible to optimize this routine a fair amount. + + First, once the _dot conditions are met, they will also be met in + all further subdivisions. So we might recurse to a different + routine that only checks the _perp conditions. + + Second, the distance _should_ decrease according to fairly + predictable rules (a factor of 4 with each subdivision). So it might + be possible to note that the distance is within a factor of 4 of + acceptable, and subdivide once. But proving this might be hard. + + Third, at the last subdivision, x_m and y_m can be computed more + expeditiously (as in the routine above). + + Finally, if we were able to subdivide by, say 2 or 3, this would + allow considerably finer-grain control, i.e. fewer points for the + same flatness tolerance. This would speed things up downstream. + + In any case, this routine is unlikely to be the bottleneck. It's + just that I have this undying quest for more speed... + + */ + + x3_0 = x3 - x0; + y3_0 = y3 - y0; + + /* z3_0_dot is dist z0-z3 squared */ + z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; + + if (z3_0_dot < 0.001) { + /* if start and end point are almost identical, the flatness tests + * don't work properly, so fall back on testing whether both of + * the other two control points are the same as the start point, + * too. + */ + if (hypot(x1 - x0, y1 - y0) < 0.001 + && hypot(x2 - x0, y2 - y0) < 0.001) + goto nosubdivide; + else + goto subdivide; + } + + /* we can avoid subdivision if: + + z1 has distance no more than flatness from the z0-z3 line + + z1 is no more z0'ward than flatness past z0-z3 + + z1 is more z0'ward than z3'ward on the line traversing z0-z3 + + and correspondingly for z2 */ + + /* perp is distance from line, multiplied by dist z0-z3 */ + max_perp_sq = flatness * flatness * z3_0_dot; + + z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0; + if (z1_perp * z1_perp > max_perp_sq) + goto subdivide; + + z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0; + if (z2_perp * z2_perp > max_perp_sq) + goto subdivide; + + z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0; + if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) + goto subdivide; + + z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0; + if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) + goto subdivide; + + if (z1_dot + z1_dot > z3_0_dot) + goto subdivide; + + if (z2_dot + z2_dot > z3_0_dot) + goto subdivide; + + +nosubdivide: + /* don't subdivide */ + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, x3, y3); + return; + +subdivide: + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; + + art_vpath_render_bez(p_vpath, pn, pn_max, + x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness); + art_vpath_render_bez(p_vpath, pn, pn_max, + x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness); +} + +/** + * art_bez_path_to_vec: Create vpath from bezier path. + * @bez: Bezier path. + * @flatness: Flatness control. + * + * Creates a vector path closely approximating the bezier path defined by + * @bez. The @flatness argument controls the amount of subdivision. In + * general, the resulting vpath deviates by at most @flatness pixels + * from the "ideal" path described by @bez. + * + * Return value: Newly allocated vpath. + **/ +ArtVpath *art_bez_path_to_vec(const ArtBpath *bez, double flatness) { + ArtVpath *vec; + int vec_n, vec_n_max; + int bez_index; + double x, y; + + vec_n = 0; + vec_n_max = RENDER_SIZE; + vec = art_new(ArtVpath, vec_n_max); + + /* Initialization is unnecessary because of the precondition that the + bezier path does not begin with LINETO or CURVETO, but is here + to make the code warning-free. */ + x = 0; + y = 0; + + bez_index = 0; + do { + /* make sure space for at least one more code */ + if (vec_n >= vec_n_max) + art_expand(vec, ArtVpath, vec_n_max); + switch (bez[bez_index].code) { + case ART_MOVETO_OPEN: + case ART_MOVETO: + case ART_LINETO: + x = bez[bez_index].x3; + y = bez[bez_index].y3; + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = x; + vec[vec_n].y = y; + vec_n++; + break; + case ART_END: + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = 0; + vec[vec_n].y = 0; + vec_n++; + break; + case ART_CURVETO: + art_vpath_render_bez(&vec, &vec_n, &vec_n_max, + x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3, + flatness); + x = bez[bez_index].x3; + y = bez[bez_index].y3; + break; + } + } while (bez[bez_index++].code != ART_END); + return vec; +} + + +#define EPSILON_6 1e-6 +#define EPSILON_2 1e-12 + +/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1, + yc + y1), centered at (xc, yc), and with given radius. Both x0^2 + + y0^2 and x1^2 + y1^2 should be equal to radius^2. + + A positive value of radius means curve to the left, negative means + curve to the right. +*/ +static void art_svp_vpath_stroke_arc(ArtVpath **p_vpath, int *pn, int *pn_max, + double xc, double yc, + double x0, double y0, + double x1, double y1, + double radius, + double flatness) { + double theta; + double th_0, th_1; + int n_pts; + int i; + double aradius; + + aradius = fabs(radius); + theta = 2 * M_SQRT2 * sqrt(flatness / aradius); + th_0 = atan2(y0, x0); + th_1 = atan2(y1, x1); + if (radius > 0) { + /* curve to the left */ + if (th_0 < th_1) th_0 += M_PI * 2; + n_pts = (int)ceil((th_0 - th_1) / theta); + } else { + /* curve to the right */ + if (th_1 < th_0) th_1 += M_PI * 2; + n_pts = (int)ceil((th_1 - th_0) / theta); + } + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x0, yc + y0); + for (i = 1; i < n_pts; i++) { + theta = th_0 + (th_1 - th_0) * i / n_pts; + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + cos(theta) * aradius, + yc + sin(theta) * aradius); + } + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x1, yc + y1); +} + +/* Assume that forw and rev are at point i0. Bring them to i1, + joining with the vector i1 - i2. + + This used to be true, but isn't now that the stroke_raw code is + filtering out (near)zero length vectors: {It so happens that all + invocations of this function maintain the precondition i1 = i0 + 1, + so we could decrease the number of arguments by one. We haven't + done that here, though.} + + forw is to the line's right and rev is to its left. + + Precondition: no zero-length vectors, otherwise a divide by + zero will happen. */ +static void render_seg(ArtVpath **p_forw, int *pn_forw, int *pn_forw_max, + ArtVpath **p_rev, int *pn_rev, int *pn_rev_max, + ArtVpath *vpath, int i0, int i1, int i2, + ArtPathStrokeJoinType join, + double line_width, double miter_limit, double flatness) { + double dx0, dy0; + double dx1, dy1; + double dlx0, dly0; + double dlx1, dly1; + double dmx, dmy; + double dmr2; + double scale; + double cross; + + /* The vectors of the lines from i0 to i1 and i1 to i2. */ + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + dx1 = vpath[i2].x - vpath[i1].x; + dy1 = vpath[i2].y - vpath[i1].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx1 * dx1 + dy1 * dy1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + + /* now, forw's last point is expected to be colinear along d[xy]0 + to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */ + + /* positive for positive area (i.e. left turn) */ + cross = dx1 * dy0 - dx0 * dy1; + + dmx = (dlx0 + dlx1) * 0.5; + dmy = (dly0 + dly1) * 0.5; + dmr2 = dmx * dmx + dmy * dmy; + + if (join == ART_PATH_STROKE_JOIN_MITER && + dmr2 * miter_limit * miter_limit < line_width * line_width) + join = ART_PATH_STROKE_JOIN_BEVEL; + + /* the case when dmr2 is zero or very small bothers me + (i.e. near a 180 degree angle) + ALEX: So, we avoid the optimization when dmr2 is very small. This should + be safe since dmx/y is only used in optimization and in MITER case, and MITER + should be converted to BEVEL when dmr2 is very small. */ + if (dmr2 > EPSILON_2) { + scale = line_width * line_width / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (cross *cross < EPSILON_2 && dx0 *dx1 + dy0 *dy1 >= 0) { + /* going straight */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + } else if (cross > 0) { + /* left turn, forw is outside and rev is inside */ + + if ( + (dmr2 > EPSILON_2) && + /* check that i1 + dm[xy] is inside i0-i1 rectangle */ + (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 && + /* and that i1 + dm[xy] is inside i1-i2 rectangle */ + ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */ + (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 && + /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_forw, pn_forw, pn_forw_max, + vpath[i1].x, vpath[i1].y, + -dlx0, -dly0, + -dlx1, -dly1, + line_width, + flatness); + } else { + /* right turn, rev is outside and forw is inside */ + + if ( + (dmr2 > EPSILON_2) && + /* check that i1 - dm[xy] is inside i0-i1 rectangle */ + (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 && + /* and that i1 - dm[xy] is inside i1-i2 rectangle */ + ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */ + (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 && + /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_rev, pn_rev, pn_rev_max, + vpath[i1].x, vpath[i1].y, + dlx0, dly0, + dlx1, dly1, + -line_width, + flatness); + + } +} + +/* caps i1, under the assumption of a vector from i0 */ +static void render_cap(ArtVpath **p_result, int *pn_result, int *pn_result_max, + ArtVpath *vpath, int i0, int i1, + ArtPathStrokeCapType cap, double line_width, double flatness) { + double dx0, dy0; + double dlx0, dly0; + double scale; + int n_pts; + int i; + + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + switch (cap) { + case ART_PATH_STROKE_CAP_BUTT: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_ROUND: + n_pts = (int)ceil(M_PI / (2.0 * M_SQRT2 * sqrt(flatness / line_width))); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + for (i = 1; i < n_pts; i++) { + double theta, c_th, s_th; + + theta = M_PI * i / n_pts; + c_th = cos(theta); + s_th = sin(theta); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 * c_th - dly0 * s_th, + vpath[i1].y - dly0 * c_th + dlx0 * s_th); + } + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_SQUARE: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 - dly0, + vpath[i1].y - dly0 + dlx0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x + dlx0 - dly0, + vpath[i1].y + dly0 + dlx0); + break; + } +} + +/** + * art_svp_from_vpath_raw: Stroke a vector path, raw version + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Exactly the same as art_svp_vpath_stroke(), except that the resulting + * stroke outline may self-intersect and have regions of winding number + * greater than 1. + * + * Return value: Resulting raw stroked outline in svp format. + **/ +ArtVpath *art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + int begin_idx, end_idx; + int i; + ArtVpath *forw, *rev; + int n_forw, n_rev; + int n_forw_max, n_rev_max; + ArtVpath *result; + int n_result, n_result_max; + double half_lw = 0.5 * line_width; + int closed; + int last, this_, next, second; + double dx, dy; + + n_forw_max = 16; + forw = art_new(ArtVpath, n_forw_max); + + n_rev_max = 16; + rev = art_new(ArtVpath, n_rev_max); + + n_result = 0; + n_result_max = 16; + result = art_new(ArtVpath, n_result_max); + + for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx) { + n_forw = 0; + n_rev = 0; + + closed = (vpath[begin_idx].code == ART_MOVETO); + + /* we don't know what the first point joins with until we get to the + last point and see if it's closed. So we start with the second + line in the path. + + Note: this is not strictly true (we now know it's closed from + the opening pathcode), but why fix code that isn't broken? + */ + + this_ = begin_idx; + /* skip over identical points at the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + second = next; + + /* invariant: this doesn't coincide with next */ + while (vpath[next].code == ART_LINETO) { + last = this_; + this_ = next; + /* skip over identical points after the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + if (vpath[next].code != ART_LINETO) { + /* reached end of path */ + /* make "closed" detection conform to PostScript + semantics (i.e. explicit closepath code rather than + just the fact that end of the path is the beginning) */ + if (closed && + vpath[this_].x == vpath[begin_idx].x && + vpath[this_].y == vpath[begin_idx].y) { + int j; + + /* path is closed, render join to beginning */ + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, second, + join, half_lw, miter_limit, flatness); + + /* do forward path */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[n_forw - 1].x, + forw[n_forw - 1].y); + for (j = 0; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + + /* do reverse path, reversed */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, rev[0].x, + rev[0].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + } else { + /* path is open */ + int j; + + /* add to forw rather than result to ensure that + forw has at least one point. */ + render_cap(&forw, &n_forw, &n_forw_max, + vpath, last, this_, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[0].x, + forw[0].y); + for (j = 1; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + render_cap(&result, &n_result, &n_result_max, + vpath, second, begin_idx, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[0].x, + forw[0].y); + } + } else + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, next, + join, half_lw, miter_limit, flatness); + } + end_idx = next; + } + + free(forw); + free(rev); + art_vpath_add_point(&result, &n_result, &n_result_max, ART_END, 0, 0); + return result; +} + + +/* Render a vector path into a stroked outline. + + Status of this routine: + + Basic correctness: Only miter and bevel line joins are implemented, + and only butt line caps. Otherwise, seems to be fine. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Should be pretty good. + + Precision: The perturbation fuzzes the coordinates slightly, + but not enough to be visible. */ + +/** + * art_svp_vpath_stroke: Stroke a vector path. + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Computes an svp representing the stroked outline of @vpath. The + * width of the stroked line is @line_width. + * + * Lines are joined according to the @join rule. Possible values are + * ART_PATH_STROKE_JOIN_MITER (for mitered joins), + * ART_PATH_STROKE_JOIN_ROUND (for round joins), and + * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join + * is converted to a bevelled join if the miter would extend to a + * distance of more than @miter_limit * @line_width from the actual + * join point. + * + * If there are open subpaths, the ends of these subpaths are capped + * according to the @cap rule. Possible values are + * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end + * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at + * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap, + * extending half @line_width past the end point). + * + * The @flatness parameter controls the accuracy of the rendering. It + * is most important for determining the number of points to use to + * approximate circular arcs for round lines and joins. In general, the + * resulting vector path will be within @flatness pixels of the "ideal" + * path containing actual circular arcs. I reserve the right to use + * the @flatness parameter to convert bevelled joins to miters for very + * small turn angles, as this would reduce the number of points in the + * resulting outline path. + * + * The resulting path is "clean" with respect to self-intersections, i.e. + * the winding number is 0 or 1 at each point. + * + * Return value: Resulting stroked outline in svp format. + **/ +ArtSVP *art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + ArtVpath *vpath_stroke; + ArtSVP *svp, *svp2; + ArtSvpWriter *swr; + + vpath_stroke = art_svp_vpath_stroke_raw(vpath, join, cap, + line_width, miter_limit, flatness); + svp = art_svp_from_vpath(vpath_stroke); + free(vpath_stroke); + + swr = art_svp_writer_rewind_new(ART_WIND_RULE_NONZERO); + art_svp_intersector(svp, swr); + + svp2 = art_svp_writer_rewind_reap(swr); + art_svp_free(svp); + return svp2; +} + + +/* Testbed implementation of the new intersection code. +*/ + +typedef struct _ArtPriQ ArtPriQ; +typedef struct _ArtPriPoint ArtPriPoint; + +struct _ArtPriQ { + int n_items; + int n_items_max; + ArtPriPoint **items; +}; + +struct _ArtPriPoint { + double x; + double y; + void *user_data; +}; + +static ArtPriQ *art_pri_new(void) { + ArtPriQ *result = art_new(ArtPriQ, 1); + + result->n_items = 0; + result->n_items_max = 16; + result->items = art_new(ArtPriPoint *, result->n_items_max); + return result; +} + +static void art_pri_free(ArtPriQ *pq) { + free(pq->items); + free(pq); +} + +static art_boolean art_pri_empty(ArtPriQ *pq) { + return pq->n_items == 0; +} + +/* This heap implementation is based on Vasek Chvatal's course notes: + http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */ + +static void art_pri_bubble_up(ArtPriQ *pq, int vacant, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int parent; + + parent = (vacant - 1) >> 1; + while (vacant > 0 && (missing->y < items[parent]->y || + (missing->y == items[parent]->y && + missing->x < items[parent]->x))) { + items[vacant] = items[parent]; + vacant = parent; + parent = (vacant - 1) >> 1; + } + + items[vacant] = missing; +} + +static void art_pri_insert(ArtPriQ *pq, ArtPriPoint *point) { + if (pq->n_items == pq->n_items_max) + art_expand(pq->items, ArtPriPoint *, pq->n_items_max); + + art_pri_bubble_up(pq, pq->n_items++, point); +} + +static void art_pri_sift_down_from_root(ArtPriQ *pq, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int vacant = 0, child = 2; + int n = pq->n_items; + + while (child < n) { + if (items[child - 1]->y < items[child]->y || + (items[child - 1]->y == items[child]->y && + items[child - 1]->x < items[child]->x)) + child--; + items[vacant] = items[child]; + vacant = child; + child = (vacant + 1) << 1; + } + if (child == n) { + items[vacant] = items[n - 1]; + vacant = n - 1; + } + + art_pri_bubble_up(pq, vacant, missing); +} + +static ArtPriPoint *art_pri_choose(ArtPriQ *pq) { + ArtPriPoint *result = pq->items[0]; + + art_pri_sift_down_from_root(pq, pq->items[--pq->n_items]); + return result; +} + +/* A virtual class for an "svp writer". A client of this object creates an + SVP by repeatedly calling "add segment" and "add point" methods on it. +*/ + +typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind; + +/* An implementation of the svp writer virtual class that applies the + winding rule. */ + +struct _ArtSvpWriterRewind { + ArtSvpWriter super; + ArtWindRule rule; + ArtSVP *svp; + int n_segs_max; + int *n_points_max; +}; + +static int art_svp_writer_rewind_add_segment(ArtSvpWriter *self, int wind_left, + int delta_wind, double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *svp; + ArtSVPSeg *seg; + art_boolean left_filled = 0, right_filled = 0; + int wind_right = wind_left + delta_wind; + int seg_num; + const int init_n_points_max = 4; + + switch (swr->rule) { + case ART_WIND_RULE_NONZERO: + left_filled = (wind_left != 0); + right_filled = (wind_right != 0); + break; + case ART_WIND_RULE_INTERSECT: + left_filled = (wind_left > 1); + right_filled = (wind_right > 1); + break; + case ART_WIND_RULE_ODDEVEN: + left_filled = (wind_left & 1); + right_filled = (wind_right & 1); + break; + case ART_WIND_RULE_POSITIVE: + left_filled = (wind_left > 0); + right_filled = (wind_right > 0); + break; + default: + art_die("Unknown wind rule %d\n", swr->rule); + } + if (left_filled == right_filled) { + /* discard segment now */ + return -1; + } + + svp = swr->svp; + seg_num = svp->n_segs++; + if (swr->n_segs_max == seg_num) { + swr->n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (swr->n_segs_max - 1) * + sizeof(ArtSVPSeg)); + swr->svp = svp; + swr->n_points_max = art_renew(swr->n_points_max, int, + swr->n_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = 1; + seg->dir = right_filled; + swr->n_points_max[seg_num] = init_n_points_max; + seg->bbox.x0 = x; + seg->bbox.y0 = y; + seg->bbox.x1 = x; + seg->bbox.y1 = y; + seg->points = art_new(ArtPoint, init_n_points_max); + seg->points[0].x = x; + seg->points[0].y = y; + return seg_num; +} + +static void art_svp_writer_rewind_add_point(ArtSvpWriter *self, int seg_id, + double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + int n_points; + + if (seg_id < 0) + /* omitted segment */ + return; + + seg = &swr->svp->segs[seg_id]; + n_points = seg->n_points++; + if (swr->n_points_max[seg_id] == n_points) + art_expand(seg->points, ArtPoint, swr->n_points_max[seg_id]); + seg->points[n_points].x = x; + seg->points[n_points].y = y; + if (x < seg->bbox.x0) + seg->bbox.x0 = x; + if (x > seg->bbox.x1) + seg->bbox.x1 = x; + seg->bbox.y1 = y; +} + +static void art_svp_writer_rewind_close_segment(ArtSvpWriter *self, int seg_id) { + /* Not needed for this simple implementation. A potential future + optimization is to merge segments that can be merged safely. */ +} + +ArtSVP *art_svp_writer_rewind_reap(ArtSvpWriter *self) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *result = swr->svp; + + free(swr->n_points_max); + free(swr); + return result; +} + +ArtSvpWriter *art_svp_writer_rewind_new(ArtWindRule rule) { + ArtSvpWriterRewind *result = art_new(ArtSvpWriterRewind, 1); + + result->super.add_segment = art_svp_writer_rewind_add_segment; + result->super.add_point = art_svp_writer_rewind_add_point; + result->super.close_segment = art_svp_writer_rewind_close_segment; + + result->rule = rule; + result->n_segs_max = 16; + result->svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (result->n_segs_max - 1) * sizeof(ArtSVPSeg)); + result->svp->n_segs = 0; + result->n_points_max = art_new(int, result->n_segs_max); + + return &result->super; +} + +/* Now, data structures for the active list */ + +typedef struct _ArtActiveSeg ArtActiveSeg; + +/* Note: BNEG is 1 for \ lines, and 0 for /. Thus, + x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */ +#define ART_ACTIVE_FLAGS_BNEG 1 + +/* This flag is set if the segment has been inserted into the active + list. */ +#define ART_ACTIVE_FLAGS_IN_ACTIVE 2 + +/* This flag is set when the segment is to be deleted in the + horiz commit process. */ +#define ART_ACTIVE_FLAGS_DEL 4 + +/* This flag is set if the seg_id is a valid output segment. */ +#define ART_ACTIVE_FLAGS_OUT 8 + +/* This flag is set if the segment is in the horiz list. */ +#define ART_ACTIVE_FLAGS_IN_HORIZ 16 + +struct _ArtActiveSeg { + int flags; + int wind_left, delta_wind; + ArtActiveSeg *left, *right; /* doubly linked list structure */ + + const ArtSVPSeg *in_seg; + int in_curs; + + double x[2]; + double y0, y1; + double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1, + and a>0 */ + + /* bottom point and intersection point stack */ + int n_stack; + int n_stack_max; + ArtPoint *stack; + + /* horiz commit list */ + ArtActiveSeg *horiz_left, *horiz_right; + double horiz_x; + int horiz_delta_wind; + int seg_id; +}; + +typedef struct _ArtIntersectCtx ArtIntersectCtx; + +struct _ArtIntersectCtx { + const ArtSVP *in; + ArtSvpWriter *out; + + ArtPriQ *pq; + + ArtActiveSeg *active_head; + + double y; + ArtActiveSeg *horiz_first; + ArtActiveSeg *horiz_last; + + /* segment index of next input segment to be added to pri q */ + int in_curs; +}; + +#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */ + +/** + * art_svp_intersect_setup_seg: Set up an active segment from input segment. + * @seg: Active segment. + * @pri_pt: Priority queue point to initialize. + * + * Sets the x[], a, b, c, flags, and stack fields according to the + * line from the current cursor value. Sets the priority queue point + * to the bottom point of this line. Also advances the input segment + * cursor. + **/ +static void art_svp_intersect_setup_seg(ArtActiveSeg *seg, ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs++; + double x0, y0, x1, y1; + double dx, dy, s; + double a, b, r2; + + x0 = in_seg->points[in_curs].x; + y0 = in_seg->points[in_curs].y; + x1 = in_seg->points[in_curs + 1].x; + y1 = in_seg->points[in_curs + 1].y; + pri_pt->x = x1; + pri_pt->y = y1; + dx = x1 - x0; + dy = y1 - y0; + r2 = dx * dx + dy * dy; + s = r2 == 0 ? 1 : 1 / sqrt(r2); + seg->a = a = dy * s; + seg->b = b = -dx * s; + seg->c = -(a * x0 + b * y0); + seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0); + seg->x[0] = x0; + seg->x[1] = x1; + seg->y0 = y0; + seg->y1 = y1; + seg->n_stack = 1; + seg->stack[0].x = x1; + seg->stack[0].y = y1; +} + +/** + * art_svp_intersect_add_horiz: Add point to horizontal list. + * @ctx: Intersector context. + * @seg: Segment with point to insert into horizontal list. + * + * Inserts @seg into horizontal list, keeping it in ascending horiz_x + * order. + * + * Note: the horiz_commit routine processes "clusters" of segs in the + * horiz list, all sharing the same horiz_x value. The cluster is + * processed in active list order, rather than horiz list order. Thus, + * the order of segs in the horiz list sharing the same horiz_x + * _should_ be irrelevant. Even so, we use b as a secondary sorting key, + * as a "belt and suspenders" defensive coding tactic. + **/ +static void art_svp_intersect_add_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg **pp = &ctx->horiz_last; + ArtActiveSeg *place; + ArtActiveSeg *place_right = NULL; + + if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ) { + art_warn("*** attempt to put segment in horiz list twice\n"); + return; + } + seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ; + + for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x || + (place->horiz_x == seg->horiz_x && + place->b < seg->b)); + place = *pp) { + place_right = place; + pp = &place->horiz_left; + } + *pp = seg; + seg->horiz_left = place; + seg->horiz_right = place_right; + if (place == NULL) + ctx->horiz_first = seg; + else + place->horiz_right = seg; +} + +static void art_svp_intersect_push_pt(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x, double y) { + ArtPriPoint *pri_pt; + int n_stack = seg->n_stack; + + if (n_stack == seg->n_stack_max) + art_expand(seg->stack, ArtPoint, seg->n_stack_max); + seg->stack[n_stack].x = x; + seg->stack[n_stack].y = y; + seg->n_stack++; + + seg->x[1] = x; + seg->y1 = y; + + pri_pt = art_new(ArtPriPoint, 1); + pri_pt->x = x; + pri_pt->y = y; + pri_pt->user_data = seg; + art_pri_insert(ctx->pq, pri_pt); +} + +typedef enum { + ART_BREAK_LEFT = 1, + ART_BREAK_RIGHT = 2 +} ArtBreakFlags; + +/** + * art_svp_intersect_break: Break an active segment. + * + * Note: y must be greater than the top point's y, and less than + * the bottom's. + * + * Return value: x coordinate of break point. + */ +static double art_svp_intersect_break(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x_ref, double y, ArtBreakFlags break_flags) { + double x0, y0, x1, y1; + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + double x; + + x0 = in_seg->points[in_curs - 1].x; + y0 = in_seg->points[in_curs - 1].y; + x1 = in_seg->points[in_curs].x; + y1 = in_seg->points[in_curs].y; + x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0)); + if ((break_flags == ART_BREAK_LEFT && x > x_ref) || + (break_flags == ART_BREAK_RIGHT && x < x_ref)) { + } + + /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane + arithmetic, but it might be worthwhile to check just in case. */ + + if (y > ctx->y) + art_svp_intersect_push_pt(ctx, seg, x, y); + else { + seg->x[0] = x; + seg->y0 = y; + seg->horiz_x = x; + art_svp_intersect_add_horiz(ctx, seg); + } + + return x; +} + +/** + * art_svp_intersect_add_point: Add a point, breaking nearby neighbors. + * @ctx: Intersector context. + * @x: X coordinate of point to add. + * @y: Y coordinate of point to add. + * @seg: "nearby" segment, or NULL if leftmost. + * + * Return value: Segment immediately to the left of the new point, or + * NULL if the new point is leftmost. + **/ +static ArtActiveSeg *art_svp_intersect_add_point(ArtIntersectCtx *ctx, double x, double y, + ArtActiveSeg *seg, ArtBreakFlags break_flags) { + ArtActiveSeg *left, *right; + double x_min = x, x_max = x; + art_boolean left_live, right_live; + double d; + double new_x; + ArtActiveSeg *test, *result = NULL; + double x_test; + + left = seg; + if (left == NULL) + right = ctx->active_head; + else + right = left->right; + left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL); + right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL); + while (left_live || right_live) { + if (left_live) { + if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != left->y0 && y < left->y1) { + d = x_min * left->a + y * left->b + left->c; + if (d < EPSILON_A) { + new_x = art_svp_intersect_break(ctx, left, x_min, y, + ART_BREAK_LEFT); + if (new_x > x_max) { + x_max = new_x; + right_live = (right != NULL); + } else if (new_x < x_min) + x_min = new_x; + left = left->left; + left_live = (left != NULL); + } else + left_live = ART_FALSE; + } else + left_live = ART_FALSE; + } else if (right_live) { + if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != right->y0 && y < right->y1) { + d = x_max * right->a + y * right->b + right->c; + if (d > -EPSILON_A) { + new_x = art_svp_intersect_break(ctx, right, x_max, y, + ART_BREAK_RIGHT); + if (new_x < x_min) { + x_min = new_x; + left_live = (left != NULL); + } else if (new_x >= x_max) + x_max = new_x; + right = right->right; + right_live = (right != NULL); + } else + right_live = ART_FALSE; + } else + right_live = ART_FALSE; + } + } + + /* Ascending order is guaranteed by break_flags. Thus, we don't need + to actually fix up non-ascending pairs. */ + + /* Now, (left, right) defines an interval of segments broken. Sort + into ascending x order. */ + test = left == NULL ? ctx->active_head : left->right; + result = left; + if (test != NULL && test != right) { + if (y == test->y0) + x_test = test->x[0]; + else /* assert y == test->y1, I think */ + x_test = test->x[1]; + for (;;) { + if (x_test <= x) + result = test; + test = test->right; + if (test == right) + break; + new_x = x_test; + if (new_x < x_test) { + art_warn("art_svp_intersect_add_point: non-ascending x\n"); + } + x_test = new_x; + } + } + return result; +} + +static void art_svp_intersect_swap_active(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg) { + right_seg->left = left_seg->left; + if (right_seg->left != NULL) + right_seg->left->right = right_seg; + else + ctx->active_head = right_seg; + left_seg->right = right_seg->right; + if (left_seg->right != NULL) + left_seg->right->left = left_seg; + left_seg->left = right_seg; + right_seg->right = left_seg; +} + +/** + * art_svp_intersect_test_cross: Test crossing of a pair of active segments. + * @ctx: Intersector context. + * @left_seg: Left segment of the pair. + * @right_seg: Right segment of the pair. + * @break_flags: Flags indicating whether to break neighbors. + * + * Tests crossing of @left_seg and @right_seg. If there is a crossing, + * inserts the intersection point into both segments. + * + * Return value: True if the intersection took place at the current + * scan line, indicating further iteration is needed. + **/ +static art_boolean art_svp_intersect_test_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg, + ArtBreakFlags break_flags) { + double left_x0, left_y0, left_x1; + double left_y1 = left_seg->y1; + double right_y1 = right_seg->y1; + double d; + + const ArtSVPSeg *in_seg; + int in_curs; + double d0, d1, t; + double x, y; /* intersection point */ + + if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0]) { + /* Top points of left and right segments coincide. This case + feels like a bit of duplication - we may want to merge it + with the cases below. However, this way, we're sure that this + logic makes only localized changes. */ + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + /* I'm unsure about the break flags here. */ + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + /* See above regarding break flags. */ + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + + /* The segments cross. Find the intersection point. */ + + in_seg = left_seg->in_seg; + in_curs = left_seg->in_curs; + left_x0 = in_seg->points[in_curs - 1].x; + left_y0 = in_seg->points[in_curs - 1].y; + left_x1 = in_seg->points[in_curs].x; + left_y1 = in_seg->points[in_curs].y; + d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c; + d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d0 == d1) { + x = left_x0; + y = left_y0; + } else { + /* Is this division always safe? It could possibly overflow. */ + t = d0 / (d0 - d1); + if (t <= 0) { + x = left_x0; + y = left_y0; + } else if (t >= 1) { + x = left_x1; + y = left_y1; + } else { + x = left_x0 + t * (left_x1 - left_x0); + y = left_y0 + t * (left_y1 - left_y0); + } + } + + /* Make sure intersection point is within bounds of right seg. */ + if (y < right_seg->y0) { + x = right_seg->x[0]; + y = right_seg->y0; + } else if (y > right_seg->y1) { + x = right_seg->x[1]; + y = right_seg->y1; + } else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) + x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]; + else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]) + x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]; + + if (y == left_seg->y0) { + if (y != right_seg->y0) { + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, + break_flags); + } else { + /* Intersection takes place at current scan line; process + immediately rather than queueing intersection point into + priq. */ + ArtActiveSeg *winner, *loser; + + /* Choose "most vertical" segement */ + if (left_seg->a > right_seg->a) { + winner = left_seg; + loser = right_seg; + } else { + winner = right_seg; + loser = left_seg; + } + + loser->x[0] = winner->x[0]; + loser->horiz_x = loser->x[0]; + loser->horiz_delta_wind += loser->delta_wind; + winner->horiz_delta_wind -= loser->delta_wind; + + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + } else if (y == right_seg->y0) { + art_svp_intersect_push_pt(ctx, left_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, + break_flags); + } else { + /* Insert the intersection point into both segments. */ + art_svp_intersect_push_pt(ctx, left_seg, x, y); + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, break_flags); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, break_flags); + } + return ART_FALSE; +} + +/** + * art_svp_intersect_active_delete: Delete segment from active list. + * @ctx: Intersection context. + * @seg: Segment to delete. + * + * Deletes @seg from the active list. + **/ +static void art_svp_intersect_active_delete(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg *left = seg->left, *right = seg->right; + + if (left != NULL) + left->right = right; + else + ctx->active_head = right; + if (right != NULL) + right->left = left; +} + +/** + * art_svp_intersect_active_free: Free an active segment. + * @seg: Segment to delete. + * + * Frees @seg. + **/ +static void art_svp_intersect_active_free(ArtActiveSeg *seg) { + free(seg->stack); + free(seg); +} + +/** + * art_svp_intersect_insert_cross: Test crossings of newly inserted line. + * + * Tests @seg against its left and right neighbors for intersections. + * Precondition: the line in @seg is not purely horizontal. + **/ +static void art_svp_intersect_insert_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + ArtActiveSeg *left = seg, *right = seg; + + for (;;) { + if (left != NULL) { + ArtActiveSeg *leftc; + + for (leftc = left->left; leftc != NULL; leftc = leftc->left) + if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (leftc != NULL && + art_svp_intersect_test_cross(ctx, leftc, left, + ART_BREAK_LEFT)) { + if (left == right || right == NULL) + right = left->right; + } else { + left = NULL; + } + } else if (right != NULL && right->right != NULL) { + ArtActiveSeg *rightc; + + for (rightc = right->right; rightc != NULL; rightc = rightc->right) + if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (rightc != NULL && + art_svp_intersect_test_cross(ctx, right, rightc, + ART_BREAK_RIGHT)) { + if (left == right || left == NULL) + left = right->left; + } else { + right = NULL; + } + } else + break; + } +} + +/** + * art_svp_intersect_horiz: Add horizontal line segment. + * @ctx: Intersector context. + * @seg: Segment on which to add horizontal line. + * @x0: Old x position. + * @x1: New x position. + * + * Adds a horizontal line from @x0 to @x1, and updates the current + * location of @seg to @x1. + **/ +static void art_svp_intersect_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x0, double x1) { + ArtActiveSeg *hs; + + if (x0 == x1) + return; + + hs = art_new(ArtActiveSeg, 1); + + hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT); + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + + swr->add_point(swr, seg->seg_id, x0, ctx->y); + } + hs->seg_id = seg->seg_id; + hs->horiz_x = x0; + hs->horiz_delta_wind = seg->delta_wind; + hs->stack = NULL; + + /* Ideally, the (a, b, c) values will never be read. However, there + are probably some tests remaining that don't check for _DEL + before evaluating the line equation. For those, these + initializations will at least prevent a UMR of the values, which + can crash on some platforms. */ + hs->a = 0.0; + hs->b = 0.0; + hs->c = 0.0; + + seg->horiz_delta_wind -= seg->delta_wind; + + art_svp_intersect_add_horiz(ctx, hs); + + if (x0 > x1) { + ArtActiveSeg *left; + art_boolean first = ART_TRUE; + + for (left = seg->left; left != NULL; left = seg->left) { + int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG; + + if (left->x[left_bneg] <= x1) + break; + if (left->x[left_bneg ^ 1] <= x1 && + x1 *left->a + ctx->y *left->b + left->c >= 0) + break; + if (left->y0 != ctx->y && left->y1 != ctx->y) { + art_svp_intersect_break(ctx, left, x1, ctx->y, ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, left, seg); + if (first && left->right != NULL) { + art_svp_intersect_test_cross(ctx, left, left->right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } else { + ArtActiveSeg *right; + art_boolean first = ART_TRUE; + + for (right = seg->right; right != NULL; right = seg->right) { + int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG; + + if (right->x[right_bneg ^ 1] >= x1) + break; + if (right->x[right_bneg] >= x1 && + x1 *right->a + ctx->y *right->b + right->c <= 0) + break; + if (right->y0 != ctx->y && right->y1 != ctx->y) { + art_svp_intersect_break(ctx, right, x1, ctx->y, + ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, seg, right); + if (first && right->left != NULL) { + art_svp_intersect_test_cross(ctx, right->left, right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + + seg->x[0] = x1; + seg->x[1] = x1; + seg->horiz_x = x1; + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +} + +/** + * art_svp_intersect_insert_line: Insert a line into the active list. + * @ctx: Intersector context. + * @seg: Segment containing line to insert. + * + * Inserts the line into the intersector context, taking care of any + * intersections, and adding the appropriate horizontal points to the + * active list. + **/ +static void art_svp_intersect_insert_line(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + if (seg->y1 == seg->y0) { + art_svp_intersect_horiz(ctx, seg, seg->x[0], seg->x[1]); + } else { + art_svp_intersect_insert_cross(ctx, seg); + art_svp_intersect_add_horiz(ctx, seg); + } +} + +static void art_svp_intersect_process_intersection(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + int n_stack = --seg->n_stack; + seg->x[1] = seg->stack[n_stack - 1].x; + seg->y1 = seg->stack[n_stack - 1].y; + seg->x[0] = seg->stack[n_stack].x; + seg->y0 = seg->stack[n_stack].y; + seg->horiz_x = seg->x[0]; + art_svp_intersect_insert_line(ctx, seg); +} + +static void art_svp_intersect_advance_cursor(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL; + + if (swr != NULL) + swr->add_point(swr, seg->seg_id, seg->x[1], seg->y1); + if (in_curs + 1 == in_seg->n_points) { + ArtActiveSeg *left = seg->left, *right = seg->right; + + seg->flags |= ART_ACTIVE_FLAGS_DEL; + art_svp_intersect_add_horiz(ctx, seg); + art_svp_intersect_active_delete(ctx, seg); + if (left != NULL && right != NULL) + art_svp_intersect_test_cross(ctx, left, right, + (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + free(pri_pt); + } else { + seg->horiz_x = seg->x[1]; + + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + art_svp_intersect_insert_line(ctx, seg); + } +} + +static void art_svp_intersect_add_seg(ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg) { + ArtActiveSeg *seg = art_new(ArtActiveSeg, 1); + ArtActiveSeg *test; + double x0, y0; + ArtActiveSeg *beg_range; + ArtActiveSeg *last = NULL; + ArtActiveSeg *left, *right; + ArtPriPoint *pri_pt = art_new(ArtPriPoint, 1); + + seg->flags = 0; + seg->in_seg = in_seg; + seg->in_curs = 0; + + seg->n_stack_max = 4; + seg->stack = art_new(ArtPoint, seg->n_stack_max); + + seg->horiz_delta_wind = 0; + + seg->wind_left = 0; + + pri_pt->user_data = seg; + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + + /* Find insertion place for new segment */ + /* This is currently a left-to-right scan, but should be replaced + with a binary search as soon as it's validated. */ + + x0 = in_seg->points[0].x; + y0 = in_seg->points[0].y; + beg_range = NULL; + for (test = ctx->active_head; test != NULL; test = test->right) { + double d; + int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG; + + if (x0 < test->x[test_bneg]) { + if (x0 < test->x[test_bneg ^ 1]) + break; + d = x0 * test->a + y0 * test->b + test->c; + if (d < 0) + break; + } + last = test; + } + + left = art_svp_intersect_add_point(ctx, x0, y0, last, (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + seg->left = left; + if (left == NULL) { + right = ctx->active_head; + ctx->active_head = seg; + } else { + right = left->right; + left->right = seg; + } + seg->right = right; + if (right != NULL) + right->left = seg; + + seg->delta_wind = in_seg->dir ? 1 : -1; + seg->horiz_x = x0; + + art_svp_intersect_insert_line(ctx, seg); +} + +/** + * art_svp_intersect_horiz_commit: Commit points in horiz list to output. + * @ctx: Intersection context. + * + * The main function of the horizontal commit is to output new + * points to the output writer. + * + * This "commit" pass is also where winding numbers are assigned, + * because doing it here provides much greater tolerance for inputs + * which are not in strict SVP order. + * + * Each cluster in the horiz_list contains both segments that are in + * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not, + * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We + * need to deal with both. + **/ +static void art_svp_intersect_horiz_commit(ArtIntersectCtx *ctx) { + ArtActiveSeg *seg; + int winding_number = 0; /* initialization just to avoid warning */ + int horiz_wind = 0; + double last_x = 0; /* initialization just to avoid warning */ + + /* Output points to svp writer. */ + for (seg = ctx->horiz_first; seg != NULL;) { + /* Find a cluster with common horiz_x, */ + ArtActiveSeg *curs; + double x = seg->horiz_x; + + /* Generate any horizontal segments. */ + if (horiz_wind != 0) { + ArtSvpWriter *swr = ctx->out; + int seg_id; + + seg_id = swr->add_segment(swr, winding_number, horiz_wind, + last_x, ctx->y); + swr->add_point(swr, seg_id, x, ctx->y); + swr->close_segment(swr, seg_id); + } + + /* Find first active segment in cluster. */ + + for (curs = seg; curs != NULL && curs->horiz_x == x; + curs = curs->horiz_right) + if (!(curs->flags & ART_ACTIVE_FLAGS_DEL)) + break; + + if (curs != NULL && curs->horiz_x == x) { + /* There exists at least one active segment in this cluster. */ + + /* Find beginning of cluster. */ + for (; curs->left != NULL; curs = curs->left) + if (curs->left->horiz_x != x) + break; + + if (curs->left != NULL) + winding_number = curs->left->wind_left + curs->left->delta_wind; + else + winding_number = 0; + + do { + if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) || + curs->wind_left != winding_number) { + ArtSvpWriter *swr = ctx->out; + + if (curs->flags & ART_ACTIVE_FLAGS_OUT) { + swr->add_point(swr, curs->seg_id, + curs->horiz_x, ctx->y); + swr->close_segment(swr, curs->seg_id); + } + + curs->seg_id = swr->add_segment(swr, winding_number, + curs->delta_wind, + x, ctx->y); + curs->flags |= ART_ACTIVE_FLAGS_OUT; + } + curs->wind_left = winding_number; + winding_number += curs->delta_wind; + curs = curs->right; + } while (curs != NULL && curs->horiz_x == x); + } + + /* Skip past cluster. */ + do { + ArtActiveSeg *next = seg->horiz_right; + + seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ; + horiz_wind += seg->horiz_delta_wind; + seg->horiz_delta_wind = 0; + if (seg->flags & ART_ACTIVE_FLAGS_DEL) { + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + swr->close_segment(swr, seg->seg_id); + } + art_svp_intersect_active_free(seg); + } + seg = next; + } while (seg != NULL && seg->horiz_x == x); + + last_x = x; + } + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; +} + +void art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out) { + ArtIntersectCtx *ctx; + ArtPriQ *pq; + ArtPriPoint *first_point; + + if (in->n_segs == 0) + return; + + ctx = art_new(ArtIntersectCtx, 1); + ctx->in = in; + ctx->out = out; + pq = art_pri_new(); + ctx->pq = pq; + + ctx->active_head = NULL; + + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; + + ctx->in_curs = 0; + first_point = art_new(ArtPriPoint, 1); + first_point->x = in->segs[0].points[0].x; + first_point->y = in->segs[0].points[0].y; + first_point->user_data = NULL; + ctx->y = first_point->y; + art_pri_insert(pq, first_point); + + while (!art_pri_empty(pq)) { + ArtPriPoint *pri_point = art_pri_choose(pq); + ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data; + + if (ctx->y != pri_point->y) { + art_svp_intersect_horiz_commit(ctx); + ctx->y = pri_point->y; + } + + if (seg == NULL) { + /* Insert new segment from input */ + const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++]; + art_svp_intersect_add_seg(ctx, in_seg); + if (ctx->in_curs < in->n_segs) { + const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs]; + pri_point->x = next_seg->points[0].x; + pri_point->y = next_seg->points[0].y; + /* user_data is already NULL */ + art_pri_insert(pq, pri_point); + } else + free(pri_point); + } else { + int n_stack = seg->n_stack; + + if (n_stack > 1) { + art_svp_intersect_process_intersection(ctx, seg); + free(pri_point); + } else { + art_svp_intersect_advance_cursor(ctx, seg, pri_point); + } + } + } + + art_svp_intersect_horiz_commit(ctx); + + art_pri_free(pq); + free(ctx); +} + + +/* The spiffy antialiased renderer for sorted vector paths. */ + +typedef double artfloat; + +struct _ArtSVPRenderAAIter { + const ArtSVP *svp; + int x0, x1; + int y; + int seg_ix; + + int *active_segs; + int n_active_segs; + int *cursor; + artfloat *seg_x; + artfloat *seg_dx; + + ArtSVPRenderAAStep *steps; +}; + +static void art_svp_render_insert_active(int i, int *active_segs, int n_active_segs, + artfloat *seg_x, artfloat *seg_dx) { + int j; + artfloat x; + int tmp1, tmp2; + + /* this is a cheap hack to get ^'s sorted correctly */ + x = seg_x[i] + 0.001 * seg_dx[i]; + for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++) + ; + + tmp1 = i; + while (j < n_active_segs) { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + j++; + } + active_segs[j] = tmp1; +} + +static void art_svp_render_delete_active(int *active_segs, int j, int n_active_segs) { + int k; + + for (k = j; k < n_active_segs; k++) + active_segs[k] = active_segs[k + 1]; +} + +/* Render the sorted vector path in the given rectangle, antialiased. + + This interface uses a callback for the actual pixel rendering. The + callback is called y1 - y0 times (once for each scan line). The y + coordinate is given as an argument for convenience (it could be + stored in the callback's private data and incremented on each + call). + + The rendered polygon is represented in a semi-runlength format: a + start value and a sequence of "steps". Each step has an x + coordinate and a value delta. The resulting value at position x is + equal to the sum of the start value and all step delta values for + which the step x coordinate is less than or equal to x. An + efficient algorithm will traverse the steps left to right, keeping + a running sum. + + All x coordinates in the steps are guaranteed to be x0 <= x < x1. + (This guarantee is a change from the gfonted vpaar renderer, and is + designed to simplify the callback). + + There is now a further guarantee that no two steps will have the + same x value. This may allow for further speedup and simplification + of renderers. + + The value 0x8000 represents 0% coverage by the polygon, while + 0xff8000 represents 100% coverage. This format is designed so that + >> 16 results in a standard 0x00..0xff value range, with nice + rounding. + + Status of this routine: + + Basic correctness: OK + + Numerical stability: pretty good, although probably not + bulletproof. + + Speed: Needs more aggressive culling of bounding boxes. Can + probably speed up the [x0,x1) clipping of step values. Can do more + of the step calculation in fixed point. + + Precision: No known problems, although it should be tested + thoroughly, especially for symmetry. + +*/ + +ArtSVPRenderAAIter *art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1) { + ArtSVPRenderAAIter *iter = art_new(ArtSVPRenderAAIter, 1); + + iter->svp = svp; + iter->y = y0; + iter->x0 = x0; + iter->x1 = x1; + iter->seg_ix = 0; + + iter->active_segs = art_new(int, svp->n_segs); + iter->cursor = art_new(int, svp->n_segs); + iter->seg_x = art_new(artfloat, svp->n_segs); + iter->seg_dx = art_new(artfloat, svp->n_segs); + iter->steps = art_new(ArtSVPRenderAAStep, x1 - x0); + iter->n_active_segs = 0; + + return iter; +} + +#define ADD_STEP(xpos, xdelta) \ + /* stereotype code fragment for adding a step */ \ + if (n_steps == 0 || steps[n_steps - 1].x < xpos) \ + { \ + sx = n_steps; \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + else \ + { \ + for (sx = n_steps; sx > 0; sx--) \ + { \ + if (steps[sx - 1].x == xpos) \ + { \ + steps[sx - 1].delta += xdelta; \ + sx = n_steps; \ + break; \ + } \ + else if (steps[sx - 1].x < xpos) \ + { \ + break; \ + } \ + } \ + if (sx < n_steps) \ + { \ + memmove (&steps[sx + 1], &steps[sx], \ + (n_steps - sx) * sizeof(steps[0])); \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + } + +void art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps) { + const ArtSVP *svp = iter->svp; + int *active_segs = iter->active_segs; + int n_active_segs = iter->n_active_segs; + int *cursor = iter->cursor; + artfloat *seg_x = iter->seg_x; + artfloat *seg_dx = iter->seg_dx; + int i = iter->seg_ix; + int j; + int x0 = iter->x0; + int x1 = iter->x1; + int y = iter->y; + int seg_index; + + int x; + ArtSVPRenderAAStep *steps = iter->steps; + int n_steps; + artfloat y_top, y_bot; + artfloat x_top, x_bot; + artfloat x_min, x_max; + int ix_min, ix_max; + artfloat delta; /* delta should be int too? */ + int last, this_; + int xdelta; + artfloat rslope, drslope; + int start; + const ArtSVPSeg *seg; + int curs; + artfloat dy; + + int sx; + + /* insert new active segments */ + for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) { + if (svp->segs[i].bbox.y1 > y && + svp->segs[i].bbox.x0 < x1) { + seg = &svp->segs[i]; + /* move cursor to topmost vector which overlaps [y,y+1) */ + for (curs = 0; seg->points[curs + 1].y < y; curs++) + ; + cursor[i] = curs; + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON_6) + seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / + dy; + else + seg_dx[i] = 1e12; + seg_x[i] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[i]; + art_svp_render_insert_active(i, active_segs, n_active_segs++, + seg_x, seg_dx); + } + } + + n_steps = 0; + + /* render the runlengths, advancing and deleting as we go */ + start = 0x8000; + + for (j = 0; j < n_active_segs; j++) { + seg_index = active_segs[j]; + seg = &svp->segs[seg_index]; + curs = cursor[seg_index]; + while (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + y_top = y; + if (y_top < seg->points[curs].y) + y_top = seg->points[curs].y; + y_bot = y + 1; + if (y_bot > seg->points[curs + 1].y) + y_bot = seg->points[curs + 1].y; + if (y_top != y_bot) { + delta = (seg->dir ? 16711680.0 : -16711680.0) * + (y_bot - y_top); + x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index]; + x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index]; + if (x_top < x_bot) { + x_min = x_top; + x_max = x_bot; + } else { + x_min = x_bot; + x_max = x_top; + } + ix_min = (int)floor(x_min); + ix_max = (int)floor(x_max); + if (ix_min >= x1) { + /* skip; it starts to the right of the render region */ + } else if (ix_max < x0) + /* it ends to the left of the render region */ + start += (int)delta; + else if (ix_min == ix_max) { + /* case 1, antialias a single pixel */ + xdelta = (int)((ix_min + 1 - (x_min + x_max) * 0.5) * delta); + + ADD_STEP(ix_min, xdelta) + + if (ix_min + 1 < x1) { + xdelta = (int)(delta - xdelta); + + ADD_STEP(ix_min + 1, xdelta) + } + } else { + /* case 2, antialias a run */ + rslope = 1.0 / fabs(seg_dx[seg_index]); + drslope = delta * rslope; + last = + (int)(drslope * 0.5 * + (ix_min + 1 - x_min) * (ix_min + 1 - x_min)); + xdelta = last; + if (ix_min >= x0) { + ADD_STEP(ix_min, xdelta) + + x = ix_min + 1; + } else { + start += last; + x = x0; + } + if (ix_max > x1) + ix_max = x1; + for (; x < ix_max; x++) { + this_ = (int)((seg->dir ? 16711680.0 : -16711680.0) * rslope * + (x + 0.5 - x_min)); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + } + if (x < x1) { + this_ = + (int)(delta * (1 - 0.5 * + (x_max - ix_max) * (x_max - ix_max) * + rslope)); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + + if (x + 1 < x1) { + xdelta = (int)(delta - last); + + ADD_STEP(x + 1, xdelta) + } + } + } + } + curs++; + if (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON_6) + seg_dx[seg_index] = (seg->points[curs + 1].x - + seg->points[curs].x) / dy; + else + seg_dx[seg_index] = 1e12; + seg_x[seg_index] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[seg_index]; + } + /* break here, instead of duplicating predicate in while? */ + } + if (seg->points[curs].y >= y + 1) { + curs--; + cursor[seg_index] = curs; + seg_x[seg_index] += seg_dx[seg_index]; + } else { + art_svp_render_delete_active(active_segs, j--, + --n_active_segs); + } + } + + *p_start = start; + *p_steps = steps; + *p_n_steps = n_steps; + + iter->seg_ix = i; + iter->n_active_segs = n_active_segs; + iter->y++; +} + +void art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter) { + free(iter->steps); + + free(iter->seg_dx); + free(iter->seg_x); + free(iter->cursor); + free(iter->active_segs); + free(iter); +} + +/** + * art_svp_render_aa: Render SVP antialiased. + * @svp: The #ArtSVP to render. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @callback: The callback which actually paints the pixels. + * @callback_data: Private data for @callback. + * + * Renders the sorted vector path in the given rectangle, antialiased. + * + * This interface uses a callback for the actual pixel rendering. The + * callback is called @y1 - @y0 times (once for each scan line). The y + * coordinate is given as an argument for convenience (it could be + * stored in the callback's private data and incremented on each + * call). + * + * The rendered polygon is represented in a semi-runlength format: a + * start value and a sequence of "steps". Each step has an x + * coordinate and a value delta. The resulting value at position x is + * equal to the sum of the start value and all step delta values for + * which the step x coordinate is less than or equal to x. An + * efficient algorithm will traverse the steps left to right, keeping + * a running sum. + * + * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1. + * (This guarantee is a change from the gfonted vpaar renderer from + * which this routine is derived, and is designed to simplify the + * callback). + * + * The value 0x8000 represents 0% coverage by the polygon, while + * 0xff8000 represents 100% coverage. This format is designed so that + * >> 16 results in a standard 0x00..0xff value range, with nice + * rounding. + * + **/ +void art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data) { + ArtSVPRenderAAIter *iter; + int y; + int start; + ArtSVPRenderAAStep *steps; + int n_steps; + + iter = art_svp_render_aa_iter(svp, x0, y0, x1, y1); + + + for (y = y0; y < y1; y++) { + art_svp_render_aa_iter_step(iter, &start, &steps, &n_steps); + (*callback)(callback_data, y, start, steps, n_steps); + } + + art_svp_render_aa_iter_done(iter); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/art.h b/engines/sword25/gfx/image/art.h new file mode 100644 index 0000000000..90baa770cf --- /dev/null +++ b/engines/sword25/gfx/image/art.h @@ -0,0 +1,279 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* Simple macros to set up storage allocation and basic types for libart + functions. */ + +#ifndef __ART_H__ +#define __ART_H__ + +#include "common/scummsys.h" + +namespace Sword25 { + +typedef byte art_u8; +typedef uint16 art_u16; +typedef uint32 art_u32; + +/* These aren't, strictly speaking, configuration macros, but they're + damn handy to have around, and may be worth playing with for + debugging. */ +#define art_new(type, n) ((type *)malloc ((n) * sizeof(type))) + +#define art_renew(p, type, n) ((type *)realloc (p, (n) * sizeof(type))) + +/* This one must be used carefully - in particular, p and max should + be variables. They can also be pstruct->el lvalues. */ +#define art_expand(p, type, max) do { if(max) { p = art_renew (p, type, max <<= 1); } else { max = 1; p = art_new(type, 1); } } while (0) + +typedef int art_boolean; +#define ART_FALSE 0 +#define ART_TRUE 1 + +/* define pi */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* M_PI */ + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif /* M_SQRT2 */ + +/* Provide macros to feature the GCC function attribute. + */ +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) +#define ART_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#define ART_GNUC_NORETURN \ + __attribute__((__noreturn__)) +#else /* !__GNUC__ */ +#define ART_GNUC_PRINTF( format_idx, arg_idx ) +#define ART_GNUC_NORETURN +#endif /* !__GNUC__ */ + +void ART_GNUC_NORETURN +art_die(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +void +art_warn(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +typedef struct _ArtDRect ArtDRect; +typedef struct _ArtIRect ArtIRect; + +struct _ArtDRect { + /*< public >*/ + double x0, y0, x1, y1; +}; + +struct _ArtIRect { + /*< public >*/ + int x0, y0, x1, y1; +}; + +typedef struct _ArtPoint ArtPoint; + +struct _ArtPoint { + /*< public >*/ + double x, y; +}; + +/* Basic data structures and constructors for sorted vector paths */ + +typedef struct _ArtSVP ArtSVP; +typedef struct _ArtSVPSeg ArtSVPSeg; + +struct _ArtSVPSeg { + int n_points; + int dir; /* == 0 for "up", 1 for "down" */ + ArtDRect bbox; + ArtPoint *points; +}; + +struct _ArtSVP { + int n_segs; + ArtSVPSeg segs[1]; +}; + +void +art_svp_free(ArtSVP *svp); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +/* Basic data structures and constructors for bezier paths */ + +typedef enum { + ART_MOVETO, + ART_MOVETO_OPEN, + ART_CURVETO, + ART_LINETO, + ART_END +} ArtPathcode; + +typedef struct _ArtBpath ArtBpath; + +struct _ArtBpath { + /*< public >*/ + ArtPathcode code; + double x1; + double y1; + double x2; + double y2; + double x3; + double y3; +}; + +/* Basic data structures and constructors for simple vector paths */ + +typedef struct _ArtVpath ArtVpath; + +/* CURVETO is not allowed! */ +struct _ArtVpath { + ArtPathcode code; + double x; + double y; +}; + +/* Some of the functions need to go into their own modules */ + +void +art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y); + +ArtVpath *art_bez_path_to_vec(const ArtBpath *bez, double flatness); + +/* The funky new SVP intersector. */ + +#ifndef ART_WIND_RULE_DEFINED +#define ART_WIND_RULE_DEFINED +typedef enum { + ART_WIND_RULE_NONZERO, + ART_WIND_RULE_INTERSECT, + ART_WIND_RULE_ODDEVEN, + ART_WIND_RULE_POSITIVE +} ArtWindRule; +#endif + +typedef struct _ArtSvpWriter ArtSvpWriter; + +struct _ArtSvpWriter { + int (*add_segment)(ArtSvpWriter *self, int wind_left, int delta_wind, + double x, double y); + void (*add_point)(ArtSvpWriter *self, int seg_id, double x, double y); + void (*close_segment)(ArtSvpWriter *self, int seg_id); +}; + +ArtSvpWriter * +art_svp_writer_rewind_new(ArtWindRule rule); + +ArtSVP * +art_svp_writer_rewind_reap(ArtSvpWriter *self); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +void +art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out); + + +/* Sort vector paths into sorted vector paths. */ + +ArtSVP * +art_svp_from_vpath(ArtVpath *vpath); + +/* Sort vector paths into sorted vector paths. */ + +typedef enum { + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_JOIN_ROUND, + ART_PATH_STROKE_JOIN_BEVEL +} ArtPathStrokeJoinType; + +typedef enum { + ART_PATH_STROKE_CAP_BUTT, + ART_PATH_STROKE_CAP_ROUND, + ART_PATH_STROKE_CAP_SQUARE +} ArtPathStrokeCapType; + +ArtSVP * +art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +/* This version may have winding numbers exceeding 1. */ +ArtVpath * +art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + + +/* The spiffy antialiased renderer for sorted vector paths. */ + +typedef struct _ArtSVPRenderAAStep ArtSVPRenderAAStep; +typedef struct _ArtSVPRenderAAIter ArtSVPRenderAAIter; + +struct _ArtSVPRenderAAStep { + int x; + int delta; /* stored with 16 fractional bits */ +}; + +ArtSVPRenderAAIter * +art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1); + +void +art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps); + +void +art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter); + +void +art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data); + +} // End of namespace Sword25 + +#endif /* __ART_H__ */ diff --git a/engines/sword25/gfx/image/image.h b/engines/sword25/gfx/image/image.h new file mode 100644 index 0000000000..5ac6d1ac25 --- /dev/null +++ b/engines/sword25/gfx/image/image.h @@ -0,0 +1,222 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_Image + -------- + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_IMAGE_H +#define SWORD25_IMAGE_H + +// Includes +#include "sword25/kernel/common.h" +#include "common/rect.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +class Image { +public: + virtual ~Image() {} + + // Enums + /** + @brief Die möglichen Flippingparameter für die Blit-Methode. + */ + enum FLIP_FLAGS { + /// Das Bild wird nicht gespiegelt. + FLIP_NONE = 0, + /// Das Bild wird an der horizontalen Achse gespiegelt. + FLIP_H = 1, + /// Das Bild wird an der vertikalen Achse gespiegelt. + FLIP_V = 2, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_HV = FLIP_H | FLIP_V, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_VH = FLIP_H | FLIP_V + }; + + //@{ + /** @name Accessor-Methoden */ + + /** + @brief Gibt die Breite des Bildes in Pixeln zurück + */ + virtual int getWidth() const = 0; + + /** + @brief Gibt die Höhe des Bildes in Pixeln zurück + */ + virtual int getHeight() const = 0; + + /** + @brief Gibt das Farbformat des Bildes zurück + */ + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const = 0; + + //@} + + //@{ + /** @name Render-Methoden */ + + /** + @brief Rendert das Bild in den Framebuffer. + @param pDest ein Pointer auf das Zielbild. In den meisten Fällen ist dies der Framebuffer. + @param PosX die Position auf der X-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param PosY die Position auf der Y-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param Flipping gibt an, wie das Bild gespiegelt werden soll.<br> + Der Standardwert ist BS_Image::FLIP_NONE (keine Spiegelung) + @param pSrcPartRect Pointer auf ein Common::Rect, welches den Ausschnitt des Quellbildes spezifiziert, der gerendert + werden soll oder NULL, falls das gesamte Bild gerendert werden soll.<br> + Dieser Ausschnitt bezieht sich auf das ungespiegelte und unskalierte Bild.<br> + Der Standardwert ist NULL. + @param Color ein ARGB Farbwert, der die Parameter für die Farbmodulation und fürs Alphablending festlegt.<br> + Die Alpha-Komponente der Farbe bestimmt den Alphablending Parameter (0 = keine Deckung, 255 = volle Deckung).<br> + Die Farbkomponenten geben die Farbe für die Farbmodulation an.<br> + Der Standardwert is BS_ARGB(255, 255, 255, 255) (volle Deckung, keine Farbmodulation). + Zum Erzeugen des Farbwertes können die Makros BS_RGB und BS_ARGB benutzt werden. + @param Width gibt die Ausgabebreite des Bildausschnittes an. + Falls diese von der Breite des Bildausschnittes abweicht wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @param Width gibt die Ausgabehöhe des Bildausschnittes an. + Falls diese von der Höhe des Bildauschnittes abweicht, wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark Er werden nicht alle Blitting-Operationen von allen BS_Image-Klassen unterstützt.<br> + Mehr Informationen gibt es in der Klassenbeschreibung von BS_Image und durch folgende Methoden: + - IsBlitTarget() + - IsScalingAllowed() + - IsFillingAllowed() + - IsAlphaAllowed() + - IsColorModulationAllowed() + - IsSetContentAllowed() + */ + virtual bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1) = 0; + + /** + @brief Füllt einen Rechteckigen Bereich des Bildes mit einer Farbe. + @param pFillRect Pointer auf ein Common::Rect, welches den Ausschnitt des Bildes spezifiziert, der gefüllt + werden soll oder NULL, falls das gesamte Bild gefüllt werden soll.<br> + Der Standardwert ist NULL. + @param Color der 32 Bit Farbwert mit dem der Bildbereich gefüllt werden soll. + @remark Es ist möglich über die Methode transparente Rechtecke darzustellen, indem man eine Farbe mit einem Alphawert ungleich + 255 angibt. + @remark Unabhängig vom Farbformat des Bildes muss ein 32 Bit Farbwert angegeben werden. Zur Erzeugung, können die Makros + BS_RGB und BS_ARGB benutzt werden. + @remark Falls das Rechteck nicht völlig innerhalb des Bildschirms ist, wird es automatisch zurechtgestutzt. + */ + virtual bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)) = 0; + + /** + @brief Füllt den Inhalt des Bildes mit Pixeldaten. + @param Pixeldata ein Vector der die Pixeldaten enthält. Sie müssen in dem Farbformat des Bildes vorliegen und es müssen genügend Daten + vorhanden sein, um das ganze Bild zu füllen. + @param Offset der Offset in Byte im Pixeldata-Vector an dem sich der erste zu schreibende Pixel befindet.<br> + Der Standardwert ist 0. + @param Stride der Abstand in Byte zwischen dem Zeilenende und dem Beginn einer neuen Zeile im Pixeldata-Vector.<br> + Der Standardwert ist 0. + @return Gibt false zurück, falls der Aufruf fehlgeschlagen ist. + @remark Ein Aufruf dieser Methode ist nur erlaubt, wenn IsSetContentAllowed() true zurückgibt. + */ + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride) = 0; + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + virtual uint getPixel(int x, int y) = 0; + + //@{ + /** @name Auskunfts-Methoden */ + + /** + @brief Überprüft, ob an dem BS_Image Blit() aufgerufen werden darf. + @return Gibt false zurück, falls ein Blit()-Aufruf an diesem Objekt nicht gestattet ist. + */ + virtual bool isBlitSource() const = 0; + + /** + @brief Überprüft, ob das BS_Image ein Zielbild für einen Blit-Aufruf sein kann. + @return Gibt false zurück, falls ein Blit-Aufruf mit diesem Objekt als Ziel nicht gestattet ist. + */ + virtual bool isBlitTarget() const = 0; + + /** + @brief Gibt true zurück, falls das BS_Image bei einem Aufruf von Blit() skaliert dargestellt werden kann. + */ + virtual bool isScalingAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image mit einem Aufruf von Fill() gefüllt werden kann. + */ + virtual bool isFillingAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit einem Alphawert dargestellt werden kann. + */ + virtual bool isAlphaAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit Farbmodulation dargestellt werden kann. + */ + virtual bool isColorModulationAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn der Inhalt des BS_Image durch eine Aufruf von SetContent() ausgetauscht werden kann. + */ + virtual bool isSetContentAllowed() const = 0; + + //@} +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/pngloader.cpp b/engines/sword25/gfx/image/pngloader.cpp new file mode 100644 index 0000000000..1b72595a8f --- /dev/null +++ b/engines/sword25/gfx/image/pngloader.cpp @@ -0,0 +1,255 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// Disable symbol overrides so that we can use png.h +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/image/pngloader.h" +#include <png.h> + +namespace Sword25 { + +#define BS_LOG_PREFIX "PNGLOADER" + + +/** + * Load a NULL-terminated string from the given stream. + */ +static Common::String loadString(Common::ReadStream &in, uint maxSize = 999) { + Common::String result; + + while (!in.eos() && (result.size() < maxSize)) { + char ch = (char)in.readByte(); + if (ch == '\0') + break; + + result += ch; + } + + return result; +} + +/** + * Check if the given data is a savegame, and if so, locate the + * offset to the image data. + * @return offset to image data if fileDataPtr contains a savegame; 0 otherwise + */ +static uint findEmbeddedPNG(const byte *fileDataPtr, uint fileSize) { + if (fileSize < 100) + return 0; + if (memcmp(fileDataPtr, "BS25SAVEGAME", 12)) + return 0; + + // Read in the header + Common::MemoryReadStream stream(fileDataPtr, fileSize); + stream.seek(0, SEEK_SET); + + // Headerinformationen der Spielstandes einlesen. + uint compressedGamedataSize; + loadString(stream); // Marker + loadString(stream); // Version + loadString(stream); // Description + Common::String gameSize = loadString(stream); + compressedGamedataSize = atoi(gameSize.c_str()); + loadString(stream); + + // Return the offset of where the thumbnail starts + return static_cast<uint>(stream.pos() + compressedGamedataSize); +} + +static void png_user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { + const byte **ref = (const byte **)png_get_io_ptr(png_ptr); + memcpy(data, *ref, length); + *ref += length; +} + +static bool doIsCorrectImageFormat(const byte *fileDataPtr, uint fileSize) { + return (fileSize > 8) && png_check_sig(const_cast<byte *>(fileDataPtr), 8); +} + + +bool PNGLoader::doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch) { + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + int bitDepth; + int colorType; + int interlaceType; + int i; + + // Check for valid PNG signature + if (!doIsCorrectImageFormat(fileDataPtr, fileSize)) { + error("png_check_sig failed"); + } + + // Die beiden PNG Strukturen erstellen + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + error("Could not create libpng read struct."); + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + error("Could not create libpng info struct."); + } + + // Alternative Lesefunktion benutzen + const byte **ref = &fileDataPtr; + png_set_read_fn(png_ptr, (void *)ref, png_user_read_data); + + // PNG Header einlesen + png_read_info(png_ptr, info_ptr); + + // PNG Informationen auslesen + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, &interlaceType, NULL, NULL); + + // Pitch des Ausgabebildes berechnen + pitch = GraphicEngine::calcPitch(GraphicEngine::CF_ARGB32, width); + + // Speicher für die endgültigen Bilddaten reservieren + // Dieses geschieht vor dem reservieren von Speicher für temporäre Bilddaten um die Fragmentierung des Speichers gering zu halten + uncompressedDataPtr = new byte[pitch * height]; + if (!uncompressedDataPtr) { + error("Could not allocate memory for output image."); + } + + // Bilder jeglicher Farbformate werden zunächst in ARGB Bilder umgewandelt + if (bitDepth == 16) + png_set_strip_16(png_ptr); + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_expand(png_ptr); + if (bitDepth < 8) + png_set_expand(png_ptr); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_expand(png_ptr); + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + png_set_bgr(png_ptr); + + if (colorType != PNG_COLOR_TYPE_RGB_ALPHA) + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + + // Nachdem die Transformationen registriert wurden, werden die Bilddaten erneut eingelesen + png_read_update_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, NULL, NULL, NULL); + + if (interlaceType == PNG_INTERLACE_NONE) { + // PNGs without interlacing can simply be read row by row. + for (i = 0; i < height; i++) { + png_read_row(png_ptr, uncompressedDataPtr + i * pitch, NULL); + } + } else { + // PNGs with interlacing require us to allocate an auxillary + // buffer with pointers to all row starts. + + // Allocate row pointer buffer + png_bytep *pRowPtr = new png_bytep[height]; + if (!pRowPtr) { + error("Could not allocate memory for row pointers."); + } + + // Initialize row pointers + for (i = 0; i < height; i++) + pRowPtr[i] = uncompressedDataPtr + i * pitch; + + // Read image data + png_read_image(png_ptr, pRowPtr); + + // Free row pointer buffer + delete[] pRowPtr; + } + + // Read additional data at the end. + png_read_end(png_ptr, NULL); + + // Destroy libpng structures + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + // Signal success + return true; +} + +bool PNGLoader::decodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch) { + uint pngOffset = findEmbeddedPNG(fileDataPtr, fileSize); + return doDecodeImage(fileDataPtr + pngOffset, fileSize - pngOffset, uncompressedDataPtr, width, height, pitch); +} + +bool PNGLoader::doImageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height) { + // Check for valid PNG signature + if (!doIsCorrectImageFormat(fileDataPtr, fileSize)) + return false; + + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + // Die beiden PNG Strukturen erstellen + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + error("Could not create libpng read struct."); + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + error("Could not create libpng info struct."); + } + + // Alternative Lesefunktion benutzen + const byte **ref = &fileDataPtr; + png_set_read_fn(png_ptr, (void *)ref, png_user_read_data); + + // PNG Header einlesen + png_read_info(png_ptr, info_ptr); + + // PNG Informationen auslesen + int bitDepth; + int colorType; + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, NULL, NULL, NULL); + + // Die Strukturen freigeben + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + return true; + +} + +bool PNGLoader::imageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height) { + uint pngOffset = findEmbeddedPNG(fileDataPtr, fileSize); + return doImageProperties(fileDataPtr + pngOffset, fileSize - pngOffset, width, height); +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/pngloader.h b/engines/sword25/gfx/image/pngloader.h new file mode 100644 index 0000000000..e0d68ff8b9 --- /dev/null +++ b/engines/sword25/gfx/image/pngloader.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PNGLOADER2_H +#define SWORD25_PNGLOADER2_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +/** + * Class for loading PNG files, and PNG data embedded into savegames. + * + * Originally written by Malte Thiesen. + */ +class PNGLoader { +protected: + PNGLoader() {} // Protected constructor to prevent instances + + static bool doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch); + static bool doImageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height); + +public: + + /** + * Decode an image. + * @param[in] fileDatePtr pointer to the image data + * @param[in] fileSize size of the image data in bytes + * @param[out] pUncompressedData if successful, this is set to a pointer containing the decoded image data + * @param[out] width if successful, this is set to the width of the image + * @param[out] height if successful, this is set to the height of the image + * @param[out] pitch if successful, this is set to the number of bytes per scanline in the image + * @return false in case of an error + * + * @remark The size of the output data equals pitch * height. + * @remark This function does not free the image buffer passed to it, + * it is the callers responsibility to do so. + */ + static bool decodeImage(const byte *pFileData, uint fileSize, + byte *&pUncompressedData, + int &width, int &height, + int &pitch); + /** + * Extract the properties of an image. + * @param[in] fileDatePtr pointer to the image data + * @param[in] fileSize size of the image data in bytes + * @param[out] width if successful, this is set to the width of the image + * @param[out] height if successful, this is set to the height of the image + * @return returns true if extraction of the properties was successful, false in case of an error + * + * @remark This function does not free the image buffer passed to it, + * it is the callers responsibility to do so. + */ + static bool imageProperties(const byte *fileDatePtr, uint fileSize, + int &width, + int &height); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/renderedimage.cpp b/engines/sword25/gfx/image/renderedimage.cpp new file mode 100644 index 0000000000..9392eca044 --- /dev/null +++ b/engines/sword25/gfx/image/renderedimage.cpp @@ -0,0 +1,397 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/pngloader.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "common/system.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEREDIMAGE" + +// ----------------------------------------------------------------------------- +// CONSTRUCTION / DESTRUCTION +// ----------------------------------------------------------------------------- + +RenderedImage::RenderedImage(const Common::String &filename, bool &result) : + _data(0), + _width(0), + _height(0) { + result = false; + + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = (byte *)pPackage->getFile(filename, &fileSize))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return; + } + + // Bildeigenschaften bestimmen + int pitch; + if (!PNGLoader::imageProperties(pFileData, fileSize, _width, _height)) { + BS_LOG_ERRORLN("Could not read image properties."); + delete[] pFileData; + return; + } + + // Das Bild dekomprimieren + if (!PNGLoader::decodeImage(pFileData, fileSize, _data, _width, _height, pitch)) { + BS_LOG_ERRORLN("Could not decode image."); + delete[] pFileData; + return; + } + + // Dateidaten freigeben + delete[] pFileData; + + _doCleanup = true; + + result = true; + return; +} + +// ----------------------------------------------------------------------------- + +RenderedImage::RenderedImage(uint width, uint height, bool &result) : + _width(width), + _height(height) { + + _data = new byte[width * height * 4]; + Common::set_to(_data, &_data[width * height * 4], 0); + + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + _doCleanup = true; + + result = true; + return; +} + +RenderedImage::RenderedImage() : _width(0), _height(0), _data(0) { + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + _doCleanup = false; + + return; +} + +// ----------------------------------------------------------------------------- + +RenderedImage::~RenderedImage() { + if (_doCleanup) + delete[] _data; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + // Überprüfen, ob PixelData ausreichend viele Pixel enthält um ein Bild der Größe Width * Height zu erzeugen + if (size < static_cast<uint>(_width * _height * 4)) { + BS_LOG_ERRORLN("PixelData vector is too small to define a 32 bit %dx%d image.", _width, _height); + return false; + } + + const byte *in = &pixeldata[offset]; + byte *out = _data; + + for (int i = 0; i < _height; i++) { + memcpy(out, in, _width * 4); + out += _width * 4; + in += stride; + } + + return true; +} + +void RenderedImage::replaceContent(byte *pixeldata, int width, int height) { + _width = width; + _height = height; + _data = pixeldata; +} +// ----------------------------------------------------------------------------- + +uint RenderedImage::getPixel(int x, int y) { + BS_LOG_ERRORLN("GetPixel() is not supported. Returning black."); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::blit(int posX, int posY, int flipping, Common::Rect *pPartRect, uint color, int width, int height) { + int ca = (color >> 24) & 0xff; + + // Check if we need to draw anything at all + if (ca == 0) + return true; + + int cr = (color >> 16) & 0xff; + int cg = (color >> 8) & 0xff; + int cb = (color >> 0) & 0xff; + + // Compensate for transparency. Since we're coming + // down to 255 alpha, we just compensate for the colors here + if (ca != 255) { + cr = cr * ca >> 8; + cg = cg * ca >> 8; + cb = cb * ca >> 8; + } + + // Create an encapsulating surface for the data + Graphics::Surface srcImage; + srcImage.bytesPerPixel = 4; + srcImage.pitch = _width * 4; + srcImage.w = _width; + srcImage.h = _height; + srcImage.pixels = _data; + + if (pPartRect) { + srcImage.pixels = &_data[pPartRect->top * srcImage.pitch + pPartRect->left * 4]; + srcImage.w = pPartRect->right - pPartRect->left; + srcImage.h = pPartRect->bottom - pPartRect->top; + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, + pPartRect->left, pPartRect->top, pPartRect->width(), pPartRect->height(), color, width, height); + } else { + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, 0, 0, + srcImage.w, srcImage.h, color, width, height); + } + + if (width == -1) + width = srcImage.w; + if (height == -1) + height = srcImage.h; + +#ifdef SCALING_TESTING + // Hardcode scaling to 66% to test scaling + width = width * 2 / 3; + height = height * 2 / 3; +#endif + + Graphics::Surface *img; + Graphics::Surface *imgScaled = NULL; + byte *savedPixels = NULL; + if ((width != srcImage.w) || (height != srcImage.h)) { + // Scale the image + img = imgScaled = scale(srcImage, width, height); + savedPixels = (byte *)img->pixels; + } else { + img = &srcImage; + } + + // Handle off-screen clipping + if (posY < 0) { + img->h = MAX(0, (int)img->h - -posY); + img->pixels = (byte *)img->pixels + img->pitch * -posY; + posY = 0; + } + + if (posX < 0) { + img->w = MAX(0, (int)img->w - -posX); + img->pixels = (byte *)img->pixels + (-posX * 4); + posX = 0; + } + + img->w = CLIP((int)img->w, 0, (int)MAX((int)_backSurface->w - posX, 0)); + img->h = CLIP((int)img->h, 0, (int)MAX((int)_backSurface->h - posY, 0)); + + if ((img->w > 0) && (img->h > 0)) { + int xp = 0, yp = 0; + + int inStep = 4; + int inoStep = img->pitch; + if (flipping & Image::FLIP_V) { + inStep = -inStep; + xp = img->w - 1; + } + + if (flipping & Image::FLIP_H) { + inoStep = -inoStep; + yp = img->h - 1; + } + + byte *ino = (byte *)img->getBasePtr(xp, yp); + byte *outo = (byte *)_backSurface->getBasePtr(posX, posY); + byte *in, *out; + + for (int i = 0; i < img->h; i++) { + out = outo; + in = ino; + for (int j = 0; j < img->w; j++) { + int r = in[0]; + int g = in[1]; + int b = in[2]; + int a = in[3]; + in += inStep; + + if (ca != 255) { + a = a * ca >> 8; + } + + switch (a) { + case 0: // Full transparency + out += 4; + break; + case 255: // Full opacity + if (cr != 255) + *out++ = (r * cr) >> 8; + else + *out++ = r; + + if (cg != 255) + *out++ = (g * cg) >> 8; + else + *out++ = g; + + if (cb != 255) + *out++ = (b * cb) >> 8; + else + *out++ = b; + + *out++ = a; + break; + + default: // alpha blending + if (cr != 255) + *out += ((r - *out) * a * cr) >> 16; + else + *out += ((r - *out) * a) >> 8; + out++; + if (cg != 255) + *out += ((g - *out) * a * cg) >> 16; + else + *out += ((g - *out) * a) >> 8; + out++; + if (cb != 255) + *out += ((b - *out) * a * cb) >> 16; + else + *out += ((b - *out) * a) >> 8; + out++; + *out = 255; + out++; + } + } + outo += _backSurface->pitch; + ino += inoStep; + } + + g_system->copyRectToScreen((byte *)_backSurface->getBasePtr(posX, posY), _backSurface->pitch, posX, posY, + img->w, img->h); + } + + if (imgScaled) { + imgScaled->pixels = savedPixels; + imgScaled->free(); + delete imgScaled; + } + + return true; +} + +/** + * Scales a passed surface, creating a new surface with the result + * @param srcImage Source image to scale + * @param scaleFactor Scale amount. Must be between 0 and 1.0 (but not zero) + * @remarks Caller is responsible for freeing the returned surface + */ +Graphics::Surface *RenderedImage::scale(const Graphics::Surface &srcImage, int xSize, int ySize) { + Graphics::Surface *s = new Graphics::Surface(); + s->create(xSize, ySize, srcImage.bytesPerPixel); + + int *horizUsage = scaleLine(xSize, srcImage.w); + int *vertUsage = scaleLine(ySize, srcImage.h); + + // Loop to create scaled version + for (int yp = 0; yp < ySize; ++yp) { + const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]); + byte *destP = (byte *)s->getBasePtr(0, yp); + + for (int xp = 0; xp < xSize; ++xp) { + const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.bytesPerPixel); + for (int byteCtr = 0; byteCtr < srcImage.bytesPerPixel; ++byteCtr) { + *destP++ = *tempSrcP++; + } + } + } + + // Delete arrays and return surface + delete[] horizUsage; + delete[] vertUsage; + return s; +} + +/** + * Returns an array indicating which pixels of a source image horizontally or vertically get + * included in a scaled image + */ +int *RenderedImage::scaleLine(int size, int srcSize) { + int scale = 100 * size / srcSize; + assert(scale > 0); + int *v = new int[size]; + Common::set_to(v, &v[size], 0); + + int distCtr = 0; + int *destP = v; + for (int distIndex = 0; distIndex < srcSize; ++distIndex) { + distCtr += scale; + while (distCtr >= 100) { + assert(destP < &v[size]); + *destP++ = distIndex; + distCtr -= 100; + } + } + + return v; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/renderedimage.h b/engines/sword25/gfx/image/renderedimage.h new file mode 100644 index 0000000000..a9f2f1823c --- /dev/null +++ b/engines/sword25/gfx/image/renderedimage.h @@ -0,0 +1,121 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDERED_IMAGE_H +#define SWORD25_RENDERED_IMAGE_H + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +class RenderedImage : public Image { +public: + RenderedImage(const Common::String &filename, bool &result); + + /** + @brief Erzeugt ein leeres BS_RenderedImage + + @param Width die Breite des zu erzeugenden Bildes. + @param Height die Höhe des zu erzeugenden Bildes + @param Result gibt dem Aufrufer bekannt, ob der Konstruktor erfolgreich ausgeführt wurde. Wenn es nach dem Aufruf false enthalten sollte, + dürfen keine Methoden am Objekt aufgerufen werden und das Objekt ist sofort zu zerstören. + */ + RenderedImage(uint width, uint height, bool &result); + RenderedImage(); + + virtual ~RenderedImage(); + + virtual int getWidth() const { + return _width; + } + virtual int getHeight() const { + return _height; + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + + virtual bool blit(int posX = 0, int posY = 0, + int flipping = Image::FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + virtual bool fill(const Common::Rect *pFillRect, uint color); + virtual bool setContent(const byte *pixeldata, uint size, uint offset = 0, uint stride = 0); + void replaceContent(byte *pixeldata, int width, int height); + virtual uint getPixel(int x, int y); + + virtual bool isBlitSource() const { + return true; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return true; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return true; + } + virtual bool isColorModulationAllowed() const { + return true; + } + virtual bool isSetContentAllowed() const { + return true; + } + + static Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize); +private: + byte *_data; + int _width; + int _height; + bool _doCleanup; + + Graphics::Surface *_backSurface; + + static int *scaleLine(int size, int srcSize); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/swimage.cpp b/engines/sword25/gfx/image/swimage.cpp new file mode 100644 index 0000000000..f0a8899bb6 --- /dev/null +++ b/engines/sword25/gfx/image/swimage.cpp @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/pngloader.h" +#include "sword25/gfx/image/swimage.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "SWIMAGE" + + +SWImage::SWImage(const Common::String &filename, bool &result) : + _imageDataPtr(0), + _width(0), + _height(0) { + result = false; + + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = (byte *)pPackage->getFile(filename, &fileSize))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return; + } + + // Bildeigenschaften bestimmen + int pitch; + if (!PNGLoader::imageProperties(pFileData, fileSize, _width, _height)) { + BS_LOG_ERRORLN("Could not read image properties."); + return; + } + + // Das Bild dekomprimieren + byte *pUncompressedData; + if (!PNGLoader::decodeImage(pFileData, fileSize, pUncompressedData, _width, _height, pitch)) { + BS_LOG_ERRORLN("Could not decode image."); + return; + } + + // Dateidaten freigeben + delete[] pFileData; + + _imageDataPtr = (uint *)pUncompressedData; + + result = true; + return; +} + +SWImage::~SWImage() { + delete[] _imageDataPtr; +} + + +bool SWImage::blit(int posX, int posY, + int flipping, + Common::Rect *pPartRect, + uint color, + int width, int height) { + BS_LOG_ERRORLN("Blit() is not supported."); + return false; +} + +bool SWImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + +bool SWImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() is not supported."); + return false; +} + +uint SWImage::getPixel(int x, int y) { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + return _imageDataPtr[_width * y + x]; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/swimage.h b/engines/sword25/gfx/image/swimage.h new file mode 100644 index 0000000000..a914c4f41f --- /dev/null +++ b/engines/sword25/gfx/image/swimage.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_SWIMAGE_H +#define SWORD25_SWIMAGE_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/graphicengine.h" + + +namespace Sword25 { + +class SWImage : public Image { +public: + SWImage(const Common::String &filename, bool &result); + virtual ~SWImage(); + + virtual int getWidth() const { + return _width; + } + virtual int getHeight() const { + return _height; + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + + virtual bool blit(int posX = 0, int posY = 0, + int flipping = Image::FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + virtual bool fill(const Common::Rect *fillRectPtr, uint color); + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + virtual uint getPixel(int x, int y); + + virtual bool isBlitSource() const { + return false; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return false; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return false; + } + virtual bool isColorModulationAllowed() const { + return false; + } + virtual bool isSetContentAllowed() const { + return false; + } +private: + uint *_imageDataPtr; + + int _width; + int _height; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/vectorimage.cpp b/engines/sword25/gfx/image/vectorimage.cpp new file mode 100644 index 0000000000..5c15c4771a --- /dev/null +++ b/engines/sword25/gfx/image/vectorimage.cpp @@ -0,0 +1,636 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/gfx/image/art.h" +#include "sword25/gfx/image/vectorimage.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "graphics/colormasks.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "VECTORIMAGE" + +#define BEZSMOOTHNESS 0.5 + +// ----------------------------------------------------------------------------- +// SWF Datentypen +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Bitstream Hilfsklasse +// ----------------------------------------------------------------------------- +// Das Parsen von SWF-Dateien erfordert sowohl bitweises Auslesen als auch an +// Bytegrenzen ausgerichtetes Lesen. +// Diese Klasse ist speziell dafür ausgestattet. +// ----------------------------------------------------------------------------- + +class VectorImage::SWFBitStream { +public: + SWFBitStream(const byte *pData, uint dataSize) : + m_Pos(pData), m_End(pData + dataSize), m_WordMask(0) + {} + + inline uint32 getBits(uint bitCount) { + if (bitCount == 0 || bitCount > 32) { + error("SWFBitStream::GetBits() must read at least 1 and at most 32 bits at a time"); + } + + uint32 value = 0; + while (bitCount) { + if (m_WordMask == 0) + flushByte(); + + value <<= 1; + value |= ((m_Word & m_WordMask) != 0) ? 1 : 0; + m_WordMask >>= 1; + + --bitCount; + } + + return value; + } + + inline int32 getSignedBits(uint bitCount) { + // Bits einlesen + uint32 temp = getBits(bitCount); + + // Falls das Sign-Bit gesetzt ist, den Rest des Rückgabewertes mit 1-Bits auffüllen (Sign Extension) + if (temp & 1 << (bitCount - 1)) + return (0xffffffff << bitCount) | temp; + else + return temp; + } + + inline uint32 getUInt32() { + uint32 byte1 = getByte(); + uint32 byte2 = getByte(); + uint32 byte3 = getByte(); + uint32 byte4 = getByte(); + + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); + } + + inline uint16 getUInt16() { + uint32 byte1 = getByte(); + uint32 byte2 = getByte(); + + return byte1 | (byte2 << 8); + } + + inline byte getByte() { + flushByte(); + byte value = m_Word; + m_WordMask = 0; + flushByte(); + + return value; + } + + inline void flushByte() { + if (m_WordMask != 128) { + if (m_Pos >= m_End) { + error("Attempted to read past end of file"); + } else { + m_Word = *m_Pos++; + m_WordMask = 128; + } + } + } + + inline void skipBytes(uint skipLength) { + flushByte(); + if (m_Pos + skipLength >= m_End) { + error("Attempted to read past end of file"); + } else { + m_Pos += skipLength; + m_Word = *(m_Pos - 1); + } + } + +private: + const byte *m_Pos; + const byte *m_End; + + byte m_Word; + uint m_WordMask; +}; + + +// ----------------------------------------------------------------------------- +// Konstanten und Hilfsfunktionen +// ----------------------------------------------------------------------------- + +namespace { +// ----------------------------------------------------------------------------- +// Konstanten +// ----------------------------------------------------------------------------- + +const uint32 MAX_ACCEPTED_FLASH_VERSION = 3; // Die höchste Flash-Dateiversion, die vom Lader akzeptiert wird + + +// ----------------------------------------------------------------------------- +// Konvertiert SWF-Rechteckdaten in einem Bitstrom in Common::Rect-Objekte +// ----------------------------------------------------------------------------- + +Common::Rect flashRectToBSRect(VectorImage::SWFBitStream &bs) { + bs.flushByte(); + + // Feststellen mit wie vielen Bits die einzelnen Komponenten kodiert sind + uint32 bitsPerValue = bs.getBits(5); + + // Die einzelnen Komponenten einlesen + int32 xMin = bs.getSignedBits(bitsPerValue); + int32 xMax = bs.getSignedBits(bitsPerValue); + int32 yMin = bs.getSignedBits(bitsPerValue); + int32 yMax = bs.getSignedBits(bitsPerValue); + + return Common::Rect(xMin, yMin, xMax + 1, yMax + 1); +} + +// ----------------------------------------------------------------------------- +// Berechnet die Bounding-Box eines BS_VectorImageElement +// ----------------------------------------------------------------------------- + +Common::Rect CalculateBoundingBox(const VectorImageElement &vectorImageElement) { + double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0; + + for (int j = vectorImageElement.getPathCount() - 1; j >= 0; j--) { + ArtBpath *bez = vectorImageElement.getPathInfo(j).getVec(); + ArtVpath *vec = art_bez_path_to_vec(bez, 0.5); + + if (vec[0].code == ART_END) { + continue; + } else { + x0 = x1 = vec[0].x; + y0 = y1 = vec[0].y; + for (int i = 1; vec[i].code != ART_END; i++) { + if (vec[i].x < x0) x0 = vec[i].x; + if (vec[i].x > x1) x1 = vec[i].x; + if (vec[i].y < y0) y0 = vec[i].y; + if (vec[i].y > y1) y1 = vec[i].y; + } + } + free(vec); + } + + return Common::Rect(static_cast<int>(x0), static_cast<int>(y0), static_cast<int>(x1) + 1, static_cast<int>(y1) + 1); +} + +} + + +// ----------------------------------------------------------------------------- +// Konstruktion +// ----------------------------------------------------------------------------- + +VectorImage::VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname) : _pixelData(0), _fname(fname) { + success = false; + + // Bitstream-Objekt erzeugen + // Im Folgenden werden die Dateidaten aus diesem ausgelesen. + SWFBitStream bs(pFileData, fileSize); + + // SWF-Signatur überprüfen + uint32 signature[3]; + signature[0] = bs.getByte(); + signature[1] = bs.getByte(); + signature[2] = bs.getByte(); + if (signature[0] != 'F' || + signature[1] != 'W' || + signature[2] != 'S') { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // Versionsangabe überprüfen + uint32 version = bs.getByte(); + if (version > MAX_ACCEPTED_FLASH_VERSION) { + BS_LOG_ERRORLN("File is of version %d. Highest accepted version is %d.", version, MAX_ACCEPTED_FLASH_VERSION); + return; + } + + // Dateigröße auslesen und mit der tatsächlichen Größe vergleichen + uint32 storedFileSize = bs.getUInt32(); + if (storedFileSize != fileSize) { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // SWF-Maße auslesen + Common::Rect movieRect = flashRectToBSRect(bs); + + // Framerate und Frameanzahl auslesen + /* uint32 frameRate = */ + bs.getUInt16(); + /* uint32 frameCount = */ + bs.getUInt16(); + + // Tags parsen + // Da wir uns nur für das erste DefineShape-Tag interessieren + bool keepParsing = true; + while (keepParsing) { + // Tags beginnen immer an Bytegrenzen + bs.flushByte(); + + // Tagtyp und Länge auslesen + uint16 tagTypeAndLength = bs.getUInt16(); + uint32 tagType = tagTypeAndLength >> 6; + uint32 tagLength = tagTypeAndLength & 0x3f; + if (tagLength == 0x3f) + tagLength = bs.getUInt32(); + + switch (tagType) { + case 2: + // DefineShape + success = parseDefineShape(2, bs); + return; + case 22: + // DefineShape2 + success = parseDefineShape(2, bs); + return; + case 32: + success = parseDefineShape(3, bs); + return; + default: + // Unbekannte Tags ignorieren + bs.skipBytes(tagLength); + } + } + + // Die Ausführung darf nicht an dieser Stelle ankommen: Entweder es wird ein Shape gefunden, dann wird die Funktion mit vorher verlassen, oder + // es wird keines gefunden, dann tritt eine Exception auf sobald über das Ende der Datei hinaus gelesen wird. + BS_ASSERT(false); +} + +VectorImage::~VectorImage() { + for (int j = _elements.size() - 1; j >= 0; j--) + for (int i = _elements[j].getPathCount() - 1; i >= 0; i--) + if (_elements[j].getPathInfo(i).getVec()) + free(_elements[j].getPathInfo(i).getVec()); + + if (_pixelData) + free(_pixelData); +} + + +ArtBpath *ensureBezStorage(ArtBpath *bez, int nodes, int *allocated) { + if (*allocated <= nodes) { + (*allocated) += 20; + + return art_renew(bez, ArtBpath, *allocated); + } + + return bez; +} + +ArtBpath *VectorImage::storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated) { + (*bezNodes)++; + + bez = ensureBezStorage(bez, *bezNodes, bezAllocated); + bez[*bezNodes].code = ART_END; + + ArtBpath *bez1 = art_new(ArtBpath, *bezNodes + 1); + + for (int i = 0; i <= *bezNodes; i++) + bez1[i] = bez[i]; + + _elements.back()._pathInfos.push_back(VectorPathInfo(bez1, *bezNodes, lineStyle, fillStyle0, fillStyle1)); + + return bez; +} + +bool VectorImage::parseDefineShape(uint shapeType, SWFBitStream &bs) { + /*uint32 shapeID = */bs.getUInt16(); + + // Bounding Box auslesen + _boundingBox = flashRectToBSRect(bs); + + // Erstes Image-Element erzeugen + _elements.resize(1); + + // Styles einlesen + uint numFillBits; + uint numLineBits; + if (!parseStyles(shapeType, bs, numFillBits, numLineBits)) + return false; + + uint lineStyle = 0; + uint fillStyle0 = 0; + uint fillStyle1 = 0; + + // Shaperecord parsen + // ------------------ + + double curX = 0; + double curY = 0; + int bezNodes = 0; + int bezAllocated = 10; + ArtBpath *bez = art_new(ArtBpath, bezAllocated); + + bool endOfShapeDiscovered = false; + while (!endOfShapeDiscovered) { + uint32 typeFlag = bs.getBits(1); + + // Non-Edge Record + if (typeFlag == 0) { + // Feststellen welche Parameter gesetzt werden + uint32 stateNewStyles = bs.getBits(1); + uint32 stateLineStyle = bs.getBits(1); + uint32 stateFillStyle1 = bs.getBits(1); + uint32 stateFillStyle0 = bs.getBits(1); + uint32 stateMoveTo = bs.getBits(1); + + uint prevLineStyle = lineStyle; + uint prevFillStyle0 = fillStyle0; + uint prevFillStyle1 = fillStyle1; + + // End der Shape-Definition erreicht? + if (!stateNewStyles && !stateLineStyle && !stateFillStyle0 && !stateFillStyle1 && !stateMoveTo) { + endOfShapeDiscovered = true; + // Parameter dekodieren + } else { + if (stateMoveTo) { + uint32 moveToBits = bs.getBits(5); + curX = bs.getSignedBits(moveToBits); + curY = bs.getSignedBits(moveToBits); + } + + if (stateFillStyle0) { + if (numFillBits > 0) + fillStyle0 = bs.getBits(numFillBits); + else + fillStyle0 = 0; + } + + if (stateFillStyle1) { + if (numFillBits > 0) + fillStyle1 = bs.getBits(numFillBits); + else + fillStyle1 = 0; + } + + if (stateLineStyle) { + if (numLineBits) + lineStyle = bs.getBits(numLineBits); + else + numLineBits = 0; + } + + // Ein neuen Pfad erzeugen, es sei denn, es wurden nur neue Styles definiert + if (stateLineStyle || stateFillStyle0 || stateFillStyle1 || stateMoveTo) { + // Store previous curve if any + if (bezNodes) { + bez = storeBez(bez, prevLineStyle, prevFillStyle0, prevFillStyle1, &bezNodes, &bezAllocated); + } + + // Start new curve + bez = ensureBezStorage(bez, 1, &bezAllocated); + bez[0].code = ART_MOVETO_OPEN; + bez[0].x3 = curX; + bez[0].y3 = curY; + bezNodes = 0; + } + + if (stateNewStyles) { + // An dieser Stelle werden in Flash die alten Style-Definitionen verworfen und mit den neuen überschrieben. + // Es wird ein neues Element begonnen. + _elements.resize(_elements.size() + 1); + if (!parseStyles(shapeType, bs, numFillBits, numLineBits)) + return false; + } + } + } else { + // Edge Record + uint32 edgeFlag = bs.getBits(1); + uint32 numBits = bs.getBits(4) + 2; + + // Curved edge + if (edgeFlag == 0) { + double controlDeltaX = bs.getSignedBits(numBits); + double controlDeltaY = bs.getSignedBits(numBits); + double anchorDeltaX = bs.getSignedBits(numBits); + double anchorDeltaY = bs.getSignedBits(numBits); + + double controlX = curX + controlDeltaX; + double controlY = curY + controlDeltaY; + double newX = controlX + anchorDeltaX; + double newY = controlY + anchorDeltaY; + +#define WEIGHT (2.0/3.0) + + bezNodes++; + bez = ensureBezStorage(bez, bezNodes, &bezAllocated); + bez[bezNodes].code = ART_CURVETO; + bez[bezNodes].x1 = WEIGHT * controlX + (1 - WEIGHT) * curX; + bez[bezNodes].y1 = WEIGHT * controlY + (1 - WEIGHT) * curY; + bez[bezNodes].x2 = WEIGHT * controlX + (1 - WEIGHT) * newX; + bez[bezNodes].y2 = WEIGHT * controlY + (1 - WEIGHT) * newY; + bez[bezNodes].x3 = newX; + bez[bezNodes].y3 = newY; + + curX = newX; + curY = newY; + } else { + // Staight edge + int32 deltaX = 0; + int32 deltaY = 0; + + uint32 generalLineFlag = bs.getBits(1); + if (generalLineFlag) { + deltaX = bs.getSignedBits(numBits); + deltaY = bs.getSignedBits(numBits); + } else { + uint32 vertLineFlag = bs.getBits(1); + if (vertLineFlag) + deltaY = bs.getSignedBits(numBits); + else + deltaX = bs.getSignedBits(numBits); + } + + curX += deltaX; + curY += deltaY; + + bezNodes++; + bez = ensureBezStorage(bez, bezNodes, &bezAllocated); + bez[bezNodes].code = ART_LINETO; + bez[bezNodes].x3 = curX; + bez[bezNodes].y3 = curY; + } + } + } + + // Store last curve + if (bezNodes) + bez = storeBez(bez, lineStyle, fillStyle0, fillStyle1, &bezNodes, &bezAllocated); + + free(bez); + + // Bounding-Boxes der einzelnen Elemente berechnen + Common::Array<VectorImageElement>::iterator it = _elements.begin(); + for (; it != _elements.end(); ++it) + it->_boundingBox = CalculateBoundingBox(*it); + + return true; +} + + +// ----------------------------------------------------------------------------- + +bool VectorImage::parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits) { + bs.flushByte(); + + // Fillstyles parsen + // ----------------- + + // Anzahl an Fillstyles bestimmen + uint fillStyleCount = bs.getByte(); + if (fillStyleCount == 0xff) + fillStyleCount = bs.getUInt16(); + + // Alle Fillstyles einlesen, falls ein Fillstyle mit Typ != 0 gefunden wird, wird das Parsen abgebrochen. + // Es wird nur "solid fill" (Typ 0) unterstützt. + _elements.back()._fillStyles.reserve(fillStyleCount); + for (uint i = 0; i < fillStyleCount; ++i) { + byte type = bs.getByte(); + uint32 color; + byte r = bs.getByte(); + byte g = bs.getByte(); + byte b = bs.getByte(); + byte a = 0xff; + + if (shapeType == 3) + a = bs.getByte(); + + color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b); + + if (type != 0) + return false; + + _elements.back()._fillStyles.push_back(color); + } + + // Linestyles parsen + // ----------------- + + // Anzahl an Linestyles bestimmen + uint lineStyleCount = bs.getByte(); + if (lineStyleCount == 0xff) + lineStyleCount = bs.getUInt16(); + + // Alle Linestyles einlesen + _elements.back()._lineStyles.reserve(lineStyleCount); + for (uint i = 0; i < lineStyleCount; ++i) { + double width = bs.getUInt16(); + uint32 color; + byte r = bs.getByte(); + byte g = bs.getByte(); + byte b = bs.getByte(); + byte a = 0xff; + + if (shapeType == 3) + a = bs.getByte(); + + color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b); + + _elements.back()._lineStyles.push_back(VectorImageElement::LineStyleType(width, color)); + } + + // Bitbreite für die folgenden Styleindizes auslesen + numFillBits = bs.getBits(4); + numLineBits = bs.getBits(4); + + return true; +} + + +// ----------------------------------------------------------------------------- + +bool VectorImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + + +// ----------------------------------------------------------------------------- + +uint VectorImage::getPixel(int x, int y) { + BS_LOG_ERRORLN("GetPixel() is not supported. Returning black."); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool VectorImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() is not supported."); + return 0; +} + +bool VectorImage::blit(int posX, int posY, + int flipping, + Common::Rect *pPartRect, + uint color, + int width, int height) { + static VectorImage *oldThis = 0; + static int oldWidth = -2; + static int oldHeight = -2; + + // Falls Breite oder Höhe 0 sind, muss nichts dargestellt werden. + if (width == 0 || height == 0) + return true; + + // Feststellen, ob das alte Bild im Cache nicht wiederbenutzt werden kann und neu Berechnet werden muss + if (!(oldThis == this && oldWidth == width && oldHeight == height)) { + render(width, height); + + oldThis = this; + oldHeight = height; + oldWidth = width; + } + + RenderedImage *rend = new RenderedImage(); + + rend->replaceContent(_pixelData, width, height); + rend->blit(posX, posY, flipping, pPartRect, color, width, height); + + delete rend; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/vectorimage.h b/engines/sword25/gfx/image/vectorimage.h new file mode 100644 index 0000000000..3477463b43 --- /dev/null +++ b/engines/sword25/gfx/image/vectorimage.h @@ -0,0 +1,237 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_VECTORIMAGE_H +#define SWORD25_VECTORIMAGE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "common/rect.h" + +#include "art.h" + +namespace Sword25 { + +class VectorImage; + +/** + @brief Pfadinformationen zu BS_VectorImageElement Objekten + + Jedes BS_VectorImageElement besteht aus Kantenzügen, oder auch Pfaden. Jeder dieser Pfad hat Eigenschaften, die in Objekten diesen Typs + gespeichert werden. +*/ + +class VectorPathInfo { +public: + VectorPathInfo(ArtBpath *vec, int len, uint lineStyle, uint fillStyle0, uint fillStyle1) : + _vec(vec), _lineStyle(lineStyle), _fillStyle0(fillStyle0), _fillStyle1(fillStyle1), _len(len) {} + + VectorPathInfo() { + _lineStyle = _fillStyle0 = _fillStyle1 = _len = 0; + _vec = 0; + } + + ArtBpath *getVec() const { + return _vec; + } + int getVecLen() const { + return _len; + } + uint getLineStyle() const { + return _lineStyle; + } + uint getFillStyle0() const { + return _fillStyle0; + } + uint getFillStyle1() const { + return _fillStyle1; + } + +private: + ArtBpath *_vec; + uint _lineStyle; + uint _fillStyle0; + uint _fillStyle1; + uint _len; +}; + +/** + @brief Ein Element eines Vektorbild. Ein BS_VectorImage besteht aus diesen Elementen, die jeweils einen Teil der Graphik definieren. + Werden alle Elemente eines Vektorbildes übereinandergelegt, ergibt sich das komplette Bild. +*/ +class VectorImageElement { + friend class VectorImage; +public: + uint getPathCount() const { + return _pathInfos.size(); + } + const VectorPathInfo &getPathInfo(uint pathNr) const { + BS_ASSERT(pathNr < getPathCount()); + return _pathInfos[pathNr]; + } + + double getLineStyleWidth(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].width; + } + + uint getLineStyleCount() const { + return _lineStyles.size(); + } + + uint32 getLineStyleColor(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].color; + } + + uint getFillStyleCount() const { + return _fillStyles.size(); + } + + uint32 getFillStyleColor(uint fillStyle) const { + BS_ASSERT(fillStyle < _fillStyles.size()); + return _fillStyles[fillStyle]; + } + + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + +private: + struct LineStyleType { + LineStyleType(double width_, uint32 color_) : width(width_), color(color_) {} + LineStyleType() { + width = 0; + color = 0; + } + double width; + uint32 color; + }; + + Common::Array<VectorPathInfo> _pathInfos; + Common::Array<LineStyleType> _lineStyles; + Common::Array<uint32> _fillStyles; + Common::Rect _boundingBox; +}; + + +/** + @brief Eine Vektorgraphik + + Objekte dieser Klasse enthalten die Informationen eines SWF-Shapes. +*/ + +class VectorImage : public Image { +public: + VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname); + ~VectorImage(); + + uint getElementCount() const { + return _elements.size(); + } + const VectorImageElement &getElement(uint elementNr) const { + BS_ASSERT(elementNr < _elements.size()); + return _elements[elementNr]; + } + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + + // + // Die abstrakten Methoden von BS_Image + // + virtual int getWidth() const { + return _boundingBox.width(); + } + virtual int getHeight() const { + return _boundingBox.height(); + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + virtual bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)); + + void render(int width, int height); + + virtual uint getPixel(int x, int y); + virtual bool isBlitSource() const { + return true; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return true; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return true; + } + virtual bool isColorModulationAllowed() const { + return true; + } + virtual bool isSetContentAllowed() const { + return false; + } + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + virtual bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + + class SWFBitStream; + +private: + bool parseDefineShape(uint shapeType, SWFBitStream &bs); + bool parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits); + + ArtBpath *storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated); + Common::Array<VectorImageElement> _elements; + Common::Rect _boundingBox; + + byte *_pixelData; + + Common::String _fname; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/vectorimagerenderer.cpp b/engines/sword25/gfx/image/vectorimagerenderer.cpp new file mode 100644 index 0000000000..43ac8683ac --- /dev/null +++ b/engines/sword25/gfx/image/vectorimagerenderer.cpp @@ -0,0 +1,461 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code contains portions of Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* + * This code contains portions of Swfdec + * + * Copyright (c) 2004-2006 David Schleef <ds@schleef.org> + * + * Licensed under GNU GPL v2 + * + */ + +#include "art.h" + +#include "sword25/gfx/image/vectorimage.h" +#include "graphics/colormasks.h" + +namespace Sword25 { + +void art_rgb_fill_run1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) { + int i; + + if (r == g && g == b && r == 255) { + memset(buf, g, n + n + n + n); + } else { + uint32 *alt = (uint32 *)buf; + uint32 color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(0xff, r, g, b); + + for (i = 0; i < n; i++) + *alt++ = color; + } +} + +void art_rgb_run_alpha1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) { + int i; + int v; + + for (i = 0; i < n; i++) { + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = MIN(v + alpha, 0xff); + } +} + +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void art_rgb_svp_alpha_callback1(void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } else { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + + data->buf += data->rowstride; +} + +static void art_rgb_svp_alpha_opaque_callback1(void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, run_x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x0 - x0) * 4, r, g, b, run_x1 - run_x0); + else + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x1 - x0) * 4, r, g, b, x1 - run_x1); + else + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } + } else { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + } + + data->buf += data->rowstride; +} + +void art_rgb_svp_alpha1(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + uint32 color, + art_u8 *buf, int rowstride) { + ArtRgbSVPAlphaData data; + byte r, g, b, alpha; + int i; + int a, da; + + Graphics::colorToARGB<Graphics::ColorMasks<8888> >(color, alpha, r, g, b); + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback1, &data); + else + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback1, &data); +} + +static int art_vpath_len(ArtVpath *a) { + int i = 0; + while (a[i].code != ART_END) + i++; + return i; +} + +ArtVpath *art_vpath_cat(ArtVpath *a, ArtVpath *b) { + ArtVpath *dest; + ArtVpath *p; + int len_a, len_b; + + len_a = art_vpath_len(a); + len_b = art_vpath_len(b); + dest = art_new(ArtVpath, len_a + len_b + 1); + p = dest; + + for (int i = 0; i < len_a; i++) + *p++ = *a++; + for (int i = 0; i <= len_b; i++) + *p++ = *b++; + + return dest; +} + +void art_svp_make_convex(ArtSVP *svp) { + int i; + + if (svp->n_segs > 0 && svp->segs[0].dir == 0) { + for (i = 0; i < svp->n_segs; i++) { + svp->segs[i].dir = !svp->segs[i].dir; + } + } +} + +ArtVpath *art_vpath_reverse(ArtVpath *a) { + ArtVpath *dest; + ArtVpath it; + int len; + int state = 0; + int i; + + len = art_vpath_len(a); + dest = art_new(ArtVpath, len + 1); + + for (i = 0; i < len; i++) { + it = a[len - i - 1]; + if (state) { + it.code = ART_LINETO; + } else { + it.code = ART_MOVETO_OPEN; + state = 1; + } + if (a[len - i - 1].code == ART_MOVETO || + a[len - i - 1].code == ART_MOVETO_OPEN) { + state = 0; + } + dest[i] = it; + } + dest[len] = a[len]; + + return dest; +} + +ArtVpath *art_vpath_reverse_free(ArtVpath *a) { + ArtVpath *dest; + + dest = art_vpath_reverse(a); + free(a); + + return dest; +} + +void drawBez(ArtBpath *bez1, ArtBpath *bez2, art_u8 *buffer, int width, int height, int deltaX, int deltaY, double scaleX, double scaleY, double penWidth, unsigned int color) { + ArtVpath *vec = NULL; + ArtVpath *vec1 = NULL; + ArtVpath *vec2 = NULL; + ArtSVP *svp = NULL; + +#if 0 + const char *codes[] = {"ART_MOVETO", "ART_MOVETO_OPEN", "ART_CURVETO", "ART_LINETO", "ART_END"}; + for (int i = 0;; i++) { + printf(" bez[%d].code = %s;\n", i, codes[bez[i].code]); + if (bez[i].code == ART_END) + break; + if (bez[i].code == ART_CURVETO) { + printf(" bez[%d].x1 = %f; bez[%d].y1 = %f;\n", i, bez[i].x1, i, bez[i].y1); + printf(" bez[%d].x2 = %f; bez[%d].y2 = %f;\n", i, bez[i].x2, i, bez[i].y2); + } + printf(" bez[%d].x3 = %f; bez[%d].y3 = %f;\n", i, bez[i].x3, i, bez[i].y3); + } + + printf(" drawBez(bez, buffer, 1.0, 1.0, %f, 0x%08x);\n", penWidth, color); +#endif + + // HACK: Some frames have green bounding boxes drawn. + // Perhaps they were used by original game artist Umriss + // We skip them just like the original + if (bez2 == 0 && color == Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(0xff, 0x00, 0xff, 0x00)) { + return; + } + + vec1 = art_bez_path_to_vec(bez1, 0.5); + if (bez2 != 0) { + vec2 = art_bez_path_to_vec(bez2, 0.5); + vec2 = art_vpath_reverse_free(vec2); + vec = art_vpath_cat(vec1, vec2); + + free(vec1); + free(vec2); + } else { + vec = vec1; + } + + int size = art_vpath_len(vec); + ArtVpath *vect = art_new(ArtVpath, size + 1); + + int k; + for (k = 0; k < size; k++) { + vect[k].code = vec[k].code; + vect[k].x = (vec[k].x - deltaX) * scaleX; + vect[k].y = (vec[k].y - deltaY) * scaleY; + } + vect[k].code = ART_END; + + if (bez2 == 0) { // Line drawing + svp = art_svp_vpath_stroke(vect, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5); + } else { + svp = art_svp_from_vpath(vect); + art_svp_make_convex(svp); + } + + art_rgb_svp_alpha1(svp, 0, 0, width, height, color, buffer, width * 4); + + free(vect); + art_svp_free(svp); + free(vec); +} + +void VectorImage::render(int width, int height) { + double scaleX = (width == - 1) ? 1 : static_cast<double>(width) / static_cast<double>(getWidth()); + double scaleY = (height == - 1) ? 1 : static_cast<double>(height) / static_cast<double>(getHeight()); + + debug(3, "VectorImage::render(%d, %d) %s", width, height, _fname.c_str()); + + if (_pixelData) + free(_pixelData); + + _pixelData = (byte *)malloc(width * height * 4); + memset(_pixelData, 0, width * height * 4); + + for (uint e = 0; e < _elements.size(); e++) { + + //// Draw shapes + for (uint s = 0; s < _elements[e].getFillStyleCount(); s++) { + int fill0len = 0; + int fill1len = 0; + + // Count vector sizes in order to minimize memory + // fragmentation + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) + fill0len += _elements[e].getPathInfo(p).getVecLen(); + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) + fill1len += _elements[e].getPathInfo(p).getVecLen(); + } + + // Now lump together vectors + ArtBpath *fill1 = art_new(ArtBpath, fill1len + 1); + ArtBpath *fill0 = art_new(ArtBpath, fill0len + 1); + ArtBpath *fill1pos = fill1; + ArtBpath *fill0pos = fill0; + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill0pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill1pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + } + + // Close vectors + (*fill0pos).code = ART_END; + (*fill1pos).code = ART_END; + + drawBez(fill1, fill0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, -1, _elements[e].getFillStyleColor(s)); + + free(fill0); + free(fill1); + } + + //// Draw strokes + for (uint s = 0; s < _elements[e].getLineStyleCount(); s++) { + double penWidth = _elements[e].getLineStyleWidth(s); + penWidth *= sqrt(fabs(scaleX * scaleY)); + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getLineStyle() == s + 1) { + drawBez(_elements[e].getPathInfo(p).getVec(), 0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, penWidth, _elements[e].getLineStyleColor(s)); + } + } + } + } +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/panel.cpp b/engines/sword25/gfx/panel.cpp new file mode 100644 index 0000000000..2de1de133a --- /dev/null +++ b/engines/sword25/gfx/panel.cpp @@ -0,0 +1,111 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/panel.h" + +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/image/image.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "PANEL" + +Panel::Panel(RenderObjectPtr<RenderObject> parentPtr, int width, int height, uint color) : + RenderObject(parentPtr, RenderObject::TYPE_PANEL), + _color(color) { + _initSuccess = false; + + _width = width; + _height = height; + + if (_width < 0) { + BS_LOG_ERRORLN("Tried to initialise a panel with an invalid width (%d).", _width); + return; + } + + if (_height < 0) { + BS_LOG_ERRORLN("Tried to initialise a panel with an invalid height (%d).", _height); + return; + } + + _initSuccess = true; +} + +Panel::Panel(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + RenderObject(parentPtr, RenderObject::TYPE_PANEL, handle) { + _initSuccess = unpersist(reader); +} + +Panel::~Panel() { +} + +bool Panel::doRender() { + // Falls der Alphawert 0 ist, ist das Panel komplett durchsichtig und es muss nichts gezeichnet werden. + if (_color >> 24 == 0) + return true; + + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + return gfxPtr->fill(&_bbox, _color); +} + +bool Panel::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + writer.write(_color); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool Panel::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + uint color; + reader.read(color); + setColor(color); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/panel.h b/engines/sword25/gfx/panel.h new file mode 100644 index 0000000000..6fe96369a6 --- /dev/null +++ b/engines/sword25/gfx/panel.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PANEL_H +#define SWORD25_PANEL_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Panel : public RenderObject { + friend class RenderObject; + +private: + Panel(RenderObjectPtr<RenderObject> parentPtr, int width, int height, uint color); + Panel(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + virtual ~Panel(); + + uint getColor() const { + return _color; + } + void setColor(uint color) { + _color = color; + forceRefresh(); + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + uint _color; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobject.cpp b/engines/sword25/gfx/renderobject.cpp new file mode 100644 index 0000000000..77af0bee92 --- /dev/null +++ b/engines/sword25/gfx/renderobject.cpp @@ -0,0 +1,517 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobject.h" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/gfx/renderobjectregistry.h" +#include "sword25/gfx/renderobjectmanager.h" +#include "sword25/gfx/graphicengine.h" + +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/staticbitmap.h" +#include "sword25/gfx/dynamicbitmap.h" +#include "sword25/gfx/animation.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/text.h" +#include "sword25/gfx/animationtemplate.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECT" + +RenderObject::RenderObject(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle) : + _managerPtr(0), + _parentPtr(parentPtr), + _x(0), + _y(0), + _z(0), + _oldX(-1), + _oldY(-1), + _oldZ(-1), + _width(0), + _height(0), + _visible(true), + _oldVisible(false), + _childChanged(true), + _type(type), + _initSuccess(false), + _refreshForced(true), + _handle(0) { + + // Renderobject registrieren, abhängig vom Handle-Parameter entweder mit beliebigem oder vorgegebenen Handle. + if (handle == 0) + _handle = RenderObjectRegistry::instance().registerObject(this); + else + _handle = RenderObjectRegistry::instance().registerObject(this, handle); + + if (_handle == 0) + return; + + updateAbsolutePos(); + + // Dieses Objekt zu den Kindern der Elternobjektes hinzufügen, falls nicht Wurzel (ParentPtr ungültig) und dem + // selben RenderObjektManager zuweisen. + if (_parentPtr.isValid()) { + _managerPtr = _parentPtr->getManager(); + _parentPtr->addObject(this->getHandle()); + } else { + if (getType() != TYPE_ROOT) { + BS_LOG_ERRORLN("Tried to create a non-root render object and has no parent. All non-root render objects have to have a parent."); + return; + } + } + + updateObjectState(); + + _initSuccess = true; +} + +RenderObject::~RenderObject() { + // Objekt aus dem Elternobjekt entfernen. + if (_parentPtr.isValid()) + _parentPtr->detatchChildren(this->getHandle()); + + deleteAllChildren(); + + // Objekt deregistrieren. + RenderObjectRegistry::instance().deregisterObject(this); +} + +bool RenderObject::render() { + // Objektänderungen validieren + validateObject(); + + // Falls das Objekt nicht sichtbar ist, muss gar nichts gezeichnet werden + if (!_visible) + return true; + + // Falls notwendig, wird die Renderreihenfolge der Kinderobjekte aktualisiert. + if (_childChanged) { + sortRenderObjects(); + _childChanged = false; + } + + // Objekt zeichnen. + doRender(); + + // Dann müssen die Kinder gezeichnet werden + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (!(*it)->render()) + return false; + + return true; +} + +void RenderObject::validateObject() { + // Die Veränderungen in den Objektvariablen aufheben + _oldBbox = _bbox; + _oldVisible = _visible; + _oldX = _x; + _oldY = _y; + _oldZ = _z; + _refreshForced = false; +} + +bool RenderObject::updateObjectState() { + // Falls sich das Objekt verändert hat, muss der interne Zustand neu berechnet werden und evtl. Update-Regions für den nächsten Frame + // registriert werden. + if ((calcBoundingBox() != _oldBbox) || + (_visible != _oldVisible) || + (_x != _oldX) || + (_y != _oldY) || + (_z != _oldZ) || + _refreshForced) { + // Renderrang des Objektes neu bestimmen, da sich dieser verändert haben könnte + if (_parentPtr.isValid()) + _parentPtr->signalChildChange(); + + // Die Bounding-Box neu berechnen und Update-Regions registrieren. + updateBoxes(); + + // Änderungen Validieren + validateObject(); + } + + // Dann muss der Objektstatus der Kinder aktualisiert werden. + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (!(*it)->updateObjectState()) + return false; + + return true; +} + +void RenderObject::updateBoxes() { + // Bounding-Box aktualisieren + _bbox = calcBoundingBox(); +} + +Common::Rect RenderObject::calcBoundingBox() const { + // Die Bounding-Box mit der Objektgröße initialisieren. + Common::Rect bbox(0, 0, _width, _height); + + // Die absolute Position der Bounding-Box berechnen. + bbox.translate(_absoluteX, _absoluteY); + + // Die Bounding-Box am Elternobjekt clippen. + if (_parentPtr.isValid()) { + bbox.clip(_parentPtr->getBbox()); + } + + return bbox; +} + +void RenderObject::calcAbsolutePos(int &x, int &y) const { + x = calcAbsoluteX(); + y = calcAbsoluteY(); +} + +int RenderObject::calcAbsoluteX() const { + if (_parentPtr.isValid()) + return _parentPtr->getAbsoluteX() + _x; + else + return _x; +} + +int RenderObject::calcAbsoluteY() const { + if (_parentPtr.isValid()) + return _parentPtr->getAbsoluteY() + _y; + else + return _y; +} + +void RenderObject::deleteAllChildren() { + // Es ist nicht notwendig die Liste zu iterieren, da jedes Kind für sich DetatchChildren an diesem Objekt aufruft und sich somit + // selber entfernt. Daher muss immer nur ein beliebiges Element (hier das letzte) gelöscht werden, bis die Liste leer ist. + while (!_children.empty()) { + RenderObjectPtr<RenderObject> curPtr = _children.back(); + curPtr.erase(); + } +} + +bool RenderObject::addObject(RenderObjectPtr<RenderObject> pObject) { + if (!pObject.isValid()) { + BS_LOG_ERRORLN("Tried to add a null object to a renderobject."); + return false; + } + + // Objekt in die Kinderliste einfügen. + _children.push_back(pObject); + + // Sicherstellen, dass vor dem nächsten Rendern die Renderreihenfolge aktualisiert wird. + if (_parentPtr.isValid()) + _parentPtr->signalChildChange(); + + return true; +} + +bool RenderObject::detatchChildren(RenderObjectPtr<RenderObject> pObject) { + // Kinderliste durchgehen und Objekt entfernen falls vorhanden + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (*it == pObject) { + _children.erase(it); + return true; + } + + BS_LOG_ERRORLN("Tried to detach children from a render object that isn't its parent."); + return false; +} + +void RenderObject::sortRenderObjects() { + Common::sort(_children.begin(), _children.end(), greater); +} + +void RenderObject::updateAbsolutePos() { + calcAbsolutePos(_absoluteX, _absoluteY); + + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + (*it)->updateAbsolutePos(); +} + +bool RenderObject::getObjectIntersection(RenderObjectPtr<RenderObject> pObject, Common::Rect &result) { + result = pObject->getBbox(); + result.clip(_bbox); + return result.isValidRect(); +} + +void RenderObject::setPos(int x, int y) { + _x = x; + _y = y; + updateAbsolutePos(); +} + +void RenderObject::setX(int x) { + _x = x; + updateAbsolutePos(); +} + +void RenderObject::setY(int y) { + _y = y; + updateAbsolutePos(); +} + +void RenderObject::setZ(int z) { + if (z < 0) + BS_LOG_ERRORLN("Tried to set a negative Z value (%d).", z); + else + _z = z; +} + +void RenderObject::setVisible(bool visible) { + _visible = visible; +} + +RenderObjectPtr<Animation> RenderObject::addAnimation(const Common::String &filename) { + RenderObjectPtr<Animation> aniPtr((new Animation(this->getHandle(), filename))->getHandle()); + if (aniPtr.isValid() && aniPtr->getInitSuccess()) + return aniPtr; + else { + if (aniPtr.isValid()) + aniPtr.erase(); + return RenderObjectPtr<Animation>(); + } +} + +RenderObjectPtr<Animation> RenderObject::addAnimation(const AnimationTemplate &animationTemplate) { + Animation *aniPtr = new Animation(this->getHandle(), animationTemplate); + if (aniPtr && aniPtr->getInitSuccess()) + return aniPtr->getHandle(); + else { + delete aniPtr; + return RenderObjectPtr<Animation>(); + } +} + +RenderObjectPtr<Bitmap> RenderObject::addBitmap(const Common::String &filename) { + RenderObjectPtr<Bitmap> bitmapPtr((new StaticBitmap(this->getHandle(), filename))->getHandle()); + if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) + return RenderObjectPtr<Bitmap>(bitmapPtr); + else { + if (bitmapPtr.isValid()) + bitmapPtr.erase(); + return RenderObjectPtr<Bitmap>(); + } +} + +RenderObjectPtr<Bitmap> RenderObject::addDynamicBitmap(uint width, uint height) { + RenderObjectPtr<Bitmap> bitmapPtr((new DynamicBitmap(this->getHandle(), width, height))->getHandle()); + if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) + return bitmapPtr; + else { + if (bitmapPtr.isValid()) + bitmapPtr.erase(); + return RenderObjectPtr<Bitmap>(); + } +} + +RenderObjectPtr<Panel> RenderObject::addPanel(int width, int height, uint color) { + RenderObjectPtr<Panel> panelPtr((new Panel(this->getHandle(), width, height, color))->getHandle()); + if (panelPtr.isValid() && panelPtr->getInitSuccess()) + return panelPtr; + else { + if (panelPtr.isValid()) + panelPtr.erase(); + return RenderObjectPtr<Panel>(); + } +} + +RenderObjectPtr<Text> RenderObject::addText(const Common::String &font, const Common::String &text) { + RenderObjectPtr<Text> textPtr((new Text(this->getHandle()))->getHandle()); + if (textPtr.isValid() && textPtr->getInitSuccess() && textPtr->setFont(font)) { + textPtr->setText(text); + return textPtr; + } else { + if (textPtr.isValid()) + textPtr.erase(); + return RenderObjectPtr<Text>(); + } +} + +bool RenderObject::persist(OutputPersistenceBlock &writer) { + // Typ und Handle werden als erstes gespeichert, damit beim Laden ein Objekt vom richtigen Typ mit dem richtigen Handle erzeugt werden kann. + writer.write(static_cast<uint>(_type)); + writer.write(_handle); + + // Restliche Objekteigenschaften speichern. + writer.write(_x); + writer.write(_y); + writer.write(_absoluteX); + writer.write(_absoluteY); + writer.write(_z); + writer.write(_width); + writer.write(_height); + writer.write(_visible); + writer.write(_childChanged); + writer.write(_initSuccess); + writer.write(_bbox.left); + writer.write(_bbox.top); + writer.write(_bbox.right); + writer.write(_bbox.bottom); + writer.write(_oldBbox.left); + writer.write(_oldBbox.top); + writer.write(_oldBbox.right); + writer.write(_oldBbox.bottom); + writer.write(_oldX); + writer.write(_oldY); + writer.write(_oldZ); + writer.write(_oldVisible); + writer.write(_parentPtr.isValid() ? _parentPtr->getHandle() : 0); + writer.write(_refreshForced); + + return true; +} + +bool RenderObject::unpersist(InputPersistenceBlock &reader) { + // Typ und Handle wurden schon von RecreatePersistedRenderObject() ausgelesen. Jetzt werden die restlichen Objekteigenschaften ausgelesen. + reader.read(_x); + reader.read(_y); + reader.read(_absoluteX); + reader.read(_absoluteY); + reader.read(_z); + reader.read(_width); + reader.read(_height); + reader.read(_visible); + reader.read(_childChanged); + reader.read(_initSuccess); + reader.read(_bbox.left); + reader.read(_bbox.top); + reader.read(_bbox.right); + reader.read(_bbox.bottom); + reader.read(_oldBbox.left); + reader.read(_oldBbox.top); + reader.read(_oldBbox.right); + reader.read(_oldBbox.bottom); + reader.read(_oldX); + reader.read(_oldY); + reader.read(_oldZ); + reader.read(_oldVisible); + uint parentHandle; + reader.read(parentHandle); + _parentPtr = RenderObjectPtr<RenderObject>(parentHandle); + reader.read(_refreshForced); + + updateAbsolutePos(); + updateObjectState(); + + return reader.isGood(); +} + +bool RenderObject::persistChildren(OutputPersistenceBlock &writer) { + bool result = true; + + // Kinderanzahl speichern. + writer.write(_children.size()); + + // Rekursiv alle Kinder speichern. + RENDEROBJECT_LIST::iterator it = _children.begin(); + while (it != _children.end()) { + result &= (*it)->persist(writer); + ++it; + } + + return result; +} + +bool RenderObject::unpersistChildren(InputPersistenceBlock &reader) { + bool result = true; + + // Kinderanzahl einlesen. + uint childrenCount; + reader.read(childrenCount); + if (!reader.isGood()) + return false; + + // Alle Kinder rekursiv wieder herstellen. + for (uint i = 0; i < childrenCount; ++i) { + if (!recreatePersistedRenderObject(reader).isValid()) + return false; + } + + return result && reader.isGood(); +} + +RenderObjectPtr<RenderObject> RenderObject::recreatePersistedRenderObject(InputPersistenceBlock &reader) { + RenderObjectPtr<RenderObject> result; + + // Typ und Handle auslesen. + uint type; + uint handle; + reader.read(type); + reader.read(handle); + if (!reader.isGood()) + return result; + + switch (type) { + case TYPE_PANEL: + result = (new Panel(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_STATICBITMAP: + result = (new StaticBitmap(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_DYNAMICBITMAP: + result = (new DynamicBitmap(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_TEXT: + result = (new Text(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_ANIMATION: + result = (new Animation(reader, this->getHandle(), handle))->getHandle(); + break; + + default: + BS_LOG_ERRORLN("Cannot recreate render object of unknown type %d.", type); + } + + return result; +} + +bool RenderObject::greater(const RenderObjectPtr<RenderObject> lhs, const RenderObjectPtr<RenderObject> rhs) { + // Das Objekt mit dem kleinem Z-Wert müssen zuerst gerendert werden. + if (lhs->_z != rhs->_z) + return lhs->_z < rhs->_z; + // Falls der Z-Wert gleich ist, wird das weiter oben gelegenen Objekte zuerst gezeichnet. + return lhs->_y < rhs->_y; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobject.h b/engines/sword25/gfx/renderobject.h new file mode 100644 index 0000000000..7b7b9047f7 --- /dev/null +++ b/engines/sword25/gfx/renderobject.h @@ -0,0 +1,525 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_RenderObject + --------------- + Dieses ist die Klasse die sämtliche sichtbaren Objekte beschreibt. Alle anderen sichtbaren Objekte müssen von ihr abgeleitet werden. + Diese Klasse erledigt Aufgaben wie: minimales Neuzeichnen, Renderreihenfolge, Objekthierachie. + Alle BS_RenderObject Instanzen werden von einem BS_RenderObjectManager in einem Baum verwaltet. + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_RENDEROBJECT_H +#define SWORD25_RENDEROBJECT_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "common/rect.h" +#include "sword25/gfx/renderobjectptr.h" + +#include "common/list.h" + +namespace Sword25 { + +class Kernel; +class RenderObjectManager; +class Bitmap; +class Animation; +class AnimationTemplate; +class Panel; +class Text; + +// Klassendefinition +/** + @brief Dieses ist die Klasse die sämtliche sichtbaren Objekte beschreibt. + + Alle anderen sichtbaren Objekte müssen von ihr abgeleitet werden. + Diese Klasse erledigt Aufgaben wie: minimales Neuzeichnen, Renderreihenfolge, Objekthierachie. + Alle BS_RenderObject Instanzen werden von einem BS_RenderObjektManager in einem Baum verwaltet. + */ +class RenderObject { +public: + // Konstanten + // ---------- + enum TYPES { + /// Das Wurzelobjekt. Siehe BS_RenderObjectManager + TYPE_ROOT, + /// Ein Image. Siehe BS_Bitmap. + TYPE_STATICBITMAP, + TYPE_DYNAMICBITMAP, + /// Eine Animation. Siehe BS_Animation. + TYPE_ANIMATION, + /// Eine farbige Fläche. Siehe BS_Panel. + TYPE_PANEL, + /// Ein Text. Siehe BS_Text. + TYPE_TEXT, + /// Ein unbekannter Objekttyp. Diesen Typ sollte kein Renderobjekt annehmen. + TYPE_UNKNOWN + }; + + // Add-Methoden + // ------------ + + /** + @brief Erzeugt ein Bitmap als Kinderobjekt des Renderobjektes. + @param FileName der Dateiname der Quellbilddatei + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> addBitmap(const Common::String &fileName); + /** + @brief Erzeugt ein veränderbares Bitmap als Kinderobjekt des Renderobjektes. + @param Width die Breite des Bitmaps + @param Height die Höhe des Bitmaps + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> addDynamicBitmap(uint width, uint height); + /** + @brief Erzeugt eine Animation auf Basis einer Animationsdatei als Kinderobjekt des Renderobjektes. + @param FileName der Dateiname der Quelldatei + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Animation> addAnimation(const Common::String &fileName); + /** + @brief Erzeugt eine Animation auf Basis eines Animationstemplate als Kinderobjekt des Renderobjektes. + @param pAnimationTemplate ein Pointer auf das Animationstemplate + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + @remark Das Renderobjekt übernimmt die Verwaltung des Animationstemplate. + */ + RenderObjectPtr<Animation> addAnimation(const AnimationTemplate &animationTemplate); + /** + @brief Erzeugt ein neues Farbpanel als Kinderobjekt des Renderobjektes. + @param Width die Breite des Panels + @param Height die Höhe des Panels + @param Color die Farbe des Panels.<br> + Der Standardwert ist Schwarz (BS_RGB(0, 0, 0)). + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + + RenderObjectPtr<Panel> addPanel(int width, int height, uint color = 0xff000000); + /** + @brief Erzeugt ein Textobjekt als Kinderobjekt des Renderobjektes. + @param Font der Dateiname des zu verwendenen Fonts + @param Text der anzuzeigende Text.<br> + Der Standardwert ist "". + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Text> addText(const Common::String &font, const Common::String &text = ""); + + // Cast-Methoden + // ------------- + /** + @brief Castet das Objekt zu einem BS_Bitmap-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> toBitmap() { + if (_type == TYPE_STATICBITMAP || _type == TYPE_DYNAMICBITMAP) + return RenderObjectPtr<Bitmap>(this->getHandle()); + else + return RenderObjectPtr<Bitmap>(); + } + /** + @brief Castet das Objekt zu einem BS_Animation-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Animation> toAnimation() { + if (_type == TYPE_ANIMATION) + return RenderObjectPtr<Animation>(this->getHandle()); + else + return RenderObjectPtr<Animation>(); + } + /** + @brief Castet das Objekt zu einem BS_Panel-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Panel> toPanel() { + if (_type == TYPE_PANEL) + return RenderObjectPtr<Panel>(this->getHandle()); + else + return RenderObjectPtr<Panel>(); + } + /** + @brief Castet das Object zu einem BS_Text-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Text> toText() { + if (_type == TYPE_TEXT) + return RenderObjectPtr<Text>(this->getHandle()); + else + return RenderObjectPtr<Text>(); + } + + // Konstruktor / Desktruktor + // ------------------------- + /** + @brief Erzeugt ein neues BS_RenderObject. + @param pKernel ein Pointer auf den Kernel + @param pParent ein Pointer auf das Elternobjekt des neuen Objektes im Objektbaum.<br> + Der Pointer darf nicht NULL sein. + @param Type der Objekttyp<br> + Der Typ BS_RenderObject::TYPE_ROOT ist nicht zulässig. Wurzelknoten müssen mit dem alternativen Konstruktor erzeugt + werden. + @param Handle das Handle, welches dem Objekt zugewiesen werden soll.<br> + Dieser Parameter erzwingt ein bestimmtes Handle für das neue Objekt, oder wählt automatisch ein Handle, wenn der Parameter 0 ist. + Ist das gewünschte Handle bereits vergeben, gibt GetInitSuccess() false zurück.<br> + Der Standardwert ist 0. + @remark Nach dem Aufruf des Konstruktors kann über die Methode GetInitSuccess() abgefragt werden, ob die Konstruktion erfolgreich war.<br> + Es ist nicht notwendig alle BS_RenderObject Instanzen einzeln zu löschen. Dieses geschiet automatisch beim Löschen eines + Vorfahren oder beim Löschen des zuständigen BS_RenderObjectManager. + */ + RenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle = 0); + virtual ~RenderObject(); + + // Interface + // --------- + /** + @brief Rendert des Objekt und alle seine Unterobjekte. + @return Gibt false zurück, falls beim Rendern ein Fehler aufgetreten ist. + @remark Vor jedem Aufruf dieser Methode muss ein Aufruf von UpdateObjectState() erfolgt sein. + Dieses kann entweder direkt geschehen oder durch den Aufruf von UpdateObjectState() an einem Vorfahren-Objekt.<br> + Diese Methode darf nur von BS_RenderObjectManager aufgerufen werden. + */ + bool render(); + /** + @brief Bereitet das Objekt und alle seine Unterobjekte auf einen Rendervorgang vor. + Hierbei werden alle Dirty-Rectangles berechnet und die Renderreihenfolge aktualisiert. + @return Gibt false zurück, falls ein Fehler aufgetreten ist. + @remark Diese Methode darf nur von BS_RenderObjectManager aufgerufen werden. + */ + bool updateObjectState(); + /** + @brief Löscht alle Kinderobjekte. + */ + void deleteAllChildren(); + + // Accessor-Methoden + // ----------------- + /** + @brief Setzt die Position des Objektes. + @param X die neue X-Koordinate des Objektes relativ zum Elternobjekt. + @param Y die neue Y-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setPos(int x, int y); + /** + @brief Setzt die Position des Objektes auf der X-Achse. + @param X die neue X-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setX(int x); + /** + @brief Setzt die Position des Objektes auf der Y-Achse. + @param Y die neue Y-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setY(int y); + /** + @brief Setzt den Z-Wert des Objektes. + @param Z der neue Z-Wert des Objektes relativ zum Elternobjekt<br> + Negative Z-Werte sind nicht zulässig. + @remark Der Z-Wert legt die Renderreihenfolge der Objekte fest. Objekte mit niedrigem Z-Wert werden vor Objekten mit höherem + Z-Wert gezeichnet. Je höher der Z-Wert desto weiter "vorne" liegt ein Objekt also.<br> + Wie alle andere Attribute ist auch dieses relativ zum Elternobjekt, ein Kinderobjekt kann also nie unter seinem + Elternobjekt liegen, auch wenn es einen Z-Wert von 0 hat. + */ + virtual void setZ(int z); + /** + @brief Setzt die Sichtbarkeit eine Objektes. + @param Visible der neue Sichtbarkeits-Zustand des Objektes<br> + true entspricht sichtbar, false entspricht unsichtbar. + */ + virtual void setVisible(bool visible); + /** + @brief Gibt die Position des Objektes auf der X-Achse relativ zum Elternobjekt zurück. + */ + virtual int getX() const { + return _x; + } + /** + @brief Gibt die Position des Objektes auf der Y-Achse relativ zum Elternobjekt zurück. + */ + virtual int getY() const { + return _y; + } + /** + @brief Gibt die absolute Position des Objektes auf der X-Achse zurück. + */ + virtual int getAbsoluteX() const { + return _absoluteX; + } + /** + @brief Gibt die absolute Position des Objektes auf der Y-Achse zurück. + */ + virtual int getAbsoluteY() const { + return _absoluteY; + } + /** + @brief Gibt den Z-Wert des Objektes relativ zum Elternobjekt zurück. + @remark Der Z-Wert legt die Renderreihenfolge der Objekte fest. Objekte mit niedrigem Z-Wert werden vor Objekten mit höherem + Z-Wert gezeichnet. Je höher der Z-Wert desto weiter "vorne" liegt ein Objekt also.<br> + Wie alle andere Attribute ist auch dieses relativ zum Elternobjekt, ein Kinderobjekt kann also nie unter seinem + Elternobjekt liegen, auch wenn es einen Z-Wert von 0 hat. + */ + int getZ() const { + return _z; + } + /** + @brief Gibt die Breite des Objektes zurück. + */ + int getWidth() const { + return _width; + } + /** + @brief Gibt die Höhe des Objektes zurück. + */ + int getHeight() const { + return _height; + } + /** + @brief Gibt den Sichtbarkeitszustand des Objektes zurück. + @return Gibt den Sichtbarkeitszustand des Objektes zurück.<br> + true entspricht sichtbar, false entspricht unsichtbar. + */ + bool isVisible() const { + return _visible; + } + /** + @brief Gibt den Typ des Objektes zurück. + */ + TYPES getType() const { + return _type; + } + /** + @brief Gibt zurück, ob das Objekt erfolgreich initialisiert wurde. + @remark Hässlicher Workaround um das Problem, dass Konstruktoren keine Rückgabewerte haben. + */ + bool getInitSuccess() const { + return _initSuccess; + } + /** + @brief Gibt die Bounding-Box des Objektes zurück. + @remark Diese Angabe erfolgt ausnahmsweise in Bildschirmkoordianten und nicht relativ zum Elternobjekt. + */ + const Common::Rect &getBbox() const { + return _bbox; + } + /** + @brief Stellt sicher, dass das Objekt im nächsten Frame neu gezeichnet wird. + */ + void forceRefresh() { + _refreshForced = true; + } + /** + @brief Gibt das Handle des Objekte zurück. + */ + uint getHandle() const { + return _handle; + } + + // Persistenz-Methoden + // ------------------- + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + // TODO: Evtl. protected + bool persistChildren(OutputPersistenceBlock &writer); + bool unpersistChildren(InputPersistenceBlock &reader); + // TODO: Evtl. private + RenderObjectPtr<RenderObject> recreatePersistedRenderObject(InputPersistenceBlock &reader); + +protected: + // Typen + // ----- + typedef Common::List<RenderObjectPtr<RenderObject> > RENDEROBJECT_LIST; + typedef Common::List<RenderObjectPtr<RenderObject> >::iterator RENDEROBJECT_ITER; + + int _x; ///< Die X-Position des Objektes relativ zum Eltern-Objekt + int _y; ///< Die Y-Position des Objektes relativ zum Eltern-Objekt + int _absoluteX; ///< Die absolute X-Position des Objektes + int _absoluteY; ///< Die absolute Y-Position des Objektes + int _z; ///< Der Z-Wert des Objektes relativ zum Eltern-Objekt + int _width; ///< Die Breite des Objektes + int _height; ///< Die Höhe des Objektes + bool _visible; ///< Ist true, wenn das Objekt sichtbar ist + bool _childChanged; ///< Ist true, wenn sich ein Kinderobjekt verändert hat + TYPES _type; ///< Der Objekttyp + bool _initSuccess; ///< Ist true, wenn Objekt erfolgreich intialisiert werden konnte + Common::Rect _bbox; ///< Die Bounding-Box des Objektes in Bildschirmkoordinaten + + // Kopien der Variablen, die für die Errechnung des Dirty-Rects und zur Bestimmung der Objektveränderung notwendig sind + Common::Rect _oldBbox; + int _oldX; + int _oldY; + int _oldZ; + bool _oldVisible; + + /// Ein Pointer auf den BS_RenderObjektManager, der das Objekt verwaltet. + RenderObjectManager *_managerPtr; + + // Render-Methode + // -------------- + /** + @brief Einschubmethode, die den tatsächlichen Redervorgang durchführt. + + Diese Methode wird von Render() aufgerufen um das Objekt darzustellen. + Diese Methode sollte von allen Klassen implementiert werden, die von BS_RederObject erben, um das Zeichnen umzusetzen. + + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark + */ + virtual bool doRender() = 0; // { return true; }; + + // RenderObject-Baum Variablen + // --------------------------- + // Der Baum legt die hierachische Ordnung der BS_RenderObjects fest. + // Alle Eigenschaften wie X, Y, Z und Visible eines BS_RenderObjects sind relativ zu denen seines Vaters. + // Außerdem sind die Objekte von links nach rechts in ihrer Renderreihenfolge sortiert. + // Das primäre Sortierkriterium ist hierbei der Z-Wert und das sekundäre der Y-Wert (von oben nach unten). + // Beispiel: + // Screen + // / | \. + // / | \. + // / | \. + // / | \. + // Background Interface Maus + // / \ / | \. + // / \ / | \. + // George Tür Icn1 Icn2 Icn3 + // + // Wenn jetzt das Interface mit SetVisible() ausgeblendet würde, verschwinden auch die Icons, die sich im Interface + // befinden. + // Wenn der Hintergrund bewegt wird (Scrolling), bewegen sich auch die darauf befindlichen Gegenstände und Personen. + + /// Ein Pointer auf das Elternobjekt. + RenderObjectPtr<RenderObject> _parentPtr; + /// Die Liste der Kinderobjekte nach der Renderreihenfolge geordnet + RENDEROBJECT_LIST _children; + + /** + @brief Gibt einen Pointer auf den BS_RenderObjektManager zurück, der das Objekt verwaltet. + */ + RenderObjectManager *getManager() const { + return _managerPtr; + } + /** + @brief Fügt dem Objekt ein neues Kinderobjekt hinzu. + @param pObject ein Pointer auf das einzufügende Objekt + @return Gibt false zurück, falls das Objekt nicht eingefügt werden konnte. + */ + bool addObject(RenderObjectPtr<RenderObject> pObject); + +private: + /// Ist true, wenn das Objekt in nächsten Frame neu gezeichnet werden soll + bool _refreshForced; + + uint _handle; + + /** + @brief Entfernt ein Objekt aus der Kinderliste. + @param pObject ein Pointer auf das zu entfernende Objekt + @return Gibt false zurück, falls das zu entfernende Objekt nicht in der Liste gefunden werden konnte. + */ + bool detatchChildren(RenderObjectPtr<RenderObject> pObject); + /** + @brief Berechnet die Bounding-Box und registriert das Dirty-Rect beim BS_RenderObjectManager. + */ + void updateBoxes(); + /** + @brief Berechnet die Bounding-Box des Objektes. + @return Gibt die Bounding-Box des Objektes in Bildschirmkoordinaten zurück. + */ + Common::Rect calcBoundingBox() const; + /** + @brief Berechnet das Dirty-Rectangle des Objektes. + @return Gibt das Dirty-Rectangle des Objektes in Bildschirmkoordinaten zurück. + */ + Common::Rect calcDirtyRect() const; + /** + @brief Berechnet die absolute Position des Objektes. + */ + void calcAbsolutePos(int &x, int &y) const; + /** + @brief Berechnet die absolute Position des Objektes auf der X-Achse. + */ + int calcAbsoluteX() const; + /** + @brief Berechnet die absolute Position des Objektes. + */ + int calcAbsoluteY() const; + /** + @brief Sortiert alle Kinderobjekte nach ihrem Renderang. + */ + void sortRenderObjects(); + /** + @brief Validiert den Zustand eines Objektes nachdem die durch die Veränderung verursachten Folgen abgearbeitet wurden. + */ + void validateObject(); + /** + @brief Berechnet die absolute Position des Objektes und aller seiner Kinderobjekte neu. + + Diese Methode muss aufgerufen werden, wann immer sich die Position des Objektes verändert. Damit die Kinderobjekte immer die + richtige absolute Position haben. + */ + void updateAbsolutePos(); + /** + @brief Teilt dem Objekt mit, dass sich eines seiner Kinderobjekte dahingehend verändert hat, die eine erneute Bestimmung der + Rendereihenfolge verlangt. + */ + void signalChildChange() { + _childChanged = true; + } + /** + @brief Berechnet des Schnittrechteck der Bounding-Box des Objektes mit einem anderen Objekt. + @param pObjekt ein Pointer auf das Objekt mit dem geschnitten werden soll + @param Result das Ergebnisrechteck + @return Gibt false zurück, falls sich die Objekte gar nicht schneiden. + */ + bool getObjectIntersection(RenderObjectPtr<RenderObject> pObject, Common::Rect &result); + /** + @brief Vergleichsoperator der auf Objektpointern basiert statt auf Objekten. + @remark Diese Methode wird fürs Sortieren der Kinderliste nach der Rendereihenfolge benutzt. + */ + static bool greater(const RenderObjectPtr<RenderObject> lhs, const RenderObjectPtr<RenderObject> rhs); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectmanager.cpp b/engines/sword25/gfx/renderobjectmanager.cpp new file mode 100644 index 0000000000..94f7a04b45 --- /dev/null +++ b/engines/sword25/gfx/renderobjectmanager.cpp @@ -0,0 +1,151 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobjectmanager.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "common/rect.h" +#include "sword25/gfx/renderobject.h" +#include "sword25/gfx/timedrenderobject.h" +#include "sword25/gfx/rootrenderobject.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECTMANAGER" + +RenderObjectManager::RenderObjectManager(int width, int height, int framebufferCount) : + _frameStarted(false) { + // Wurzel des BS_RenderObject-Baumes erzeugen. + _rootPtr = (new RootRenderObject(this, width, height))->getHandle(); +} + +RenderObjectManager::~RenderObjectManager() { + // Die Wurzel des Baumes löschen, damit werden alle BS_RenderObjects mitgelöscht. + _rootPtr.erase(); +} + +void RenderObjectManager::startFrame() { + _frameStarted = true; + + // Verstrichene Zeit bestimmen + int timeElapsed = Kernel::getInstance()->getGfx()->getLastFrameDurationMicro(); + + // Alle BS_TimedRenderObject Objekte über den Framestart und die verstrichene Zeit in Kenntnis setzen + RenderObjectList::iterator iter = _timedRenderObjects.begin(); + for (; iter != _timedRenderObjects.end(); ++iter) + (*iter)->frameNotification(timeElapsed); +} + +bool RenderObjectManager::render() { + // Den Objekt-Status des Wurzelobjektes aktualisieren. Dadurch werden rekursiv alle Baumelemente aktualisiert. + // Beim aktualisieren des Objekt-Status werden auch die Update-Rects gefunden, so dass feststeht, was neu gezeichnet + // werden muss. + if (!_rootPtr.isValid() || !_rootPtr->updateObjectState()) + return false; + + _frameStarted = false; + + // Die Render-Methode der Wurzel aufrufen. Dadurch wird das rekursive Rendern der Baumelemente angestoßen. + return _rootPtr->render(); +} + +void RenderObjectManager::attatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> renderObjectPtr) { + _timedRenderObjects.push_back(renderObjectPtr); +} + +void RenderObjectManager::detatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> renderObjectPtr) { + for (uint i = 0; i < _timedRenderObjects.size(); i++) + if (_timedRenderObjects[i] == renderObjectPtr) { + _timedRenderObjects.remove_at(i); + break; + } +} + +bool RenderObjectManager::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // Alle Kinder des Wurzelknotens speichern. Dadurch werden alle BS_RenderObjects gespeichert rekursiv gespeichert. + result &= _rootPtr->persistChildren(writer); + + writer.write(_frameStarted); + + // Referenzen auf die TimedRenderObjects persistieren. + writer.write(_timedRenderObjects.size()); + RenderObjectList::const_iterator iter = _timedRenderObjects.begin(); + while (iter != _timedRenderObjects.end()) { + writer.write((*iter)->getHandle()); + ++iter; + } + + // Alle BS_AnimationTemplates persistieren. + result &= AnimationTemplateRegistry::instance().persist(writer); + + return result; +} + +bool RenderObjectManager::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Alle Kinder des Wurzelknotens löschen. Damit werden alle BS_RenderObjects gelöscht. + _rootPtr->deleteAllChildren(); + + // Alle BS_RenderObjects wieder hestellen. + if (!_rootPtr->unpersistChildren(reader)) + return false; + + reader.read(_frameStarted); + + // Momentan gespeicherte Referenzen auf TimedRenderObjects löschen. + _timedRenderObjects.resize(0); + + // Referenzen auf die TimedRenderObjects wieder herstellen. + uint timedObjectCount; + reader.read(timedObjectCount); + for (uint i = 0; i < timedObjectCount; ++i) { + uint handle; + reader.read(handle); + _timedRenderObjects.push_back(handle); + } + + // Alle BS_AnimationTemplates wieder herstellen. + result &= AnimationTemplateRegistry::instance().unpersist(reader); + + return result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobjectmanager.h b/engines/sword25/gfx/renderobjectmanager.h new file mode 100644 index 0000000000..8511382d6e --- /dev/null +++ b/engines/sword25/gfx/renderobjectmanager.h @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_RenderObjectManager + ---------------------- + Diese Klasse ist für die Verwaltung von BS_RenderObjects zuständig. + + Sie sorgt z.B. dafür, dass die BS_RenderObjects in der richtigen Reihenfolge gerendert werden. + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_RENDEROBJECTMANAGER_H +#define SWORD25_RENDEROBJECTMANAGER_H + +#include "common/rect.h" +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobjectptr.h" +#include "sword25/kernel/persistable.h" + +namespace Sword25 { + +class Kernel; +class RenderObject; +class TimedRenderObject; + +/** + @brief Diese Klasse ist für die Verwaltung von BS_RenderObjects zuständig. + + Sie sorgt dafür, dass die BS_RenderObjects in der richtigen Reihenfolge gerendert werden und ermöglicht den Zugriff auf die + BS_RenderObjects über einen String. +*/ +class RenderObjectManager : public Persistable { +public: + /** + @brief Erzeugt ein neues BS_RenderObjectManager-Objekt. + @param Width die horizontale Bildschirmauflösung in Pixeln + @param Height die vertikale Bildschirmauflösung in Pixeln + @param Die Anzahl an Framebuffern, die eingesetzt wird (Backbuffer + Primary). + */ + RenderObjectManager(int width, int height, int framebufferCount); + virtual ~RenderObjectManager(); + + // Interface + // --------- + /** + @brief Initialisiert den Manager für einen neuen Frame. + @remark Alle Veränderungen an Objekten müssen nach einem Aufruf dieser Methode geschehen, damit sichergestellt ist, dass diese + visuell umgesetzt werden.<br> + Mit dem Aufruf dieser Methode werden die Rückgabewerte von GetUpdateRects() und GetUpdateRectCount() auf ihre Startwerte + zurückgesetzt. Wenn man also mit diesen Werten arbeiten möchten, muss man dies nach einem Aufruf von Render() und vor + einem Aufruf von StartFrame() tun. + */ + void startFrame(); + /** + @brief Rendert alle Objekte die sich während des letzten Aufrufes von Render() verändert haben. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + */ + bool render(); + /** + @brief Gibt einen Pointer auf die Wurzel des Objektbaumes zurück. + */ + RenderObjectPtr<RenderObject> getTreeRoot() { + return _rootPtr; + } + /** + @brief Fügt ein BS_TimedRenderObject in die Liste der zeitabhängigen Render-Objekte. + + Alle Objekte die sich in dieser Liste befinden werden vor jedem Frame über die seit dem letzten Frame + vergangene Zeit informiert, so dass sich ihren Zustand zeitabhängig verändern können. + + @param RenderObject das einzufügende BS_TimedRenderObject + */ + void attatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> pRenderObject); + /** + @brief Entfernt ein BS_TimedRenderObject aus der Liste für zeitabhängige Render-Objekte. + */ + void detatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> pRenderObject); + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + bool _frameStarted; + typedef Common::Array<RenderObjectPtr<TimedRenderObject> > RenderObjectList; + RenderObjectList _timedRenderObjects; + + // RenderObject-Tree Variablen + // --------------------------- + // Der Baum legt die hierachische Ordnung der BS_RenderObjects fest. + // Zu weiteren Informationen siehe: "renderobject.h" + RenderObjectPtr<RenderObject> _rootPtr; // Die Wurzel der Baumes +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectptr.h b/engines/sword25/gfx/renderobjectptr.h new file mode 100644 index 0000000000..c22c6e83e7 --- /dev/null +++ b/engines/sword25/gfx/renderobjectptr.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDER_OBJECT_PTR_H +#define SWORD25_RENDER_OBJECT_PTR_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobjectregistry.h" + +namespace Sword25 { + +class RenderObject; + +template<class T> +class RenderObjectPtr { +public: + RenderObjectPtr() : _handle(0) {} + + RenderObjectPtr(uint handle) : _handle(handle) {} + + T *operator->() const { + return static_cast<T *>(RenderObjectRegistry::instance().resolveHandle(_handle)); + } + + bool operator==(const RenderObjectPtr<T> & other) { + return _handle == other._handle; + } + + bool isValid() const { + return RenderObjectRegistry::instance().resolveHandle(_handle) != 0; + } + + void erase() { + delete static_cast<T *>(RenderObjectRegistry::instance().resolveHandle(_handle)); + _handle = 0; + } + +private: + uint _handle; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectregistry.cpp b/engines/sword25/gfx/renderobjectregistry.cpp new file mode 100644 index 0000000000..c52e2f1a45 --- /dev/null +++ b/engines/sword25/gfx/renderobjectregistry.cpp @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobjectregistry.h" + +DECLARE_SINGLETON(Sword25::RenderObjectRegistry) + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECTREGISTRY" + +void RenderObjectRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void RenderObjectRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobjectregistry.h b/engines/sword25/gfx/renderobjectregistry.h new file mode 100644 index 0000000000..357d041068 --- /dev/null +++ b/engines/sword25/gfx/renderobjectregistry.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDEROBJECTREGISTRY_H +#define SWORD25_RENDEROBJECTREGISTRY_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/objectregistry.h" + +#include "common/singleton.h" + +namespace Sword25 { + +class RenderObject; + +class RenderObjectRegistry : + public ObjectRegistry<RenderObject>, + public Common::Singleton<RenderObjectRegistry> { +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/rootrenderobject.h b/engines/sword25/gfx/rootrenderobject.h new file mode 100644 index 0000000000..e4e3fba3c8 --- /dev/null +++ b/engines/sword25/gfx/rootrenderobject.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ROOTRENDEROBJECT_H +#define SWORD25_ROOTRENDEROBJECT_H + +// Includes +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +// ----------------------------------------------------------------------------- +// Forward Declarations +// ----------------------------------------------------------------------------- + +class Kernel; + +// Klassendefinition +class RenderObjectManager; + +class RootRenderObject : public RenderObject { + friend class RenderObjectManager; + +private: + RootRenderObject(RenderObjectManager *managerPtr, int width, int height) : + RenderObject(RenderObjectPtr<RenderObject>(), TYPE_ROOT) { + _managerPtr = managerPtr; + _width = width; + _height = height; + } + +protected: + virtual bool doRender() { + return true; + } +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/screenshot.cpp b/engines/sword25/gfx/screenshot.cpp new file mode 100644 index 0000000000..88417b72c5 --- /dev/null +++ b/engines/sword25/gfx/screenshot.cpp @@ -0,0 +1,185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// Disable symbol overrides so that we can use png.h +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#define BS_LOG_PREFIX "SCREENSHOT" + +#include "common/system.h" +#include "common/savefile.h" +#include "sword25/gfx/screenshot.h" +#include "sword25/kernel/filesystemutil.h" +#include <png.h> + +namespace Sword25 { + +#include "common/pack-start.h" +struct RGB_PIXEL { + byte red; + byte green; + byte blue; +} PACKED_STRUCT; +#include "common/pack-end.h" + +void userWriteFn(png_structp png_ptr, png_bytep data, png_size_t length) { + static_cast<Common::WriteStream *>(png_get_io_ptr(png_ptr))->write(data, length); +} + +void userFlushFn(png_structp png_ptr) { +} + +bool Screenshot::saveToFile(Graphics::Surface *data, Common::WriteStream *stream) { + // Reserve buffer space + RGB_PIXEL *pixelBuffer = new RGB_PIXEL[data->w * data->h]; + + // Convert the RGBA data to RGB + const byte *pSrc = (const byte *)data->getBasePtr(0, 0); + RGB_PIXEL *pDest = pixelBuffer; + + for (uint y = 0; y < data->h; y++) { + for (uint x = 0; x < data->w; x++) { + uint32 srcPixel = READ_LE_UINT32(pSrc); + pSrc += sizeof(uint32); + pDest->red = (srcPixel >> 16) & 0xff; + pDest->green = (srcPixel >> 8) & 0xff; + pDest->blue = srcPixel & 0xff; + ++pDest; + } + } + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + error("Could not create PNG write-struct."); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + error("Could not create PNG info-struct."); + + // The compression buffer must be large enough to the entire image. + // This ensures that only an IDAT chunk is created. + // When buffer size is used 110% of the raw data size to be sure. + png_set_compression_buffer_size(png_ptr, (data->w * data->h * 3 * 110) / 100); + + // Initialise PNG-Info structure + png_set_IHDR(png_ptr, info_ptr, + data->w, // Width + data->h, // Height + 8, // Bits depth + PNG_COLOR_TYPE_RGB, // Colour type + PNG_INTERLACE_NONE, // No interlacing + PNG_COMPRESSION_TYPE_DEFAULT, // Compression type + PNG_FILTER_TYPE_DEFAULT); // Filter Type + + // Rowpointer erstellen + png_bytep *rowPointers = new png_bytep[data->h]; + for (uint i = 0; i < data->h; i++) { + rowPointers[i] = (png_bytep)&pixelBuffer[data->w * i]; + } + png_set_rows(png_ptr, info_ptr, &rowPointers[0]); + + // Write out the png data to the file + png_set_write_fn(png_ptr, (void *)stream, userWriteFn, userFlushFn); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + delete[] pixelBuffer; + delete[] rowPointers; + + return true; +} + +// ----------------------------------------------------------------------------- + +Common::MemoryReadStream *Screenshot::createThumbnail(Graphics::Surface *data) { + // This method takes a screen image with a dimension of 800x600, and creates a screenshot with a dimension of 200x125. + // First 50 pixels are cut off the top and bottom (the interface boards in the game). The remaining image of 800x500 + // will be on a 16th of its size, reduced by being handed out in 4x4 pixel blocks and the average of each block + // generates a pixel of the target image. Finally, the result as a PNG file is stored as a file. + + // The source image must be 800x600. + if (data->w != 800 || data->h != 600 || data->bytesPerPixel != 4) { + BS_LOG_ERRORLN("The sreenshot dimensions have to be 800x600 in order to be saved as a thumbnail."); + return false; + } + + // Buffer for the output thumbnail + Graphics::Surface thumbnail; + thumbnail.create(200, 125, 4); + + // Über das Zielbild iterieren und einen Pixel zur Zeit berechnen. + uint x, y; + x = y = 0; + + for (byte *pDest = (byte *)thumbnail.pixels; pDest < ((byte *)thumbnail.pixels + thumbnail.pitch * thumbnail.h); ) { + // Get an average over a 4x4 pixel block in the source image + int alpha, red, green, blue; + alpha = red = green = blue = 0; + for (int j = 0; j < 4; ++j) { + const uint32 *srcP = (const uint32 *)data->getBasePtr(x * 4, y * 4 + j + 50); + for (int i = 0; i < 4; ++i) { + uint32 pixel = READ_LE_UINT32(srcP + i); + alpha += (pixel >> 24); + red += (pixel >> 16) & 0xff; + green += (pixel >> 8) & 0xff; + blue += pixel & 0xff; + } + } + + // Write target pixel + *pDest++ = blue / 16; + *pDest++ = green / 16; + *pDest++ = red / 16; + *pDest++ = alpha / 16; + + // Move to next block + ++x; + if (x == thumbnail.w) { + x = 0; + ++y; + } + } + + // Create a PNG representation of the thumbnail data + Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); + saveToFile(&thumbnail, stream); + + // Output a MemoryReadStream that encompasses the written data + Common::MemoryReadStream *result = new Common::MemoryReadStream(stream->getData(), stream->size(), + DisposeAfterUse::YES); + return result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/screenshot.h b/engines/sword25/gfx/screenshot.h new file mode 100644 index 0000000000..eefaa1bca6 --- /dev/null +++ b/engines/sword25/gfx/screenshot.h @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_SCREENSHOT_H +#define SWORD25_SCREENSHOT_H + +#include "graphics/surface.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class Screenshot { +public: + static bool saveToFile(Graphics::Surface *data, Common::WriteStream *stream); + static Common::MemoryReadStream *createThumbnail(Graphics::Surface *data); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/staticbitmap.cpp b/engines/sword25/gfx/staticbitmap.cpp new file mode 100644 index 0000000000..3019fe0ac1 --- /dev/null +++ b/engines/sword25/gfx/staticbitmap.cpp @@ -0,0 +1,185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/staticbitmap.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "STATICBITMAP" + +StaticBitmap::StaticBitmap(RenderObjectPtr<RenderObject> parentPtr, const Common::String &filename) : + Bitmap(parentPtr, TYPE_STATICBITMAP) { + // Das BS_Bitmap konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + _initSuccess = initBitmapResource(filename); +} + +StaticBitmap::StaticBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + Bitmap(parentPtr, TYPE_STATICBITMAP, handle) { + _initSuccess = unpersist(reader); +} + +bool StaticBitmap::initBitmapResource(const Common::String &filename) { + // Bild-Resource laden + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(filename); + if (!resourcePtr) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", filename.c_str()); + return false; + } + if (resourcePtr->getType() != Resource::TYPE_BITMAP) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a bitmap.", filename.c_str()); + return false; + } + + BitmapResource *bitmapPtr = static_cast<BitmapResource *>(resourcePtr); + + // Den eindeutigen Dateinamen zum späteren Referenzieren speichern + _resourceFilename = bitmapPtr->getFileName(); + + // RenderObject Eigenschaften aktualisieren + _originalWidth = _width = bitmapPtr->getWidth(); + _originalHeight = _height = bitmapPtr->getHeight(); + + // Bild-Resource freigeben + bitmapPtr->release(); + + return true; +} + +StaticBitmap::~StaticBitmap() { +} + +bool StaticBitmap::doRender() { + // Bitmap holen + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(resourcePtr); + BS_ASSERT(resourcePtr->getType() == Resource::TYPE_BITMAP); + BitmapResource *bitmapResourcePtr = static_cast<BitmapResource *>(resourcePtr); + + // Framebufferobjekt holen + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + // Bitmap zeichnen + bool result; + if (_scaleFactorX == 1.0f && _scaleFactorY == 1.0f) { + result = bitmapResourcePtr->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } else { + result = bitmapResourcePtr->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } + + // Resource freigeben + bitmapResourcePtr->release(); + + return result; +} + +uint StaticBitmap::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmapResource = static_cast<BitmapResource *>(pResource); + uint result = pBitmapResource->getPixel(x, y); + pResource->release(); + return result; +} + +bool StaticBitmap::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() ist not supported with this object."); + return false; +} + +bool StaticBitmap::isAlphaAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isAlphaAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::isColorModulationAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isColorModulationAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::isScalingAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isScalingAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= Bitmap::persist(writer); + writer.writeString(_resourceFilename); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool StaticBitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= Bitmap::unpersist(reader); + Common::String resourceFilename; + reader.readString(resourceFilename); + result &= initBitmapResource(resourceFilename); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/staticbitmap.h b/engines/sword25/gfx/staticbitmap.h new file mode 100644 index 0000000000..b5b4c4f5a2 --- /dev/null +++ b/engines/sword25/gfx/staticbitmap.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_STATIC_BITMAP_H +#define SWORD25_STATIC_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/bitmap.h" + +namespace Sword25 { + +class StaticBitmap : public Bitmap { + friend class RenderObject; + +private: + /** + @remark Filename muss absoluter Pfad sein + */ + StaticBitmap(RenderObjectPtr<RenderObject> parentPtr, const Common::String &filename); + StaticBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + virtual ~StaticBitmap(); + + virtual uint getPixel(int x, int y) const; + + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + + virtual bool isScalingAllowed() const; + virtual bool isAlphaAllowed() const; + virtual bool isColorModulationAllowed() const; + virtual bool isSetContentAllowed() const { + return false; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + Common::String _resourceFilename; + + bool initBitmapResource(const Common::String &filename); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/text.cpp b/engines/sword25/gfx/text.cpp new file mode 100644 index 0000000000..b8e12ba570 --- /dev/null +++ b/engines/sword25/gfx/text.cpp @@ -0,0 +1,358 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// TODO: +// Entweder Fontfile absolut abspeichern, oder Verzeichniswechseln verbieten +// Eine relative Fontfile-Angabe könnte verwandt werden nachdem das Verzeichnis bereits gewechselt wurde und die Datei würde nicht mehr gefunden + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/fontresource.h" +#include "sword25/gfx/bitmapresource.h" + +#include "sword25/gfx/text.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "TEXT" + +namespace { +const uint AUTO_WRAP_THRESHOLD_DEFAULT = 300; +} + +Text::Text(RenderObjectPtr<RenderObject> parentPtr) : + RenderObject(parentPtr, RenderObject::TYPE_TEXT), + _modulationColor(0xffffffff), + _autoWrap(false), + _autoWrapThreshold(AUTO_WRAP_THRESHOLD_DEFAULT) { + +} + +Text::Text(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + RenderObject(parentPtr, TYPE_TEXT, handle), + // Temporarily set fields prior to unpersisting actual values + _modulationColor(0xffffffff), + _autoWrap(false), + _autoWrapThreshold(AUTO_WRAP_THRESHOLD_DEFAULT) { + + // Unpersist the fields + _initSuccess = unpersist(reader); +} + +bool Text::setFont(const Common::String &font) { + // Font precachen. + if (getResourceManager()->precacheResource(font)) { + _font = font; + updateFormat(); + forceRefresh(); + return true; + } else { + BS_LOG_ERRORLN("Could not precache font \"%s\". Font probably does not exist.", font.c_str()); + return false; + } + +} + +void Text::setText(const Common::String &text) { + _text = text; + updateFormat(); + forceRefresh(); +} + +void Text::setColor(uint modulationColor) { + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Text::setAlpha(int alpha) { + BS_ASSERT(alpha >= 0 && alpha < 256); + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Text::setAutoWrap(bool autoWrap) { + if (autoWrap != _autoWrap) { + _autoWrap = autoWrap; + updateFormat(); + forceRefresh(); + } +} + +void Text::setAutoWrapThreshold(uint autoWrapThreshold) { + if (autoWrapThreshold != _autoWrapThreshold) { + _autoWrapThreshold = autoWrapThreshold; + updateFormat(); + forceRefresh(); + } +} + +bool Text::doRender() { + // Font-Resource locken. + FontResource *fontPtr = lockFontResource(); + if (!fontPtr) + return false; + + // Charactermap-Resource locken. + ResourceManager *rmPtr = getResourceManager(); + BitmapResource *charMapPtr; + { + Resource *pResource = rmPtr->requestResource(fontPtr->getCharactermapFileName()); + if (!pResource) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", fontPtr->getCharactermapFileName().c_str()); + return false; + } + if (pResource->getType() != Resource::TYPE_BITMAP) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a bitmap.", fontPtr->getCharactermapFileName().c_str()); + return false; + } + + charMapPtr = static_cast<BitmapResource *>(pResource); + } + + // Framebufferobjekt holen. + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + bool result = true; + Common::Array<Line>::iterator iter = _lines.begin(); + for (; iter != _lines.end(); ++iter) { + // Feststellen, ob überhaupt Buchstaben der aktuellen Zeile vom Update betroffen sind. + Common::Rect checkRect = (*iter).bbox; + checkRect.translate(_absoluteX, _absoluteY); + + // Jeden Buchstaben einzeln Rendern. + int curX = _absoluteX + (*iter).bbox.left; + int curY = _absoluteY + (*iter).bbox.top; + for (uint i = 0; i < (*iter).text.size(); ++i) { + Common::Rect curRect = fontPtr->getCharacterRect((byte)(*iter).text[i]); + + Common::Rect renderRect(curX, curY, curX + curRect.width(), curY + curRect.height()); + int renderX = curX + (renderRect.left - renderRect.left); + int renderY = curY + (renderRect.top - renderRect.top); + renderRect.translate(curRect.left - curX, curRect.top - curY); + result = charMapPtr->blit(renderX, renderY, Image::FLIP_NONE, &renderRect, _modulationColor); + if (!result) + break; + + curX += curRect.width() + fontPtr->getGapWidth(); + } + } + + // Charactermap-Resource freigeben. + charMapPtr->release(); + + // Font-Resource freigeben. + fontPtr->release(); + + return result; +} + +ResourceManager *Text::getResourceManager() { + // Pointer auf den Resource-Manager holen. + return Kernel::getInstance()->getResourceManager(); +} + +FontResource *Text::lockFontResource() { + ResourceManager *rmPtr = getResourceManager(); + + // Font-Resource locken. + FontResource *fontPtr; + { + Resource *resourcePtr = rmPtr->requestResource(_font); + if (!resourcePtr) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", _font.c_str()); + return NULL; + } + if (resourcePtr->getType() != Resource::TYPE_FONT) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a font.", _font.c_str()); + return NULL; + } + + fontPtr = static_cast<FontResource *>(resourcePtr); + } + + return fontPtr; +} + +void Text::updateFormat() { + FontResource *fontPtr = lockFontResource(); + BS_ASSERT(fontPtr); + + updateMetrics(*fontPtr); + + _lines.resize(1); + if (_autoWrap && (uint) _width >= _autoWrapThreshold && _text.size() >= 2) { + _width = 0; + uint curLineWidth = 0; + uint curLineHeight = 0; + uint curLine = 0; + uint tempLineWidth = 0; + uint lastSpace = 0; // we need at least 1 space character to start a new line... + _lines[0].text = ""; + for (uint i = 0; i < _text.size(); ++i) { + uint j; + tempLineWidth = 0; + lastSpace = 0; + for (j = i; j < _text.size(); ++j) { + if ((byte)_text[j] == ' ') + lastSpace = j; + + const Common::Rect &curCharRect = fontPtr->getCharacterRect((byte)_text[j]); + tempLineWidth += curCharRect.width(); + tempLineWidth += fontPtr->getGapWidth(); + + if ((tempLineWidth >= _autoWrapThreshold) && (lastSpace > 0)) + break; + } + + if (j == _text.size()) // everything in 1 line. + lastSpace = _text.size(); + + curLineWidth = 0; + curLineHeight = 0; + for (j = i; j < lastSpace; ++j) { + _lines[curLine].text += _text[j]; + + const Common::Rect &curCharRect = fontPtr->getCharacterRect((byte)_text[j]); + curLineWidth += curCharRect.width(); + curLineWidth += fontPtr->getGapWidth(); + if ((uint)curCharRect.height() > curLineHeight) + curLineHeight = curCharRect.height(); + } + + _lines[curLine].bbox.right = curLineWidth; + _lines[curLine].bbox.bottom = curLineHeight; + if ((uint)_width < curLineWidth) + _width = curLineWidth; + + if (lastSpace < _text.size()) { + ++curLine; + BS_ASSERT(curLine == _lines.size()); + _lines.resize(curLine + 1); + _lines[curLine].text = ""; + } + + i = lastSpace; + } + + // Bounding-Box der einzelnen Zeilen relativ zur ersten festlegen (vor allem zentrieren). + _height = 0; + Common::Array<Line>::iterator iter = _lines.begin(); + for (; iter != _lines.end(); ++iter) { + Common::Rect &bbox = (*iter).bbox; + bbox.left = (_width - bbox.right) / 2; + bbox.right = bbox.left + bbox.right; + bbox.top = (iter - _lines.begin()) * fontPtr->getLineHeight(); + bbox.bottom = bbox.top + bbox.bottom; + _height += bbox.height(); + } + } else { + // Keine automatische Formatierung, also wird der gesamte Text in nur eine Zeile kopiert. + _lines[0].text = _text; + _lines[0].bbox = Common::Rect(0, 0, _width, _height); + } + + fontPtr->release(); +} + +void Text::updateMetrics(FontResource &fontResource) { + _width = 0; + _height = 0; + + for (uint i = 0; i < _text.size(); ++i) { + const Common::Rect &curRect = fontResource.getCharacterRect((byte)_text[i]); + _width += curRect.width(); + if (i != _text.size() - 1) + _width += fontResource.getGapWidth(); + if (_height < curRect.height()) + _height = curRect.height(); + } +} + +bool Text::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + + writer.write(_modulationColor); + writer.writeString(_font); + writer.writeString(_text); + writer.write(_autoWrap); + writer.write(_autoWrapThreshold); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool Text::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + // Farbe und Alpha einlesen. + reader.read(_modulationColor); + + // Beim Laden der anderen Member werden die Set-Methoden benutzt statt der tatsächlichen Member. + // So wird das Layout automatisch aktualisiert und auch alle anderen notwendigen Methoden ausgeführt. + + Common::String font; + reader.readString(font); + setFont(font); + + Common::String text; + reader.readString(text); + setText(text); + + bool autoWrap; + reader.read(autoWrap); + setAutoWrap(autoWrap); + + uint autoWrapThreshold; + reader.read(autoWrapThreshold); + setAutoWrapThreshold(autoWrapThreshold); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/text.h b/engines/sword25/gfx/text.h new file mode 100644 index 0000000000..42c1cd7c5d --- /dev/null +++ b/engines/sword25/gfx/text.h @@ -0,0 +1,169 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_TEXT_H +#define SWORD25_TEXT_H + +#include "sword25/kernel/common.h" +#include "common/rect.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Kernel; +class FontResource; +class ResourceManager; + +class Text : public RenderObject { + friend class RenderObject; + +public: + /** + @brief Setzt den Font mit dem der Text dargestellt werden soll. + @param Font der Dateiname der Fontdatei. + @return Gibt false zurück, wenn der Font nicht gefunden wurde. + */ + bool setFont(const Common::String &font); + + /** + @brief Setzt den darzustellenden Text. + @param Text der darzustellende Text + */ + void setText(const Common::String &text); + + /** + @brief Setzt den Alphawert des Textes. + @param Alpha der neue Alphawert des Textes (0 = keine Deckung, 255 = volle Deckung). + */ + void setAlpha(int alpha); + + /** + @brief Legt fest, ob der Text automatisch umgebrochen werden soll. + + Wenn dieses Attribut auf true gesetzt ist, wird der Text umgebrochen, sofern er länger als GetAutoWrapThreshold() ist. + + @param AutoWrap gibt an, ob der automatische Umbruch aktiviert oder deaktiviert werden soll. + @remark Dieses Attribut wird mit dem Wert false initialisiert. + */ + void setAutoWrap(bool autoWrap); + + /** + @brief Legt die Längengrenze des Textes in Pixeln fest, ab der ein automatischer Zeilenumbruch vorgenommen wird. + @remark Dieses Attribut wird mit dem Wert 300 initialisiert. + @remark Eine automatische Formatierung wird nur vorgenommen, wenn diese durch einen Aufruf von SetAutoWrap() aktiviert wurde. + */ + void setAutoWrapThreshold(uint autoWrapThreshold); + + /** + @brief Gibt den dargestellten Text zurück. + */ + const Common::String &getText() { + return _text; + } + + /** + @brief Gibt den Namen das momentan benutzten Fonts zurück. + */ + const Common::String &getFont() { + return _font; + } + + /** + @brief Setzt die Farbe des Textes. + @param Color eine 24-Bit RGB Farbe, die die Farbe des Textes festlegt. + */ + void setColor(uint modulationColor); + + /** + @brief Gibt den Alphawert des Textes zurück. + @return Der Alphawert des Textes (0 = keine Deckung, 255 = volle Deckung). + */ + int getAlpha() const { + return _modulationColor >> 24; + } + + /** + @brief Gibt die Farbe des Textes zurück. + @return Eine 24-Bit RGB Farbe, die die Farbe des Textes angibt. + */ + int getColor() const { + return _modulationColor & 0x00ffffff; + } + + /** + @brief Gibt zurück, ob die automatische Formatierung aktiviert ist. + */ + bool isAutoWrapActive() const { + return _autoWrap; + } + + /** + @brief Gibt die Längengrenze des Textes in Pixeln zurück, ab der eine automatische Formatierung vorgenommen wird. + */ + uint getAutoWrapThreshold() const { + return _autoWrapThreshold; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + Text(RenderObjectPtr<RenderObject> parentPtr); + Text(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + + uint _modulationColor; + Common::String _font; + Common::String _text; + bool _autoWrap; + uint _autoWrapThreshold; + + struct Line { + Common::Rect bbox; + Common::String text; + }; + + Common::Array<Line> _lines; + + void updateFormat(); + void updateMetrics(FontResource &fontResource); + ResourceManager *getResourceManager(); + FontResource *lockFontResource(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/timedrenderobject.cpp b/engines/sword25/gfx/timedrenderobject.cpp new file mode 100644 index 0000000000..eaa9b90d26 --- /dev/null +++ b/engines/sword25/gfx/timedrenderobject.cpp @@ -0,0 +1,52 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/timedrenderobject.h" + +#include "sword25/gfx/renderobjectmanager.h" + +namespace Sword25 { + +TimedRenderObject::TimedRenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle) : + RenderObject(pParent, type, handle) { + BS_ASSERT(getManager()); + getManager()->attatchTimedRenderObject(this->getHandle()); +} + +TimedRenderObject::~TimedRenderObject() { + BS_ASSERT(getManager()); + getManager()->detatchTimedRenderObject(this->getHandle()); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/timedrenderobject.h b/engines/sword25/gfx/timedrenderobject.h new file mode 100644 index 0000000000..6fee19882a --- /dev/null +++ b/engines/sword25/gfx/timedrenderobject.h @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +/** + @brief +*/ + +class TimedRenderObject : public RenderObject { +public: + TimedRenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle = 0); + ~TimedRenderObject(); + + /** + @brief Teilt dem Objekt mit, dass ein neuer Frame begonnen wird. + + Diese Methode wird jeden Frame an jedem BS_TimedRenderObject aufgerufen um diesen zu ermöglichen + ihren Zustand Zeitabhängig zu verändern (z.B. Animationen).<br> + @param int TimeElapsed gibt an wie viel Zeit (in Microsekunden) seit dem letzten Frame vergangen ist. + */ + virtual void frameNotification(int timeElapsed) = 0; +}; + +} // End of namespace Sword25 |