Skip to content

Commit

Permalink
fix: overlapping Polygon cutting & color/translucency mixing (#1901)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaffaKetchup authored Jun 8, 2024
1 parent c321865 commit 0167891
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 9 deletions.
65 changes: 65 additions & 0 deletions example/lib/pages/polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,71 @@ class _PolygonPageState extends State<PolygonPage> {
subtitle: 'This one still works with performant rendering',
),
),
Polygon(
points: const [
LatLng(61.861042, 0.946502),
LatLng(61.861458, 0.949468),
LatLng(61.861427, 0.949626),
LatLng(61.859015, 0.951513),
LatLng(61.858129, 0.952652)
],
holePointsList: [],
color: Colors.lightGreen.withOpacity(0.5),
borderColor: Colors.lightGreen.withOpacity(0.5),
borderStrokeWidth: 10,
hitValue: (
title: 'Testing opacity treatment (small)',
subtitle:
"Holes shouldn't be cut, and colors should be mixed correctly",
),
),
Polygon(
points: const [
LatLng(61.861042, 0.946502),
LatLng(61.861458, 0.949468),
LatLng(61.861427, 0.949626),
LatLng(61.859015, 0.951513),
LatLng(61.858129, 0.952652),
LatLng(61.857633, 0.953214),
LatLng(61.855842, 0.954683),
LatLng(61.855769, 0.954692),
LatLng(61.855679, 0.954565),
LatLng(61.855417, 0.953926),
LatLng(61.855268, 0.953431),
LatLng(61.855173, 0.952443),
LatLng(61.855161, 0.951147),
LatLng(61.855222, 0.950822),
LatLng(61.855928, 0.948422),
LatLng(61.856365, 0.946638),
LatLng(61.856456, 0.946586),
LatLng(61.856787, 0.946656),
LatLng(61.857578, 0.946675),
LatLng(61.859338, 0.946453),
LatLng(61.861042, 0.946502)
],
holePointsList: const [
[
LatLng(61.858881, 0.947234),
LatLng(61.858728, 0.947126),
LatLng(61.858562, 0.947132),
LatLng(61.858458, 0.947192),
LatLng(61.85844, 0.947716),
LatLng(61.858488, 0.947819),
LatLng(61.858766, 0.947818),
LatLng(61.858893, 0.947779),
LatLng(61.858975, 0.947542),
LatLng(61.858881, 0.947234)
]
],
color: Colors.lightGreen.withOpacity(0.5),
borderColor: Colors.lightGreen.withOpacity(0.5),
borderStrokeWidth: 10,
hitValue: (
title: 'Testing opacity treatment (large)',
subtitle:
"Holes shouldn't be cut, and colors should be mixed correctly",
),
),
];
late final _polygons =
Map.fromEntries(_polygonsRaw.map((e) => MapEntry(e.hitValue, e)));
Expand Down
48 changes: 39 additions & 9 deletions lib/src/layer/polygon_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ base class _PolygonPainter<R extends Object>
/// Reference to the bounding box of the [Polygon].
final LatLngBounds bounds;

/// Whether to draw per-polygon labels
/// Whether to draw per-polygon labels ([Polygon.label])
///
/// Note that drawing labels will reduce performance, as the internal
/// canvas must be drawn to and 'saved' more frequently to ensure the proper
/// stacking order is maintained. This can be avoided, potentially at the
/// expense of appearance, by setting [PolygonLayer.drawLabelsLast].
///
/// It is safe to ignore this property, and the performance pitfalls described
/// above, if no [Polygon]s have labels specified.
final bool polygonLabels;

/// Whether to draw labels last and thus over all the polygons
///
/// This may improve performance: see [polygonLabels] for more information.
final bool drawLabelsLast;

/// Create a new [_PolygonPainter] instance.
Expand Down Expand Up @@ -85,6 +95,8 @@ base class _PolygonPainter<R extends Object>

@override
void paint(Canvas canvas, Size size) {
const checkOpacity = true; // for debugging purposes only, should be true

final trianglePoints = <Offset>[];

final filledPath = Path();
Expand Down Expand Up @@ -154,8 +166,12 @@ base class _PolygonPainter<R extends Object>
// The hash is based on the polygons visual properties. If the hash from
// the current and the previous polygon no longer match, we need to flush
// the batch previous polygons.
// We also need to flush if the opacity is not 1 or 0, so that they get
// mixed properly. Otherwise, holes get cut, or colors aren't mixed,
// depending on the holes handler.
final hash = polygon.renderHashCode;
if (lastHash != hash) {
final opacity = polygon.color?.opacity ?? 0;
if (lastHash != hash || (checkOpacity && opacity > 0 && opacity < 1)) {
drawPaths();
}
lastPolygon = polygon;
Expand Down Expand Up @@ -193,13 +209,11 @@ base class _PolygonPainter<R extends Object>
}

// Afterwards deal with more complicated holes.
// Improper handling of opacity and fill methods may result in normal
// polygons cutting holes into other polygons, when they should be mixing:
// https://github.com/fleaflet/flutter_map/issues/1898.
final holePointsList = polygon.holePointsList;
if (holePointsList != null && holePointsList.isNotEmpty) {
// Ideally we'd use `Path.combine(PathOperation.difference, ...)`
// instead of evenOdd fill-type, however it creates visual artifacts
// using the web renderer.
filledPath.fillType = PathFillType.evenOdd;

final holeOffsetsList = List<List<Offset>>.generate(
holePointsList.length,
(i) => getOffsets(camera, origin, holePointsList[i]),
Expand All @@ -208,11 +222,27 @@ base class _PolygonPainter<R extends Object>

for (final holeOffsets in holeOffsetsList) {
filledPath.addPolygon(holeOffsets, true);

// TODO: Potentially more efficient and may change the need to do
// opacity checking - needs testing. However,
// https://github.com/flutter/flutter/issues/44572 prevents this.
/*filledPath = Path.combine(
PathOperation.difference,
filledPath,
Path()..addPolygon(holeOffsets, true),
);*/
}

if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) {
_addHoleBordersToPath(borderPath, polygon, holeOffsetsList, size,
canvas, _getBorderPaint(polygon), polygon.borderStrokeWidth);
_addHoleBordersToPath(
borderPath,
polygon,
holeOffsetsList,
size,
canvas,
_getBorderPaint(polygon),
polygon.borderStrokeWidth,
);
}
}

Expand Down
14 changes: 14 additions & 0 deletions lib/src/layer/polygon_layer/polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class Polygon<R extends Object> {
final List<List<LatLng>>? holePointsList;

/// The fill color of the [Polygon].
///
/// Note that translucent (opacity is not 1 or 0) colors will reduce
/// performance, as the internal canvas must be drawn to and 'saved' more
/// frequently to ensure the colors of overlapping polygons are mixed
/// correctly.
final Color? color;

/// The stroke width of the [Polygon] outline.
Expand Down Expand Up @@ -69,6 +74,11 @@ class Polygon<R extends Object> {
final StrokeJoin strokeJoin;

/// The optional label of the [Polygon].
///
/// Note that specifying a label will reduce performance, as the internal
/// canvas must be drawn to and 'saved' more frequently to ensure the proper
/// stacking order is maintained. This can be avoided, potentially at the
/// expense of appearance, by setting [PolygonLayer.drawLabelsLast].
final String? label;

/// The [TextStyle] of the [Polygon.label].
Expand All @@ -78,6 +88,8 @@ class Polygon<R extends Object> {
///
/// [PolygonLabelPlacement.polylabel] can be expensive for some polygons. If
/// there is a large lag spike, try using [PolygonLabelPlacement.centroid].
///
/// Labels will not be drawn if there is not enough space.
final PolygonLabelPlacement labelPlacement;

/// Whether to rotate the label counter to the camera's rotation, to ensure
Expand Down Expand Up @@ -187,6 +199,8 @@ class Polygon<R extends Object> {
int? _renderHashCode;

/// An optimized hash code dedicated to be used inside the [_PolygonPainter].
///
/// Note that opacity is handled in the painter.
int get renderHashCode => _renderHashCode ??= Object.hash(
color,
borderStrokeWidth,
Expand Down

0 comments on commit 0167891

Please sign in to comment.