diff --git a/include/tgfx/layers/ShapeLayer.h b/include/tgfx/layers/ShapeLayer.h index 23e63134..008aea3e 100644 --- a/include/tgfx/layers/ShapeLayer.h +++ b/include/tgfx/layers/ShapeLayer.h @@ -24,6 +24,24 @@ #include "tgfx/layers/ShapeStyle.h" namespace tgfx { +/** + * The alignment of the stroke concerning the boundaries of the shape. + */ +enum class StrokeAlign { + /** + * Draw a stroke centered along the shape boundary. + */ + Center, + /** + * Draw a stroke inside the shape boundary. + */ + Inside, + /** + * Draw a stroke outside the shape boundary. + */ + Outside +}; + /** * ShapeLayer represents a layer that draws a shape. */ @@ -207,6 +225,18 @@ class ShapeLayer : public Layer { */ void setStrokeEnd(float end); + /** + * Returns the stroke alignment applied to the shape’s path when stroked. The default stroke alignment is Center. + */ + StrokeAlign strokeAlign() const { + return _strokeAlign; + } + + /** + * Sets the stroke alignment applied to the shape’s path when stroked. + */ + void setStrokeAlign(StrokeAlign align); + ~ShapeLayer() override; protected: @@ -223,5 +253,6 @@ class ShapeLayer : public Layer { float _lineDashPhase = 0.0f; float _strokeStart = 0.0f; float _strokeEnd = 1.0f; + StrokeAlign _strokeAlign = StrokeAlign::Center; }; } // namespace tgfx diff --git a/src/layers/ShapeLayer.cpp b/src/layers/ShapeLayer.cpp index dee8b73a..1455853a 100644 --- a/src/layers/ShapeLayer.cpp +++ b/src/layers/ShapeLayer.cpp @@ -153,6 +153,14 @@ void ShapeLayer::setStrokeEnd(float end) { invalidateContent(); } +void ShapeLayer::setStrokeAlign(StrokeAlign align) { + if (_strokeAlign == align) { + return; + } + _strokeAlign = align; + invalidateContent(); +} + ShapeLayer::~ShapeLayer() { detachProperty(_strokeStyle.get()); detachProperty(_fillStyle.get()); @@ -182,7 +190,18 @@ std::unique_ptr ShapeLayer::onUpdateContent() { PathEffect::MakeDash(dashes.data(), static_cast(dashes.size()), _lineDashPhase); strokeShape = Shape::ApplyEffect(std::move(strokeShape), std::move(pathEffect)); } - strokeShape = Shape::ApplyStroke(std::move(strokeShape), &stroke); + if (_strokeAlign != StrokeAlign::Center) { + auto terminalStroke = stroke; + terminalStroke.width *= 2; + strokeShape = Shape::ApplyStroke(std::move(strokeShape), &terminalStroke); + if (_strokeAlign == StrokeAlign::Inside) { + strokeShape = Shape::Merge(std::move(strokeShape), _shape, PathOp::Intersect); + } else { + strokeShape = Shape::Merge(std::move(strokeShape), _shape, PathOp::Difference); + } + } else { + strokeShape = Shape::ApplyStroke(std::move(strokeShape), &stroke); + } auto content = std::make_unique(std::move(strokeShape), _strokeStyle->getShader()); contents.push_back(std::move(content)); diff --git a/test/baseline/version.json b/test/baseline/version.json index 652ee6ff..d2a97c6b 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -83,7 +83,7 @@ "Layer_hitTestPointNested": "0ee494a", "ModeColorFilter": "43cd416", "PassThoughAndNormal": "43cd416", - "draw_shape": "d069eb4", + "draw_shape": "738fc92", "draw_solid": "b4a1231", "draw_text": "612c09e", "dropShadow": "43cd416", diff --git a/test/src/LayerTest.cpp b/test/src/LayerTest.cpp index 0fc74b9d..7b417f44 100644 --- a/test/src/LayerTest.cpp +++ b/test/src/LayerTest.cpp @@ -441,35 +441,45 @@ TGFX_TEST(LayerTest, shapeLayer) { auto device = DevicePool::Make(); ASSERT_TRUE(device != nullptr); auto context = device->lockContext(); - auto surface = Surface::Make(context, 200, 100); + auto surface = Surface::Make(context, 200, 300); auto displayList = std::make_unique(); auto layer = Layer::Make(); displayList->root()->addChild(layer); - auto shaperLayer = ShapeLayer::Make(); - auto rect = Rect::MakeXYWH(10, 10, 150, 80); - Path path = {}; - path.addRect(rect); - shaperLayer->setPath(path); - auto filleStyle = Gradient::MakeLinear({10, 10}, {150, 80}); - filleStyle->setColors({{0.f, 0.f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}); - shaperLayer->setFillStyle(filleStyle); - // stroke style - shaperLayer->setLineWidth(10.0f); - shaperLayer->setLineCap(LineCap::Butt); - shaperLayer->setLineJoin(LineJoin::Miter); - shaperLayer->setMiterLimit(2.0f); - auto strokeStyle = SolidColor::Make(Color::Red()); - shaperLayer->setStrokeStyle(strokeStyle); - std::vector dashPattern = {10.0f, 10.0f}; - shaperLayer->setLineDashPattern(dashPattern); - shaperLayer->setLineDashPhase(0.0f); - - layer->addChild(shaperLayer); - auto shapeLayerRect = shaperLayer->getBounds(); - auto bounds = Rect::MakeXYWH(5, 5, 160, 90); - - EXPECT_EQ(shapeLayerRect, bounds); - + for (int i = 0; i < 3; i++) { + auto shaperLayer = ShapeLayer::Make(); + auto rect = Rect::MakeXYWH(10, 10 + 100 * i, 140, 80); + Path path = {}; + path.addRect(rect); + shaperLayer->setPath(path); + auto filleStyle = Gradient::MakeLinear({rect.left, rect.top}, {rect.right, rect.bottom}); + filleStyle->setColors({{0.f, 0.f, 1.f, 1.f}, {0.f, 1.f, 0.f, 1.f}}); + shaperLayer->setFillStyle(filleStyle); + // stroke style + shaperLayer->setLineWidth(10.0f); + shaperLayer->setLineCap(LineCap::Butt); + shaperLayer->setLineJoin(LineJoin::Miter); + auto strokeStyle = SolidColor::Make(Color::Red()); + shaperLayer->setStrokeStyle(strokeStyle); + std::vector dashPattern = {10.0f, 10.0f}; + shaperLayer->setLineDashPattern(dashPattern); + shaperLayer->setLineDashPhase(5.0f); + shaperLayer->setStrokeAlign(static_cast(i)); + layer->addChild(shaperLayer); + auto shapeLayerRect = shaperLayer->getBounds(); + switch (i) { + case 0: + EXPECT_EQ(shapeLayerRect, Rect::MakeLTRB(5, 5, 155, 95)); + break; + case 1: + EXPECT_EQ(shapeLayerRect, Rect::MakeLTRB(0, 100, 160, 200)); + break; + case 2: + EXPECT_EQ(shapeLayerRect, Rect::MakeLTRB(0, 200, 160, 300)); + break; + default: + break; + } + } displayList->render(surface.get()); context->submit(); EXPECT_TRUE(Baseline::Compare(surface, "LayerTest/draw_shape"));