diff --git a/NEWS.md b/NEWS.md index 6d792eb134..f99537e8fb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/docs/manual/layers.rst b/docs/manual/layers.rst index 11a8b910a3..6fc6e7661d 100644 --- a/docs/manual/layers.rst +++ b/docs/manual/layers.rst @@ -212,6 +212,50 @@ separate images for it. The tint color can also be set on a :ref:`Group Layer `, in which case it is inherited by all layers in the group. +.. raw:: html + +
New in Tiled 1.12
+ +.. _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 +`__, where you can see examples and +calculation details. + +=========== =========================================================================== +Mode SVG equivalent +=========== =========================================================================== +Normal `src-over `__ +Add `plus `__ +Multiply `multiply `__ +Screen `screen `__ +Overlay `overlay `__ +Darken `darken `__ +Lighten `lighten `__ +Color Dodge `color-dodge `__ +Color Burn `color-burn `__ +Hard Light `hard-light `__ +Soft Light `soft-light `__ +Difference `difference `__ +Exclusion `exclusion `__ +=========== =========================================================================== + +In OpenGL, these blend modes can be implemented using ``glBlendEquation`` with +values from the `KHR_blend_equation_advanced +`__ +extension. In Vulkan, they are part of the `VK_EXT_blend_operation_advanced +`__ +extension. + .. topic:: Future Extensions :class: future diff --git a/docs/reference/json-map-format.rst b/docs/reference/json-map-format.rst index 88b51f9a2c..30248d99b6 100644 --- a/docs/reference/json-map-format.rst +++ b/docs/reference/json-map-format.rst @@ -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 `. ``group`` only." locked, bool, "Whether layer is locked in the editor (default: false). (since 1.8.2)" + mode, string, "The :ref:`blend mode ` to use when rendering the layer. (since 1.12)" name, string, "Name assigned to this layer" objects, array, "Array of :ref:`objects `. ``objectgroup`` only." offsetx, double, "Horizontal layer offset in pixels (default: 0)" @@ -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 @@ -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 ~~~~~~~~~~~~ diff --git a/docs/reference/tmx-changelog.rst b/docs/reference/tmx-changelog.rst index bf6d88699a..066eb11309 100644 --- a/docs/reference/tmx-changelog.rst +++ b/docs/reference/tmx-changelog.rst @@ -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 ---------- diff --git a/docs/reference/tmx-map-format.rst b/docs/reference/tmx-map-format.rst index 1df38f5e57..f6b6269080 100644 --- a/docs/reference/tmx-map-format.rst +++ b/docs/reference/tmx-map-format.rst @@ -428,6 +428,10 @@ tiles. (since 0.14) - **parallaxx:** Horizontal :ref:`parallax factor ` for this layer. Defaults to 1. (since 1.5) - **parallaxy:** Vertical :ref:`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` diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index 7ef86f5c4d..45e49208f0 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -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. */ @@ -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. */ diff --git a/src/libtiled/layer.cpp b/src/libtiled/layer.cpp index f982709dab..8019ca1eb8 100644 --- a/src/libtiled/layer.cpp +++ b/src/libtiled/layer.cpp @@ -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; diff --git a/src/libtiled/layer.h b/src/libtiled/layer.h index ae6881e5b0..f2737bf9c3 100644 --- a/src/libtiled/layer.h +++ b/src/libtiled/layer.h @@ -32,6 +32,7 @@ #include "object.h" #include "tileset.h" +#include #include #include #include @@ -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; @@ -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; @@ -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(mBlendMode); +} + /** * An iterator for iterating over the layers of a map, in the order in which diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 30ff7c1693..8981ae6041 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -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 MapReaderPrivate::readTileLayer() diff --git a/src/libtiled/maptovariantconverter.cpp b/src/libtiled/maptovariantconverter.cpp index 212a9b648a..d79ae4d43a 100644 --- a/src/libtiled/maptovariantconverter.cpp +++ b/src/libtiled/maptovariantconverter.cpp @@ -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()); } diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index d6c4f3265d..2a9926ab68 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -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, diff --git a/src/libtiled/minimaprenderer.cpp b/src/libtiled/minimaprenderer.cpp index de7e125578..f99a43f9dc 100644 --- a/src/libtiled/minimaprenderer.cpp +++ b/src/libtiled/minimaprenderer.cpp @@ -172,6 +172,7 @@ 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); @@ -179,6 +180,8 @@ void MiniMapRenderer::renderToImage(QImage &image, RenderFlags renderFlags) cons switch (layer->layerType()) { case Layer::TileLayerType: { if (drawTileLayers) { + painter.setCompositionMode(compositionMode); + const TileLayer *tileLayer = static_cast(layer); mRenderer->drawTileLayer(&painter, tileLayer); } @@ -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(); @@ -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(layer); mRenderer->drawImageLayer(&painter, imageLayer); } diff --git a/src/libtiled/tiled.cpp b/src/libtiled/tiled.cpp index 1be34f8a31..3e46790559 100644 --- a/src/libtiled/tiled.cpp +++ b/src/libtiled/tiled.cpp @@ -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; +} diff --git a/src/libtiled/tiled.h b/src/libtiled/tiled.h index 54a782c824..b42a0692fb 100644 --- a/src/libtiled/tiled.h +++ b/src/libtiled/tiled.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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); diff --git a/src/libtiled/varianttomapconverter.cpp b/src/libtiled/varianttomapconverter.cpp index 2c2b9bf3b5..972d2a6145 100644 --- a/src/libtiled/varianttomapconverter.cpp +++ b/src/libtiled/varianttomapconverter.cpp @@ -619,6 +619,9 @@ std::unique_ptr VariantToMapConverter::toLayer(const QVariant &variant) parallaxFactor.setY(factorY); layer->setParallaxFactor(parallaxFactor); + + const auto mode = variantMap[QStringLiteral("mode")].toString(); + layer->setBlendMode(blendModeFromString(mode)); } return layer; diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp index e8d53b2d37..a0c05216d3 100644 --- a/src/plugins/lua/luaplugin.cpp +++ b/src/plugins/lua/luaplugin.cpp @@ -28,7 +28,6 @@ #include "map.h" #include "mapobject.h" #include "objectgroup.h" -#include "objecttemplate.h" #include "properties.h" #include "savefile.h" #include "tile.h" @@ -781,6 +780,9 @@ void LuaWriter::writeLayerProperties(const Layer *layer) if (layer->tintColor().isValid()) writeColor("tintcolor", layer->tintColor()); + + if (layer->blendMode() != BlendMode::Normal) + mWriter.writeKeyAndValue("mode", blendModeToString(layer->blendMode())); } void LuaWriter::writePolygon(const MapObject *mapObject) diff --git a/src/tiled/changeevents.h b/src/tiled/changeevents.h index 313bb36dfa..51a8008d1d 100644 --- a/src/tiled/changeevents.h +++ b/src/tiled/changeevents.h @@ -128,7 +128,8 @@ class LayerChangeEvent : public ChangeEvent LockedProperty = 1 << 3, OffsetProperty = 1 << 4, ParallaxFactorProperty = 1 << 5, - TintColorProperty = 1 << 6, + BlendModeProperty = 1 << 6, + TintColorProperty = 1 << 7, PositionProperties = OffsetProperty | ParallaxFactorProperty, AllProperties = 0xFF }; diff --git a/src/tiled/changelayer.cpp b/src/tiled/changelayer.cpp index 39427fb664..1d4a6b4300 100644 --- a/src/tiled/changelayer.cpp +++ b/src/tiled/changelayer.cpp @@ -190,6 +190,28 @@ void SetLayerParallaxFactor::setValue(Layer *layer, const QPointF &value) const } +SetLayerBlendMode::SetLayerBlendMode(Document *document, + QList layers, + BlendMode mode, + QUndoCommand *parent) + : ChangeValue(document, std::move(layers), mode, parent) +{ + setText(QCoreApplication::translate("Undo Commands", + "Change Layer Blend Mode")); +} + +BlendMode SetLayerBlendMode::getValue(const Layer *layer) const +{ + return layer->blendMode(); +} + +void SetLayerBlendMode::setValue(Layer *layer, const BlendMode &value) const +{ + layer->setBlendMode(value); + emit document()->changed(LayerChangeEvent(layer, LayerChangeEvent::BlendModeProperty)); +} + + SetTileLayerSize::SetTileLayerSize(Document *document, TileLayer *tileLayer, QSize size, diff --git a/src/tiled/changelayer.h b/src/tiled/changelayer.h index a86c98b3ec..dec86839b8 100644 --- a/src/tiled/changelayer.h +++ b/src/tiled/changelayer.h @@ -21,6 +21,7 @@ #pragma once #include "changevalue.h" +#include "tiled.h" #include "undocommands.h" #include @@ -153,6 +154,24 @@ class SetLayerParallaxFactor : public ChangeValue void setValue(Layer *layer, const QPointF &value) const override; }; +/** + * Used for changing the layer parallax factor. + */ +class SetLayerBlendMode : public ChangeValue +{ +public: + SetLayerBlendMode(Document *document, + QList layers, + BlendMode blendMode, + QUndoCommand *parent = nullptr); + + int id() const override { return Cmd_ChangeLayerBlendMode; } + +private: + BlendMode getValue(const Layer *layer) const override; + void setValue(Layer *layer, const BlendMode &value) const override; +}; + /** * Used for changing the tile layer size. * diff --git a/src/tiled/editablelayer.cpp b/src/tiled/editablelayer.cpp index 24115ebcf7..21393b4a80 100644 --- a/src/tiled/editablelayer.cpp +++ b/src/tiled/editablelayer.cpp @@ -210,6 +210,15 @@ void EditableLayer::setParallaxFactor(QPointF factor) layer()->setParallaxFactor(factor); } +void EditableLayer::setBlendMode(BlendModeNS::Value mode) +{ + const auto blendMode = static_cast(mode); + if (auto doc = document()) + asset()->push(new SetLayerBlendMode(doc, { layer() }, blendMode)); + else if (!checkReadOnly()) + layer()->setBlendMode(blendMode); +} + void EditableLayer::setSelected(bool selected) { auto document = mapDocument(); diff --git a/src/tiled/editablelayer.h b/src/tiled/editablelayer.h index 0e9f317ea8..2c1e464974 100644 --- a/src/tiled/editablelayer.h +++ b/src/tiled/editablelayer.h @@ -31,6 +31,28 @@ class EditableGroupLayer; class EditableMap; class MapDocument; +// A copy of the BlendMode enum for the scripting API +namespace BlendModeNS { + Q_NAMESPACE + + enum Value { + Normal = static_cast(BlendMode::Normal), + Add = static_cast(BlendMode::Add), + Multiply = static_cast(BlendMode::Multiply), + Screen = static_cast(BlendMode::Screen), + Overlay = static_cast(BlendMode::Overlay), + Darken = static_cast(BlendMode::Darken), + Lighten = static_cast(BlendMode::Lighten), + ColorDodge = static_cast(BlendMode::ColorDodge), + ColorBurn = static_cast(BlendMode::ColorBurn), + HardLight = static_cast(BlendMode::HardLight), + SoftLight = static_cast(BlendMode::SoftLight), + Difference = static_cast(BlendMode::Difference), + Exclusion = static_cast(BlendMode::Exclusion), + }; + Q_ENUM_NS(Value) +} + class EditableLayer : public EditableObject { Q_OBJECT @@ -43,6 +65,7 @@ class EditableLayer : public EditableObject Q_PROPERTY(bool locked READ isLocked WRITE setLocked) Q_PROPERTY(QPointF offset READ offset WRITE setOffset) Q_PROPERTY(QPointF parallaxFactor READ parallaxFactor WRITE setParallaxFactor) + Q_PROPERTY(BlendModeNS::Value blendMode READ blendMode WRITE setBlendMode) Q_PROPERTY(Tiled::EditableMap *map READ map) Q_PROPERTY(Tiled::EditableGroupLayer *parentLayer READ parentLayer) Q_PROPERTY(bool selected READ isSelected WRITE setSelected) @@ -77,6 +100,7 @@ class EditableLayer : public EditableObject bool isLocked() const; QPointF offset() const; QPointF parallaxFactor() const; + BlendModeNS::Value blendMode() const; EditableMap *map() const; EditableGroupLayer *parentLayer() const; bool isSelected() const; @@ -104,6 +128,7 @@ public slots: void setLocked(bool locked); void setOffset(QPointF offset); void setParallaxFactor(QPointF factor); + void setBlendMode(BlendModeNS::Value mode); void setSelected(bool selected); protected: @@ -155,6 +180,11 @@ inline QPointF EditableLayer::parallaxFactor() const return layer()->parallaxFactor(); } +inline BlendModeNS::Value EditableLayer::blendMode() const +{ + return static_cast(layer()->blendMode()); +} + inline bool EditableLayer::isTileLayer() const { return layer()->isTileLayer(); diff --git a/src/tiled/imagelayeritem.cpp b/src/tiled/imagelayeritem.cpp index d2ddef5333..899591110a 100644 --- a/src/tiled/imagelayeritem.cpp +++ b/src/tiled/imagelayeritem.cpp @@ -55,5 +55,6 @@ void ImageLayerItem::paint(QPainter *painter, { // TODO: Display a border around the layer when selected MapRenderer *renderer = mMapDocument->renderer(); + painter->setCompositionMode(layer()->compositionMode()); renderer->drawImageLayer(painter, imageLayer(), option->exposedRect); } diff --git a/src/tiled/mapitem.cpp b/src/tiled/mapitem.cpp index 27f0f1f4a9..95d1618046 100644 --- a/src/tiled/mapitem.cpp +++ b/src/tiled/mapitem.cpp @@ -548,8 +548,10 @@ void MapItem::layerChanged(const LayerChangeEvent &change) QGraphicsItem *layerItem = mLayerItems.value(layer); Q_ASSERT(layerItem); - if (change.properties & LayerChangeEvent::TintColorProperty) - layerTintColorChanged(layer); + if (change.properties & (LayerChangeEvent::TintColorProperty | + LayerChangeEvent::BlendModeProperty)) { + updateLayerItems(layer); + } layerItem->setVisible(layer->isVisible()); @@ -584,7 +586,7 @@ void MapItem::layerChanged(const LayerChangeEvent &change) updateBoundingRect(); // possible layer offset change } -void MapItem::layerTintColorChanged(Layer *layer) +void MapItem::updateLayerItems(Layer *layer) { switch (layer->layerType()) { case Layer::TileLayerType: @@ -600,7 +602,7 @@ void MapItem::layerTintColorChanged(Layer *layer) case Layer::GroupLayerType: // Recurse into group layers since tint color is inherited for (auto childLayer : static_cast(layer)->layers()) - layerTintColorChanged(childLayer); + updateLayerItems(childLayer); break; } } diff --git a/src/tiled/mapitem.h b/src/tiled/mapitem.h index 04fefd1d67..fd651b1e25 100644 --- a/src/tiled/mapitem.h +++ b/src/tiled/mapitem.h @@ -107,7 +107,7 @@ class MapItem : public QGraphicsObject void layerAboutToBeRemoved(GroupLayer *parentLayer, int index); void layerRemoved(Layer *layer); void layerChanged(const LayerChangeEvent &change); - void layerTintColorChanged(Layer *layer); + void updateLayerItems(Layer *layer); void imageLayerChanged(ImageLayer *imageLayer); diff --git a/src/tiled/mapobjectitem.cpp b/src/tiled/mapobjectitem.cpp index 70c679fecb..8219f5f3a2 100644 --- a/src/tiled/mapobjectitem.cpp +++ b/src/tiled/mapobjectitem.cpp @@ -141,6 +141,12 @@ void MapObjectItem::paint(QPainter *painter, if (mIsHoveredIndicator) painter->setOpacity(0.4); + auto mode = QPainter::CompositionMode_SourceOver; + if (ObjectGroup *objectGroup = mObject->objectGroup()) + if (mObject->isTileObject()) + mode = objectGroup->compositionMode(); + painter->setCompositionMode(mode); + // This is the same as pos(), except for hover indicators const QPointF pixelPos = renderer->pixelToScreenCoords(mObject->position()); diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index b2f5890cac..0474975fc1 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -195,6 +195,25 @@ template<> EnumData enumData() return { names, {}, icons }; } +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("BlendMode", "Normal"), + QCoreApplication::translate("BlendMode", "Add"), + QCoreApplication::translate("BlendMode", "Multiply"), + QCoreApplication::translate("BlendMode", "Screen"), + QCoreApplication::translate("BlendMode", "Overlay"), + QCoreApplication::translate("BlendMode", "Darken"), + QCoreApplication::translate("BlendMode", "Lighten"), + QCoreApplication::translate("BlendMode", "Color Dodge"), + QCoreApplication::translate("BlendMode", "Color Burn"), + QCoreApplication::translate("BlendMode", "Hard Light"), + QCoreApplication::translate("BlendMode", "Soft Light"), + QCoreApplication::translate("BlendMode", "Difference"), + QCoreApplication::translate("BlendMode", "Exclusion"), + }}; +} + class FlippingProperty : public IntProperty { @@ -1002,6 +1021,15 @@ class LayerProperties : public ObjectProperties value)); }); + mBlendModeProperty = new EnumProperty( + tr("Blend Mode"), + [this] { return layer()->blendMode(); }, + [this](BlendMode mode) { + push(new SetLayerBlendMode(mapDocument(), + mapDocument()->selectedLayers(), + mode)); + }); + mOffsetProperty = new PointFProperty( tr("Offset"), [this] { return layer()->offset(); }, @@ -1046,6 +1074,7 @@ class LayerProperties : public ObjectProperties mLayerProperties->addProperty(mLockedProperty); mLayerProperties->addProperty(mOpacityProperty); mLayerProperties->addProperty(mTintColorProperty); + mLayerProperties->addProperty(mBlendModeProperty); mLayerProperties->addProperty(mOffsetProperty); mLayerProperties->addProperty(mParallaxFactorProperty); @@ -1075,6 +1104,8 @@ class LayerProperties : public ObjectProperties emit mOpacityProperty->valueChanged(); if (layerChange.properties & LayerChangeEvent::TintColorProperty) emit mTintColorProperty->valueChanged(); + if (layerChange.properties & LayerChangeEvent::BlendModeProperty) + emit mBlendModeProperty->valueChanged(); if (layerChange.properties & LayerChangeEvent::OffsetProperty) emit mOffsetProperty->valueChanged(); if (layerChange.properties & LayerChangeEvent::ParallaxFactorProperty) @@ -1112,6 +1143,7 @@ class LayerProperties : public ObjectProperties BoolProperty *mLockedProperty; IntProperty *mOpacityProperty; Property *mTintColorProperty; + BaseEnumProperty *mBlendModeProperty; Property *mOffsetProperty; PointFProperty *mParallaxFactorProperty; }; diff --git a/src/tiled/scriptmanager.cpp b/src/tiled/scriptmanager.cpp index fc36e682da..711cac76af 100644 --- a/src/tiled/scriptmanager.cpp +++ b/src/tiled/scriptmanager.cpp @@ -105,6 +105,7 @@ ScriptManager::ScriptManager(QObject *parent) connect(&mResetTimer, &QTimer::timeout, this, &ScriptManager::reset); qRegisterMetaType("AssetType"); + qRegisterMetaType("BlendMode"); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -395,6 +396,7 @@ void ScriptManager::initialize() globalObject.setProperty(QStringLiteral("tiled"), engine->newQObject(mModule)); globalObject.setProperty(QStringLiteral("Tiled"), engine->newQMetaObject()); globalObject.setProperty(QStringLiteral("AssetType"), engine->newQMetaObject(&AssetType::staticMetaObject)); + globalObject.setProperty(QStringLiteral("BlendMode"), engine->newQMetaObject(&BlendModeNS::staticMetaObject)); globalObject.setProperty(QStringLiteral("GroupLayer"), engine->newQMetaObject()); globalObject.setProperty(QStringLiteral("Image"), engine->newQMetaObject()); globalObject.setProperty(QStringLiteral("ImageLayer"), engine->newQMetaObject()); diff --git a/src/tiled/tilelayeritem.cpp b/src/tiled/tilelayeritem.cpp index 16f1585712..b968898675 100644 --- a/src/tiled/tilelayeritem.cpp +++ b/src/tiled/tilelayeritem.cpp @@ -68,5 +68,6 @@ void TileLayerItem::paint(QPainter *painter, { MapRenderer *renderer = mMapDocument->renderer(); // TODO: Display a border around the layer when selected + painter->setCompositionMode(layer()->compositionMode()); renderer->drawTileLayer(painter, tileLayer(), option->exposedRect); } diff --git a/src/tiled/undocommands.h b/src/tiled/undocommands.h index 2627662510..32abbda596 100644 --- a/src/tiled/undocommands.h +++ b/src/tiled/undocommands.h @@ -32,6 +32,7 @@ enum UndoCommands { Cmd_ChangeClassName, Cmd_ChangeImageLayerRepeatX, Cmd_ChangeImageLayerRepeatY, + Cmd_ChangeLayerBlendMode, Cmd_ChangeLayerLocked, Cmd_ChangeLayerName, Cmd_ChangeLayerOffset, diff --git a/src/tmxrasterizer/tmxrasterizer.cpp b/src/tmxrasterizer/tmxrasterizer.cpp index 05b081fca6..f731feb6c3 100644 --- a/src/tmxrasterizer/tmxrasterizer.cpp +++ b/src/tmxrasterizer/tmxrasterizer.cpp @@ -58,18 +58,18 @@ void TmxRasterizer::drawMapLayers(const MapRenderer &renderer, continue; const auto offset = layer->totalOffset() + mapOffset; + const auto compositionMode = layer->compositionMode(); + painter.setOpacity(layer->effectiveOpacity()); painter.translate(offset); - auto *tileLayer = dynamic_cast(layer); - auto *imageLayer = dynamic_cast(layer); - auto *objectGroup = dynamic_cast(layer); - - if (tileLayer) { - renderer.drawTileLayer(&painter, tileLayer); - } else if (imageLayer) { - renderer.drawImageLayer(&painter, imageLayer); - } else if (objectGroup) { + switch (layer->layerType()) { + case Layer::TileLayerType: + painter.setCompositionMode(compositionMode); + renderer.drawTileLayer(&painter, static_cast(layer)); + break; + case Layer::ObjectGroupType: { + const auto objectGroup = static_cast(layer); QList objects = objectGroup->objects(); if (objectGroup->drawOrder() == ObjectGroup::TopDownOrder) @@ -77,6 +77,11 @@ void TmxRasterizer::drawMapLayers(const MapRenderer &renderer, for (const MapObject *object : std::as_const(objects)) { if (shouldDrawObject(object)) { + if (object->isTileObject()) + painter.setCompositionMode(compositionMode); + else + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + if (object->rotation() != qreal(0)) { QPointF origin = renderer.pixelToScreenCoords(object->position()); painter.save(); @@ -91,6 +96,15 @@ void TmxRasterizer::drawMapLayers(const MapRenderer &renderer, painter.restore(); } } + break; + } + case Layer::ImageLayerType: + painter.setCompositionMode(compositionMode); + renderer.drawImageLayer(&painter, static_cast(layer)); + break; + case Layer::GroupLayerType: + // Recursion handled by LayerIterator + break; } painter.translate(-offset);