diff --git a/include/tgfx/layers/Layer.h b/include/tgfx/layers/Layer.h index 04937905..9b1c713f 100644 --- a/include/tgfx/layers/Layer.h +++ b/include/tgfx/layers/Layer.h @@ -502,7 +502,7 @@ class Layer { Paint getLayerPaint(float alpha, BlendMode blendMode); - std::shared_ptr getLayerFilter(float contentScale); + std::shared_ptr applyFilters(std::shared_ptr source, float contentScale); LayerContent* getRasterizedCache(const DrawArgs& args); diff --git a/include/tgfx/layers/filters/BlendFilter.h b/include/tgfx/layers/filters/BlendFilter.h index 77e72505..523e4c6b 100644 --- a/include/tgfx/layers/filters/BlendFilter.h +++ b/include/tgfx/layers/filters/BlendFilter.h @@ -18,14 +18,16 @@ #pragma once -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" namespace tgfx { -class BlendFilter : public LayerFilter { +/** + * A filter that applies blends between the constant color (src) and input color (dst) based on the + * BlendMode. + */ +class BlendFilter : public LayerImageFilter { public: - virtual ~BlendFilter() = default; - /** * Creates a new ColorFilter that applies blends between the constant color (src) and input color * (dst) based on the BlendMode. diff --git a/include/tgfx/layers/filters/BlurFilter.h b/include/tgfx/layers/filters/BlurFilter.h index 49e7a7a2..86ea8c31 100644 --- a/include/tgfx/layers/filters/BlurFilter.h +++ b/include/tgfx/layers/filters/BlurFilter.h @@ -18,14 +18,15 @@ #pragma once -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" namespace tgfx { -class BlurFilter : public LayerFilter { +/** + * A filter that blurs its input by the separate X and Y blurriness. + */ +class BlurFilter : public LayerImageFilter { public: - virtual ~BlurFilter() = default; - /** * Create a filter that blurs its input by the separate X and Y blurriness. The provided tile mode * is used when the blur kernel goes outside the input image. diff --git a/include/tgfx/layers/filters/ColorMatrixFilter.h b/include/tgfx/layers/filters/ColorMatrixFilter.h index 26063e15..72651127 100644 --- a/include/tgfx/layers/filters/ColorMatrixFilter.h +++ b/include/tgfx/layers/filters/ColorMatrixFilter.h @@ -18,14 +18,15 @@ #pragma once -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" namespace tgfx { -class ColorMatrixFilter : public LayerFilter { +/** + * A filter that transforms the color using the given 4x5 matrix. + */ +class ColorMatrixFilter : public LayerImageFilter { public: - virtual ~ColorMatrixFilter() = default; - /** * Creates a new ColorMatrixFilter that transforms the color using the given 4x5 matrix. The matrix can * be passed as a single array, and is treated as follows: diff --git a/include/tgfx/layers/filters/DropShadowFilter.h b/include/tgfx/layers/filters/DropShadowFilter.h index d9070585..62b93ba7 100644 --- a/include/tgfx/layers/filters/DropShadowFilter.h +++ b/include/tgfx/layers/filters/DropShadowFilter.h @@ -18,13 +18,14 @@ #pragma once -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" namespace tgfx { -class DropShadowFilter : public LayerFilter { +/** + * A filter draws a drop shadow under the input content. + */ +class DropShadowFilter : public LayerImageFilter { public: - virtual ~DropShadowFilter() = default; - /** * Create a filter that draws a drop shadow under the input content. */ diff --git a/include/tgfx/layers/filters/InnerShadowFilter.h b/include/tgfx/layers/filters/InnerShadowFilter.h index e54893ba..e5b49132 100644 --- a/include/tgfx/layers/filters/InnerShadowFilter.h +++ b/include/tgfx/layers/filters/InnerShadowFilter.h @@ -18,13 +18,15 @@ #pragma once -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" namespace tgfx { -class InnerShadowFilter : public LayerFilter { - public: - virtual ~InnerShadowFilter() = default; +/** + * A filter draws an inner shadow over the input content. + */ +class InnerShadowFilter : public LayerImageFilter { + public: /** * Create a filter that draws an inner shadow over the input content. */ diff --git a/include/tgfx/layers/filters/LayerFilter.h b/include/tgfx/layers/filters/LayerFilter.h index d78d6c7e..f78170d5 100644 --- a/include/tgfx/layers/filters/LayerFilter.h +++ b/include/tgfx/layers/filters/LayerFilter.h @@ -29,34 +29,19 @@ namespace tgfx { class LayerFilter : public LayerProperty { public: /** - * Returns the current image filter for the given scale factor. If the filter has not been - * created yet, it will be created and cached. - * @param scale The scale factor to apply to the filter. - * @return The current image filter. + * Applies the filter to the scaled Image of the layer content and draws it on the canvas. + * @param contentScale The scale factor of the source Image relative to its original size. + * Some filters have size-related parameters that must be adjusted with this scale factor. + * @return True if the filter was applied and drawn, false otherwise. */ - std::shared_ptr getImageFilter(float scale); + virtual bool applyFilter(Canvas* canvas, std::shared_ptr image, float contentScale) = 0; - protected: /** - * Creates a new image filter for the given scale factor. When it is necessary to recreate the - * ImageFilter, the onCreateImageFilter method will be called. - * @param scale The scale factor to apply to the filter. - * @return A new image filter. + * Returns the bounds after applying the filter to the scaled layer bounds + * @param contentScale The scale factor of the source bounds relative to its original size. + * Some filters have size-related parameters that must be adjusted with this scale factor + * @return The bounds of the filtered image. */ - virtual std::shared_ptr onCreateImageFilter(float scale) = 0; - - /** - * Marks the filter as dirty and invalidates the cached filter. - */ - void invalidateFilter(); - - private: - bool dirty = true; - - float lastScale = 1.0f; - - std::unique_ptr _clipBounds = nullptr; - - std::shared_ptr lastFilter; + virtual Rect filterBounds(const Rect& srcRect, float contentScale) = 0; }; } // namespace tgfx diff --git a/include/tgfx/layers/filters/LayerImageFilter.h b/include/tgfx/layers/filters/LayerImageFilter.h new file mode 100644 index 00000000..2384fdf0 --- /dev/null +++ b/include/tgfx/layers/filters/LayerImageFilter.h @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/layers/filters/LayerFilter.h" + +namespace tgfx { + +/** + * LayerImageFilter is a filter that applies an image filter to a layer. + */ +class LayerImageFilter : public LayerFilter { + public: + bool applyFilter(Canvas* canvas, std::shared_ptr image, float contentScale) override; + + Rect filterBounds(const Rect& srcRect, float contentScale) override; + + protected: + /** + * Creates a new image filter for the given scale factor. When it is necessary to recreate the + * ImageFilter, the onCreateImageFilter method will be called. + * @param contentScale The scale factor of the source Image relative to its original size. + * Some filters have size-related parameters that must be adjusted with this scale factor. + * @return A new image filter. + */ + virtual std::shared_ptr onCreateImageFilter(float contentScale) = 0; + + /** + * Marks the filter as dirty and invalidates the cached filter. + */ + void invalidateFilter(); + + private: + std::shared_ptr getImageFilter(float scale); + + bool dirty = true; + + float lastScale = 1.0f; + + std::shared_ptr lastFilter; +}; +} // namespace tgfx diff --git a/src/layers/Layer.cpp b/src/layers/Layer.cpp index 525ce14f..3b5b1d60 100644 --- a/src/layers/Layer.cpp +++ b/src/layers/Layer.cpp @@ -31,6 +31,21 @@ namespace tgfx { static std::atomic_bool AllowsEdgeAntialiasing = true; static std::atomic_bool AllowsGroupOpacity = false; +static std::shared_ptr CreatePictureImage(std::shared_ptr picture, Point* offset) { + if (picture == nullptr) { + return nullptr; + } + auto bounds = picture->getBounds(); + bounds.roundOut(); + auto matrix = Matrix::MakeTrans(-bounds.x(), -bounds.y()); + auto image = Image::MakeFrom(std::move(picture), static_cast(bounds.width()), + static_cast(bounds.height()), &matrix); + if (offset) { + *offset = Point::Make(bounds.x(), bounds.y()); + } + return image; +} + bool Layer::DefaultAllowsEdgeAntialiasing() { return AllowsEdgeAntialiasing; } @@ -346,9 +361,8 @@ Rect Layer::getBounds(const Layer* targetCoordinateSpace) { bounds.join(childBounds); } - auto imageFilter = getLayerFilter(1.0f); - if (imageFilter) { - bounds = imageFilter->filterBounds(bounds); + for (auto filter : _filters) { + bounds = filter->filterBounds(bounds, 1.0f); } if (targetCoordinateSpace && targetCoordinateSpace != this) { @@ -535,19 +549,31 @@ Paint Layer::getLayerPaint(float alpha, BlendMode blendMode) { return paint; } -std::shared_ptr Layer::getLayerFilter(float contentScale) { - if (_filters.empty() || FloatNearlyZero(contentScale)) { +std::shared_ptr Layer::applyFilters(std::shared_ptr source, float contentScale) { + if (source == nullptr) { return nullptr; } - std::vector> imageFilters; + if (_filters.empty()) { + return source; + } + Point offset = Point::Zero(); + auto image = CreatePictureImage(std::move(source), &offset); + Recorder recorder = {}; for (const auto& filter : _filters) { - auto imageFilter = filter->getImageFilter(contentScale); - if (!imageFilter) { + auto canvas = recorder.beginRecording(); + if (!filter->applyFilter(canvas, image, contentScale)) { continue; } - imageFilters.push_back(imageFilter); + Point filterOffset = Point::Zero(); + image = CreatePictureImage(recorder.finishRecordingAsPicture(), &filterOffset); + if (image == nullptr) { + return nullptr; + } + offset += filterOffset; } - return ImageFilter::Compose(imageFilters); + auto canvas = recorder.beginRecording(); + canvas->drawImage(image, offset.x, offset.y); + return recorder.finishRecordingAsPicture(); } LayerContent* Layer::getRasterizedCache(const DrawArgs& args) { @@ -579,36 +605,26 @@ LayerContent* Layer::getRasterizedCache(const DrawArgs& args) { std::shared_ptr Layer::getRasterizedImage(const DrawArgs& args, float contentScale, Matrix* drawingMatrix) { DEBUG_ASSERT(drawingMatrix != nullptr); + if (FloatNearlyZero(contentScale)) { + return nullptr; + } auto picture = getLayerContents(args, contentScale); + picture = applyFilters(std::move(picture), contentScale); if (!picture) { return nullptr; } - auto bounds = picture->getBounds(); - auto width = static_cast(ceilf(bounds.width())); - auto height = static_cast(ceilf(bounds.height())); - auto matrix = Matrix::MakeTrans(-bounds.left, -bounds.top); - auto image = Image::MakeFrom(std::move(picture), width, height, &matrix); + Point offset = Point::Zero(); + auto image = CreatePictureImage(std::move(picture), &offset); if (image == nullptr) { return nullptr; } drawingMatrix->setScale(1.0f / contentScale, 1.0f / contentScale); - drawingMatrix->preTranslate(bounds.left, bounds.top); - if (auto filter = getLayerFilter(contentScale)) { - auto offset = Point::Zero(); - image = image->makeWithFilter(std::move(filter), &offset); - if (image == nullptr) { - return nullptr; - } - drawingMatrix->preTranslate(offset.x, offset.y); - } + drawingMatrix->preTranslate(offset.x, offset.y); return image; } std::shared_ptr Layer::getLayerContents(const DrawArgs& args, float contentScale) { - if (FloatNearlyZero(contentScale)) { - return nullptr; - } - Recorder recorder; + Recorder recorder = {}; auto contentCanvas = recorder.beginRecording(); contentCanvas->scale(contentScale, contentScale); drawContents(args, contentCanvas, 1.0f); @@ -673,7 +689,11 @@ std::shared_ptr Layer::getMaskFilter(const DrawArgs& args, float sca void Layer::drawOffscreen(const DrawArgs& args, Canvas* canvas, float alpha, BlendMode blendMode) { auto contentScale = canvas->getMatrix().getMaxScale(); + if (FloatNearlyZero(contentScale)) { + return; + } auto picture = getLayerContents(args, contentScale); + picture = applyFilters(std::move(picture), contentScale); if (picture == nullptr) { return; } @@ -681,8 +701,6 @@ void Layer::drawOffscreen(const DrawArgs& args, Canvas* canvas, float alpha, Ble paint.setAlpha(alpha); paint.setBlendMode(blendMode); paint.setMaskFilter(getMaskFilter(args, contentScale)); - auto filter = getLayerFilter(contentScale); - paint.setImageFilter(filter); auto matrix = Matrix::MakeScale(1.0f / contentScale); canvas->drawPicture(std::move(picture), &matrix, &paint); } diff --git a/src/layers/filters/BlendFilter.cpp b/src/layers/filters/BlendFilter.cpp index e3bc2f98..e20ca273 100644 --- a/src/layers/filters/BlendFilter.cpp +++ b/src/layers/filters/BlendFilter.cpp @@ -45,7 +45,7 @@ std::shared_ptr BlendFilter::onCreateImageFilter(float) { } BlendFilter::BlendFilter(const Color& color, BlendMode blendMode) - : LayerFilter(), _color(std::move(color)), _blendMode(blendMode) { + : _color(std::move(color)), _blendMode(blendMode) { } } // namespace tgfx diff --git a/src/layers/filters/BlurFilter.cpp b/src/layers/filters/BlurFilter.cpp index ee4faebd..a20c25b1 100644 --- a/src/layers/filters/BlurFilter.cpp +++ b/src/layers/filters/BlurFilter.cpp @@ -50,7 +50,7 @@ void BlurFilter::setTileMode(TileMode tileMode) { } BlurFilter::BlurFilter(float blurrinessX, float blurrinessY, TileMode tileMode) - : LayerFilter(), _blurrinessX(blurrinessX), _blurrinessY(blurrinessY), _tileMode(tileMode) { + : _blurrinessX(blurrinessX), _blurrinessY(blurrinessY), _tileMode(tileMode) { } std::shared_ptr BlurFilter::onCreateImageFilter(float scale) { diff --git a/src/layers/filters/ColorMatrixFilter.cpp b/src/layers/filters/ColorMatrixFilter.cpp index 68fab02b..9ff763da 100644 --- a/src/layers/filters/ColorMatrixFilter.cpp +++ b/src/layers/filters/ColorMatrixFilter.cpp @@ -37,7 +37,7 @@ std::shared_ptr ColorMatrixFilter::onCreateImageFilter(float) { } ColorMatrixFilter::ColorMatrixFilter(const std::array& matrix) - : LayerFilter(), _matrix(std::move(matrix)) { + : _matrix(std::move(matrix)) { } } // namespace tgfx \ No newline at end of file diff --git a/src/layers/filters/DropShadowFilter.cpp b/src/layers/filters/DropShadowFilter.cpp index b625e2dc..74a13eee 100644 --- a/src/layers/filters/DropShadowFilter.cpp +++ b/src/layers/filters/DropShadowFilter.cpp @@ -86,8 +86,8 @@ std::shared_ptr DropShadowFilter::onCreateImageFilter(float scale) DropShadowFilter::DropShadowFilter(float offsetX, float offsetY, float blurrinessX, float blurrinessY, const Color& color, bool dropsShadowOnly) - : LayerFilter(), _offsetX(offsetX), _offsetY(offsetY), _blurrinessX(blurrinessX), - _blurrinessY(blurrinessY), _color(std::move(color)), _dropsShadowOnly(dropsShadowOnly) { + : _offsetX(offsetX), _offsetY(offsetY), _blurrinessX(blurrinessX), _blurrinessY(blurrinessY), + _color(std::move(color)), _dropsShadowOnly(dropsShadowOnly) { } } // namespace tgfx diff --git a/src/layers/filters/InnerShadowFilter.cpp b/src/layers/filters/InnerShadowFilter.cpp index 3277b892..7d1bdda9 100644 --- a/src/layers/filters/InnerShadowFilter.cpp +++ b/src/layers/filters/InnerShadowFilter.cpp @@ -86,8 +86,8 @@ std::shared_ptr InnerShadowFilter::onCreateImageFilter(float scale) InnerShadowFilter::InnerShadowFilter(float offsetX, float offsetY, float blurrinessX, float blurrinessY, const Color& color, bool innerShadowOnly) - : LayerFilter(), _offsetX(offsetX), _offsetY(offsetY), _blurrinessX(blurrinessX), - _blurrinessY(blurrinessY), _color(std::move(color)), _innerShadowOnly(innerShadowOnly) { + : _offsetX(offsetX), _offsetY(offsetY), _blurrinessX(blurrinessX), _blurrinessY(blurrinessY), + _color(std::move(color)), _innerShadowOnly(innerShadowOnly) { } } // namespace tgfx diff --git a/src/layers/filters/LayerFilter.cpp b/src/layers/filters/LayerImageFilter.cpp similarity index 56% rename from src/layers/filters/LayerFilter.cpp rename to src/layers/filters/LayerImageFilter.cpp index 4c09002f..88e430f7 100644 --- a/src/layers/filters/LayerFilter.cpp +++ b/src/layers/filters/LayerImageFilter.cpp @@ -16,22 +16,42 @@ // ///////////////////////////////////////////////////////////////////////////////////////////////// -#include "tgfx/layers/filters/LayerFilter.h" +#include "tgfx/layers/filters/LayerImageFilter.h" #include "tgfx/layers/Layer.h" namespace tgfx { -std::shared_ptr LayerFilter::getImageFilter(float filterScale) { - if (dirty || lastScale != filterScale) { - lastFilter = onCreateImageFilter(filterScale); - lastScale = filterScale; - dirty = false; +bool LayerImageFilter::applyFilter(Canvas* canvas, std::shared_ptr image, + float contentScale) { + auto filter = getImageFilter(contentScale); + if (!filter) { + return false; } - return lastFilter; + Paint paint = {}; + paint.setImageFilter(filter); + canvas->drawImage(image, &paint); + return true; +} + +Rect LayerImageFilter::filterBounds(const Rect& srcRect, float contentScale) { + auto filter = getImageFilter(contentScale); + if (!filter) { + return srcRect; + } + return filter->filterBounds(srcRect); } -void LayerFilter::invalidateFilter() { +void LayerImageFilter::invalidateFilter() { dirty = true; invalidate(); } +std::shared_ptr LayerImageFilter::getImageFilter(float scale) { + if (dirty || lastScale != scale) { + lastFilter = onCreateImageFilter(scale); + lastScale = scale; + dirty = false; + } + return lastFilter; +} + } // namespace tgfx \ No newline at end of file diff --git a/test/baseline/version.json b/test/baseline/version.json index 02426910..5db9e8e8 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -97,6 +97,7 @@ "dropShadow": "43cd416", "filterClip": "43cd416", "filterTest": "44166b7", + "filters": "9000907", "getBounds": "3888c19", "getLayersUnderPoint": "b062b9a", "greyColorMatrix": "a1605b2", @@ -104,8 +105,10 @@ "imageLayer": "99a5cd9", "imageMask": "af2e3ff", "innerShadow": "01cce38", + "layerShadowFilter": "9000907", + "layerShadowFilter1": "9000907", "shapeMask": "612c09e", - "textMask": "b062b9a" + "textMask": "9000907" }, "MaskTest": { "rasterize_emoji": "c0f39f8", diff --git a/test/src/LayerShadowFilter.cpp b/test/src/LayerShadowFilter.cpp new file mode 100644 index 00000000..b2eb521d --- /dev/null +++ b/test/src/LayerShadowFilter.cpp @@ -0,0 +1,105 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "LayerShadowFilter.h" +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Recorder.h" + +namespace tgfx { + +std::shared_ptr LayerShadowFilter::Make( + const std::vector& params) { + return std::shared_ptr(new LayerShadowFilter(params)); +} + +LayerShadowFilter::LayerShadowFilter(const std::vector& params) : params(params) { +} + +void LayerShadowFilter::setShadowParams(const std::vector& shadowParams) { + if (shadowParams == params) { + return; + } + params = shadowParams; + invalidate(); +} + +void LayerShadowFilter::setShowBehindTransparent(bool showBehindTransparent) { + if (showBehindTransparent == _showBehindTransparent) { + return; + } + _showBehindTransparent = showBehindTransparent; + invalidate(); +} + +bool LayerShadowFilter::applyFilter(Canvas* canvas, std::shared_ptr image, + float contentScale) { + if (!image) { + return false; + } + + drawShadows(canvas, image, contentScale); + + canvas->drawImage(image); + return true; +} + +Rect LayerShadowFilter::filterBounds(const Rect& srcRect, float scale) { + auto maxRect = srcRect; + for (const auto& param : params) { + auto filter = CreateShadowFilter(param, scale); + if (!filter) { + continue; + } + maxRect.join(filter->filterBounds(srcRect)); + } + return maxRect; +} + +std::shared_ptr LayerShadowFilter::CreateShadowFilter(const LayerShadowParam& param, + float scale) { + return ImageFilter::DropShadowOnly(param.offsetX * scale, param.offsetY * scale, + param.blurrinessX * scale, param.blurrinessY * scale, + param.color); +} + +void LayerShadowFilter::drawShadows(Canvas* canvas, std::shared_ptr image, + float contentScale) { + // create opaque image + auto opaqueFilter = ImageFilter::ColorFilter(ColorFilter::AlphaThreshold(0)); + auto opaqueImage = image->makeWithFilter(opaqueFilter); + + auto shader = Shader::MakeImageShader(opaqueImage, TileMode::Decal, TileMode::Decal); + Point offset = Point::Zero(); + + for (const auto& param : params) { + if (auto filter = CreateShadowFilter(param, contentScale)) { + auto shadowImage = opaqueImage->makeWithFilter(filter, &offset); + Paint paint{}; + if (!_showBehindTransparent) { + auto matrixShader = shader->makeWithMatrix(Matrix::MakeTrans(-offset.x, -offset.y)); + paint.setMaskFilter(MaskFilter::MakeShader(matrixShader, true)); + } + canvas->drawImage(shadowImage, offset.x, offset.y, &paint); + } + } +} + +} // namespace tgfx \ No newline at end of file diff --git a/test/src/LayerShadowFilter.h b/test/src/LayerShadowFilter.h new file mode 100644 index 00000000..c94206c9 --- /dev/null +++ b/test/src/LayerShadowFilter.h @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/layers/filters/LayerFilter.h" + +namespace tgfx { + +/** + * Parameters for the drop shadow. + */ +struct LayerShadowParam { + float offsetX = 0.0f; + float offsetY = 0.0f; + float blurrinessX = 0.0f; + float blurrinessY = 0.0f; + Color color = Color::Black(); + + bool operator==(const LayerShadowParam& value) const { + return offsetX == value.offsetX && offsetY == value.offsetY && + blurrinessX == value.blurrinessX && blurrinessY == value.blurrinessY && + color == value.color; + } +}; + +class LayerShadowFilter : public LayerFilter { + public: + /** + * Create a filter that draws drop shadows under the input content. + */ + static std::shared_ptr Make(const std::vector& params); + + /** + * The parameters for the drop shadows. + */ + std::vector shadowParams() const { + return params; + } + + void setShadowParams(const std::vector& params); + + /** + * Whether to show shadows behind transparent regions of the input content. + */ + bool showBehindTransparent() const { + return _showBehindTransparent; + } + + void setShowBehindTransparent(bool showBehindTransparent); + + bool applyFilter(Canvas* canvas, std::shared_ptr image, float scale) override; + + Rect filterBounds(const Rect& srcRect, float scale) override; + + private: + static std::shared_ptr CreateShadowFilter(const LayerShadowParam& param, + float scale); + + explicit LayerShadowFilter(const std::vector& params); + + void drawShadows(Canvas* canvas, std::shared_ptr image, float contentScale); + + std::vector params = {}; + + bool _showBehindTransparent = false; +}; + +} // namespace tgfx diff --git a/test/src/LayerTest.cpp b/test/src/LayerTest.cpp index d65326c6..0186f8f8 100644 --- a/test/src/LayerTest.cpp +++ b/test/src/LayerTest.cpp @@ -18,6 +18,7 @@ #include #include +#include "LayerShadowFilter.h" #include "core/filters/BlurImageFilter.h" #include "core/utils/Profiling.h" #include "tgfx/core/PathEffect.h" @@ -1741,4 +1742,61 @@ TGFX_TEST(LayerTest, DirtyFlag) { EXPECT_TRUE(!child->bitFields.childrenDirty && !child->bitFields.contentDirty); EXPECT_TRUE(root->bitFields.childrenDirty && !root->bitFields.contentDirty); } + +TGFX_TEST(LayerTest, LayerShadowFilter) { + ContextScope scope; + auto context = scope.getContext(); + EXPECT_TRUE(context != nullptr); + auto surface = Surface::Make(context, 150, 150); + auto displayList = std::make_unique(); + auto layer = ShapeLayer::Make(); + layer->setMatrix(Matrix::MakeTrans(30, 30)); + Path path; + path.addRect(Rect::MakeWH(100, 100)); + layer->setPath(path); + auto fillStyle = SolidColor::Make(Color::FromRGBA(100, 0, 0, 128)); + layer->setFillStyle(fillStyle); + auto filter = LayerShadowFilter::Make({}); + layer->setFilters({filter}); + LayerShadowParam param0 = {}; + param0.offsetX = 10; + param0.offsetY = 10; + LayerShadowParam param1 = {}; + param1.offsetX = -10; + param1.offsetY = -10; + param1.color = Color::White(); + filter->setShadowParams({param0, param1}); + displayList->root()->addChild(layer); + displayList->render(surface.get()); + EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/layerShadowFilter")); + + param1.offsetX = 0; + param1.offsetY = -20; + filter->setShadowParams({param0, param1}); + filter->setShowBehindTransparent(true); + displayList->render(surface.get()); + EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/layerShadowFilter1")); +} + +TGFX_TEST(LayerTest, Filters) { + ContextScope scope; + auto context = scope.getContext(); + EXPECT_TRUE(context != nullptr); + auto surface = Surface::Make(context, 150, 150); + auto displayList = std::make_unique(); + auto layer = ShapeLayer::Make(); + layer->setMatrix(Matrix::MakeTrans(30, 30)); + Path path; + path.addRect(Rect::MakeWH(100, 100)); + layer->setPath(path); + auto fillStyle = SolidColor::Make(Color::FromRGBA(100, 0, 0, 128)); + layer->setFillStyle(fillStyle); + auto filter = BlurFilter::Make(10, 10); + auto filter2 = DropShadowFilter::Make(10, 10, 0, 0, Color::Black()); + auto filter3 = InnerShadowFilter::Make(10, 10, 0, 0, Color::White()); + layer->setFilters({filter, filter2, filter3}); + displayList->root()->addChild(layer); + displayList->render(surface.get()); + EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/filters")); +} } // namespace tgfx