Skip to content

Commit

Permalink
Add a Shape class to defer Path computation and perform it in parallel.
Browse files Browse the repository at this point in the history
  • Loading branch information
domchen committed Nov 12, 2024
1 parent 0ee494a commit de02e92
Show file tree
Hide file tree
Showing 55 changed files with 1,471 additions and 368 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ file(GLOB_RECURSE SRC_FILES
src/core/filters/*.*
src/core/images/*.*
src/core/shaders/*.*
src/core/shapes/*.*
src/core/utils/*.*
src/gpu/ops/*.*
src/gpu/processors/*.*
Expand Down
8 changes: 6 additions & 2 deletions include/tgfx/core/Canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
#pragma once

#include <stack>
#include "tgfx/core/BlendMode.h"
#include "tgfx/core/Font.h"
#include "tgfx/core/Image.h"
#include "tgfx/core/Paint.h"
#include "tgfx/core/Path.h"
#include "tgfx/core/Picture.h"
#include "tgfx/core/SamplingOptions.h"
#include "tgfx/core/Shape.h"
#include "tgfx/core/TextBlob.h"

namespace tgfx {
Expand Down Expand Up @@ -223,6 +223,11 @@ class Canvas {
*/
void drawPath(const Path& path, const Paint& paint);

/**
* Draws a shape using the current clip, matrix, and specified paint.
*/
void drawShape(std::shared_ptr<Shape> shape, const Paint& paint);

/**
* Draws an image, with its top-left corner at (left, top), using current clip, matrix and
* optional paint. If image->hasMipmaps() is true, it uses FilterMode::Linear and
Expand Down Expand Up @@ -321,7 +326,6 @@ class Canvas {

explicit Canvas(DrawContext* drawContext);
Canvas(DrawContext* drawContext, const Path& initClip);
bool drawSimplePath(const Path& path, const FillStyle& style);
void drawImage(std::shared_ptr<Image> image, const SamplingOptions& sampling, const Paint* paint,
const Matrix* extraMatrix);
void drawLayer(std::shared_ptr<Picture> picture, const MCState& state, const FillStyle& style,
Expand Down
4 changes: 2 additions & 2 deletions include/tgfx/core/Picture.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class Picture {

/**
* Returns the bounding box of the Picture when drawn with the given Matrix. Since the Picture
* may contain text drawing commands, and text outlines can change with different scale factors,
* it's best to use the final drawing matrix for calculating the bounds to ensure accuracy.
* may contain shape and text drawing commands whose outlines can change with different scale
* factors, it's best to use the final drawing matrix to calculate the bounds for accuracy.
*/
Rect getBounds(const Matrix* matrix = nullptr) const;

Expand Down
11 changes: 11 additions & 0 deletions include/tgfx/core/RRect.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#pragma once

#include "tgfx/core/Matrix.h"
#include "tgfx/core/Rect.h"

namespace tgfx {
Expand All @@ -28,6 +29,16 @@ struct RRect {
Rect rect = Rect::MakeEmpty();
Point radii = Point::Zero();

/**
* Returns true if the bounds are a simple rectangle.
*/
bool isRect() const;

/**
* Returns true if the bounds are an oval.
*/
bool isOval() const;

/**
* Sets to rounded rectangle with the same radii for all four corners.
* @param rect bounds of rounded rectangle
Expand Down
153 changes: 153 additions & 0 deletions include/tgfx/core/Shape.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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/PathEffect.h"
#include "tgfx/core/RRect.h"
#include "tgfx/core/TextBlob.h"

namespace tgfx {
class UniqueKey;

/**
* Shape represents a deferred Path object. It postpones path computations, such as PathOps and
* PathEffects, until the path is actually required. Using Shape is recommended when the path is
* expensive to compute and not required immediately. It can leverage multi-threading to compute the
* path in parallel during drawing and cache the rasterized form in the GPU for repeated drawing.
* Shape is thread-safe and immutable once created.
*/
class Shape {
public:
/**
* Wraps an existing path in a Shape object. Returns nullptr if the path is empty.
*/
static std::shared_ptr<Shape> MakeFrom(Path path);

/**
* Creates a new Shape from the given text blob. Returns nullptr if the text blob is nullptr or
* contains a typeface that can't generate a path, such as color emoji typefaces.
*/
static std::shared_ptr<Shape> MakeFrom(std::shared_ptr<TextBlob> textBlob);

/**
* Merges two Shapes into a new Shape using the specified path operation. If either Shape is
* nullptr, the other Shape is returned. Returns nullptr if both Shapes are nullptr.
*/
static std::shared_ptr<Shape> Merge(std::shared_ptr<Shape> first, std::shared_ptr<Shape> second,
PathOp pathOp = PathOp::Append);

/**
* Applies the specified stroke to the given Shape. If the stroke is nullptr, the original Shape
* is returned. Returns nullptr if the Shape is nullptr.
*/
static std::shared_ptr<Shape> ApplyStroke(std::shared_ptr<Shape> shape, const Stroke* stroke);

/**
* Applies the specified matrix to the given Shape. If the matrix is identity, the original Shape
* is returned. Returns nullptr if the Shape is nullptr.
*/
static std::shared_ptr<Shape> ApplyMatrix(std::shared_ptr<Shape> shape, const Matrix& matrix);

/**
* Applies the specified path effect to the given Shape. If the effect is nullptr, the original
* Shape is returned. Returns nullptr if the Shape is nullptr.
*/
static std::shared_ptr<Shape> ApplyEffect(std::shared_ptr<Shape> shape,
std::shared_ptr<PathEffect> effect);

virtual ~Shape() = default;

/**
* Returns true if the Shape is a Line and stores points in line. Otherwise, returns false and
* leaves line unchanged.
*/
virtual bool isLine(Point line[2] = nullptr) const;

/**
* Returns true if the Shape is a Rect and stores the Rect in rect. Otherwise, returns false and
* leaves rect unchanged.
*/
virtual bool isRect(Rect* rect = nullptr) const;

/**
* Returns true if the Shape is an oval or circle and stores the bounding Rect in bounds.
* Otherwise, returns false and leaves the bounds unchanged.
*/
virtual bool isOval(Rect* bounds = nullptr) const;

/**
* Returns true if the Shape is a RRect and stores the RRect in rRect. Otherwise, returns false
* and leaves rRect unchanged. Please note that this method returns false if the path is
* representable as oval, circle, or Rect.
*/
virtual bool isRRect(RRect* rRect = nullptr) const;

/**
* Returns the bounding box of the Shape. The bounds might be larger than the actual shape because
* the exact bounds can't be determined until the shape is computed. Note: Since the Shape may
* contain strokes or text glyphs whose outlines can change with different scale factors, it's
* best to pass the final drawing scale factor in the resolutionScale for computing the bounds to
* ensure accuracy. However, the resolutionScale is not applied to the returned bounds; it just
* affects the precision of the bounds.
* @param resolutionScale The intended resolution for the Shape. The default value is 1.0. Higher
* values (res > 1) mean the result should be more precise, as it will be zoomed up and small
* errors will be magnified. Lower values (0 < res < 1) mean the result can be less precise, as it
* will be zoomed down and small errors may be invisible.
* @return The bounding box of the Shape.
*/
virtual Rect getBounds(float resolutionScale = 1.0f) const;

/**
* Returns the computed path of the Shape. Note: Since the Shape may contain strokes or text
* glyphs whose outlines can change with different scale factors, it's best to pass the final
* drawing scale factor in the resolutionScale for computing the path to ensure accuracy. However,
* the resolutionScale is not applied to the returned path; it just affects the precision of the
* path.
* @param resolutionScale The intended resolution for the Shape. The default value is 1.0. Higher
* values (res > 1) mean the result should be more precise, as it will be zoomed up and small
* errors will be magnified. Lower values (0 < res < 1) mean the result can be less precise, as it
* will be zoomed down and small errors may be invisible.
* @return The computed path of the Shape.
*/
virtual Path getPath(float resolutionScale = 1.0f) const;

protected:
enum class Type { Append, Effect, Glyph, Matrix, Merge, Path, Stroke };

/**
* Returns the type of the Shape.
*/
virtual Type type() const = 0;

/**
* Generates a unique key for the Shape. The key is used to cache the rasterized form of the Shape
* in the GPU.
*/
virtual UniqueKey getUniqueKey() const = 0;

private:
static void Append(std::vector<std::shared_ptr<Shape>>* shapes, std::shared_ptr<Shape> shape);

friend class StrokeShape;
friend class MatrixShape;
friend class ShapeDrawOp;
friend class ProxyProvider;
friend class Canvas;
};
} // namespace tgfx
11 changes: 8 additions & 3 deletions include/tgfx/core/Stroke.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,15 @@ class Stroke {
}

/**
* Applies this stroke to the given path. Returns false if this stroke cannot be applied, and
* leaves the path unchanged.
* Applies the stroke options to the given path.
* @param path The path to which the stroke options will be applied.
* @param resolutionScale The intended resolution for the output. The default value is 1.0.
* Higher values (res > 1) mean the result should be more precise, as it will be zoomed up and
* small errors will be magnified. Lower values (0 < res < 1) mean the result can be less precise,
* as it will be zoomed down and small errors may be invisible.
* @return false if the stroke cannot be applied, leaving the path unchanged.
*/
bool applyToPath(Path* path) const;
bool applyToPath(Path* path, float resolutionScale = 1.0f) const;

/**
* The thickness of the pen used to outline the paths or glyphs.
Expand Down
12 changes: 8 additions & 4 deletions include/tgfx/core/TextBlob.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ class TextBlob {
virtual ~TextBlob() = default;

/**
* Returns the bounding box of the TextBlob when drawn with the given Matrix. Since text outlines
* may change with different scale factors, it's best to use the final drawing matrix for
* calculating the bounds to ensure accuracy.
* Returns the bounding box of the TextBlob. Since text outlines may change with different scale
* factors, it's best to pass the final drawing scale factor in the resolutionScale for
* calculating the bounds to ensure accuracy. However, the resolutionScale is not applied to the
* returned bounds; it just affects the precision of the bounds.
* @param resolutionScale The intended resolution for the TextBlob. The default value is 1.0. It
* is used to scale the glyphs before measuring.
* @return The bounding box of the TextBlob.
*/
Rect getBounds(const Matrix* matrix = nullptr) const;
Rect getBounds(float resolutionScale = 1.0f) const;

private:
std::vector<std::shared_ptr<GlyphRunList>> glyphRunLists = {};
Expand Down
45 changes: 18 additions & 27 deletions src/core/Canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,44 +225,35 @@ void Canvas::drawRRect(const RRect& rRect, const Paint& paint) {
}

void Canvas::drawPath(const Path& path, const Paint& paint) {
if (path.isEmpty() || paint.nothingToDraw()) {
auto shape = Shape::MakeFrom(path);
drawShape(std::move(shape), paint);
}

void Canvas::drawShape(std::shared_ptr<Shape> shape, const Paint& paint) {
if (shape == nullptr || paint.nothingToDraw()) {
return;
}
auto stroke = paint.getStroke();
shape = Shape::ApplyStroke(std::move(shape), paint.getStroke());
auto style = CreateFillStyle(paint);
if (stroke && path.isLine()) {
auto fillPath = path;
stroke->applyToPath(&fillPath);
if (drawSimplePath(fillPath, style)) {
return;
}
}
if (!stroke && drawSimplePath(path, style)) {
return;
}
drawContext->drawPath(path, *mcState, style, stroke);
}

bool Canvas::drawSimplePath(const Path& path, const FillStyle& style) {
Rect rect = {};
if (path.isRect(&rect)) {
if (shape->isRect(&rect)) {
drawContext->drawRect(rect, *mcState, style);
return true;
return;
}
RRect rRect;
if (path.isOval(&rect)) {
RRect rRect = {};
if (shape->isOval(&rect)) {
rRect.setOval(rect);
drawContext->drawRRect(rRect, *mcState, style);
return true;
return;
}
if (path.isRRect(&rRect)) {
if (shape->isRRect(&rRect)) {
drawContext->drawRRect(rRect, *mcState, style);
return true;
return;
}
return false;
drawContext->drawShape(std::move(shape), *mcState, style);
}

static SamplingOptions GetDefaultSamplingOptions(std::shared_ptr<Image> image) {
static SamplingOptions GetDefaultSamplingOptions(Image* image) {
if (image == nullptr) {
return {};
}
Expand All @@ -275,12 +266,12 @@ void Canvas::drawImage(std::shared_ptr<Image> image, float left, float top, cons
}

void Canvas::drawImage(std::shared_ptr<Image> image, const Matrix& matrix, const Paint* paint) {
auto sampling = GetDefaultSamplingOptions(image);
auto sampling = GetDefaultSamplingOptions(image.get());
drawImage(std::move(image), sampling, paint, &matrix);
}

void Canvas::drawImage(std::shared_ptr<Image> image, const Paint* paint) {
auto sampling = GetDefaultSamplingOptions(image);
auto sampling = GetDefaultSamplingOptions(image.get());
drawImage(std::move(image), sampling, paint, nullptr);
}

Expand Down
9 changes: 4 additions & 5 deletions src/core/DrawContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@
#include "core/FillStyle.h"
#include "core/GlyphRunList.h"
#include "core/MCState.h"
#include "tgfx/core/Matrix.h"
#include "tgfx/core/Path.h"
#include "tgfx/core/Picture.h"
#include "tgfx/core/Shape.h"

namespace tgfx {
class Surface;
Expand Down Expand Up @@ -60,10 +59,10 @@ class DrawContext {
virtual void drawRRect(const RRect& rRect, const MCState& state, const FillStyle& style) = 0;

/**
* Draws a complex Path with the specified MCState, FillStyle and optional Stroke.
* Draws a complex Shape with the specified MCState and FillStyle.
*/
virtual void drawPath(const Path& path, const MCState& state, const FillStyle& style,
const Stroke* stroke) = 0;
virtual void drawShape(std::shared_ptr<Shape> shape, const MCState& state,
const FillStyle& style) = 0;

/**
* Draws an Image with the specified SamplingOptions, MCState, and FillStyle.
Expand Down
Loading

0 comments on commit de02e92

Please sign in to comment.