Skip to content

Commit

Permalink
Added blend mode to layer (#4140)
Browse files Browse the repository at this point in the history
Only supports the SVG 1.2 blend modes for now, not the Porter and Duff
operators because it's unclear what their use would be within the 
current functionality (which does not include compositing groups into
intermediate buffers).

The blend mode does not do anything useful on group layers at the 
moment. This might change in a future version. Currently group layers
effectively behave as "pass through".

The blend mode only affects images (tile layers, tile objects and image 
layers), not shape objects.

Closes #3932
  • Loading branch information
bjorn authored Jan 21, 2025
1 parent cc77ce6 commit 9694522
Show file tree
Hide file tree
Showing 30 changed files with 388 additions and 16 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Unreleased

* Added support for SVG 1.2 / CSS blending modes to layers (#3932)
* YY plugin: Fixed compatibility with GameMaker 2024 (#4132)
* snap: Fixed crash on startup on Wayland
* Raised minimum supported Qt version from 5.12 to 5.15
Expand Down
44 changes: 44 additions & 0 deletions docs/manual/layers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,50 @@ separate images for it.
The tint color can also be set on a :ref:`Group Layer <group-layers>`, in
which case it is inherited by all layers in the group.

.. raw:: html

<div class="new">New in Tiled 1.12</div>

.. _blend-mode:

Blend Modes
-----------

Tiled provides support for several common blend modes (also called compositing
operators) for layers. These modes allow you to modify the appearance of a
layer by blending it with the layers beneath it in various ways. By default,
layers in Tiled use the Normal blend mode.

Below is the full list of blend modes available in Tiled, along with links to
their equivalents in the `SVG Compositing Specification
<https://www.w3.org/TR/SVGCompositing/>`__, where you can see examples and
calculation details.

=========== ===========================================================================
Mode SVG equivalent
=========== ===========================================================================
Normal `src-over <https://www.w3.org/TR/SVGCompositing/#comp-op-src-over>`__
Add `plus <https://www.w3.org/TR/SVGCompositing/#comp-op-plus>`__
Multiply `multiply <https://www.w3.org/TR/SVGCompositing/#comp-op-multiply>`__
Screen `screen <https://www.w3.org/TR/SVGCompositing/#comp-op-screen>`__
Overlay `overlay <https://www.w3.org/TR/SVGCompositing/#comp-op-overlay>`__
Darken `darken <https://www.w3.org/TR/SVGCompositing/#comp-op-darken>`__
Lighten `lighten <https://www.w3.org/TR/SVGCompositing/#comp-op-lighten>`__
Color Dodge `color-dodge <https://www.w3.org/TR/SVGCompositing/#comp-op-color-dodge>`__
Color Burn `color-burn <https://www.w3.org/TR/SVGCompositing/#comp-op-color-burn>`__
Hard Light `hard-light <https://www.w3.org/TR/SVGCompositing/#comp-op-hard-light>`__
Soft Light `soft-light <https://www.w3.org/TR/SVGCompositing/#comp-op-soft-light>`__
Difference `difference <https://www.w3.org/TR/SVGCompositing/#comp-op-difference>`__
Exclusion `exclusion <https://www.w3.org/TR/SVGCompositing/#comp-op-exclusion>`__
=========== ===========================================================================

In OpenGL, these blend modes can be implemented using ``glBlendEquation`` with
values from the `KHR_blend_equation_advanced
<https://registry.khronos.org/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt>`__
extension. In Vulkan, they are part of the `VK_EXT_blend_operation_advanced
<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_EXT_blend_operation_advanced>`__
extension.


.. topic:: Future Extensions
:class: future
Expand Down
31 changes: 31 additions & 0 deletions docs/reference/json-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Layer
imagewidth, int, "Width of the image used by this layer. ``imagelayer`` only. (since 1.11.1)"
layers, array, "Array of :ref:`layers <json-layer>`. ``group`` only."
locked, bool, "Whether layer is locked in the editor (default: false). (since 1.8.2)"
mode, string, "The :ref:`blend mode <json-blend-mode>` to use when rendering the layer. (since 1.12)"
name, string, "Name assigned to this layer"
objects, array, "Array of :ref:`objects <json-object>`. ``objectgroup`` only."
offsetx, double, "Horizontal layer offset in pixels (default: 0)"
Expand Down Expand Up @@ -168,6 +169,30 @@ Object Layer Example
"y":0
}
.. _json-blend-mode:

Blend Mode
~~~~~~~~~~

The following values are supported for the ``mode`` property on
:ref:`json-layer`:

- ``normal`` (default)
- ``add``
- ``multiply``
- ``screen``
- ``overlay``
- ``darken``
- ``lighten``
- ``color-dodge``
- ``color-burn``
- ``hard-light``
- ``soft-light``
- ``difference``
- ``exclusion``


.. _json-chunk:

Chunk
Expand Down Expand Up @@ -738,6 +763,12 @@ A point on a polygon or a polyline, relative to the position of the object.
Changelog
---------

Tiled 1.12
~~~~~~~~~~

* Added ``mode`` property to :ref:`json-layer` to specify the blend mode to use
when rendering the layer.

Tiled 1.11.1
~~~~~~~~~~~~

Expand Down
5 changes: 5 additions & 0 deletions docs/reference/tmx-changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ TMX Changelog
Below are described the changes/additions that were made to the
:doc:`tmx-map-format` for recent versions of Tiled.

Tiled 1.12
----------

- Added ``mode`` attribute on :ref:`tmx-layer` to specific its blend mode.

Tiled 1.10
----------

Expand Down
4 changes: 4 additions & 0 deletions docs/reference/tmx-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ tiles.
(since 0.14)
- **parallaxx:** Horizontal :ref:`parallax factor <parallax-factor>` for this layer. Defaults to 1. (since 1.5)
- **parallaxy:** Vertical :ref:`parallax factor <parallax-factor>` for this layer. Defaults to 1. (since 1.5)
- **mode:** The blend mode to use when rendering the layer. Valid values are
``normal``, ``add``, ``multiply``, ``screen``, ``overlay``, ``darken``, ``lighten``,
``color-dodge``, ``color-burn``, ``hard-light``, ``soft-light``,
``difference`` and ``exclusion`` (since 1.12, defaults to ``normal``).

Can contain at most one: :ref:`tmx-properties`, :ref:`tmx-data`

Expand Down
40 changes: 40 additions & 0 deletions docs/scripting-doc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2744,6 +2744,40 @@ declare class Tile extends TiledObject {
setImage(image: Image, source?: string): void;
}

/**
* The various blend modes supported by Tiled.
*
* @since 1.12
*/
declare enum BlendMode {
/** SVG equivalent: [src-over](https://www.w3.org/TR/SVGCompositing/#comp-op-src-over) */
Normal,
/** SVG equivalent: [plus](https://www.w3.org/TR/SVGCompositing/#comp-op-plus) */
Add,
/** SVG equivalent: [multiply](https://www.w3.org/TR/SVGCompositing/#comp-op-multiply) */
Multiply,
/** SVG equivalent: [screen](https://www.w3.org/TR/SVGCompositing/#comp-op-screen) */
Screen,
/** SVG equivalent: [overlay](https://www.w3.org/TR/SVGCompositing/#comp-op-overlay) */
Overlay,
/** SVG equivalent: [darken](https://www.w3.org/TR/SVGCompositing/#comp-op-darken) */
Darken,
/** SVG equivalent: [lighten](https://www.w3.org/TR/SVGCompositing/#comp-op-lighten) */
Lighten,
/** SVG equivalent: [color-dodge](https://www.w3.org/TR/SVGCompositing/#comp-op-color-dodge) */
ColorDodge,
/** SVG equivalent: [color-burn](https://www.w3.org/TR/SVGCompositing/#comp-op-color-burn) */
ColorBurn,
/** SVG equivalent: [hard-light](https://www.w3.org/TR/SVGCompositing/#comp-op-hard-light) */
HardLight,
/** SVG equivalent: [soft-light](https://www.w3.org/TR/SVGCompositing/#comp-op-soft-light) */
SoftLight,
/** SVG equivalent: [difference](https://www.w3.org/TR/SVGCompositing/#comp-op-difference) */
Difference,
/** SVG equivalent: [exclusion](https://www.w3.org/TR/SVGCompositing/#comp-op-exclusion) */
Exclusion
}

/**
* The base class of the various supported layer types.
*/
Expand Down Expand Up @@ -2802,6 +2836,12 @@ declare class Layer extends TiledObject {
*/
parallaxFactor: point;

/**
* The blend mode used when rendering images on this layer. Affects tile
* layers, tile objects and image layers.
*/
blendMode: BlendMode;

/**
* Map that this layer is part of, or `null` in case of a standalone layer.
*/
Expand Down
1 change: 1 addition & 0 deletions src/libtiled/layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Layer *Layer::initializeClone(Layer *clone) const
clone->mOffset = mOffset;
clone->mParallaxFactor = mParallaxFactor;
clone->mOpacity = mOpacity;
clone->mBlendMode = mBlendMode;
clone->mTintColor = mTintColor;
clone->mVisible = mVisible;
clone->mLocked = mLocked;
Expand Down
21 changes: 21 additions & 0 deletions src/libtiled/layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "object.h"
#include "tileset.h"

#include <QPainter>
#include <QPixmap>
#include <QRect>
#include <QSet>
Expand Down Expand Up @@ -192,6 +193,10 @@ class TILEDSHARED_EXPORT Layer : public Object
QPointF parallaxFactor() const;
QPointF effectiveParallaxFactor() const;

BlendMode blendMode() const;
void setBlendMode(BlendMode mode);
QPainter::CompositionMode compositionMode() const;

bool canMergeDown() const;

virtual bool isEmpty() const = 0;
Expand Down Expand Up @@ -263,6 +268,7 @@ class TILEDSHARED_EXPORT Layer : public Object
int mY = 0;
QPointF mOffset;
QPointF mParallaxFactor = { 1.0, 1.0 };
BlendMode mBlendMode = BlendMode::Normal;
qreal mOpacity = 1.0;
QColor mTintColor;
bool mVisible = true;
Expand Down Expand Up @@ -307,6 +313,21 @@ inline QPointF Layer::parallaxFactor() const
return mParallaxFactor;
}

inline BlendMode Layer::blendMode() const
{
return mBlendMode;
}

inline void Layer::setBlendMode(BlendMode mode)
{
mBlendMode = mode;
}

inline QPainter::CompositionMode Layer::compositionMode() const
{
return static_cast<QPainter::CompositionMode>(mBlendMode);
}


/**
* An iterator for iterating over the layers of a map, in the order in which
Expand Down
3 changes: 3 additions & 0 deletions src/libtiled/mapreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,9 @@ static void readLayerAttributes(Layer &layer,
parallaxFactor.setY(factorY);

layer.setParallaxFactor(parallaxFactor);

const auto mode = atts.value(QLatin1String("mode")).toString();
layer.setBlendMode(blendModeFromString(mode));
}

std::unique_ptr<TileLayer> MapReaderPrivate::readTileLayer()
Expand Down
3 changes: 3 additions & 0 deletions src/libtiled/maptovariantconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,9 @@ void MapToVariantConverter::addLayerAttributes(QVariantMap &layerVariant,
if (layer.tintColor().isValid())
layerVariant[QStringLiteral("tintcolor")] = colorToString(layer.tintColor());

if (layer.blendMode() != BlendMode::Normal)
layerVariant[QStringLiteral("mode")] = blendModeToString(layer.blendMode());

addProperties(layerVariant, layer.properties());
}

Expand Down
3 changes: 3 additions & 0 deletions src/libtiled/mapwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,9 @@ void MapWriterPrivate::writeLayerAttributes(QXmlStreamWriter &w,
w.writeAttribute(QStringLiteral("parallaxx"), QString::number(parallaxFactor.x()));
if (parallaxFactor.y() != 1.0)
w.writeAttribute(QStringLiteral("parallaxy"), QString::number(parallaxFactor.y()));

if (layer.blendMode() != BlendMode::Normal)
w.writeAttribute(QStringLiteral("mode"), blendModeToString(layer.blendMode()));
}

void MapWriterPrivate::writeObjectGroup(QXmlStreamWriter &w,
Expand Down
10 changes: 10 additions & 0 deletions src/libtiled/minimaprenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,16 @@ void MiniMapRenderer::renderToImage(QImage &image, RenderFlags renderFlags) cons
continue;

const auto offset = layer->totalOffset();
const auto compositionMode = layer->compositionMode();

painter.setOpacity(layer->effectiveOpacity());
painter.translate(offset);

switch (layer->layerType()) {
case Layer::TileLayerType: {
if (drawTileLayers) {
painter.setCompositionMode(compositionMode);

const TileLayer *tileLayer = static_cast<const TileLayer*>(layer);
mRenderer->drawTileLayer(&painter, tileLayer);
}
Expand All @@ -195,6 +198,11 @@ void MiniMapRenderer::renderToImage(QImage &image, RenderFlags renderFlags) cons

for (const MapObject *object : std::as_const(objects)) {
if (object->isVisible()) {
if (object->isTileObject())
painter.setCompositionMode(compositionMode);
else
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);

if (object->rotation() != qreal(0)) {
QPointF origin = mRenderer->pixelToScreenCoords(object->position());
painter.save();
Expand All @@ -214,6 +222,8 @@ void MiniMapRenderer::renderToImage(QImage &image, RenderFlags renderFlags) cons
}
case Layer::ImageLayerType: {
if (drawImageLayers) {
painter.setCompositionMode(compositionMode);

const ImageLayer *imageLayer = static_cast<const ImageLayer*>(layer);
mRenderer->drawImageLayer(&painter, imageLayer);
}
Expand Down
38 changes: 38 additions & 0 deletions src/libtiled/tiled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,41 @@ void Tiled::increaseImageAllocationLimit(int mbLimit)
Q_UNUSED(mbLimit);
#endif
}

static constexpr struct BlendModeMapping {
Tiled::BlendMode mode;
const char *name;
} blendModeMapping[] = {
{ Tiled::BlendMode::Normal, "normal" },
{ Tiled::BlendMode::Add, "add" },
{ Tiled::BlendMode::Multiply, "multiply" },
{ Tiled::BlendMode::Screen, "screen" },
{ Tiled::BlendMode::Overlay, "overlay" },
{ Tiled::BlendMode::Darken, "darken" },
{ Tiled::BlendMode::Lighten, "lighten" },
{ Tiled::BlendMode::ColorDodge, "color-dodge" },
{ Tiled::BlendMode::ColorBurn, "color-burn" },
{ Tiled::BlendMode::HardLight, "hard-light" },
{ Tiled::BlendMode::SoftLight, "soft-light" },
{ Tiled::BlendMode::Difference, "difference" },
{ Tiled::BlendMode::Exclusion, "exclusion" },
};

QString Tiled::blendModeToString(BlendMode mode)
{
for (const auto &mapping : blendModeMapping)
if (mapping.mode == mode)
return QString::fromLatin1(mapping.name);

return QString();
}

Tiled::BlendMode Tiled::blendModeFromString(const QString &name)
{
if (!name.isEmpty())
for (const auto &mapping : blendModeMapping)
if (QLatin1String(mapping.name) == name)
return mapping.mode;

return BlendMode::Normal;
}
23 changes: 23 additions & 0 deletions src/libtiled/tiled.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <QColor>
#include <QDir>
#include <QMetaType>
#include <QPainter>
#include <QRectF>
#include <QString>
#include <QUrl>
Expand Down Expand Up @@ -78,6 +79,25 @@ enum CompatibilityVersion {
Tiled_Latest = 65535,
};

// All values can be casted to QPainter::CompositionMode
enum class BlendMode {
Normal = QPainter::CompositionMode_SourceOver,

// For now we only support the SVG 1.2 blend modes
Add = QPainter::CompositionMode_Plus,
Multiply = QPainter::CompositionMode_Multiply,
Screen = QPainter::CompositionMode_Screen,
Overlay = QPainter::CompositionMode_Overlay,
Darken = QPainter::CompositionMode_Darken,
Lighten = QPainter::CompositionMode_Lighten,
ColorDodge = QPainter::CompositionMode_ColorDodge,
ColorBurn = QPainter::CompositionMode_ColorBurn,
HardLight = QPainter::CompositionMode_HardLight,
SoftLight = QPainter::CompositionMode_SoftLight,
Difference = QPainter::CompositionMode_Difference,
Exclusion = QPainter::CompositionMode_Exclusion,
};

const int CHUNK_SIZE = 16;
const int CHUNK_BITS = 4;
const int CHUNK_SIZE_MIN = 4;
Expand Down Expand Up @@ -132,6 +152,9 @@ TILEDSHARED_EXPORT CompatibilityVersion versionFromString(const QString &);

TILEDSHARED_EXPORT void increaseImageAllocationLimit(int mbLimit = 4096);

TILEDSHARED_EXPORT QString blendModeToString(BlendMode);
TILEDSHARED_EXPORT BlendMode blendModeFromString(const QString &);

} // namespace Tiled

Q_DECLARE_METATYPE(Tiled::Alignment);
Loading

0 comments on commit 9694522

Please sign in to comment.