Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

Commit 04345ee

Browse files
committed
Port line-dasharray fix from gl-js
* Fix artifact for zero-lenghted dash array Fixes issue mapbox/mapbox-gl-js#9213 and add render test for coverage around 0-length dash arrays Update distance field generation for regular dashes as follows: 1. Compute the dash array ranges and stretch location along the distance field 2. Collapse any 0-length distance field range 3. Collapse neighbouring same-type parts into single part * combine consecutive dashes and parts mapbox/mapbox-gl-js#9246 #16181
1 parent d3535f1 commit 04345ee

File tree

2 files changed

+115
-55
lines changed

2 files changed

+115
-55
lines changed

src/mbgl/geometry/line_atlas.cpp

+108-55
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,112 @@ size_t getDashPatternHash(const std::vector<float>& dasharray, const LinePattern
2020
return key;
2121
}
2222

23+
std::vector<DashRange> getDashRanges(const std::vector<float>& dasharray, float stretch) {
24+
// If dasharray has an odd length, both the first and last parts
25+
// are dashes and should be joined seamlessly.
26+
const bool oddDashArray = dasharray.size() % 2 == 1;
27+
28+
float left = oddDashArray ? -dasharray.back() * stretch : 0.0f;
29+
float right = dasharray.front() * stretch;
30+
bool isDash = true;
31+
32+
std::vector<DashRange> ranges;
33+
ranges.push_back({left, right, isDash, dasharray.front() == 0.0f});
34+
35+
float currentDashLength = dasharray.front();
36+
for (size_t i = 1; i < dasharray.size(); ++i) {
37+
isDash = !isDash;
38+
39+
const float dashLength = dasharray[i];
40+
left = currentDashLength * stretch;
41+
currentDashLength += dashLength;
42+
right = currentDashLength * stretch;
43+
44+
ranges.push_back({left, right, isDash, dashLength == 0.0f});
45+
}
46+
47+
return ranges;
48+
}
49+
50+
void addRoundDash(const std::vector<DashRange>& ranges, const int32_t yOffset, float stretch, const int n, AlphaImage& image) {
51+
const float halfStretch = stretch * 0.5f;
52+
53+
for (int y = -n; y <= n; y++) {
54+
int row = yOffset + n + y;
55+
int index = image.size.width * row;
56+
int currIndex = 0;
57+
DashRange range = ranges[currIndex];
58+
59+
for (uint32_t x = 0; x < image.size.width; x++) {
60+
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }
61+
62+
float distLeft = fabsf(x - range.left);
63+
float distRight = fabsf(x - range.right);
64+
float minDist = fminf(distLeft, distRight);
65+
float signedDistance;
66+
67+
float distMiddle = static_cast<float>(y) / n * (halfStretch + 1.0f);
68+
if (range.isDash) {
69+
float distEdge = halfStretch - fabsf(distMiddle);
70+
signedDistance = sqrtf(minDist * minDist + distEdge * distEdge);
71+
} else {
72+
signedDistance = halfStretch - sqrtf(minDist * minDist + distMiddle * distMiddle);
73+
}
74+
75+
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
76+
}
77+
}
78+
}
79+
80+
void addRegularDash(std::vector<DashRange>& ranges, const int32_t yOffset, AlphaImage& image) {
81+
// Collapse any zero-length range
82+
for (auto it = ranges.begin(); it != ranges.end();) {
83+
if (it->isZeroLength) {
84+
ranges.erase(it);
85+
} else { ++it; }
86+
}
87+
88+
if (ranges.size() >= 2) {
89+
// Collapse neighbouring same-type parts into a single part
90+
for (auto curr = ranges.begin(), next = ranges.begin() + 1; curr != ranges.end() && next != ranges.end();) {
91+
if (next->isDash == curr->isDash) {
92+
next->left = curr->left;
93+
ranges.erase(curr);
94+
} else {
95+
++curr;
96+
++next;
97+
}
98+
}
99+
}
100+
101+
DashRange& first = ranges.front();
102+
DashRange& last = ranges.back();
103+
if (first.isDash == last.isDash) {
104+
first.left = last.left - image.size.width;
105+
last.right = first.right + image.size.width;
106+
}
107+
108+
int index = image.size.width * yOffset;
109+
int currIndex = 0;
110+
DashRange range = ranges[currIndex];
111+
112+
for (uint32_t x = 0; x < image.size.width; ++x) {
113+
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }
114+
115+
float distLeft = fabsf(x - range.left);
116+
float distRight = fabsf(x - range.right);
117+
float minDist = fminf(distLeft, distRight);
118+
float signedDistance = range.isDash ? minDist : -minDist;
119+
120+
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
121+
}
122+
}
123+
23124
LinePatternPos addDashPattern(AlphaImage& image,
24125
const int32_t yOffset,
25126
const std::vector<float>& dasharray,
26127
const LinePatternCap patternCap) {
27128
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
28-
constexpr const uint8_t offset = 128;
29129

30130
if (dasharray.size() < 2) {
31131
Log::Warning(Event::ParseStyle, "line dasharray requires at least two elements");
@@ -38,64 +138,17 @@ LinePatternPos addDashPattern(AlphaImage& image,
38138
}
39139

40140
float stretch = image.size.width / length;
41-
float halfWidth = stretch * 0.5;
42-
// If dasharray has an odd length, both the first and last parts
43-
// are dashes and should be joined seamlessly.
44-
bool oddLength = dasharray.size() % 2 == 1;
141+
std::vector<DashRange> ranges = getDashRanges(dasharray, stretch);
45142

46-
for (int y = -n; y <= n; y++) {
47-
int row = yOffset + n + y;
48-
int index = image.size.width * row;
49-
50-
float left = 0;
51-
float right = dasharray[0];
52-
unsigned int partIndex = 1;
53-
54-
if (oddLength) {
55-
left -= dasharray.back();
56-
}
57-
58-
for (uint32_t x = 0; x < image.size.width; x++) {
59-
while (right < x / stretch) {
60-
left = right;
61-
if (partIndex >= dasharray.size()) {
62-
return LinePatternPos();
63-
}
64-
right = right + dasharray[partIndex];
65-
66-
if (oddLength && partIndex == dasharray.size() - 1) {
67-
right += dasharray.front();
68-
}
69-
70-
partIndex++;
71-
}
72-
73-
float distLeft = fabs(x - left * stretch);
74-
float distRight = fabs(x - right * stretch);
75-
float dist = fmin(distLeft, distRight);
76-
bool inside = (partIndex % 2) == 1;
77-
int signedDistance;
78-
79-
if (patternCap == LinePatternCap::Round) {
80-
float distMiddle = n ? (float)y / n * (halfWidth + 1) : 0;
81-
if (inside) {
82-
float distEdge = halfWidth - fabs(distMiddle);
83-
signedDistance = sqrt(dist * dist + distEdge * distEdge);
84-
} else {
85-
signedDistance = halfWidth - sqrt(dist * dist + distMiddle * distMiddle);
86-
}
87-
88-
} else {
89-
signedDistance = int((inside ? 1 : -1) * dist);
90-
}
91-
92-
image.data[index + x] = fmax(0, fmin(255, signedDistance + offset));
93-
}
143+
if (patternCap == LinePatternCap::Round) {
144+
addRoundDash(ranges, yOffset, stretch, n, image);
145+
} else {
146+
addRegularDash(ranges, yOffset, image);
94147
}
95148

96149
LinePatternPos position;
97-
position.y = (0.5 + yOffset + n) / image.size.height;
98-
position.height = (2.0 * n + 1) / image.size.height;
150+
position.y = (0.5f + yOffset + n) / image.size.height;
151+
position.height = (2.0f * n + 1) / image.size.height;
99152
position.width = length;
100153

101154
return position;

src/mbgl/geometry/line_atlas.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ enum class LinePatternCap : bool {
2727
Round = true,
2828
};
2929

30+
struct DashRange {
31+
float left;
32+
float right;
33+
bool isDash;
34+
bool isZeroLength;
35+
};
36+
3037
class DashPatternTexture {
3138
public:
3239
DashPatternTexture(const std::vector<float>& from, const std::vector<float>& to, LinePatternCap);

0 commit comments

Comments
 (0)