From 4138fac5a4a3351184db1a06e794f6c84fa85af0 Mon Sep 17 00:00:00 2001 From: Dom Chen Date: Thu, 10 Feb 2022 21:46:17 +0800 Subject: [PATCH] Remove the GradientPaint from tgfx, use Paint.setShader() instead. --- include/pag/types.h | 6 +- src/rendering/graphics/Graphic.h | 11 + src/rendering/graphics/Picture.cpp | 1 - src/rendering/graphics/Shape.cpp | 60 +++-- src/rendering/graphics/Shape.h | 33 +-- src/rendering/graphics/Text.cpp | 6 +- tgfx/include/core/PathMeasure.h | 3 + tgfx/include/gpu/Canvas.h | 9 +- tgfx/include/gpu/GradientShader.h | 86 +++---- tgfx/include/gpu/Paint.h | 35 ++- tgfx/include/gpu/Shader.h | 48 ++++ .../core/vectors/freetype/FTScalerContext.cpp | 4 - tgfx/src/gpu/ColorShader.cpp | 37 +++ tgfx/src/gpu/ColorShader.h | 36 +++ tgfx/src/gpu/FragmentProcessor.h | 8 + tgfx/src/gpu/GradientShader.cpp | 212 +++++++++--------- tgfx/src/gpu/Paint.cpp | 1 + tgfx/src/gpu/ShaderBase.h | 29 +++ tgfx/src/gpu/opengl/GLCanvas.cpp | 57 ++--- tgfx/src/gpu/opengl/GLCanvas.h | 3 +- 20 files changed, 405 insertions(+), 280 deletions(-) create mode 100644 tgfx/include/gpu/Shader.h create mode 100644 tgfx/src/gpu/ColorShader.cpp create mode 100644 tgfx/src/gpu/ColorShader.h create mode 100644 tgfx/src/gpu/ShaderBase.h diff --git a/include/pag/types.h b/include/pag/types.h index 42ac36cdf7..47f8692e26 100644 --- a/include/pag/types.h +++ b/include/pag/types.h @@ -867,7 +867,7 @@ class PAG_API Matrix { /** * Copies nine scalar values contained by Matrix into buffer, in member value ascending order: - * kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, kMPersp0, kMPersp1, kMPersp2. + * ScaleX, SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1, Persp2. * @param buffer storage for nine scalar values */ void get9(float buffer[9]) const { @@ -875,8 +875,8 @@ class PAG_API Matrix { } /** - * Sets Matrix to nine scalar values in buffer, in member value ascending order: kMScaleX, - * kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY, kMPersp0, kMPersp1, kMPersp2. + * Sets Matrix to nine scalar values in buffer, in member value ascending order: ScaleX, + * SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1, Persp2. * * Sets matrix to: * diff --git a/src/rendering/graphics/Graphic.h b/src/rendering/graphics/Graphic.h index ad913d9b7a..74c7e2eac9 100644 --- a/src/rendering/graphics/Graphic.h +++ b/src/rendering/graphics/Graphic.h @@ -25,6 +25,17 @@ #include "gpu/Paint.h" namespace pag { +/** + * Defines attributes for drawing gradient colors. + */ +struct GradientPaint { + Enum gradientType; + Point startPoint; + Point endPoint; + std::vector colors; + std::vector alphas; + std::vector positions; +}; enum class GraphicType { Unknown, diff --git a/src/rendering/graphics/Picture.cpp b/src/rendering/graphics/Picture.cpp index 3db4e2031e..e225c9a750 100644 --- a/src/rendering/graphics/Picture.cpp +++ b/src/rendering/graphics/Picture.cpp @@ -235,7 +235,6 @@ class RGBAAAPicture : public Picture { } } // 因为视频绘制会涉及自定义的 OpenGL 操作。 - // 要先 flush 父级 SkCanvas 里当前的 OpenGL 操作,防止渲染异常。 canvas->flush(); auto snapshot = cache->getSnapshot(this); if (snapshot) { diff --git a/src/rendering/graphics/Shape.cpp b/src/rendering/graphics/Shape.cpp index 344a57afdc..376634b531 100644 --- a/src/rendering/graphics/Shape.cpp +++ b/src/rendering/graphics/Shape.cpp @@ -18,6 +18,7 @@ #include "Shape.h" #include "gpu/Canvas.h" +#include "gpu/GradientShader.h" #include "pag/file.h" namespace pag { @@ -25,25 +26,47 @@ std::shared_ptr Shape::MakeFrom(const Path& path, Color color) { if (path.isEmpty()) { return nullptr; } - auto fill = new SolidFill(); - fill->color = color; - return std::shared_ptr(new Shape(path, fill)); + Paint paint = {}; + paint.setColor(color); + return std::shared_ptr(new Shape(path, paint)); +} + +static std::shared_ptr MakeGradientShader(const GradientPaint& gradient) { + std::shared_ptr shader; + std::vector colors = {}; + int index = 0; + auto& alphas = gradient.alphas; + for (auto& color : gradient.colors) { + auto r = static_cast(color.red) / 255.0f; + auto g = static_cast(color.green) / 255.0f; + auto b = static_cast(color.blue) / 255.0f; + auto a = static_cast(alphas[index++]) / 255.0f; + colors.push_back({r, g, b, a}); + } + if (gradient.gradientType == GradientFillType::Linear) { + shader = GradientShader::MakeLinear(gradient.startPoint, gradient.endPoint, colors, + gradient.positions); + } else { + auto radius = Point::Distance(gradient.startPoint, gradient.endPoint); + shader = GradientShader::MakeRadial(gradient.startPoint, radius, colors, gradient.positions); + } + if (!shader) { + shader = Shader::MakeColorShader(gradient.colors.back(), gradient.alphas.back()); + } + return shader; } std::shared_ptr Shape::MakeFrom(const Path& path, const GradientPaint& gradient) { if (path.isEmpty()) { return nullptr; } - auto fill = new GradientFill(); - fill->gradient = gradient; - return std::shared_ptr(new Shape(path, fill)); + Paint paint = {}; + auto shader = MakeGradientShader(gradient); + paint.setShader(shader); + return std::shared_ptr(new Shape(path, paint)); } -Shape::Shape(Path path, ShapeFill* fill) : path(std::move(path)), fill(fill) { -} - -Shape::~Shape() { - delete fill; +Shape::Shape(Path path, Paint paint) : path(std::move(path)), paint(paint) { } void Shape::measureBounds(Rect* bounds) const { @@ -55,7 +78,11 @@ bool Shape::hitTest(RenderCache*, float x, float y) { } bool Shape::getPath(Path* result) const { - if (fill->type() == ShapeFillType::Gradient) { + if (paint.getAlpha() != Opaque) { + return false; + } + auto shader = paint.getShader(); + if (shader && !shader->isOpaque()) { return false; } result->addPath(path); @@ -66,14 +93,7 @@ void Shape::prepare(RenderCache*) const { } void Shape::draw(Canvas* canvas, RenderCache*) const { - switch (fill->type()) { - case ShapeFillType::Solid: - canvas->drawPath(path, static_cast(fill)->color); - break; - case ShapeFillType::Gradient: - canvas->drawPath(path, static_cast(fill)->gradient); - break; - } + canvas->drawPath(path, paint); } } // namespace pag \ No newline at end of file diff --git a/src/rendering/graphics/Shape.h b/src/rendering/graphics/Shape.h index 6551b5af0a..18362a13ac 100644 --- a/src/rendering/graphics/Shape.h +++ b/src/rendering/graphics/Shape.h @@ -21,33 +21,6 @@ #include "rendering/graphics/Graphic.h" namespace pag { -enum class ShapeFillType { Solid, Gradient }; - -class ShapeFill { - public: - virtual ~ShapeFill() = default; - - virtual ShapeFillType type() const = 0; -}; - -class SolidFill : public ShapeFill { - public: - ShapeFillType type() const override { - return ShapeFillType::Solid; - } - - Color color; -}; - -class GradientFill : public ShapeFill { - public: - ShapeFillType type() const override { - return ShapeFillType::Gradient; - } - - GradientPaint gradient = {}; -}; - class Shape : public Graphic { public: /** @@ -60,8 +33,6 @@ class Shape : public Graphic { */ static std::shared_ptr MakeFrom(const Path& path, const GradientPaint& gradient); - ~Shape() override; - GraphicType type() const override { return GraphicType::Shape; } @@ -74,8 +45,8 @@ class Shape : public Graphic { private: Path path = {}; - ShapeFill* fill = nullptr; + Paint paint = {}; - Shape(Path path, ShapeFill* fill); + Shape(Path path, Paint paint); }; } // namespace pag diff --git a/src/rendering/graphics/Text.cpp b/src/rendering/graphics/Text.cpp index 35814f05f6..354845a110 100644 --- a/src/rendering/graphics/Text.cpp +++ b/src/rendering/graphics/Text.cpp @@ -159,11 +159,7 @@ static void ApplyPaintToPath(const Paint& paint, Path* path) { if (strokeEffect) { strokeEffect->applyTo(&strokePath); } - if (paint.getStyle() == PaintStyle::Stroke) { - *path = strokePath; - } else { - path->addPath(strokePath); - } + *path = strokePath; } bool Text::hitTest(RenderCache*, float x, float y) { diff --git a/tgfx/include/core/PathMeasure.h b/tgfx/include/core/PathMeasure.h index e51e5d63b2..5384c639bc 100644 --- a/tgfx/include/core/PathMeasure.h +++ b/tgfx/include/core/PathMeasure.h @@ -21,6 +21,9 @@ #include "Path.h" namespace pag { +/** + * PathMeasure calculates the length of a Path and cuts child segments from it. + */ class PathMeasure { public: /** diff --git a/tgfx/include/gpu/Canvas.h b/tgfx/include/gpu/Canvas.h index f0c50177c7..c3f1d2a152 100644 --- a/tgfx/include/gpu/Canvas.h +++ b/tgfx/include/gpu/Canvas.h @@ -144,14 +144,9 @@ class Canvas { void drawTexture(const Texture* texture, const Matrix& matrix); /** - * Draws a path with solid color fill, using current alpha, blend mode, clip and Matrix. + * Draws a path with using current clip, matrix and specified paint. */ - virtual void drawPath(const Path& path, Color color) = 0; - - /** - * Draws a path with gradient color fill, using current alpha, blend mode, clip and Matrix. - */ - virtual void drawPath(const Path& path, const GradientPaint& gradient) = 0; + virtual void drawPath(const Path& path, const Paint& paint) = 0; /** * Draw array of glyphs with specified font, using current alpha, blend mode, clip and Matrix. diff --git a/tgfx/include/gpu/GradientShader.h b/tgfx/include/gpu/GradientShader.h index cd21fc9f04..5e9ec5e6b3 100644 --- a/tgfx/include/gpu/GradientShader.h +++ b/tgfx/include/gpu/GradientShader.h @@ -19,72 +19,40 @@ #pragma once #include "core/Color4f.h" -#include "gpu/FragmentProcessor.h" +#include "gpu/Shader.h" namespace pag { -struct FPArgs { - FPArgs(Context* context, const Matrix& localMatrix) : context(context), localMatrix(localMatrix) { - } - - Context* context = nullptr; - Matrix localMatrix = Matrix::I(); -}; - -class Shader { - public: - static std::unique_ptr MakeColorShader(Color color, Opacity opacity = Opaque); - - virtual ~Shader() = default; - - virtual std::unique_ptr asFragmentProcessor(const FPArgs& args) const = 0; -}; - -class Color4Shader : public Shader { - public: - explicit Color4Shader(Color4f color) : color(color) { - } - - std::unique_ptr asFragmentProcessor(const FPArgs& args) const override; - - private: - Color4f color; -}; - -class GradientShaderBase : public Shader { - public: - GradientShaderBase(const std::vector& colors, const std::vector& positions, - const Matrix& pointsToUnit); - - std::vector originalColors = {}; - std::vector originalPositions = {}; - - protected: - const Matrix pointsToUnit; -}; - -class LinearGradient : public GradientShaderBase { - public: - LinearGradient(const Point& startPoint, const Point& endPoint, const std::vector& colors, - const std::vector& positions); - - std::unique_ptr asFragmentProcessor(const FPArgs& args) const override; -}; - -class RadialGradient : public GradientShaderBase { - public: - RadialGradient(const Point& center, float radius, const std::vector& colors, - const std::vector& positions); - - std::unique_ptr asFragmentProcessor(const FPArgs& args) const override; -}; - +/** + * GradientShader hosts factories for creating subclasses of Shader that render linear and radial + * gradients. + */ class GradientShader { public: - static std::unique_ptr MakeLinear(const Point& startPoint, const Point& endPoint, + /** + * Returns a shader that generates a linear gradient between the two specified points. + * @param startPoint The start point for the gradient. + * @param endPoint The end point for the gradient. + * @param colors The array of colors, to be distributed between the two points. + * @param positions May be empty. The relative position of each corresponding color in the colors + * array. If this is empty, the the colors are distributed evenly between the start and end point. + * If this is not empty, the values must begin with 0, end with 1.0, and intermediate values must + * be strictly increasing. + */ + static std::shared_ptr MakeLinear(const Point& startPoint, const Point& endPoint, const std::vector& colors, const std::vector& positions); - static std::unique_ptr MakeRadial(const Point& center, float radius, + /** + * Returns a shader that generates a radial gradient given the center and radius. + * @param center The center of the circle for this gradient + * @param radius Must be positive. The radius of the circle for this gradient. + * @param colors The array of colors, to be distributed between the center and edge of the circle. + * @param positions May be empty. The relative position of each corresponding color in the colors + * array. If this is empty, the the colors are distributed evenly between the start and end point. + * If this is not empty, the values must begin with 0, end with 1.0, and intermediate values must + * be strictly increasing. + */ + static std::shared_ptr MakeRadial(const Point& center, float radius, const std::vector& colors, const std::vector& positions); }; diff --git a/tgfx/include/gpu/Paint.h b/tgfx/include/gpu/Paint.h index 7c47fc7e5d..e90fd5b703 100644 --- a/tgfx/include/gpu/Paint.h +++ b/tgfx/include/gpu/Paint.h @@ -19,21 +19,10 @@ #pragma once #include "core/Stroke.h" +#include "gpu/Shader.h" #include "pag/types.h" namespace pag { -/** - * Defines attributes for drawing gradient colors. - */ -struct GradientPaint { - Enum gradientType; - Point startPoint; - Point endPoint; - std::vector colors; - std::vector alphas; - std::vector positions; -}; - /** * Defines the layout of a RGBAAA format image, which is half RGB, half AAA. */ @@ -189,18 +178,40 @@ class Paint { stroke.miterLimit = limit; } + /** + * Returns the stroke options. + */ const Stroke* getStroke() const { return &stroke; } + /** + * Sets the stroke options. + */ void setStroke(const Stroke& newStroke) { stroke = newStroke; } + /** + * Returns optional colors used when filling a path if previously set, such as a gradient. + */ + std::shared_ptr getShader() const { + return shader; + } + + /** + * Sets optional colors used when filling a path, such as a gradient. If nullptr, color is used + * instead. + */ + void setShader(std::shared_ptr newShader) { + shader = newShader; + } + private: PaintStyle style = PaintStyle::Fill; Color color = Black; Opacity alpha = Opaque; Stroke stroke = Stroke(0); + std::shared_ptr shader = nullptr; }; } // namespace pag diff --git a/tgfx/include/gpu/Shader.h b/tgfx/include/gpu/Shader.h new file mode 100644 index 0000000000..44dae6ddb7 --- /dev/null +++ b/tgfx/include/gpu/Shader.h @@ -0,0 +1,48 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 "pag/types.h" + +namespace pag { +/** + * Shaders specify the source color(s) for what is being drawn. If a paint has no shader, then the + * paint's color is used. If the paint has a shader, then the shader's color(s) are use instead, but + * they are modulated by the paint's alpha. + */ +class Shader { + public: + /** + * Create a shader that draws the specified color. + */ + static std::shared_ptr MakeColorShader(Color color, Opacity opacity = Opaque); + + virtual ~Shader() = default; + + /** + * Returns true if the shader is guaranteed to produce only opaque colors, subject to the Paint + * using the shader to apply an opaque alpha value. Subclasses should override this to allow some + * optimizations. + */ + virtual bool isOpaque() const { + return false; + } +}; +} // namespace pag diff --git a/tgfx/src/core/vectors/freetype/FTScalerContext.cpp b/tgfx/src/core/vectors/freetype/FTScalerContext.cpp index 432d55fc41..b6218a56be 100644 --- a/tgfx/src/core/vectors/freetype/FTScalerContext.cpp +++ b/tgfx/src/core/vectors/freetype/FTScalerContext.cpp @@ -48,23 +48,19 @@ static void ComputeGivensRotation(const Point& h, Matrix* G) { if (0 == b) { c = copysignf(1.f, a); s = 0; - // r = SkScalarAbs(a); } else if (0 == a) { c = 0; s = -copysignf(1.f, b); - // r = SkScalarAbs(b); } else if (fabsf(b) > fabsf(a)) { auto t = a / b; auto u = copysignf(sqrtf(1.f + t * t), b); s = -1.f / u; c = -s * t; - // r = b * u; } else { auto t = b / a; auto u = copysignf(sqrtf(1.f + t * t), a); c = 1.f / u; s = -c * t; - // r = a * u; } G->setSinCos(s, c); diff --git a/tgfx/src/gpu/ColorShader.cpp b/tgfx/src/gpu/ColorShader.cpp new file mode 100644 index 0000000000..88205b2d5a --- /dev/null +++ b/tgfx/src/gpu/ColorShader.cpp @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 "ColorShader.h" +#include "gpu/ConstColorProcessor.h" + +namespace pag { +std::shared_ptr Shader::MakeColorShader(Color color, Opacity opacity) { + return std::make_shared( + Color4f{static_cast(color.red) / 255.0f, static_cast(color.green) / 255.0f, + static_cast(color.blue) / 255.0f, static_cast(opacity) / 255.0f}); +} + +bool ColorShader::isOpaque() const { + return color.isOpaque(); +} + +std::unique_ptr ColorShader::asFragmentProcessor(const FPArgs&) const { + return ConstColorProcessor::Make(color); +} + +} // namespace pag diff --git a/tgfx/src/gpu/ColorShader.h b/tgfx/src/gpu/ColorShader.h new file mode 100644 index 0000000000..8fdef1382f --- /dev/null +++ b/tgfx/src/gpu/ColorShader.h @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 "ShaderBase.h" + +namespace pag { +class ColorShader : public ShaderBase { + public: + explicit ColorShader(Color4f color) : color(color) { + } + + bool isOpaque() const override; + + std::unique_ptr asFragmentProcessor(const FPArgs& args) const override; + + private: + Color4f color; +}; +} // namespace pag diff --git a/tgfx/src/gpu/FragmentProcessor.h b/tgfx/src/gpu/FragmentProcessor.h index 534199ac59..3ac9091d08 100644 --- a/tgfx/src/gpu/FragmentProcessor.h +++ b/tgfx/src/gpu/FragmentProcessor.h @@ -23,6 +23,14 @@ #include "gpu/Texture.h" namespace pag { +struct FPArgs { + FPArgs(Context* context, const Matrix& localMatrix) : context(context), localMatrix(localMatrix) { + } + + Context* context = nullptr; + Matrix localMatrix = Matrix::I(); +}; + class Pipeline; class GLFragmentProcessor; diff --git a/tgfx/src/gpu/GradientShader.cpp b/tgfx/src/gpu/GradientShader.cpp index 06e940cd75..55d8552e06 100644 --- a/tgfx/src/gpu/GradientShader.cpp +++ b/tgfx/src/gpu/GradientShader.cpp @@ -1,29 +1,27 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////// // -// The MIT License (MIT) +// Tencent is pleased to support the open source community by making libpag available. // -// Copyright (c) 2016-present, Tencent. All rights reserved. +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. // -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at // -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. +// http://www.apache.org/licenses/LICENSE-2.0 // -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// 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 "gpu/GradientShader.h" #include "base/utils/MathExtra.h" +#include "gpu/ColorShader.h" #include "gpu/ConstColorProcessor.h" #include "gpu/GradientCache.h" +#include "gpu/ShaderBase.h" #include "gpu/gradients/ClampedGradientEffect.h" #include "gpu/gradients/DualIntervalGradientColorizer.h" #include "gpu/gradients/LinearGradientLayout.h" @@ -35,7 +33,8 @@ namespace pag { // Intervals smaller than this (that aren't hard stops) on low-precision-only devices force us to // use the textured gradient -static constexpr float kLowPrecisionIntervalLimit = 0.01f; +static constexpr float LowPrecisionIntervalLimit = 0.01f; +static constexpr float DegenerateThreshold = 1.0f / (1 << 15); // Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent // the gradient. @@ -74,7 +73,7 @@ static std::unique_ptr MakeColorizer(Context* context, const // that scales will be less than 100, which leaves 4 decimals of precision on 16-bit). for (int i = offset; i < count - 1; i++) { auto delta = std::abs(positions[i] - positions[i + 1]); - if (delta <= kLowPrecisionIntervalLimit && delta > FLOAT_NEARLY_ZERO) { + if (delta <= LowPrecisionIntervalLimit && delta > FLOAT_NEARLY_ZERO) { tryAnalyticColorizer = false; break; } @@ -110,6 +109,66 @@ static std::unique_ptr MakeColorizer(Context* context, const context->getGradient(colors + offset, positions + offset, count)); } +class GradientShaderBase : public ShaderBase { + public: + GradientShaderBase(const std::vector& colors, const std::vector& positions, + const Matrix& pointsToUnit) + : pointsToUnit(pointsToUnit) { + colorsAreOpaque = true; + for (auto& color : colors) { + if (!color.isOpaque()) { + colorsAreOpaque = false; + break; + } + } + auto dummyFirst = false; + auto dummyLast = false; + if (!positions.empty()) { + dummyFirst = positions[0] != 0; + dummyLast = positions[positions.size() - 1] != 1.0f; + } + // Now copy over the colors, adding the dummies as needed + if (dummyFirst) { + originalColors.push_back(colors[0]); + } + originalColors.insert(originalColors.end(), colors.begin(), colors.end()); + if (dummyLast) { + originalColors.push_back(colors[colors.size() - 1]); + } + if (positions.empty()) { + auto posScale = 1.0f / static_cast(positions.size() - 1); + for (size_t i = 0; i < positions.size(); i++) { + originalPositions.push_back(static_cast(i) * posScale); + } + } else { + float prev = 0; + originalPositions.push_back(prev); // force the first pos to 0 + for (size_t i = dummyFirst ? 0 : 1; i < colors.size() + dummyLast; ++i) { + // Pin the last value to 1.0, and make sure position is monotonic. + float curr; + if (i != colors.size()) { + curr = std::max(std::min(positions[i], 1.0f), prev); + } else { + curr = 1.0f; + } + originalPositions.push_back(curr); + prev = curr; + } + } + } + + bool isOpaque() const override { + return colorsAreOpaque; + } + + std::vector originalColors = {}; + std::vector originalPositions = {}; + + protected: + const Matrix pointsToUnit; + bool colorsAreOpaque = false; +}; + // Combines the colorizer and layout with an appropriately configured master effect based on the // gradient's tile mode static std::unique_ptr MakeGradient(Context* context, @@ -141,51 +200,6 @@ static std::unique_ptr MakeGradient(Context* context, shader.originalColors[shader.originalColors.size() - 1], !allOpaque); } -std::unique_ptr Color4Shader::asFragmentProcessor(const FPArgs&) const { - return ConstColorProcessor::Make(color); -} - -GradientShaderBase::GradientShaderBase(const std::vector& colors, - const std::vector& positions, - const Matrix& pointsToUnit) - : pointsToUnit(pointsToUnit) { - auto dummyFirst = false; - auto dummyLast = false; - if (!positions.empty()) { - dummyFirst = positions[0] != 0; - dummyLast = positions[positions.size() - 1] != 1.0f; - } - - // Now copy over the colors, adding the dummies as needed - if (dummyFirst) { - originalColors.push_back(colors[0]); - } - originalColors.insert(originalColors.end(), colors.begin(), colors.end()); - if (dummyLast) { - originalColors.push_back(colors[colors.size() - 1]); - } - if (positions.empty()) { - auto posScale = 1.0f / static_cast(positions.size() - 1); - for (size_t i = 0; i < positions.size(); i++) { - originalPositions.push_back(static_cast(i) * posScale); - } - } else { - float prev = 0; - originalPositions.push_back(prev); // force the first pos to 0 - for (size_t i = dummyFirst ? 0 : 1; i < colors.size() + dummyLast; ++i) { - // Pin the last value to 1.0, and make sure position is monotonic. - float curr; - if (i != colors.size()) { - curr = std::max(std::min(positions[i], 1.0f), prev); - } else { - curr = 1.0f; - } - originalPositions.push_back(curr); - prev = curr; - } - } -} - static Matrix PointsToUnitMatrix(const Point& startPoint, const Point& endPoint) { Point vec = {endPoint.x - startPoint.x, endPoint.y - startPoint.y}; float mag = Point::Length(vec.x, vec.y); @@ -198,17 +212,19 @@ static Matrix PointsToUnitMatrix(const Point& startPoint, const Point& endPoint) return matrix; } -LinearGradient::LinearGradient(const Point& startPoint, const Point& endPoint, - const std::vector& colors, - const std::vector& positions) - : GradientShaderBase(colors, positions, PointsToUnitMatrix(startPoint, endPoint)) { -} +class LinearGradient : public GradientShaderBase { + public: + LinearGradient(const Point& startPoint, const Point& endPoint, const std::vector& colors, + const std::vector& positions) + : GradientShaderBase(colors, positions, PointsToUnitMatrix(startPoint, endPoint)) { + } -std::unique_ptr LinearGradient::asFragmentProcessor(const FPArgs& args) const { - auto matrix = args.localMatrix; - matrix.postConcat(pointsToUnit); - return MakeGradient(args.context, *this, LinearGradientLayout::Make(matrix)); -} + std::unique_ptr asFragmentProcessor(const FPArgs& args) const override { + auto matrix = args.localMatrix; + matrix.postConcat(pointsToUnit); + return MakeGradient(args.context, *this, LinearGradientLayout::Make(matrix)); + } +}; static Matrix RadialToUnitMatrix(const Point& center, float radius) { float inv = 1 / radius; @@ -217,23 +233,21 @@ static Matrix RadialToUnitMatrix(const Point& center, float radius) { return matrix; } -RadialGradient::RadialGradient(const Point& center, float radius, - const std::vector& colors, - const std::vector& positions) - : GradientShaderBase(colors, positions, RadialToUnitMatrix(center, radius)) { -} - -std::unique_ptr RadialGradient::asFragmentProcessor(const FPArgs& args) const { - auto matrix = args.localMatrix; - matrix.postConcat(pointsToUnit); - return MakeGradient(args.context, *this, RadialGradientLayout::Make(matrix)); -} +class RadialGradient : public GradientShaderBase { + public: + RadialGradient(const Point& center, float radius, const std::vector& colors, + const std::vector& positions) + : GradientShaderBase(colors, positions, RadialToUnitMatrix(center, radius)) { + } -// The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg -// gradients defined in the wild. -static constexpr float kDegenerateThreshold = 1.0f / (1 << 15); + std::unique_ptr asFragmentProcessor(const FPArgs& args) const override { + auto matrix = args.localMatrix; + matrix.postConcat(pointsToUnit); + return MakeGradient(args.context, *this, RadialGradientLayout::Make(matrix)); + } +}; -std::unique_ptr GradientShader::MakeLinear(const Point& startPoint, const Point& endPoint, +std::shared_ptr GradientShader::MakeLinear(const Point& startPoint, const Point& endPoint, const std::vector& colors, const std::vector& positions) { if (!std::isfinite(Point::Distance(endPoint, startPoint))) { @@ -243,19 +257,19 @@ std::unique_ptr GradientShader::MakeLinear(const Point& startPoint, cons return nullptr; } if (1 == colors.size()) { - return std::make_unique(colors[0]); + return std::make_shared(colors[0]); } - if (FloatNearlyZero((endPoint - startPoint).length(), kDegenerateThreshold)) { + if (FloatNearlyZero((endPoint - startPoint).length(), DegenerateThreshold)) { // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of // the gradient approaches two half planes of solid color (first and last). However, they // are divided by the line perpendicular to the start and end point, which becomes undefined // once start and end are exactly the same, so just use the end color for a stable solution. - return std::make_unique(colors[0]); + return std::make_shared(colors[0]); } - return std::make_unique(startPoint, endPoint, colors, positions); + return std::make_shared(startPoint, endPoint, colors, positions); } -std::unique_ptr GradientShader::MakeRadial(const Point& center, float radius, +std::shared_ptr GradientShader::MakeRadial(const Point& center, float radius, const std::vector& colors, const std::vector& positions) { if (radius < 0) { @@ -265,19 +279,13 @@ std::unique_ptr GradientShader::MakeRadial(const Point& center, float ra return nullptr; } if (1 == colors.size()) { - return std::make_unique(colors[0]); + return std::make_shared(colors[0]); } - if (FloatNearlyZero(radius, kDegenerateThreshold)) { + if (FloatNearlyZero(radius, DegenerateThreshold)) { // Degenerate gradient optimization, and no special logic needed for clamped radial gradient - return std::make_unique(colors[colors.size() - 1]); + return std::make_shared(colors[colors.size() - 1]); } - return std::make_unique(center, radius, colors, positions); -} - -std::unique_ptr Shader::MakeColorShader(Color color, Opacity opacity) { - return std::make_unique( - Color4f{static_cast(color.red) / 255.0f, static_cast(color.green) / 255.0f, - static_cast(color.blue) / 255.0f, static_cast(opacity) / 255.0f}); + return std::make_shared(center, radius, colors, positions); } } // namespace pag diff --git a/tgfx/src/gpu/Paint.cpp b/tgfx/src/gpu/Paint.cpp index d762a98c3f..9d48de7311 100644 --- a/tgfx/src/gpu/Paint.cpp +++ b/tgfx/src/gpu/Paint.cpp @@ -24,5 +24,6 @@ void Paint::reset() { color = Black; alpha = Opaque; stroke = Stroke(0); + shader = nullptr; } } // namespace pag \ No newline at end of file diff --git a/tgfx/src/gpu/ShaderBase.h b/tgfx/src/gpu/ShaderBase.h new file mode 100644 index 0000000000..f8b646b6ee --- /dev/null +++ b/tgfx/src/gpu/ShaderBase.h @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making libpag available. +// +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +// except in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 "gpu/FragmentProcessor.h" +#include "gpu/Shader.h" + +namespace pag { +class ShaderBase : public Shader { + public: + virtual std::unique_ptr asFragmentProcessor(const FPArgs& args) const = 0; +}; +} // namespace pag diff --git a/tgfx/src/gpu/opengl/GLCanvas.cpp b/tgfx/src/gpu/opengl/GLCanvas.cpp index a50d22c85a..11e95c6811 100644 --- a/tgfx/src/gpu/opengl/GLCanvas.cpp +++ b/tgfx/src/gpu/opengl/GLCanvas.cpp @@ -22,8 +22,11 @@ #include "GLSurface.h" #include "base/utils/MathExtra.h" #include "core/Mask.h" +#include "core/PathEffect.h" #include "core/TextBlob.h" #include "gpu/AlphaFragmentProcessor.h" +#include "gpu/ColorShader.h" +#include "gpu/ShaderBase.h" #include "gpu/TextureFragmentProcessor.h" #include "gpu/TextureMaskFragmentProcessor.h" #include "gpu/YUVTextureFragmentProcessor.h" @@ -81,7 +84,9 @@ std::unique_ptr GLCanvas::getClipMask(const Rect& deviceQuad, auto clipSurface = getClipSurface(); auto clipCanvas = clipSurface->getCanvas(); clipCanvas->clear(); - clipCanvas->drawPath(globalPaint.clip, Black); + Paint paint = {}; + paint.setColor(Black); + clipCanvas->drawPath(globalPaint.clip, paint); return TextureMaskFragmentProcessor::MakeUseDeviceCoord(clipSurface->getTexture().get(), surface->origin()); } @@ -139,39 +144,21 @@ void GLCanvas::drawTexture(const Texture* texture, const RGBAAALayout* layout, c TextureMaskFragmentProcessor::MakeUseLocalCoord(mask, Matrix::I(), inverted), true); } -void GLCanvas::drawPath(const Path& path, Color color) { - auto shader = Shader::MakeColorShader(color); - drawPath(path, shader.get()); -} - -static std::unique_ptr MakeGradientShader(const GradientPaint& gradient) { - std::unique_ptr shader; - std::vector colors = {}; - int index = 0; - auto& alphas = gradient.alphas; - for (auto& color : gradient.colors) { - auto r = static_cast(color.red) / 255.0f; - auto g = static_cast(color.green) / 255.0f; - auto b = static_cast(color.blue) / 255.0f; - auto a = static_cast(alphas[index++]) / 255.0f; - colors.push_back({r, g, b, a}); - } - if (gradient.gradientType == GradientFillType::Linear) { - shader = GradientShader::MakeLinear(gradient.startPoint, gradient.endPoint, colors, - gradient.positions); - } else { - auto radius = Point::Distance(gradient.startPoint, gradient.endPoint); - shader = GradientShader::MakeRadial(gradient.startPoint, radius, colors, gradient.positions); +void GLCanvas::drawPath(const Path& path, const Paint& paint) { + auto shader = paint.getShader(); + if (shader == nullptr) { + shader = Shader::MakeColorShader(paint.getColor(), paint.getAlpha()); + } + if (paint.getStyle() == PaintStyle::Fill) { + drawPath(path, shader.get()); + return; } - if (!shader) { - shader = std::make_unique(colors.back()); + auto strokePath = path; + auto strokeEffect = PathEffect::MakeStroke(*paint.getStroke()); + if (strokeEffect) { + strokeEffect->applyTo(&strokePath); } - return shader; -} - -void GLCanvas::drawPath(const Path& path, const GradientPaint& gradient) { - auto shader = MakeGradientShader(gradient); - drawPath(path, shader.get()); + drawPath(strokePath, shader.get()); } static std::unique_ptr MakeSimplePathOp(const Path& path) { @@ -199,7 +186,8 @@ void GLCanvas::drawPath(const Path& path, const Shader* shader) { auto localMatrix = Matrix::MakeScale(bounds.width(), bounds.height()); localMatrix.postTranslate(bounds.x(), bounds.y()); auto args = FPArgs(getContext(), localMatrix); - draw(bounds, bounds, std::move(op), shader->asFragmentProcessor(args)); + draw(bounds, bounds, std::move(op), + static_cast(shader)->asFragmentProcessor(args)); return; } auto quad = globalPaint.matrix.mapRect(clippedLocalQuad); @@ -234,7 +222,8 @@ void GLCanvas::drawMask(Rect quad, const Texture* mask, const Shader* shader) { auto args = FPArgs(getContext(), localMatrix); save(); resetMatrix(); - draw(quad, quad, GLFillRectOp::Make(), shader->asFragmentProcessor(args), + draw(quad, quad, GLFillRectOp::Make(), + static_cast(shader)->asFragmentProcessor(args), TextureMaskFragmentProcessor::MakeUseLocalCoord(mask, Matrix::MakeScale(scale.x, scale.y))); restore(); } diff --git a/tgfx/src/gpu/opengl/GLCanvas.h b/tgfx/src/gpu/opengl/GLCanvas.h index ac0256d45c..3de328d2d3 100644 --- a/tgfx/src/gpu/opengl/GLCanvas.h +++ b/tgfx/src/gpu/opengl/GLCanvas.h @@ -32,8 +32,7 @@ class GLCanvas : public Canvas { void clear() override; void drawTexture(const Texture* texture, const Texture* mask, bool inverted) override; void drawTexture(const Texture* texture, const RGBAAALayout* layout) override; - void drawPath(const Path& path, Color color) override; - void drawPath(const Path& path, const GradientPaint& gradient) override; + void drawPath(const Path& path, const Paint& paint) override; void drawGlyphs(const GlyphID glyphIDs[], const Point positions[], size_t glyphCount, const Font& font, const Paint& paint) override; Enum hasComplexPaint(const Rect& drawingBounds) const override;