diff --git a/include/tgfx/core/Image.h b/include/tgfx/core/Image.h index 4e5bbfc6..bae6def2 100644 --- a/include/tgfx/core/Image.h +++ b/include/tgfx/core/Image.h @@ -246,18 +246,24 @@ class Image { std::shared_ptr makeOriented(Orientation orientation) const; /** - * Returns an Image scaled by the given factor. The scaled Image will have its own GPU cache at - * the new resolution and will not have mipmaps, even if the original Image does. You can call - * makeMipmapped() on the scaled Image to enable mipmaps. If the scale is greater than 1.0, it - * may result in blurring. Note that calling makeScaled() on an already scaled Image may return - * its internal non-scaled Image if the multiplied scale is 1.0. - * @param scale The factor to scale the Image by. - * @param sampling The sampling options to use when scaling the Image. - * @return The scaled Image. If the scale is 1.0, the original Image is returned. If the scale is - * less than 0, nullptr is returned. + * Returns a rasterized Image scaled by the specified rasterizationScale. A rasterized Image can + * be cached as an independent GPU resource for repeated drawing. By default, an Image directly + * backed by an ImageBuffer, an ImageGenerator, a GPU texture, or a Picture is rasterized. Other + * images aren’t rasterized unless implicitly created by this method. For example, if you create + * a subset Image from a rasterized Image, the subset Image doesn’t create its own GPU cache but + * uses the full resolution cache created by the original Image. If you want the subset Image to + * create its own GPU cache, call makeRasterized() on the subset Image. The returned Image always + * has the same mipmap state as the original Image. + * @param rasterizationScale The factor to scale the Image by when rasterizing. The default value + * is 1.0, indicating that the Image should be rasterized at its current size. If the value is + * greater than 1.0, it may result in blurring. + * @param sampling The sampling options to apply when rasterizing the Image if the + * rasterizationScale is not 1.0. + * @return If the Image is already rasterized and the rasterizationScale is 1.0, the original + * Image is returned. If the rasterizationScale is less than zero, nullptr is returned. */ - virtual std::shared_ptr makeScaled(float scale, - const SamplingOptions& sampling = {}) const; + virtual std::shared_ptr makeRasterized(float rasterizationScale = 1.0f, + const SamplingOptions& sampling = {}) const; /** * Returns a filtered Image with the specified filter. The filter has the potential to alter the @@ -320,7 +326,7 @@ class Image { friend class RuntimeImageFilter; friend class TransformImage; friend class RGBAAAImage; - friend class ScaleImage; + friend class RasterizedImage; friend class ImageShader; }; } // namespace tgfx diff --git a/src/core/filters/DropShadowImageFilter.cpp b/src/core/filters/DropShadowImageFilter.cpp index 88fca3dd..537288d0 100644 --- a/src/core/filters/DropShadowImageFilter.cpp +++ b/src/core/filters/DropShadowImageFilter.cpp @@ -59,7 +59,7 @@ Rect DropShadowImageFilter::onFilterBounds(const Rect& srcRect) const { std::unique_ptr DropShadowImageFilter::asFragmentProcessor( std::shared_ptr source, const FPArgs& args, const SamplingOptions& sampling, const Matrix* uvMatrix) const { - source = source->makeTextureImage(args.context); + source = source->makeRasterized(); std::unique_ptr shadowProcessor; auto shadowMatrix = Matrix::MakeTrans(-dx, -dy); if (uvMatrix != nullptr) { diff --git a/src/core/filters/InnerShadowImageFilter.cpp b/src/core/filters/InnerShadowImageFilter.cpp index afba7506..dd545f78 100644 --- a/src/core/filters/InnerShadowImageFilter.cpp +++ b/src/core/filters/InnerShadowImageFilter.cpp @@ -45,7 +45,7 @@ InnerShadowImageFilter::InnerShadowImageFilter(float dx, float dy, float blurrin std::unique_ptr InnerShadowImageFilter::asFragmentProcessor( std::shared_ptr source, const FPArgs& args, const SamplingOptions& sampling, const Matrix* uvMatrix) const { - source = source->makeTextureImage(args.context); + source = source->makeRasterized(); // get inverted shadow mask auto shadowMatrix = Matrix::MakeTrans(-dx, -dy); if (uvMatrix != nullptr) { diff --git a/src/core/images/Image.cpp b/src/core/images/Image.cpp index d5ca0b6b..1a0f35d4 100644 --- a/src/core/images/Image.cpp +++ b/src/core/images/Image.cpp @@ -21,7 +21,7 @@ #include "core/images/FilterImage.h" #include "core/images/OrientImage.h" #include "core/images/RGBAAAImage.h" -#include "core/images/ScaleImage.h" +#include "core/images/RasterizedImage.h" #include "core/images/SubsetImage.h" #include "core/images/TextureImage.h" #include "core/utils/Profiling.h" @@ -195,9 +195,13 @@ std::shared_ptr Image::makeSubset(const Rect& subset) const { return onMakeSubset(rect); } -std::shared_ptr Image::makeScaled(float scale, const SamplingOptions& sampling) const { - TRACE_EVENT; - return ScaleImage::MakeFrom(weakThis.lock(), scale, sampling); +std::shared_ptr Image::makeRasterized(float rasterizationScale, + const SamplingOptions& sampling) const { + auto rasterImage = RasterizedImage::MakeFrom(weakThis.lock(), rasterizationScale, sampling); + if (rasterImage != nullptr && hasMipmaps()) { + return rasterImage->makeMipmapped(true); + } + return rasterImage; } std::shared_ptr Image::onMakeSubset(const Rect& subset) const { diff --git a/src/core/images/MipmapImage.cpp b/src/core/images/MipmapImage.cpp index 0eadc5f8..0b18baf1 100644 --- a/src/core/images/MipmapImage.cpp +++ b/src/core/images/MipmapImage.cpp @@ -39,6 +39,18 @@ MipmapImage::MipmapImage(UniqueKey uniqueKey, std::shared_ptr sou : ResourceImage(std::move(uniqueKey)), source(std::move(source)) { } +std::shared_ptr MipmapImage::makeRasterized(float rasterizationScale, + const SamplingOptions& sampling) const { + if (rasterizationScale == 1.0f) { + return weakThis.lock(); + } + auto newSource = source->makeRasterized(rasterizationScale, sampling); + if (newSource != nullptr) { + return newSource->makeMipmapped(true); + } + return newSource; +} + std::shared_ptr MipmapImage::onMakeDecoded(Context* context, bool) const { TRACE_EVENT; auto newSource = std::static_pointer_cast(source->onMakeDecoded(context, false)); diff --git a/src/core/images/MipmapImage.h b/src/core/images/MipmapImage.h index cc757b7c..9a5906f0 100644 --- a/src/core/images/MipmapImage.h +++ b/src/core/images/MipmapImage.h @@ -41,6 +41,9 @@ class MipmapImage : public ResourceImage { return true; } + std::shared_ptr makeRasterized(float rasterizationScale = 1.0f, + const SamplingOptions& sampling = {}) const override; + protected: std::shared_ptr onMakeDecoded(Context* context, bool tryHardware) const override; diff --git a/src/core/images/OrientImage.cpp b/src/core/images/OrientImage.cpp index 43230acf..893d5c80 100644 --- a/src/core/images/OrientImage.cpp +++ b/src/core/images/OrientImage.cpp @@ -17,7 +17,7 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "OrientImage.h" -#include "core/images/ScaleImage.h" +#include "core/images/RasterizedImage.h" #include "core/images/SubsetImage.h" #include "core/utils/AddressOf.h" #include "gpu/ops/DrawOp.h" diff --git a/src/core/images/ScaleImage.cpp b/src/core/images/RasterizedImage.cpp similarity index 58% rename from src/core/images/ScaleImage.cpp rename to src/core/images/RasterizedImage.cpp index 4d79d1fa..7e34d8af 100644 --- a/src/core/images/ScaleImage.cpp +++ b/src/core/images/RasterizedImage.cpp @@ -16,7 +16,7 @@ // ///////////////////////////////////////////////////////////////////////////////////////////////// -#include "ScaleImage.h" +#include "RasterizedImage.h" #include "core/images/SubsetImage.h" #include "gpu/OpContext.h" #include "gpu/ops/DrawOp.h" @@ -26,68 +26,64 @@ static int GetSize(int size, float scale) { return static_cast(roundf(static_cast(size) * scale)); } -std::shared_ptr ScaleImage::MakeFrom(std::shared_ptr source, float scale, - const SamplingOptions& sampling) { +std::shared_ptr RasterizedImage::MakeFrom(std::shared_ptr source, + float rasterizationScale, + const SamplingOptions& sampling) { TRACE_EVENT; - if (source == nullptr) { + if (source == nullptr || rasterizationScale <= 0) { return nullptr; } auto sourceWidth = source->width(); auto sourceHeight = source->height(); - auto width = GetSize(sourceWidth, scale); - auto height = GetSize(sourceHeight, scale); + auto width = GetSize(sourceWidth, rasterizationScale); + auto height = GetSize(sourceHeight, rasterizationScale); if (width <= 0 || height <= 0) { return nullptr; } - if (width == sourceWidth && height == sourceHeight) { - return source; - } - auto result = std::shared_ptr( - new ScaleImage(UniqueKey::Make(), std::move(source), scale, sampling)); + auto result = std::shared_ptr( + new RasterizedImage(UniqueKey::Make(), std::move(source), rasterizationScale, sampling)); result->weakThis = result; return result; } -ScaleImage::ScaleImage(UniqueKey uniqueKey, std::shared_ptr source, float scale, - const SamplingOptions& sampling) - : OffscreenImage(std::move(uniqueKey)), source(std::move(source)), scale(scale), - sampling((sampling)) { +RasterizedImage::RasterizedImage(UniqueKey uniqueKey, std::shared_ptr source, + float rasterizationScale, const SamplingOptions& sampling) + : OffscreenImage(std::move(uniqueKey)), source(std::move(source)), + rasterizationScale(rasterizationScale), sampling((sampling)) { } -int ScaleImage::width() const { - return GetSize(source->width(), scale); +int RasterizedImage::width() const { + return GetSize(source->width(), rasterizationScale); } -int ScaleImage::height() const { - return GetSize(source->height(), scale); +int RasterizedImage::height() const { + return GetSize(source->height(), rasterizationScale); } -std::shared_ptr ScaleImage::makeScaled(float newScale, - const SamplingOptions& sampling) const { - TRACE_EVENT; - return MakeFrom(source, scale * newScale, sampling); +std::shared_ptr RasterizedImage::makeRasterized(float newScale, + const SamplingOptions& sampling) const { + return MakeFrom(source, rasterizationScale * newScale, sampling); } -std::shared_ptr ScaleImage::onMakeDecoded(Context* context, bool) const { - TRACE_EVENT; +std::shared_ptr RasterizedImage::onMakeDecoded(Context* context, bool) const { // There is no need to pass tryHardware (disabled) to the source image, as our texture proxy is // not locked from the source image. auto newSource = source->onMakeDecoded(context); if (newSource == nullptr) { return nullptr; } - auto newImage = - std::shared_ptr(new ScaleImage(uniqueKey, std::move(newSource), scale, sampling)); + auto newImage = std::shared_ptr( + new RasterizedImage(uniqueKey, std::move(newSource), rasterizationScale, sampling)); newImage->weakThis = newImage; return newImage; } -bool ScaleImage::onDraw(std::shared_ptr renderTarget, - uint32_t renderFlags) const { +bool RasterizedImage::onDraw(std::shared_ptr renderTarget, + uint32_t renderFlags) const { auto sourceWidth = source->width(); auto sourceHeight = source->height(); - auto scaledWidth = GetSize(sourceWidth, scale); - auto scaledHeight = GetSize(sourceHeight, scale); + auto scaledWidth = GetSize(sourceWidth, rasterizationScale); + auto scaledHeight = GetSize(sourceHeight, rasterizationScale); auto uvScaleX = static_cast(sourceWidth) / static_cast(scaledWidth); auto uvScaleY = static_cast(sourceHeight) / static_cast(scaledHeight); Matrix uvMatrix = Matrix::MakeScale(uvScaleX, uvScaleY); diff --git a/src/core/images/ScaleImage.h b/src/core/images/RasterizedImage.h similarity index 73% rename from src/core/images/ScaleImage.h rename to src/core/images/RasterizedImage.h index 5cd217ef..301dd1e8 100644 --- a/src/core/images/ScaleImage.h +++ b/src/core/images/RasterizedImage.h @@ -22,11 +22,14 @@ namespace tgfx { /** - * ScaleImage is an image that scales another image. + * RasterizedImage is an image that rasterizes another image with a scale and sampling options. */ -class ScaleImage : public OffscreenImage { +class RasterizedImage : public OffscreenImage { public: - static std::shared_ptr MakeFrom(std::shared_ptr source, float scale, + /** + * Note that this method always returns a non-mipmapped image. + */ + static std::shared_ptr MakeFrom(std::shared_ptr source, float rasterizationScale, const SamplingOptions& sampling); int width() const override; @@ -41,7 +44,8 @@ class ScaleImage : public OffscreenImage { return source->isFullyDecoded(); } - std::shared_ptr makeScaled(float scale, const SamplingOptions& sampling) const override; + std::shared_ptr makeRasterized(float rasterizationScale = 1.0f, + const SamplingOptions& sampling = {}) const override; protected: std::shared_ptr onMakeDecoded(Context* context, bool tryHardware) const override; @@ -50,10 +54,10 @@ class ScaleImage : public OffscreenImage { private: std::shared_ptr source = nullptr; - float scale = 1.0f; + float rasterizationScale = 1.0f; SamplingOptions sampling = {}; - ScaleImage(UniqueKey uniqueKey, std::shared_ptr source, float scale, - const SamplingOptions& sampling); + RasterizedImage(UniqueKey uniqueKey, std::shared_ptr source, float rasterizationScale, + const SamplingOptions& sampling); }; } // namespace tgfx diff --git a/src/core/images/ResourceImage.cpp b/src/core/images/ResourceImage.cpp index 71ac9b53..80a44e54 100644 --- a/src/core/images/ResourceImage.cpp +++ b/src/core/images/ResourceImage.cpp @@ -25,6 +25,14 @@ namespace tgfx { ResourceImage::ResourceImage(UniqueKey uniqueKey) : uniqueKey(std::move(uniqueKey)) { } +std::shared_ptr ResourceImage::makeRasterized(float rasterizationScale, + const SamplingOptions& sampling) const { + if (rasterizationScale == 1.0f) { + return weakThis.lock(); + } + return Image::makeRasterized(rasterizationScale, sampling); +} + std::shared_ptr ResourceImage::lockTextureProxy(const TPArgs& args) const { TRACE_EVENT; auto newArgs = args; diff --git a/src/core/images/ResourceImage.h b/src/core/images/ResourceImage.h index a7d945ac..88e39bba 100644 --- a/src/core/images/ResourceImage.h +++ b/src/core/images/ResourceImage.h @@ -32,6 +32,9 @@ class ResourceImage : public Image { public: explicit ResourceImage(UniqueKey uniqueKey); + std::shared_ptr makeRasterized(float rasterizationScale = 1.0f, + const SamplingOptions& sampling = {}) const override; + protected: UniqueKey uniqueKey = {}; diff --git a/test/baseline/version.json b/test/baseline/version.json index 08add24d..02426910 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -43,10 +43,10 @@ "mipmap_nearest": "d010fb8", "mipmap_none": "d010fb8", "path": "6fd4617", + "rasterized": "5a971dd", + "rasterized_mipmap": "5a971dd", + "rasterized_scale_up": "5a971dd", "saveLayer": "3888c19", - "scaleImage": "2028a1b", - "scaleImage_mipmap": "2028a1b", - "scaleImage_scale_up": "2028a1b", "shape": "bc64712", "text_shape": "b062b9a", "tile_mode_normal": "8cb853c", @@ -70,13 +70,13 @@ "ComposeImageFilter2": "2028a1b", "ImageFilterShader": "2028a1b", "ModeColorFilter": "c2b0b18", - "RuntimeEffect": "6f4a968", + "RuntimeEffect": "d93c573", "blur": "4d6ab20", "dropShadow": "a30de8b", "greyColorMatrix": "a30de8b", "identityMatrix": "a30de8b", "innerShadow": "01cce38", - "shaderMaskFilter": "ded3c91" + "shaderMaskFilter": "d93c573" }, "GlyphFaceTest": { "GlyphFaceSimple": "4032de2", @@ -96,7 +96,7 @@ "draw_text": "b062b9a", "dropShadow": "43cd416", "filterClip": "43cd416", - "filterTest": "43cd416", + "filterTest": "44166b7", "getBounds": "3888c19", "getLayersUnderPoint": "b062b9a", "greyColorMatrix": "a1605b2", diff --git a/test/src/CanvasTest.cpp b/test/src/CanvasTest.cpp index 61a12012..d84aedeb 100644 --- a/test/src/CanvasTest.cpp +++ b/test/src/CanvasTest.cpp @@ -322,28 +322,28 @@ TGFX_TEST(CanvasTest, filterMode) { EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/filter_mode_linear")); } -TGFX_TEST(CanvasTest, scaleImage) { +TGFX_TEST(CanvasTest, rasterizedImage) { ContextScope scope; auto context = scope.getContext(); ASSERT_TRUE(context != nullptr); auto defaultCacheLimit = context->cacheLimit(); context->setCacheLimit(0); auto image = MakeImage("resources/apitest/imageReplacement.png"); - auto scaleImage = image->makeScaled(1.0f); - EXPECT_TRUE(scaleImage == image); + auto rasterImage = image->makeRasterized(); + EXPECT_TRUE(rasterImage == image); image = MakeImage("resources/apitest/rotation.jpg"); - auto scaledImage = image->makeScaled(0.15f); - EXPECT_FALSE(scaledImage->hasMipmaps()); - EXPECT_FALSE(scaledImage == image); - EXPECT_EQ(scaledImage->width(), 454); - EXPECT_EQ(scaledImage->height(), 605); + rasterImage = image->makeRasterized(0.15f); + EXPECT_FALSE(rasterImage->hasMipmaps()); + EXPECT_FALSE(rasterImage == image); + EXPECT_EQ(rasterImage->width(), 454); + EXPECT_EQ(rasterImage->height(), 605); ASSERT_TRUE(image != nullptr); auto surface = Surface::Make(context, 1100, 1400); auto canvas = surface->getCanvas(); - canvas->drawImage(scaledImage, 100, 100); - EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/scaleImage")); - auto scaleImageUniqueKey = std::static_pointer_cast(scaledImage)->uniqueKey; - auto texture = Resource::Find(context, scaleImageUniqueKey); + canvas->drawImage(rasterImage, 100, 100); + EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/rasterized")); + auto rasterImageUniqueKey = std::static_pointer_cast(rasterImage)->uniqueKey; + auto texture = Resource::Find(context, rasterImageUniqueKey); EXPECT_TRUE(texture != nullptr); EXPECT_EQ(texture->width(), 454); EXPECT_EQ(texture->height(), 605); @@ -355,26 +355,25 @@ TGFX_TEST(CanvasTest, scaleImage) { image = image->makeMipmapped(true); EXPECT_TRUE(image->hasMipmaps()); SamplingOptions sampling(FilterMode::Linear, MipmapMode::Linear); - image = image->makeScaled(0.15f, sampling); - scaledImage = image->makeMipmapped(true); - EXPECT_TRUE(scaledImage->hasMipmaps()); - canvas->drawImage(scaledImage, 100, 100); - EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/scaleImage_mipmap")); - texture = Resource::Find(context, scaleImageUniqueKey); + rasterImage = image->makeRasterized(0.15f, sampling); + EXPECT_TRUE(rasterImage->hasMipmaps()); + canvas->drawImage(rasterImage, 100, 100); + EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/rasterized_mipmap")); + texture = Resource::Find(context, rasterImageUniqueKey); EXPECT_TRUE(texture == nullptr); - scaleImageUniqueKey = std::static_pointer_cast(scaledImage)->uniqueKey; - texture = Resource::Find(context, scaleImageUniqueKey); + rasterImageUniqueKey = std::static_pointer_cast(rasterImage)->uniqueKey; + texture = Resource::Find(context, rasterImageUniqueKey); EXPECT_TRUE(texture != nullptr); canvas->clear(); - scaledImage = image->makeMipmapped(false); - EXPECT_FALSE(scaledImage->hasMipmaps()); - scaledImage = scaledImage->makeScaled(2.0f, sampling); - EXPECT_FALSE(scaledImage->hasMipmaps()); - scaledImage = scaledImage->makeMipmapped(true); - EXPECT_EQ(scaledImage->width(), 907); - EXPECT_EQ(scaledImage->height(), 1210); - canvas->drawImage(scaledImage, 100, 100); - EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/scaleImage_scale_up")); + rasterImage = rasterImage->makeMipmapped(false); + EXPECT_FALSE(rasterImage->hasMipmaps()); + rasterImage = rasterImage->makeRasterized(2.0f, sampling); + EXPECT_FALSE(rasterImage->hasMipmaps()); + rasterImage = rasterImage->makeMipmapped(true); + EXPECT_EQ(rasterImage->width(), 907); + EXPECT_EQ(rasterImage->height(), 1210); + canvas->drawImage(rasterImage, 100, 100); + EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/rasterized_scale_up")); context->setCacheLimit(defaultCacheLimit); } diff --git a/test/src/FilterTest.cpp b/test/src/FilterTest.cpp index 1c1b9551..c22342d0 100644 --- a/test/src/FilterTest.cpp +++ b/test/src/FilterTest.cpp @@ -123,7 +123,7 @@ TGFX_TEST(FilterTest, ShaderMaskFilter) { auto image = MakeImage("resources/apitest/rotation.jpg"); image = image->makeOriented(Orientation::LeftBottom); image = image->makeMipmapped(true); - image = image->makeScaled(0.25f); + image = image->makeRasterized(0.25f); ASSERT_TRUE(image != nullptr); auto surface = Surface::Make(context, image->width(), image->height()); auto canvas = surface->getCanvas(); @@ -315,8 +315,7 @@ TGFX_TEST(FilterTest, RuntimeEffect) { auto surface = Surface::Make(context, 720, 720); auto canvas = surface->getCanvas(); image = image->makeMipmapped(true); - image = image->makeScaled(0.5f, SamplingOptions(FilterMode::Linear, MipmapMode::Linear)); - image = image->makeMipmapped(true); + image = image->makeRasterized(0.5f, SamplingOptions(FilterMode::Linear, MipmapMode::Linear)); auto effect = CornerPinEffect::Make({484, 54}, {764, 80}, {764, 504}, {482, 512}); auto filter = ImageFilter::Runtime(std::move(effect)); image = image->makeWithFilter(std::move(filter));