Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions include/tgfx/layers/Layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ class Layer : public std::enable_shared_from_this<Layer> {
*/
void detachProperty(LayerProperty* property);

Matrix getGlobalMatrix() const;

private:
/**
* Marks the layer as needing to be redrawn. Unlike invalidateContent(), this method only marks
Expand All @@ -552,8 +554,6 @@ class Layer : public std::enable_shared_from_this<Layer> {

bool doContains(const Layer* child) const;

Matrix getGlobalMatrix() const;

Matrix getMatrixWithScrollRect() const;

LayerContent* getContent();
Expand Down
2 changes: 1 addition & 1 deletion include/tgfx/layers/ShapeLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,6 @@ class ShapeLayer : public Layer {
std::vector<Paint> createShapePaints(const std::vector<std::shared_ptr<ShapeStyle>>& styles,
bool isHairline = false) const;

std::shared_ptr<Shape> createStrokeShape() const;
std::shared_ptr<Shape> createStrokeShape(bool isHairline) const;
};
} // namespace tgfx
2 changes: 1 addition & 1 deletion src/core/HitTestContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

#include "HitTestContext.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/MathExtra.h"
#include "core/utils/StrokeUtils.h"
#include "utils/Log.h"

namespace tgfx {
Expand Down
2 changes: 1 addition & 1 deletion src/core/MeasureContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

#include "MeasureContext.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/Log.h"
#include "core/utils/StrokeUtils.h"
#include "utils/MathExtra.h"

namespace tgfx {
Expand Down
2 changes: 1 addition & 1 deletion src/core/PathUserTypeface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
#include "PathUserTypeface.h"
#include "UserScalerContext.h"
#include "core/PathRasterizer.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/FauxBoldScale.h"
#include "core/utils/StrokeUtils.h"
#include "tgfx/core/Shape.h"

namespace tgfx {
Expand Down
16 changes: 15 additions & 1 deletion src/core/StyledShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "StyledShape.h"
#include "core/shapes/MatrixShape.h"
#include "core/shapes/StrokeShape.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/StrokeUtils.h"
#include "tgfx/core/Stroke.h"

namespace tgfx {
Expand Down Expand Up @@ -107,4 +107,18 @@ Path StyledShape::getPath() const {
return finalPath;
}

void StyledShape::convertToHairlineIfNecessary(Fill& fill) {
if (!_stroke.has_value()) {
return;
}
float mappedStrokeWidth = 0.f;
if (!TreatStrokeAsHairline(*_stroke, _matrix, &mappedStrokeWidth)) {
return;
}
// Adjust the alpha based on the mapped stroke width to simulate the semi-transparent effect of
// very thin stroke.
fill.color.alpha *= mappedStrokeWidth / 1.f;
_stroke->width = 0.f;
}

} // namespace tgfx
3 changes: 3 additions & 0 deletions src/core/StyledShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include <optional>
#include "tgfx/core/Fill.h"
#include "tgfx/core/Matrix.h"
#include "tgfx/core/Path.h"
#include "tgfx/core/Rect.h"
Expand Down Expand Up @@ -53,6 +54,8 @@ class StyledShape {

Path getPath() const;

void convertToHairlineIfNecessary(Fill& fill);

private:
StyledShape(std::shared_ptr<Shape> shape, const Stroke* stroke, Matrix matrix);

Expand Down
2 changes: 1 addition & 1 deletion src/core/shapes/StrokeShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

#include "StrokeShape.h"
#include "core/shapes/MatrixShape.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/Log.h"
#include "core/utils/StrokeUtils.h"
#include "core/utils/UniqueID.h"
#include "gpu/resources/ResourceKey.h"

Expand Down
13 changes: 12 additions & 1 deletion src/core/utils/StrokeUtils.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "StrokeUtils.h"
#include <cmath>
#include "ApplyStrokeToBounds.h"
#include "core/utils/MathExtra.h"

namespace tgfx {
Expand All @@ -16,4 +16,15 @@ void ApplyStrokeToBounds(const Stroke& stroke, Rect* bounds, bool applyMiterLimi
bounds->outset(expand, expand);
}

bool TreatStrokeAsHairline(const Stroke& stroke, const Matrix& matrix, float* width) {
if (stroke.isHairline()) {
return false;
}
auto maxWidth = stroke.width * matrix.getMaxScale();
if (width) {
*width = maxWidth;
}
return maxWidth < 1.f;
}

} // namespace tgfx
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"
#include "tgfx/core/Stroke.h"

Expand All @@ -26,4 +27,10 @@ namespace tgfx {
* Applies the stroke options to the given bounds.
*/
void ApplyStrokeToBounds(const Stroke& stroke, Rect* bounds, bool applyMiterLimit = false);

/**
* If the stroke is not a hairline but is very thin after transformation, it can also be treated
* as a hairline to avoid precision issues.
*/
bool TreatStrokeAsHairline(const Stroke& stroke, const Matrix& matrix, float* width = nullptr);
} // namespace tgfx
2 changes: 1 addition & 1 deletion src/core/vectors/web/WebScalerContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
#include "WebScalerContext.h"
#include "ReadPixelsFromCanvasImage.h"
#include "WebTypeface.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/Log.h"
#include "core/utils/StrokeUtils.h"
#include "platform/web/WebImageBuffer.h"

using namespace emscripten;
Expand Down
8 changes: 6 additions & 2 deletions src/gpu/OpsCompositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,15 @@ void OpsCompositor::drawShape(std::shared_ptr<Shape> shape, const MCState& state
deviceBounds = shape->isInverseFillType() ? clipBounds : styledShape->getBounds();
}
auto aaType = getAAType(fill);
Fill finalFill = fill;
if (aaType == AAType::Coverage) {
styledShape->convertToHairlineIfNecessary(finalFill);
}
auto shapeProxy =
proxyProvider()->createGPUShapeProxy(styledShape, aaType, clipBounds, renderFlags);
auto drawOp =
ShapeDrawOp::Make(std::move(shapeProxy), fill.color.premultiply(), uvMatrix, aaType);
addDrawOp(std::move(drawOp), clip, fill, localBounds, deviceBounds, drawScale);
ShapeDrawOp::Make(std::move(shapeProxy), finalFill.color.premultiply(), uvMatrix, aaType);
addDrawOp(std::move(drawOp), clip, finalFill, localBounds, deviceBounds, drawScale);
}

void OpsCompositor::discardAll() {
Expand Down
2 changes: 1 addition & 1 deletion src/gpu/RenderContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
#include "core/UserTypeface.h"
#include "core/images/SubsetImage.h"
#include "core/shapes/TextShape.h"
#include "core/utils/ApplyStrokeToBounds.h"
#include "core/utils/MathExtra.h"
#include "core/utils/StrokeUtils.h"
#include "gpu/DrawingManager.h"

namespace tgfx {
Expand Down
18 changes: 13 additions & 5 deletions src/layers/ShapeLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/////////////////////////////////////////////////////////////////////////////////////////////////

#include "tgfx/layers/ShapeLayer.h"
#include "core/utils/StrokeUtils.h"
#include "tgfx/core/Paint.h"
#include "tgfx/core/PathEffect.h"

Expand Down Expand Up @@ -274,8 +275,14 @@ void ShapeLayer::onUpdateContent(LayerRecorder* recorder) {
return;
}
auto fillPaints = createShapePaints(_fillStyles);
auto strokePaints = createShapePaints(_strokeStyles, stroke.isHairline());
auto strokeShape = strokePaints.empty() ? nullptr : createStrokeShape();
std::vector<Paint> strokePaints = {};
std::shared_ptr<Shape> strokeShape = nullptr;
if (!_strokeStyles.empty()) {
bool isHairlineRender = stroke.isHairline() || TreatStrokeAsHairline(stroke, getGlobalMatrix());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

创建图层内容的时候不能通过getGlobalMatrix来判断,图层的矩阵是一直会变化的,而图层内容不会更新。

strokePaints = createShapePaints(_strokeStyles, isHairlineRender);
strokeShape = strokePaints.empty() ? nullptr : createStrokeShape(isHairlineRender);
}

auto canvas = recorder->getCanvas(LayerContentType::Default);
for (auto& paint : fillPaints) {
canvas->drawShape(_shape, paint);
Expand Down Expand Up @@ -305,7 +312,8 @@ std::vector<Paint> ShapeLayer::createShapePaints(
paint.setShader(style->getShader());
if (isHairline) {
paint.setStyle(PaintStyle::Stroke);
paint.setStrokeWidth(0.f);
// may be is treat as hairline, set stroke width to original stroke width
paint.setStrokeWidth(stroke.width);
}
if (!paint.getFill().nothingToDraw()) {
paintList.push_back(paint);
Expand All @@ -314,7 +322,7 @@ std::vector<Paint> ShapeLayer::createShapePaints(
return paintList;
}

std::shared_ptr<Shape> ShapeLayer::createStrokeShape() const {
std::shared_ptr<Shape> ShapeLayer::createStrokeShape(bool isHairline) const {
auto strokeShape = _shape;
if ((_strokeStart != 0 || _strokeEnd != 1)) {
auto pathEffect = PathEffect::MakeTrim(_strokeStart, _strokeEnd);
Expand All @@ -330,7 +338,7 @@ std::shared_ptr<Shape> ShapeLayer::createStrokeShape() const {

strokeShape = Shape::ApplyEffect(std::move(strokeShape), std::move(dash));
}
if (stroke.isHairline()) {
if (isHairline) {
// hairline stroke ignore strokeAlign and don't apply to the shape
return strokeShape;
}
Expand Down
4 changes: 3 additions & 1 deletion test/baseline/version.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"CornerShapeTriple": "eca3cc0d",
"DiscardContent": "4c590832",
"DrawPathProvider": "0e538a2",
"ExtremelyThinStrokePath": "b6ad55b0",
"FillModifier": "fa2f12b1",
"HairLinePath": "b5a8f503",
"HairLineShape": "b5a8f503",
Expand Down Expand Up @@ -178,6 +179,7 @@
"DropShadowStyle-stroke-blur": "f58595d0",
"DropShadowStyle-stroke-blur-behindLayer": "f58595d0",
"DropShadowStyle2": "80c523ee",
"ExtremelyThinStrokeLayer": "a6a9eaf1",
"HairlineLayer": "b5a8f503",
"HasContentChanged_Offset": "bedeb932",
"HasContentChanged_Org": "bedeb932",
Expand Down Expand Up @@ -319,7 +321,7 @@
"complex3": "a6ff60e1",
"complex4": "b5a8f503",
"complex5": "b5a8f503",
"complex6": "c21fb7d7",
"complex6": "b6ad55b0",
"complex7": "c26ff3d9",
"jpg_image": "b1db872",
"mask": "da364e8",
Expand Down
23 changes: 23 additions & 0 deletions test/src/CanvasTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3079,4 +3079,27 @@ TGFX_TEST(CanvasTest, MatrixShapeStroke) {

EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/MatrixShapeStroke"));
}

TGFX_TEST(CanvasTest, ExtremelyThinStrokePath) {
ContextScope scope;
auto context = scope.getContext();
ASSERT_TRUE(context != nullptr);
auto surface = Surface::Make(context, 400, 400);
auto canvas = surface->getCanvas();
canvas->clear(Color::Black());

auto path = SVGPathParser::FromSVGString(
"M1690.5,699.5C1690.5,1113.7136,1164.2136,1449.5,750,1449.5C335.78641,1449.5,0,1113.7136,0,"
"699.5C0,285.28641,335.78641,0,750,0C1164.2136,0,1690.5,285.28641,1690."
"5,699.5Z");
Paint paint;
paint.setStyle(PaintStyle::Stroke);
paint.setStrokeWidth(1.f);
paint.setColor(Color::FromRGBA(255, 255, 0, 255));

canvas->scale(0.2f, 0.2f);
canvas->drawPath(*path, paint);

EXPECT_TRUE(Baseline::Compare(surface, "CanvasTest/ExtremelyThinStrokePath"));
}
} // namespace tgfx
27 changes: 27 additions & 0 deletions test/src/LayerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3067,4 +3067,31 @@ TGFX_TEST(LayerTest, HairlineLayer) {
displayList.render(surface.get());
EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/HairlineLayer"));
}

TGFX_TEST(LayerTest, ExtremelyThinStrokeLayer) {
ContextScope scope;
auto context = scope.getContext();
EXPECT_TRUE(context != nullptr);
auto surface = Surface::Make(context, 200, 200);
auto canvas = surface->getCanvas();
canvas->clear();

Path path2 = {};
path2.addRect(-200, -200, 200, 200);
auto shapeLayer = ShapeLayer::Make();
auto shape = Shape::MakeFrom(path2);
shape = Shape::ApplyEffect(shape, PathEffect::MakeCorner(50));
shapeLayer->setShape(shape);
shapeLayer->setLineWidth(1.0f);
auto strokeStyle = SolidColor::Make(Color::Red());
shapeLayer->setStrokeStyle(strokeStyle);
auto matrix = Matrix::MakeTrans(100, 100);
matrix.preScale(0.4f, 0.4f);
shapeLayer->setMatrix(matrix);

DisplayList displayList;
displayList.root()->addChild(shapeLayer);
displayList.render(surface.get());
EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/ExtremelyThinStrokeLayer"));
}
} // namespace tgfx
Loading