Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6eedbb5
Add matrix transformation support to LayerRecorder.
StarryThrone Jan 30, 2026
4122423
Merge remote-tracking branch 'origin/main' into feature/jasonrjchen_l…
StarryThrone Jan 30, 2026
8fe7428
Update baseline versions from main branch merge.
StarryThrone Jan 30, 2026
c7d581f
Add StrokeContent wrapper and refactor stroke handling in GeometryCon…
StarryThrone Feb 2, 2026
a330624
Wrap TextContent with StrokeContent when paint style is stroke.
StarryThrone Feb 2, 2026
3dfbd20
Merge remote-tracking branch 'origin/main' into feature/jasonrjchen_l…
StarryThrone Feb 2, 2026
6e510af
Add hasBlendMode implementation to DrawContent, MatrixContent, and St…
StarryThrone Feb 2, 2026
51586f8
Fix stroke being scaled non-uniformly in LayerRecorder handlePathAsRe…
StarryThrone Feb 3, 2026
261f2f6
Add setMatrix and resetMatrix interfaces to LayerRecorder.
StarryThrone Feb 3, 2026
50779c5
Revert StrokeContent abstraction and make stroke a DrawContent attrib…
StarryThrone Feb 3, 2026
d5c8493
Merge branch 'main' into feature/jasonrjchen_layer_recorder_matrix.
StarryThrone Feb 3, 2026
0376c36
Simplify LayerRecorderMatrix test and update baseline.
StarryThrone Feb 4, 2026
e3ea01c
Use auto for variable declaration in LayerRecorder.
StarryThrone Feb 4, 2026
aeed960
Change LayerRecorder::getMatrix() to return const reference instead o…
StarryThrone Feb 4, 2026
5325f91
Remove addTextBlob matrix overload and fix matrix handling for text c…
StarryThrone Feb 4, 2026
08177d3
Update setMatrix comment to clarify matrix affects both geometry and …
StarryThrone Feb 4, 2026
cb777c7
Replace std::optional<Matrix> with Matrix and simplify TextContent of…
StarryThrone Feb 4, 2026
82eea7e
Rename MatrixContent member and refactor LayerRecorder path simplific…
StarryThrone Feb 5, 2026
698dbce
Merge branch 'main' into feature/jasonrjchen_layer_recorder_matrix
StarryThrone Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions include/tgfx/layers/LayerRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include <memory>
#include "tgfx/core/Matrix.h"
#include "tgfx/core/Path.h"
#include "tgfx/core/RRect.h"
#include "tgfx/core/Shape.h"
Expand All @@ -30,7 +31,7 @@ class LayerContent;
class GeometryContent;

/**
* LayerRecorder records geometries and their paints as layer content. Geometries with invisible
* LayerRecorder records geometries and their paints as layer content. Geometries with invisible
* paints are still included as part of the layer's contour, but they will not be rendered.
*/
class LayerRecorder {
Expand Down Expand Up @@ -77,13 +78,21 @@ class LayerRecorder {
float y = 0);

/**
* Adds a text blob with the specified paint and transformation matrix.
* @param textBlob The text blob.
* @param paint The paint style for the text.
* @param matrix The transformation matrix to apply to the text.
* Returns the current transformation matrix. If no matrix is set, returns the identity matrix.
*/
const Matrix& getMatrix() const;

/**
* Sets the transformation matrix for subsequent drawing operations. The matrix is applied to both
* the geometry (Rect, RRect, Path, Shape, TextBlob) and the paint.
* @param matrix The transformation matrix to apply.
*/
void addTextBlob(std::shared_ptr<TextBlob> textBlob, const LayerPaint& paint,
const Matrix& matrix);
void setMatrix(const Matrix& matrix);

/**
* Resets the transformation matrix to none.
*/
void resetMatrix();

private:
enum class PendingType {
Expand All @@ -93,17 +102,31 @@ class LayerRecorder {
Shape,
};

// Current transformation matrix applied to all subsequent drawing operations.
Matrix _matrix = Matrix::I();

std::vector<std::unique_ptr<GeometryContent>> contents;
std::vector<std::unique_ptr<GeometryContent>> foregrounds;

PendingType pendingType = PendingType::None;
LayerPaint pendingPaint = {};
// Transformation matrix for the pending geometries.
Matrix pendingMatrix = Matrix::I();
std::vector<Rect> pendingRects = {};
std::vector<RRect> pendingRRects = {};
std::shared_ptr<Shape> pendingShape = nullptr;

bool canAppend(PendingType type, const LayerPaint& paint) const;
void flushPending(PendingType newType = PendingType::None, const LayerPaint& newPaint = {});
void addRect(const Rect& rect, const LayerPaint& paint, const Matrix& matrix);
void addRRect(const RRect& rRect, const LayerPaint& paint, const Matrix& matrix);
void addPath(const Path& path, const LayerPaint& paint, const Matrix& matrix);
void addShape(std::shared_ptr<Shape> shape, const LayerPaint& paint, const Matrix& matrix);

bool tryAddSimplifiedPath(const Path& path, const LayerPaint& paint, const Matrix& matrix);
bool tryAddSimplifiedMatrixShape(const std::shared_ptr<Shape>& shape, const LayerPaint& paint,
const Matrix& matrix);
bool canAppend(PendingType type, const LayerPaint& paint, const Matrix& matrix) const;
void flushPending(PendingType newType = PendingType::None, const LayerPaint& newPaint = {},
const Matrix& newMatrix = Matrix::I());

std::unique_ptr<LayerContent> finishRecording();

Expand Down
7 changes: 7 additions & 0 deletions src/core/utils/ShapeUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ float ShapeUtils::CalculateAlphaReduceFactorIfHairline(std::shared_ptr<Shape> sh
return std::min(strokeShape->stroke.width * scale, 1.f);
}

const MatrixShape* ShapeUtils::AsMatrixShape(const Shape* shape) {
if (shape != nullptr && shape->type() == Shape::Type::Matrix) {
return static_cast<const MatrixShape*>(shape);
}
return nullptr;
}

} // namespace tgfx
9 changes: 9 additions & 0 deletions src/core/utils/ShapeUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
#include <memory>
#include "tgfx/core/Path.h"
#include "tgfx/core/Shape.h"

namespace tgfx {

class MatrixShape;

class ShapeUtils {
public:
/**
Expand All @@ -33,5 +36,11 @@ class ShapeUtils {
static Path GetShapeRenderingPath(std::shared_ptr<Shape> shape, float resolutionScale);

static float CalculateAlphaReduceFactorIfHairline(std::shared_ptr<Shape> shape);

/**
* Returns the shape as a MatrixShape pointer, or nullptr if it is not a MatrixShape.
*/
static const MatrixShape* AsMatrixShape(const Shape* shape);
};

} // namespace tgfx
117 changes: 68 additions & 49 deletions src/inspect/serialization/RecordedContentSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

#include "RecordedContentSerialization.h"
#include <string>
#include "core/utils/Types.h"
#include "layers/contents/ComposeContent.h"
#include "layers/contents/MatrixContent.h"
#include "layers/contents/PathContent.h"
#include "layers/contents/RRectContent.h"
#include "layers/contents/RRectsContent.h"
#include "layers/contents/RectContent.h"
#include "layers/contents/RectsContent.h"
#include "layers/contents/ShapeContent.h"
#include "layers/contents/TextContent.h"

Expand All @@ -43,6 +47,12 @@ static std::string ContentTypeToString(ContentType type) {
return "Text";
case ContentType::Compose:
return "Compose";
case ContentType::Rects:
return "Rects";
case ContentType::RRects:
return "RRects";
case ContentType::Matrix:
return "Matrix";
default:
return "Unknown";
}
Expand Down Expand Up @@ -82,39 +92,43 @@ static void SerializeStroke(flexbuffers::Builder& fbb, const Stroke& stroke) {
fbb.EndMap(strokeStart);
}

static void SerializeGeometryContent(flexbuffers::Builder& fbb, const GeometryContent* content) {
SerializeColor(fbb, content->color);
SerializeUtils::SetFlexBufferMap(fbb, "hasShader", content->shader != nullptr);
static void SerializeDrawContent(flexbuffers::Builder& fbb, const DrawContent* content) {
SerializeColor(fbb, content->getColor());
SerializeUtils::SetFlexBufferMap(fbb, "hasShader", content->getShader() != nullptr);
SerializeUtils::SetFlexBufferMap(fbb, "blendMode",
SerializeUtils::BlendModeToString(content->blendMode));
SerializeUtils::SetFlexBufferMap(fbb, "style", PaintStyleToString(content->stroke != nullptr));
if (content->stroke) {
SerializeStroke(fbb, *content->stroke);
SerializeUtils::BlendModeToString(content->getBlendMode()));
SerializeUtils::SetFlexBufferMap(fbb, "style",
PaintStyleToString(content->getStroke() != nullptr));
if (content->getStroke()) {
SerializeStroke(fbb, *content->getStroke());
}
}

static void SerializeRectContent(flexbuffers::Builder& fbb, const RectContent* content) {
SerializeGeometryContent(fbb, content);
fbb.Key("rect");
static void SerializeRect(flexbuffers::Builder& fbb, const char* key, const Rect& rect) {
fbb.Key(key);
auto rectStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "left", content->rect.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", content->rect.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", content->rect.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", content->rect.bottom);
SerializeUtils::SetFlexBufferMap(fbb, "left", rect.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", rect.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", rect.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", rect.bottom);
fbb.EndMap(rectStart);
}

static void SerializeRectContent(flexbuffers::Builder& fbb, const RectContent* content) {
SerializeDrawContent(fbb, content);
SerializeRect(fbb, "rect", content->rect);
}

static void SerializeRectsContent(flexbuffers::Builder& fbb, const RectsContent* content) {
SerializeDrawContent(fbb, content);
SerializeUtils::SetFlexBufferMap(fbb, "rectsCount", static_cast<int>(content->rects.size()));
}

static void SerializeRRectContent(flexbuffers::Builder& fbb, const RRectContent* content) {
SerializeGeometryContent(fbb, content);
SerializeDrawContent(fbb, content);
fbb.Key("rRect");
auto rRectStart = fbb.StartMap();
fbb.Key("rect");
auto rectStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "left", content->rRect.rect.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", content->rRect.rect.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", content->rRect.rect.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", content->rRect.rect.bottom);
fbb.EndMap(rectStart);
SerializeRect(fbb, "rect", content->rRect.rect);
fbb.Key("radii");
auto radiiStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "x", content->rRect.radii.x);
Expand All @@ -123,44 +137,40 @@ static void SerializeRRectContent(flexbuffers::Builder& fbb, const RRectContent*
fbb.EndMap(rRectStart);
}

static void SerializeRRectsContent(flexbuffers::Builder& fbb, const RRectsContent* content) {
SerializeDrawContent(fbb, content);
SerializeUtils::SetFlexBufferMap(fbb, "rRectsCount", static_cast<int>(content->rRects.size()));
}

static void SerializePathContent(flexbuffers::Builder& fbb, const PathContent* content) {
SerializeGeometryContent(fbb, content);
auto bounds = content->path.getBounds();
fbb.Key("pathBounds");
auto pathBoundsStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "left", bounds.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", bounds.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", bounds.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", bounds.bottom);
fbb.EndMap(pathBoundsStart);
SerializeDrawContent(fbb, content);
SerializeRect(fbb, "pathBounds", content->path.getBounds());
}

static void SerializeShapeContent(flexbuffers::Builder& fbb, const ShapeContent* content) {
SerializeGeometryContent(fbb, content);
SerializeDrawContent(fbb, content);
if (content->shape) {
auto bounds = content->shape->getBounds();
fbb.Key("shapeBounds");
auto shapeBoundsStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "left", bounds.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", bounds.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", bounds.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", bounds.bottom);
fbb.EndMap(shapeBoundsStart);
SerializeRect(fbb, "shapeBounds", content->shape->getBounds());
}
}

static void SerializeTextContent(flexbuffers::Builder& fbb, const TextContent* content) {
SerializeGeometryContent(fbb, content);
SerializeDrawContent(fbb, content);
if (content->textBlob) {
auto bounds = content->textBlob->getBounds();
fbb.Key("textBounds");
auto textBoundsStart = fbb.StartMap();
SerializeUtils::SetFlexBufferMap(fbb, "left", bounds.left);
SerializeUtils::SetFlexBufferMap(fbb, "top", bounds.top);
SerializeUtils::SetFlexBufferMap(fbb, "right", bounds.right);
SerializeUtils::SetFlexBufferMap(fbb, "bottom", bounds.bottom);
fbb.EndMap(textBoundsStart);
SerializeRect(fbb, "textBounds", content->textBlob->getBounds());
}
}

static void SerializeMatrixContent(flexbuffers::Builder& fbb, const MatrixContent* content) {
fbb.Key("matrix");
auto matrixStart = fbb.StartMap();
float buffer[9] = {};
content->matrix.get9(buffer);
for (int i = 0; i < 9; i++) {
auto key = "[" + std::to_string(i) + "]";
SerializeUtils::SetFlexBufferMap(fbb, key.c_str(), buffer[i]);
}
fbb.EndMap(matrixStart);
}

std::shared_ptr<Data> RecordedContentSerialization::Serialize(
Expand All @@ -181,9 +191,15 @@ std::shared_ptr<Data> RecordedContentSerialization::Serialize(
case ContentType::Rect:
SerializeRectContent(fbb, static_cast<const RectContent*>(content));
break;
case ContentType::Rects:
SerializeRectsContent(fbb, static_cast<const RectsContent*>(content));
break;
case ContentType::RRect:
SerializeRRectContent(fbb, static_cast<const RRectContent*>(content));
break;
case ContentType::RRects:
SerializeRRectsContent(fbb, static_cast<const RRectsContent*>(content));
break;
case ContentType::Path:
SerializePathContent(fbb, static_cast<const PathContent*>(content));
break;
Expand All @@ -197,6 +213,9 @@ std::shared_ptr<Data> RecordedContentSerialization::Serialize(
// ComposeContent contains multiple contents, just show count.
SerializeUtils::SetFlexBufferMap(fbb, "isComposed", true);
break;
case ContentType::Matrix:
SerializeMatrixContent(fbb, static_cast<const MatrixContent*>(content));
break;
default:
break;
}
Expand Down
Loading