diff --git a/include/tgfx/core/ImageFilter.h b/include/tgfx/core/ImageFilter.h index ab8bbbb1..ed4696d8 100644 --- a/include/tgfx/core/ImageFilter.h +++ b/include/tgfx/core/ImageFilter.h @@ -82,6 +82,30 @@ class ImageFilter { static std::shared_ptr DropShadowOnly(float dx, float dy, float blurrinessX, float blurrinessY, const Color& color); + /** + * Create a filter that draws an inner shadow over the input content. This filter produces an image + * that includes the inputs' content. + * @param dx The X offset of the shadow. + * @param dy The Y offset of the shadow. + * @param blurrinessX The blur radius for the shadow, along the X axis. + * @param blurrinessY The blur radius for the shadow, along the Y axis. + * @param color The color of the inner shadow. + */ + static std::shared_ptr InnerShadow(float dx, float dy, float blurrinessX, + float blurrinessY, const Color& color); + + /** + * Create a filter that renders an inner shadow, in exactly the same manner as the InnerShadow(), + * except that the resulting image does not include the input content. + * @param dx The X offset of the shadow. + * @param dy The Y offset of the shadow. + * @param blurrinessX The blur radius for the shadow, along the X axis. + * @param blurrinessY The blur radius for the shadow, along the Y axis. + * @param color The color of the inner shadow. + */ + static std::shared_ptr InnerShadowOnly(float dx, float dy, float blurrinessX, + float blurrinessY, const Color& color); + /** * Create a filter that applies the given color filter to the input image. */ @@ -143,6 +167,7 @@ class ImageFilter { const Matrix* uvMatrix) const; friend class DropShadowImageFilter; + friend class InnerShadowImageFilter; friend class ComposeImageFilter; friend class FilterImage; }; diff --git a/include/tgfx/layers/filters/InnerShadowFilter.h b/include/tgfx/layers/filters/InnerShadowFilter.h new file mode 100644 index 00000000..e54893ba --- /dev/null +++ b/include/tgfx/layers/filters/InnerShadowFilter.h @@ -0,0 +1,126 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// 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 { +class InnerShadowFilter : public LayerFilter { + public: + virtual ~InnerShadowFilter() = default; + + /** + * Create a filter that draws an inner shadow over the input content. + */ + static std::shared_ptr Make(float offsetX, float offsetY, float blurrinessX, + float blurrinessY, const Color& color, + bool innerShadowOnly = false); + + /** + * The x offset of the shadow. + */ + float offsetX() const { + return _offsetX; + } + + /** + * Set x offset of the shadow. + * @param offsetX + */ + void setOffsetX(float offsetX); + + /** + * The y offset of the shadow. + */ + float offsetY() const { + return _offsetY; + } + + /** + * Set y offset of the shadow. + * @param offsetY + */ + void setOffsetY(float offsetY); + + /** + * The blur radius for the shadow, along the X axis. + */ + float blurrinessX() const { + return _blurrinessX; + } + + /** + * Set blur radius for the shadow, along the X axis. + * @param blurrinessX + */ + void setBlurrinessX(float blurrinessX); + + /** + * The blur radius for the shadow, along the Y axis. + */ + float blurrinessY() const { + return _blurrinessY; + } + + /** + * Set blur radius for the shadow, along the Y axis. + * @param blurrinessY + */ + void setBlurrinessY(float blurrinessY); + + /** + * The color of the shadow. + */ + Color color() const { + return _color; + } + + /** + * Set the color of the shadow. + * @param color + */ + void setColor(const Color& color); + + /** + * Whether the resulting image does not include the input content. + */ + bool innerShadowOnly() const { + return _innerShadowOnly; + } + + /** + * Set whether the resulting image does not include the input content. + */ + void setInnerShadowOnly(bool value); + + protected: + std::shared_ptr onCreateImageFilter(float scale) override; + + private: + InnerShadowFilter(float offsetX, float offsetY, float blurrinessX, float blurrinessY, + const Color& color, bool innerShadowOnly); + float _offsetX = 0.0f; + float _offsetY = 0.0f; + float _blurrinessX = 0.0f; + float _blurrinessY = 0.0f; + Color _color = Color::Black(); + bool _innerShadowOnly = false; +}; + +} // namespace tgfx diff --git a/src/core/filters/InnerShadowImageFilter.cpp b/src/core/filters/InnerShadowImageFilter.cpp new file mode 100644 index 00000000..82d4676a --- /dev/null +++ b/src/core/filters/InnerShadowImageFilter.cpp @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// 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 "InnerShadowImageFilter.h" +#include "core/images/TextureImage.h" +#include "core/utils/NeedMipmaps.h" +#include "gpu/processors/ConstColorProcessor.h" +#include "gpu/processors/FragmentProcessor.h" +#include "gpu/processors/XfermodeFragmentProcessor.h" +namespace tgfx { +std::shared_ptr ImageFilter::InnerShadow(float dx, float dy, float blurrinessX, + float blurrinessY, const Color& color) { + return std::make_shared(dx, dy, blurrinessX, blurrinessY, color, false); +} + +std::shared_ptr ImageFilter::InnerShadowOnly(float dx, float dy, float blurrinessX, + float blurrinessY, const Color& color) { + + return std::make_shared(dx, dy, blurrinessX, blurrinessY, color, true); +} + +InnerShadowImageFilter::InnerShadowImageFilter(float dx, float dy, float blurrinessX, + float blurrinessY, const Color& color, + bool shadowOnly) + + : dx(dx), dy(dy), blurFilter(ImageFilter::Blur(blurrinessX, blurrinessY)), color(color), + shadowOnly(shadowOnly) { +} + +std::unique_ptr InnerShadowImageFilter::asFragmentProcessor( + std::shared_ptr source, const FPArgs& args, const SamplingOptions& sampling, + const Matrix* uvMatrix) const { + if (source->isComplex()) { + auto needMipmaps = NeedMipmaps(sampling, args.viewMatrix, uvMatrix); + source = source->makeRasterized(needMipmaps, sampling); + } + + // get inverted shadow mask + auto shadowMatrix = Matrix::MakeTrans(-dx, -dy); + if (uvMatrix != nullptr) { + shadowMatrix.preConcat(*uvMatrix); + } + std::unique_ptr invertShadowMask; + if (blurFilter != nullptr) { + invertShadowMask = blurFilter->asFragmentProcessor(source, args, sampling, &shadowMatrix); + } else { + invertShadowMask = FragmentProcessor::Make(source, args, TileMode::Decal, TileMode::Decal, + sampling, &shadowMatrix); + } + + auto colorProcessor = ConstColorProcessor::Make(color, InputMode::Ignore); + + // get shadow mask and fill it with color + auto colorShadowProcessor = XfermodeFragmentProcessor::MakeFromTwoProcessors( + std::move(colorProcessor), std::move(invertShadowMask), BlendMode::SrcOut); + + auto imageProcessor = FragmentProcessor::Make(std::move(source), args, TileMode::Decal, + TileMode::Decal, sampling, uvMatrix); + + if (shadowOnly) { + // mask the image with origin image + return XfermodeFragmentProcessor::MakeFromTwoProcessors( + std::move(colorShadowProcessor), std::move(imageProcessor), BlendMode::SrcIn); + } else { + // mask the image with origin image and draw the inner shadow mask on top + return XfermodeFragmentProcessor::MakeFromTwoProcessors( + std::move(colorShadowProcessor), std::move(imageProcessor), BlendMode::SrcATop); + } +} + +} // namespace tgfx diff --git a/src/core/filters/InnerShadowImageFilter.h b/src/core/filters/InnerShadowImageFilter.h new file mode 100644 index 00000000..f752167e --- /dev/null +++ b/src/core/filters/InnerShadowImageFilter.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// 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/core/ImageFilter.h" + +namespace tgfx { +class InnerShadowImageFilter : public ImageFilter { + public: + InnerShadowImageFilter(float dx, float dy, float blurrinessX, float blurrinessY, + const Color& color, bool shadowOnly); + + protected: + std::unique_ptr asFragmentProcessor(std::shared_ptr source, + const FPArgs& args, + const SamplingOptions& sampling, + const Matrix* uvMatrix) const override; + + private: + float dx = 0.0f; + float dy = 0.0f; + std::shared_ptr blurFilter = nullptr; + Color color = Color::Black(); + bool shadowOnly = false; +}; +} // namespace tgfx diff --git a/src/layers/filters/InnerShadowFilter.cpp b/src/layers/filters/InnerShadowFilter.cpp new file mode 100644 index 00000000..3277b892 --- /dev/null +++ b/src/layers/filters/InnerShadowFilter.cpp @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// 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 "tgfx/layers/filters/InnerShadowFilter.h" + +namespace tgfx { +std::shared_ptr InnerShadowFilter::Make(float offsetX, float offsetY, + float blurrinessX, float blurrinessY, + const Color& color, + bool innerShadowOnly) { + return std::shared_ptr( + new InnerShadowFilter(offsetX, offsetY, blurrinessX, blurrinessY, color, innerShadowOnly)); +} + +void InnerShadowFilter::setOffsetX(float offsetX) { + if (_offsetX == offsetX) { + return; + } + _offsetX = offsetX; + invalidateFilter(); +} + +void InnerShadowFilter::setOffsetY(float offsetY) { + if (_offsetY == offsetY) { + return; + } + _offsetY = offsetY; + invalidateFilter(); +} + +void InnerShadowFilter::setBlurrinessX(float blurrinessX) { + if (_blurrinessX == blurrinessX) { + return; + } + _blurrinessX = blurrinessX; + invalidateFilter(); +} + +void InnerShadowFilter::setBlurrinessY(float blurrinessY) { + if (_blurrinessY == blurrinessY) { + return; + } + _blurrinessY = blurrinessY; + invalidateFilter(); +} + +void InnerShadowFilter::setColor(const Color& color) { + if (_color == color) { + return; + } + _color = color; + invalidateFilter(); +} + +void InnerShadowFilter::setInnerShadowOnly(bool value) { + if (_innerShadowOnly == value) { + return; + } + _innerShadowOnly = value; + invalidateFilter(); +} + +std::shared_ptr InnerShadowFilter::onCreateImageFilter(float scale) { + if (_innerShadowOnly) { + return ImageFilter::InnerShadowOnly(_offsetX * scale, _offsetY * scale, _blurrinessX * scale, + _blurrinessY * scale, _color); + } + return ImageFilter::InnerShadow(_offsetX * scale, _offsetY * scale, _blurrinessX * scale, + _blurrinessY * scale, _color); +} + +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) { +} + +} // namespace tgfx diff --git a/test/baseline/version.json b/test/baseline/version.json index 748109d1..13ef3949 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -54,6 +54,7 @@ "dropShadow": "a30de8b", "greyColorMatrix": "a30de8b", "identityMatrix": "a30de8b", + "innerShadow": "01cce38", "shaderMaskFilter": "ded3c91" }, "ImageReaderTest": { @@ -77,6 +78,7 @@ "identityMatrix": "a1605b2", "imageLayer": "99a5cd9", "imageMask": "af2e3ff", + "innerShadow": "01cce38", "shapeMask": "612c09e", "textMask": "50c58e6" }, diff --git a/test/src/FilterTest.cpp b/test/src/FilterTest.cpp index 2b1b1418..2c8f3633 100644 --- a/test/src/FilterTest.cpp +++ b/test/src/FilterTest.cpp @@ -324,4 +324,39 @@ TGFX_TEST(FilterTest, RuntimeEffect) { EXPECT_TRUE(Baseline::Compare(surface, "FilterTest/RuntimeEffect")); device->unlock(); } + +TGFX_TEST(FilterTest, InnerShadow) { + auto device = DevicePool::Make(); + ASSERT_TRUE(device != nullptr); + auto context = device->lockContext(); + ASSERT_TRUE(context != nullptr); + auto image = MakeImage("resources/apitest/imageReplacement.png"); + ASSERT_TRUE(image != nullptr); + auto imageWidth = static_cast(image->width()); + auto imageHeight = static_cast(image->height()); + auto padding = 30.f; + Paint paint; + auto surface = Surface::Make(context, static_cast(imageWidth * 2.f + padding * 3.f), + static_cast(imageHeight * 2.f + padding * 3.f)); + auto canvas = surface->getCanvas(); + canvas->concat(Matrix::MakeTrans(padding, padding)); + paint.setImageFilter(ImageFilter::Blur(15, 15)); + canvas->drawImage(image, &paint); + + canvas->concat(Matrix::MakeTrans(imageWidth + padding, 0)); + paint.setImageFilter(ImageFilter::InnerShadowOnly(0, 0, 15, 15, Color::White())); + canvas->drawImage(image, &paint); + + canvas->concat(Matrix::MakeTrans(-imageWidth - padding, imageWidth + padding)); + paint.setImageFilter(ImageFilter::InnerShadow(0, 0, 15, 15, Color::White())); + canvas->drawImage(image, &paint); + + canvas->concat(Matrix::MakeTrans(imageWidth + padding, 0)); + auto filter = ImageFilter::InnerShadow(3, 3, 0, 0, Color::White()); + paint.setImageFilter(filter); + canvas->drawImage(image, &paint); + + EXPECT_TRUE(Baseline::Compare(surface, "FilterTest/innerShadow")); + device->unlock(); +} } // namespace tgfx diff --git a/test/src/LayerTest.cpp b/test/src/LayerTest.cpp index 95669074..f1fcca68 100644 --- a/test/src/LayerTest.cpp +++ b/test/src/LayerTest.cpp @@ -31,6 +31,7 @@ #include "tgfx/layers/filters/BlurFilter.h" #include "tgfx/layers/filters/ColorMatrixFilter.h" #include "tgfx/layers/filters/DropShadowFilter.h" +#include "tgfx/layers/filters/InnerShadowFilter.h" #include "utils/TestUtils.h" #include "utils/common.h" @@ -1670,4 +1671,52 @@ TGFX_TEST(LayerTest, hitTestPointNested) { EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/Layer_hitTestPointNested")); device->unlock(); } + +TGFX_TEST(LayerTest, InnerShadowFilter) { + auto device = DevicePool::Make(); + ASSERT_TRUE(device != nullptr); + auto context = device->lockContext(); + ASSERT_TRUE(context != nullptr); + auto image = MakeImage("resources/apitest/imageReplacement.png"); + ASSERT_TRUE(image != nullptr); + auto imageWidth = static_cast(image->width()); + auto imageHeight = static_cast(image->height()); + auto padding = 30.f; + Paint paint; + auto surface = Surface::Make(context, static_cast(imageWidth * 2.f + padding * 3.f), + static_cast(imageHeight * 2.f + padding * 3.f)); + auto filter = BlurFilter::Make(15, 15); + auto layer = ImageLayer::Make(); + layer->setImage(image); + layer->setMatrix(Matrix::MakeTrans(padding, padding)); + layer->setFilters({filter}); + auto displayList = std::make_unique(); + displayList->root()->addChild(layer); + + auto layer2 = ImageLayer::Make(); + layer2->setImage(image); + layer2->setMatrix(Matrix::MakeTrans(imageWidth + padding * 2, padding)); + auto filter2 = InnerShadowFilter::Make(0, 0, 15, 15, Color::Black(), true); + layer2->setFilters({filter2}); + displayList->root()->addChild(layer2); + + auto layer3 = ImageLayer::Make(); + layer3->setImage(image); + layer3->setMatrix(Matrix::MakeTrans(padding, imageWidth + padding * 2)); + auto filter3 = InnerShadowFilter::Make(0, 0, 15, 15, Color::Black()); + layer3->setFilters({filter3}); + displayList->root()->addChild(layer3); + + auto layer4 = ImageLayer::Make(); + layer4->setImage(image); + layer4->setMatrix(Matrix::MakeTrans(imageWidth + padding * 2, imageWidth + padding * 2)); + auto filter4 = InnerShadowFilter::Make(1, 1, 0, 0, Color::Black()); + layer4->setFilters({filter4}); + displayList->root()->addChild(layer4); + + displayList->render(surface.get()); + + EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/innerShadow")); + device->unlock(); +} } // namespace tgfx