Skip to content

Refactor graphics effect API #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 9 additions & 18 deletions docs/Graphics effects.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
\page graphicsEffects Graphics effects

libscratchcpp provides API for implementing custom graphics effects.
No effects are currently included with libscratchcpp, but that is planned to change.
libscratchcpp provides API for adding custom graphics effects.
No effects are included with libscratchcpp and must be implemented by the project player.

In case you've implemented some of the effects yourself, feel free to make pull requests!
These are the issues (tasks) for the default graphics effects:

- Color: https://github.com/scratchcpp/libscratchcpp/issues/283
- Fisheye: https://github.com/scratchcpp/libscratchcpp/issues/284
- Whirl: https://github.com/scratchcpp/libscratchcpp/issues/285
- Pixelate: https://github.com/scratchcpp/libscratchcpp/issues/286
- Mosaic: https://github.com/scratchcpp/libscratchcpp/issues/287
- Brightness: https://github.com/scratchcpp/libscratchcpp/issues/288
- Ghost: https://github.com/scratchcpp/libscratchcpp/issues/289

# Implementing a graphics effect
To implement a graphics effect that libscratchcpp doesn't support,
# Adding a custom graphics effect
To add a graphics effect that libscratchcpp doesn't support,
subclass \link libscratchcpp::IGraphicsEffect IGraphicsEffect \endlink
and override all of the methods.

Expand All @@ -27,12 +16,14 @@ using namespace libscratchcpp;
The `name()` method should return the name of the effect which is
used by the set and change effect blocks, for example `ghost` or `fisheye`.

The `apply()` method is used to apply the effect on a bitmap. The bitmap
can be accessed using the `bitmap` parameter and is writable.

# Registering the graphics effect
To register the graphics effect, use \link libscratchcpp::ScratchConfiguration::registerGraphicsEffect() ScratchConfiguration::registerGraphicsEffect() \endlink.

```cpp
libscratchcpp::ScratchConfiguration::registerGraphicsEffect(std::make_shared<MyGraphicsEffect>());
```

# Why should effects be registered?
Effects in Scratch are usually identified by their name (`ghost`, `brightness`, etc.), but this isn't effective
when it comes to running projects. Those "names" are therefore replaced by \link libscratchcpp::IGraphicsEffect IGraphicsEffect \endlink
pointers which makes working with effects faster.
6 changes: 0 additions & 6 deletions include/scratchcpp/costume.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace libscratchcpp
{

class Broadcast;
class IGraphicsEffect;
class CostumePrivate;

/*! \brief The Costume class represents a Scratch costume. */
Expand Down Expand Up @@ -41,11 +40,6 @@ class LIBSCRATCHCPP_EXPORT Costume : public Asset

Rgb **bitmap() const;

double graphicsEffectValue(IGraphicsEffect *effect) const;
void setGraphicsEffectValue(IGraphicsEffect *effect, double value);

void clearGraphicsEffects();

Broadcast *broadcast();

protected:
Expand Down
5 changes: 1 addition & 4 deletions include/scratchcpp/igraphicseffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@
namespace libscratchcpp
{

/*! \brief The IGraphicsEffects class is an interface for implementing custom graphics effects. */
/*! \brief The IGraphicsEffects class is an interface for custom graphics effects. */
class LIBSCRATCHCPP_EXPORT IGraphicsEffect
{
public:
virtual ~IGraphicsEffect() { }

/*! Returns the name of the graphics effect. */
virtual std::string name() const = 0;

/*! Applies the effect on the given bitmap. */
virtual void apply(Rgb **bitmap, unsigned int width, unsigned int height, double value) const = 0;
};

} // namespace libscratchcpp
9 changes: 9 additions & 0 deletions include/scratchcpp/ispritehandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler
/*! Called when the rotation style changes. */
virtual void onRotationStyleChanged(Sprite::RotationStyle rotationStyle) = 0;

/*!
* Called when the value of the given graphics effect changes.
* \note This method isn't called when all effects are cleared, use onGraphicsEffectsCleared() for this.
*/
virtual void onGraphicsEffectChanged(IGraphicsEffect *effect, double value) = 0;

/*! Called when all graphics effects are cleared. */
virtual void onGraphicsEffectsCleared() = 0;

/*!
* Used to get the bounding rectangle of the sprite.
* \note The rectangle must be relative to the stage, so make sure to use the sprite's coordinates.
Expand Down
9 changes: 9 additions & 0 deletions include/scratchcpp/istagehandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ class LIBSCRATCHCPP_EXPORT IStageHandler

/*! Called when the video transparency changes. */
virtual void onVideoTransparencyChanged(int videoTransparency) = 0;

/*!
* Called when the value of the given graphics effect changes.
* \note This method isn't called when all effects are cleared, use onGraphicsEffectsCleared() for this.
*/
virtual void onGraphicsEffectChanged(IGraphicsEffect *effect, double value) = 0;

/*! Called when all graphics effects are cleared. */
virtual void onGraphicsEffectsCleared() = 0;
};

} // namespace libscratchcpp
5 changes: 2 additions & 3 deletions include/scratchcpp/sprite.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
Rect boundingRect() const;
void keepInFence(double newX, double newY, double *fencedX, double *fencedY) const;

double graphicsEffectValue(IGraphicsEffect *effect) const;
void setGraphicsEffectValue(IGraphicsEffect *effect, double value);
void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override;

void clearGraphicsEffects();
void clearGraphicsEffects() override;

private:
Target *dataSource() const override;
Expand Down
4 changes: 4 additions & 0 deletions include/scratchcpp/stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class LIBSCRATCHCPP_EXPORT Stage : public Target
const std::string &textToSpeechLanguage() const;
void setTextToSpeechLanguage(const std::string &newTextToSpeechLanguage);

void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override;

void clearGraphicsEffects() override;

private:
spimpl::unique_impl_ptr<StagePrivate> impl;
};
Expand Down
6 changes: 6 additions & 0 deletions include/scratchcpp/target.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Block;
class Comment;
class Costume;
class Sound;
class IGraphicsEffect;
class TargetPrivate;

/*! \brief The Target class is the Stage or a Sprite. */
Expand Down Expand Up @@ -77,6 +78,11 @@ class LIBSCRATCHCPP_EXPORT Target
double volume() const;
void setVolume(double newVolume);

double graphicsEffectValue(IGraphicsEffect *effect) const;
virtual void setGraphicsEffectValue(IGraphicsEffect *effect, double value);

virtual void clearGraphicsEffects();

IEngine *engine() const;
void setEngine(IEngine *engine);

Expand Down
36 changes: 0 additions & 36 deletions src/scratch/costume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,42 +107,6 @@ Rgb **Costume::bitmap() const
return impl->bitmap;
}

/*! Returns the value of the given graphics effect. */
double Costume::graphicsEffectValue(IGraphicsEffect *effect) const
{
auto it = impl->graphicsEffects.find(effect);

if (it == impl->graphicsEffects.cend())
return 0;
else
return it->second;
}

/*! Sets the value of the given graphics effect (this is automatically set by the sprite). */
void Costume::setGraphicsEffectValue(IGraphicsEffect *effect, double value)
{
auto it = impl->graphicsEffects.find(effect);
bool update = ((it == impl->graphicsEffects.cend()) || (it->second != value));

if (value == 0)
impl->graphicsEffects.erase(effect);
else
impl->graphicsEffects[effect] = value;

if (update)
impl->updateImage();
}

/*! Clears all graphics effects (this is automatically called by the sprite). */
void Costume::clearGraphicsEffects()
{
bool update = !impl->graphicsEffects.empty();
impl->graphicsEffects.clear();

if (update)
impl->updateImage();
}

/*!
* Returns the Broadcast linked with this costume.
* \note This is used by the "switch backdrop to and wait" block.
Expand Down
5 changes: 0 additions & 5 deletions src/scratch/costume_p.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// SPDX-License-Identifier: Apache-2.0

#include <scratchcpp/igraphicseffect.h>

#include "costume_p.h"

using namespace libscratchcpp;
Expand Down Expand Up @@ -45,9 +43,6 @@ void CostumePrivate::updateImage()
for (unsigned int j = 0; j < scaledWidth; j++)
bitmap[i][j] = image->colorAt(mirrorHorizontally ? (scaledWidth - 1 - j) : j, i, actualScale);
}

for (const auto &[effect, value] : graphicsEffects)
effect->apply(bitmap, scaledWidth, scaledHeight, value);
}

void CostumePrivate::freeImage()
Expand Down
3 changes: 0 additions & 3 deletions src/scratch/costume_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
namespace libscratchcpp
{

class IGraphicsEffect;

struct CostumePrivate
{
CostumePrivate();
Expand All @@ -31,7 +29,6 @@ struct CostumePrivate
double oldScale = 1;
double scale = 1;
bool mirrorHorizontally = false;
std::unordered_map<IGraphicsEffect *, double> graphicsEffects;
Broadcast broadcast;
};

Expand Down
38 changes: 10 additions & 28 deletions src/scratch/sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,6 @@ void Sprite::setCostumeIndex(int newCostumeIndex)
if (costume) {
costume->setScale(impl->size / 100);
costume->setMirrorHorizontally(impl->rotationStyle == RotationStyle::LeftRight);

for (const auto &[effect, value] : impl->graphicsEffects)
costume->setGraphicsEffectValue(effect, value);
}

if (impl->visible) {
Expand Down Expand Up @@ -403,51 +400,36 @@ void Sprite::keepInFence(double newX, double newY, double *fencedX, double *fenc
*fencedY = newY + dy;
}

/*! Returns the value of the given graphics effect. */
double Sprite::graphicsEffectValue(IGraphicsEffect *effect) const
{
auto it = impl->graphicsEffects.find(effect);

if (it == impl->graphicsEffects.cend())
return 0;
else
return it->second;
}

/*! Sets the value of the given graphics effect. */
/*! Overrides Target#setGraphicsEffectValue(). */
void Sprite::setGraphicsEffectValue(IGraphicsEffect *effect, double value)
{
impl->graphicsEffects[effect] = value;

auto costume = currentCostume();

if (costume)
costume->setGraphicsEffectValue(effect, value);
Target::setGraphicsEffectValue(effect, value);

if (impl->visible) {
IEngine *eng = engine();

if (eng)
eng->requestRedraw();
}

if (impl->iface)
impl->iface->onGraphicsEffectChanged(effect, value);
}

/*! Sets the value of all graphics effects to 0 (clears them). */
/*! Overrides Target#clearGraphicsEffects(). */
void Sprite::clearGraphicsEffects()
{
impl->graphicsEffects.clear();

auto costume = currentCostume();

if (costume)
costume->clearGraphicsEffects();
Target::clearGraphicsEffects();

if (impl->visible) {
IEngine *eng = engine();

if (eng)
eng->requestRedraw();
}

if (impl->iface)
impl->iface->onGraphicsEffectsCleared();
}

Target *Sprite::dataSource() const
Expand Down
1 change: 0 additions & 1 deletion src/scratch/sprite_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ struct SpritePrivate
double direction = 90;
bool draggable = false;
Sprite::RotationStyle rotationStyle = Sprite::RotationStyle::AllAround;
std::unordered_map<IGraphicsEffect *, double> graphicsEffects;
};

} // namespace libscratchcpp
27 changes: 27 additions & 0 deletions src/scratch/stage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <scratchcpp/stage.h>
#include <scratchcpp/istagehandler.h>
#include <scratchcpp/iengine.h>
#include <cassert>

#include "stage_p.h"
Expand Down Expand Up @@ -123,3 +124,29 @@ void Stage::setTextToSpeechLanguage(const std::string &newTextToSpeechLanguage)
{
impl->textToSpeechLanguage = newTextToSpeechLanguage;
}

/*! Overrides Target#setGraphicsEffectValue(). */
void Stage::setGraphicsEffectValue(IGraphicsEffect *effect, double value)
{
Target::setGraphicsEffectValue(effect, value);
IEngine *eng = engine();

if (eng)
eng->requestRedraw();

if (impl->iface)
impl->iface->onGraphicsEffectChanged(effect, value);
}

/*! Overrides Target#clearGraphicsEffects(). */
void Stage::clearGraphicsEffects()
{
Target::clearGraphicsEffects();
IEngine *eng = engine();

if (eng)
eng->requestRedraw();

if (impl->iface)
impl->iface->onGraphicsEffectsCleared();
}
23 changes: 23 additions & 0 deletions src/scratch/target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,29 @@ void Target::setVolume(double newVolume)
impl->volume = newVolume;
}

/*! Returns the value of the given graphics effect. */
double Target::graphicsEffectValue(IGraphicsEffect *effect) const
{
auto it = impl->graphicsEffects.find(effect);

if (it == impl->graphicsEffects.cend())
return 0;
else
return it->second;
}

/*! Sets the value of the given graphics effect. */
void Target::setGraphicsEffectValue(IGraphicsEffect *effect, double value)
{
impl->graphicsEffects[effect] = value;
}

/*! Sets the value of all graphics effects to 0 (clears them). */
void Target::clearGraphicsEffects()
{
impl->graphicsEffects.clear();
}

/*! Returns the engine. */
IEngine *Target::engine() const
{
Expand Down
Loading