From e4a19e505d840ce8c175209e490458bc7e17a0b0 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Tue, 7 May 2024 16:19:55 +0200 Subject: [PATCH 1/2] feat: optimization of "solid" polygon/polyline display Impacted files: * `polygon_layer/painter.dart`: minor refactoring * `polyline_layer/painter.dart`: minor refactoring * `pixel_hiker.dart`: optimized the "solid pixel hiker" relying on `addPolygon` instead of numerous `moveTo`/`lineTo` --- .../layer/misc/line_patterns/pixel_hiker.dart | 37 ++++++++++++++----- lib/src/layer/polygon_layer/painter.dart | 5 +-- lib/src/layer/polyline_layer/painter.dart | 7 +--- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/src/layer/misc/line_patterns/pixel_hiker.dart b/lib/src/layer/misc/line_patterns/pixel_hiker.dart index f0c7baa4c..395b7f34b 100644 --- a/lib/src/layer/misc/line_patterns/pixel_hiker.dart +++ b/lib/src/layer/misc/line_patterns/pixel_hiker.dart @@ -250,12 +250,24 @@ class SolidPixelHiker extends _PixelHiker { patternFit: PatternFit.none, ); - /// Returns all visible segments. - List getAllVisibleSegments() { - final List result = []; - + /// Adds all visible segments to [paths]. + void addAllVisibleSegments(final List paths) { if (offsets.length < 2) { - return result; + return; + } + + double? latestX; + double? latestY; + List polygons = []; + + void addPolygons() { + if (polygons.isEmpty) { + return; + } + for (final path in paths) { + path.addPolygon(polygons, false); + } + polygons = []; } for (int i = 0; i < offsets.length - 1 + (closePath ? 1 : 0); i++) { @@ -264,12 +276,19 @@ class SolidPixelHiker extends _PixelHiker { offsets[(i + 1) % offsets.length], canvasSize, ); - if (visibleSegment != null) { - result.add(visibleSegment); + if (visibleSegment == null) { + continue; } + if (latestX != visibleSegment.begin.dx || + latestY != visibleSegment.begin.dy) { + addPolygons(); + polygons.add(visibleSegment.begin); + } + polygons.add(visibleSegment.end); + latestX = visibleSegment.end.dx; + latestY = visibleSegment.end.dy; } - - return result; + addPolygons(); } @override diff --git a/lib/src/layer/polygon_layer/painter.dart b/lib/src/layer/polygon_layer/painter.dart index c665a6bee..162838659 100644 --- a/lib/src/layer/polygon_layer/painter.dart +++ b/lib/src/layer/polygon_layer/painter.dart @@ -324,10 +324,7 @@ class _PolygonPainter extends CustomPainter { closePath: true, canvasSize: canvasSize, ); - for (final visibleSegment in hiker.getAllVisibleSegments()) { - path.moveTo(visibleSegment.begin.dx, visibleSegment.begin.dy); - path.lineTo(visibleSegment.end.dx, visibleSegment.end.dy); - } + hiker.addAllVisibleSegments([path]); } else if (isDotted) { final DottedPixelHiker hiker = DottedPixelHiker( offsets: offsets, diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index 58f5e7bac..49ad003d9 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -214,12 +214,7 @@ class _PolylinePainter extends CustomPainter { closePath: false, canvasSize: size, ); - for (final visibleSegment in hiker.getAllVisibleSegments()) { - for (final path in paths) { - path.moveTo(visibleSegment.begin.dx, visibleSegment.begin.dy); - path.lineTo(visibleSegment.end.dx, visibleSegment.end.dy); - } - } + hiker.addAllVisibleSegments(paths); } else if (isDotted) { final DottedPixelHiker hiker = DottedPixelHiker( offsets: offsets, From 82617b58ddd7e94718138d7092a62517a4efd20f Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Mon, 20 May 2024 09:33:28 +0200 Subject: [PATCH 2/2] fix - added the `strokeWidth` parameter --- .../layer/misc/line_patterns/pixel_hiker.dart | 19 ++++++++++------- .../misc/line_patterns/visible_segment.dart | 21 ++++++++++++------- lib/src/layer/polygon_layer/painter.dart | 9 +++++++- lib/src/layer/polyline_layer/painter.dart | 8 +++++++ 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/src/layer/misc/line_patterns/pixel_hiker.dart b/lib/src/layer/misc/line_patterns/pixel_hiker.dart index 888127ace..46b6902ff 100644 --- a/lib/src/layer/misc/line_patterns/pixel_hiker.dart +++ b/lib/src/layer/misc/line_patterns/pixel_hiker.dart @@ -13,6 +13,7 @@ class DottedPixelHiker extends _PixelHiker { required super.closePath, required super.canvasSize, required super.patternFit, + required super.strokeWidth, required double stepLength, }) : super(segmentValues: [stepLength]); @@ -25,7 +26,7 @@ class DottedPixelHiker extends _PixelHiker { } void addVisibleOffset(final Offset offset) { - if (VisibleSegment.isVisible(offset, canvasSize)) { + if (VisibleSegment.isVisible(offset, canvasSize, strokeWidth)) { result.add(offset); } } @@ -69,8 +70,8 @@ class DottedPixelHiker extends _PixelHiker { /// /// Most important method of the class. List? _getVisibleDotList(Offset offset0, Offset offset1) { - final VisibleSegment? visibleSegment = - VisibleSegment.getVisibleSegment(offset0, offset1, canvasSize); + final VisibleSegment? visibleSegment = VisibleSegment.getVisibleSegment( + offset0, offset1, canvasSize, strokeWidth); if (visibleSegment == null) { addDistance(getDistance(offset0, offset1)); return null; @@ -131,6 +132,7 @@ class DashedPixelHiker extends _PixelHiker { required super.canvasSize, required super.segmentValues, required super.patternFit, + required super.strokeWidth, }); /// Returns all visible segments. @@ -155,7 +157,7 @@ class DashedPixelHiker extends _PixelHiker { if (_segmentIndex.isOdd) { if (patternFit == PatternFit.appendDot) { if (!closePath) { - if (VisibleSegment.isVisible(offsets.last, canvasSize)) { + if (VisibleSegment.isVisible(offsets.last, canvasSize, strokeWidth)) { result.add(VisibleSegment(offsets.last, offsets.last)); } } @@ -187,10 +189,7 @@ class DashedPixelHiker extends _PixelHiker { final Offset offset1, ) { final VisibleSegment? visibleSegment = VisibleSegment.getVisibleSegment( - offset0, - offset1, - canvasSize, - ); + offset0, offset1, canvasSize, strokeWidth); if (visibleSegment == null) { addDistance(getDistance(offset0, offset1)); return null; @@ -257,6 +256,7 @@ class SolidPixelHiker extends _PixelHiker { required super.offsets, required super.closePath, required super.canvasSize, + required super.strokeWidth, }) : super( segmentValues: [], patternFit: PatternFit.none, @@ -287,6 +287,7 @@ class SolidPixelHiker extends _PixelHiker { offsets[i], offsets[(i + 1) % offsets.length], canvasSize, + strokeWidth, ); if (visibleSegment == null) { continue; @@ -315,6 +316,7 @@ sealed class _PixelHiker { required this.closePath, required this.canvasSize, required this.patternFit, + required this.strokeWidth, }) { _polylinePixelDistance = _getPolylinePixelDistance(); _init(); @@ -333,6 +335,7 @@ sealed class _PixelHiker { final List segmentValues; final Size canvasSize; final PatternFit patternFit; + final double strokeWidth; /// Factor to be used on offset distances. late final double _factor; diff --git a/lib/src/layer/misc/line_patterns/visible_segment.dart b/lib/src/layer/misc/line_patterns/visible_segment.dart index 6c255f36a..e4c69e1cf 100644 --- a/lib/src/layer/misc/line_patterns/visible_segment.dart +++ b/lib/src/layer/misc/line_patterns/visible_segment.dart @@ -39,23 +39,28 @@ class VisibleSegment { return code; } - /// Returns true if the [offset] is inside the [canvasSize]. - static bool isVisible(Offset offset, Size canvasSize) => + /// Returns true if the [offset] is inside the [canvasSize] + [strokeWidth]. + static bool isVisible(Offset offset, Size canvasSize, double strokeWidth) => _computeOutCode( - offset.dx, offset.dy, 0, 0, canvasSize.width, canvasSize.height) == + offset.dx, + offset.dy, + -strokeWidth / 2, + -strokeWidth / 2, + canvasSize.width + strokeWidth / 2, + canvasSize.height + strokeWidth / 2) == _inside; /// Clips a line segment to a rectangular area (canvas). /// /// Returns null if the segment is invisible. static VisibleSegment? getVisibleSegment( - Offset p0, Offset p1, Size canvasSize) { + Offset p0, Offset p1, Size canvasSize, double strokeWidth) { // Function to compute the outCode for a point relative to the canvas - const double xMin = 0; - const double yMin = 0; - final double xMax = canvasSize.width; - final double yMax = canvasSize.height; + final double xMin = -strokeWidth / 2; + final double yMin = -strokeWidth / 2; + final double xMax = canvasSize.width + strokeWidth / 2; + final double yMax = canvasSize.height + strokeWidth / 2; double x0 = p0.dx; double y0 = p0.dy; diff --git a/lib/src/layer/polygon_layer/painter.dart b/lib/src/layer/polygon_layer/painter.dart index 162838659..880daae99 100644 --- a/lib/src/layer/polygon_layer/painter.dart +++ b/lib/src/layer/polygon_layer/painter.dart @@ -215,6 +215,7 @@ class _PolygonPainter extends CustomPainter { size, canvas, _getBorderPaint(polygon), + polygon.borderStrokeWidth, ); } @@ -238,7 +239,7 @@ class _PolygonPainter extends CustomPainter { if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) { _addHoleBordersToPath(borderPath, polygon, holeOffsetsList, size, - canvas, _getBorderPaint(polygon)); + canvas, _getBorderPaint(polygon), polygon.borderStrokeWidth); } } @@ -314,6 +315,7 @@ class _PolygonPainter extends CustomPainter { Size canvasSize, Canvas canvas, Paint paint, + double strokeWidth, ) { final isSolid = polygon.pattern == const StrokePattern.solid(); final isDashed = polygon.pattern.segments != null; @@ -323,6 +325,7 @@ class _PolygonPainter extends CustomPainter { offsets: offsets, closePath: true, canvasSize: canvasSize, + strokeWidth: strokeWidth, ); hiker.addAllVisibleSegments([path]); } else if (isDotted) { @@ -332,6 +335,7 @@ class _PolygonPainter extends CustomPainter { patternFit: polygon.pattern.patternFit!, closePath: true, canvasSize: canvasSize, + strokeWidth: strokeWidth, ); for (final visibleDot in hiker.getAllVisibleDots()) { canvas.drawCircle(visibleDot, polygon.borderStrokeWidth / 2, paint); @@ -343,6 +347,7 @@ class _PolygonPainter extends CustomPainter { patternFit: polygon.pattern.patternFit!, closePath: true, canvasSize: canvasSize, + strokeWidth: strokeWidth, ); for (final visibleSegment in hiker.getAllVisibleSegments()) { @@ -359,6 +364,7 @@ class _PolygonPainter extends CustomPainter { Size canvasSize, Canvas canvas, Paint paint, + double strokeWidth, ) { for (final offsets in holeOffsetsList) { _addBorderToPath( @@ -368,6 +374,7 @@ class _PolygonPainter extends CustomPainter { canvasSize, canvas, paint, + strokeWidth, ); } } diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index 49ad003d9..db4c9ca52 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -147,6 +147,9 @@ class _PolylinePainter extends CustomPainter { needsLayerSaving = polyline.color.opacity < 1.0 || (polyline.gradientColors?.any((c) => c.opacity < 1.0) ?? false); + // strokeWidth, or strokeWidth + borderWidth if relevant. + late double largestStrokeWidth; + late final double strokeWidth; if (polyline.useStrokeWidthInMeter) { strokeWidth = _metersToStrokeWidth( @@ -158,6 +161,7 @@ class _PolylinePainter extends CustomPainter { } else { strokeWidth = polyline.strokeWidth; } + largestStrokeWidth = strokeWidth; final isSolid = polyline.pattern == const StrokePattern.solid(); final isDashed = polyline.pattern.segments != null; @@ -182,6 +186,7 @@ class _PolylinePainter extends CustomPainter { // Outlined lines are drawn by drawing a thicker path underneath, then // stenciling the middle (in case the line fill is transparent), and // finally drawing the line fill. + largestStrokeWidth = strokeWidth + polyline.borderStrokeWidth; borderPaint = Paint() ..color = polyline.borderColor ..strokeWidth = strokeWidth + polyline.borderStrokeWidth @@ -213,6 +218,7 @@ class _PolylinePainter extends CustomPainter { offsets: offsets, closePath: false, canvasSize: size, + strokeWidth: largestStrokeWidth, ); hiker.addAllVisibleSegments(paths); } else if (isDotted) { @@ -222,6 +228,7 @@ class _PolylinePainter extends CustomPainter { patternFit: polyline.pattern.patternFit!, closePath: false, canvasSize: size, + strokeWidth: largestStrokeWidth, ); final List radii = []; @@ -244,6 +251,7 @@ class _PolylinePainter extends CustomPainter { patternFit: polyline.pattern.patternFit!, closePath: false, canvasSize: size, + strokeWidth: largestStrokeWidth, ); for (final visibleSegment in hiker.getAllVisibleSegments()) {