1 | |
2 | #include "MSDFErrorCorrection.h" |
3 | |
4 | #include <cstring> |
5 | #include "arithmetics.hpp" |
6 | #include "equation-solver.h" |
7 | #include "EdgeColor.h" |
8 | #include "bitmap-interpolation.hpp" |
9 | #include "edge-selectors.h" |
10 | #include "contour-combiners.h" |
11 | #include "ShapeDistanceFinder.h" |
12 | #include "generator-config.h" |
13 | |
14 | namespace msdfgen { |
15 | |
16 | #define ARTIFACT_T_EPSILON .01 |
17 | #define PROTECTION_RADIUS_TOLERANCE 1.001 |
18 | |
19 | #define CLASSIFIER_FLAG_CANDIDATE 0x01 |
20 | #define CLASSIFIER_FLAG_ARTIFACT 0x02 |
21 | |
22 | const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111; |
23 | const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111; |
24 | |
25 | /// The base artifact classifier recognizes artifacts based on the contents of the SDF alone. |
26 | class BaseArtifactClassifier { |
27 | public: |
28 | inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { } |
29 | /// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact. |
30 | inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const { |
31 | // For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries. |
32 | if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) { |
33 | double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span; |
34 | // Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b. |
35 | if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)) |
36 | return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT; |
37 | return CLASSIFIER_FLAG_CANDIDATE; |
38 | } |
39 | return 0; |
40 | } |
41 | /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact. |
42 | inline bool evaluate(double t, float m, int flags) const { |
43 | return (flags&2) != 0; |
44 | } |
45 | private: |
46 | double span; |
47 | bool protectedFlag; |
48 | }; |
49 | |
50 | /// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost. |
51 | template <template <typename> class ContourCombiner, int N> |
52 | class ShapeDistanceChecker { |
53 | public: |
54 | class ArtifactClassifier : public BaseArtifactClassifier { |
55 | public: |
56 | inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { } |
57 | /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact. |
58 | inline bool evaluate(double t, float m, int flags) const { |
59 | if (flags&CLASSIFIER_FLAG_CANDIDATE) { |
60 | // Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier. |
61 | if (flags&CLASSIFIER_FLAG_ARTIFACT) |
62 | return true; |
63 | Vector2 tVector = t*direction; |
64 | float oldMSD[N], newMSD[3]; |
65 | // Compute the color that would be currently interpolated at the artifact candidate's position. |
66 | Point2 sdfCoord = parent->sdfCoord+tVector; |
67 | interpolate(oldMSD, parent->sdf, sdfCoord); |
68 | // Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel. |
69 | double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y)); |
70 | float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]); |
71 | newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0])); |
72 | newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1])); |
73 | newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2])); |
74 | // Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance. |
75 | float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]); |
76 | float newPSD = median(newMSD[0], newMSD[1], newMSD[2]); |
77 | float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5); |
78 | // Compare the differences of the exact distance and the before and after distances. |
79 | return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD)); |
80 | } |
81 | return false; |
82 | } |
83 | private: |
84 | ShapeDistanceChecker *parent; |
85 | Vector2 direction; |
86 | }; |
87 | Point2 shapeCoord, sdfCoord; |
88 | const float *msd; |
89 | bool protectedFlag; |
90 | inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) { |
91 | texelSize = projection.unprojectVector(Vector2(1)); |
92 | } |
93 | inline ArtifactClassifier classifier(const Vector2 &direction, double span) { |
94 | return ArtifactClassifier(this, direction, span); |
95 | } |
96 | private: |
97 | ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder; |
98 | BitmapConstRef<float, N> sdf; |
99 | double invRange; |
100 | Vector2 texelSize; |
101 | double minImproveRatio; |
102 | }; |
103 | |
104 | MSDFErrorCorrection::MSDFErrorCorrection() { } |
105 | |
106 | MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) { |
107 | invRange = 1/range; |
108 | minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio; |
109 | minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio; |
110 | memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height); |
111 | } |
112 | |
113 | void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) { |
114 | this->minDeviationRatio = minDeviationRatio; |
115 | } |
116 | |
117 | void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) { |
118 | this->minImproveRatio = minImproveRatio; |
119 | } |
120 | |
121 | void MSDFErrorCorrection::protectCorners(const Shape &shape) { |
122 | for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) |
123 | if (!contour->edges.empty()) { |
124 | const EdgeSegment *prevEdge = contour->edges.back(); |
125 | for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { |
126 | int commonColor = prevEdge->color&(*edge)->color; |
127 | // If the color changes from prevEdge to edge, this is a corner. |
128 | if (!(commonColor&(commonColor-1))) { |
129 | // Find the four texels that envelop the corner and mark them as protected. |
130 | Point2 p = projection.project((*edge)->point(0)); |
131 | if (shape.inverseYAxis) |
132 | p.y = stencil.height-p.y; |
133 | int l = (int) floor(p.x-.5); |
134 | int b = (int) floor(p.y-.5); |
135 | int r = l+1; |
136 | int t = b+1; |
137 | // Check that the positions are within bounds. |
138 | if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) { |
139 | if (l >= 0 && b >= 0) |
140 | *stencil(l, b) |= (byte) PROTECTED; |
141 | if (r < stencil.width && b >= 0) |
142 | *stencil(r, b) |= (byte) PROTECTED; |
143 | if (l >= 0 && t < stencil.height) |
144 | *stencil(l, t) |= (byte) PROTECTED; |
145 | if (r < stencil.width && t < stencil.height) |
146 | *stencil(r, t) |= (byte) PROTECTED; |
147 | } |
148 | } |
149 | prevEdge = *edge; |
150 | } |
151 | } |
152 | } |
153 | |
154 | /// Determines if the channel contributes to an edge between the two texels a, b. |
155 | static bool edgeBetweenTexelsChannel(const float *a, const float *b, int channel) { |
156 | // Find interpolation ratio t (0 < t < 1) where an edge is expected (mix(a[channel], b[channel], t) == 0.5). |
157 | double t = (a[channel]-.5)/(a[channel]-b[channel]); |
158 | if (t > 0 && t < 1) { |
159 | // Interpolate channel values at t. |
160 | float c[3] = { |
161 | mix(a[0], b[0], t), |
162 | mix(a[1], b[1], t), |
163 | mix(a[2], b[2], t) |
164 | }; |
165 | // This is only an edge if the zero-distance channel is the median. |
166 | return median(c[0], c[1], c[2]) == c[channel]; |
167 | } |
168 | return false; |
169 | } |
170 | |
171 | /// Returns a bit mask of which channels contribute to an edge between the two texels a, b. |
172 | static int edgeBetweenTexels(const float *a, const float *b) { |
173 | return ( |
174 | RED*edgeBetweenTexelsChannel(a, b, 0)+ |
175 | GREEN*edgeBetweenTexelsChannel(a, b, 1)+ |
176 | BLUE*edgeBetweenTexelsChannel(a, b, 2) |
177 | ); |
178 | } |
179 | |
180 | /// Marks texel as protected if one of its non-median channels is present in the channel mask. |
181 | static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) { |
182 | if ( |
183 | (mask&RED && msd[0] != m) || |
184 | (mask&GREEN && msd[1] != m) || |
185 | (mask&BLUE && msd[2] != m) |
186 | ) |
187 | *stencil |= (byte) MSDFErrorCorrection::PROTECTED; |
188 | } |
189 | |
190 | template <int N> |
191 | void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) { |
192 | float radius; |
193 | // Horizontal texel pairs |
194 | radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length()); |
195 | for (int y = 0; y < sdf.height; ++y) { |
196 | const float *left = sdf(0, y); |
197 | const float *right = sdf(1, y); |
198 | for (int x = 0; x < sdf.width-1; ++x) { |
199 | float lm = median(left[0], left[1], left[2]); |
200 | float rm = median(right[0], right[1], right[2]); |
201 | if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) { |
202 | int mask = edgeBetweenTexels(left, right); |
203 | protectExtremeChannels(stencil(x, y), left, lm, mask); |
204 | protectExtremeChannels(stencil(x+1, y), right, rm, mask); |
205 | } |
206 | left += N, right += N; |
207 | } |
208 | } |
209 | // Vertical texel pairs |
210 | radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length()); |
211 | for (int y = 0; y < sdf.height-1; ++y) { |
212 | const float *bottom = sdf(0, y); |
213 | const float *top = sdf(0, y+1); |
214 | for (int x = 0; x < sdf.width; ++x) { |
215 | float bm = median(bottom[0], bottom[1], bottom[2]); |
216 | float tm = median(top[0], top[1], top[2]); |
217 | if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) { |
218 | int mask = edgeBetweenTexels(bottom, top); |
219 | protectExtremeChannels(stencil(x, y), bottom, bm, mask); |
220 | protectExtremeChannels(stencil(x, y+1), top, tm, mask); |
221 | } |
222 | bottom += N, top += N; |
223 | } |
224 | } |
225 | // Diagonal texel pairs |
226 | radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length()); |
227 | for (int y = 0; y < sdf.height-1; ++y) { |
228 | const float *lb = sdf(0, y); |
229 | const float *rb = sdf(1, y); |
230 | const float *lt = sdf(0, y+1); |
231 | const float *rt = sdf(1, y+1); |
232 | for (int x = 0; x < sdf.width-1; ++x) { |
233 | float mlb = median(lb[0], lb[1], lb[2]); |
234 | float mrb = median(rb[0], rb[1], rb[2]); |
235 | float mlt = median(lt[0], lt[1], lt[2]); |
236 | float mrt = median(rt[0], rt[1], rt[2]); |
237 | if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) { |
238 | int mask = edgeBetweenTexels(lb, rt); |
239 | protectExtremeChannels(stencil(x, y), lb, mlb, mask); |
240 | protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask); |
241 | } |
242 | if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) { |
243 | int mask = edgeBetweenTexels(rb, lt); |
244 | protectExtremeChannels(stencil(x+1, y), rb, mrb, mask); |
245 | protectExtremeChannels(stencil(x, y+1), lt, mlt, mask); |
246 | } |
247 | lb += N, rb += N, lt += N, rt += N; |
248 | } |
249 | } |
250 | } |
251 | |
252 | void MSDFErrorCorrection::protectAll() { |
253 | byte *end = stencil.pixels+stencil.width*stencil.height; |
254 | for (byte *mask = stencil.pixels; mask < end; ++mask) |
255 | *mask |= (byte) PROTECTED; |
256 | } |
257 | |
258 | /// Returns the median of the linear interpolation of texels a, b at t. |
259 | static float interpolatedMedian(const float *a, const float *b, double t) { |
260 | return median( |
261 | mix(a[0], b[0], t), |
262 | mix(a[1], b[1], t), |
263 | mix(a[2], b[2], t) |
264 | ); |
265 | } |
266 | /// Returns the median of the bilinear interpolation with the given constant, linear, and quadratic terms at t. |
267 | static float interpolatedMedian(const float *a, const float *l, const float *q, double t) { |
268 | return float(median( |
269 | t*(t*q[0]+l[0])+a[0], |
270 | t*(t*q[1]+l[1])+a[1], |
271 | t*(t*q[2]+l[2])+a[2] |
272 | )); |
273 | } |
274 | |
275 | /// Determines if the interpolated median xm is an artifact. |
276 | static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) { |
277 | return ( |
278 | // For protected texels, only report an artifact if it would cause fill inversion (change between positive and negative distance). |
279 | (!isProtected || (am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f)) && |
280 | // This is an artifact if the interpolated median is outside the range of possible values based on its distance from a, b. |
281 | !(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan) |
282 | ); |
283 | } |
284 | |
285 | /// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values. |
286 | template <class ArtifactClassifier> |
287 | static bool hasLinearArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float bm, const float *a, const float *b, float dA, float dB) { |
288 | // Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0). |
289 | double t = (double) dA/(dA-dB); |
290 | if (t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON) { |
291 | // Interpolate median at t and let the classifier decide if its value indicates an artifact. |
292 | float xm = interpolatedMedian(a, b, t); |
293 | return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm)); |
294 | } |
295 | return false; |
296 | } |
297 | |
298 | /// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values. |
299 | template <class ArtifactClassifier> |
300 | static bool hasDiagonalArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) { |
301 | // Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal. |
302 | double t[2]; |
303 | int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA); |
304 | for (int i = 0; i < solutions; ++i) { |
305 | // Solutions t[i] == 0 and t[i] == 1 are singularities and occur very often because two channels are usually equal at texels. |
306 | if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) { |
307 | // Interpolate median xm at t. |
308 | float xm = interpolatedMedian(a, l, q, t[i]); |
309 | // Determine if xm deviates too much from medians of a, d. |
310 | int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm); |
311 | // Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1. |
312 | double tEnd[2]; |
313 | float em[2]; |
314 | // tEx0 |
315 | if (tEx0 > 0 && tEx0 < 1) { |
316 | tEnd[0] = 0, tEnd[1] = 1; |
317 | em[0] = am, em[1] = dm; |
318 | tEnd[tEx0 > t[i]] = tEx0; |
319 | em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0); |
320 | rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm); |
321 | } |
322 | // tEx1 |
323 | if (tEx1 > 0 && tEx1 < 1) { |
324 | tEnd[0] = 0, tEnd[1] = 1; |
325 | em[0] = am, em[1] = dm; |
326 | tEnd[tEx1 > t[i]] = tEx1; |
327 | em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1); |
328 | rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm); |
329 | } |
330 | if (artifactClassifier.evaluate(t[i], xm, rangeFlags)) |
331 | return true; |
332 | } |
333 | } |
334 | return false; |
335 | } |
336 | |
337 | /// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b. |
338 | template <class ArtifactClassifier> |
339 | static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) { |
340 | float bm = median(b[0], b[1], b[2]); |
341 | return ( |
342 | // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects. |
343 | fabsf(am-.5f) >= fabsf(bm-.5f) && ( |
344 | // Check points where each pair of color channels meets. |
345 | hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) || |
346 | hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) || |
347 | hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2]) |
348 | ) |
349 | ); |
350 | } |
351 | |
352 | /// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal). |
353 | template <class ArtifactClassifier> |
354 | static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) { |
355 | float dm = median(d[0], d[1], d[2]); |
356 | // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects. |
357 | if (fabsf(am-.5f) >= fabsf(dm-.5f)) { |
358 | float abc[3] = { |
359 | a[0]-b[0]-c[0], |
360 | a[1]-b[1]-c[1], |
361 | a[2]-b[2]-c[2] |
362 | }; |
363 | // Compute the linear terms for bilinear interpolation. |
364 | float l[3] = { |
365 | -a[0]-abc[0], |
366 | -a[1]-abc[1], |
367 | -a[2]-abc[2] |
368 | }; |
369 | // Compute the quadratic terms for bilinear interpolation. |
370 | float q[3] = { |
371 | d[0]+abc[0], |
372 | d[1]+abc[1], |
373 | d[2]+abc[2] |
374 | }; |
375 | // Compute interpolation ratios tEx (0 < tEx[i] < 1) for the local extremes of each color channel (the derivative 2*q[i]*tEx[i]+l[i] == 0). |
376 | double tEx[3] = { |
377 | -.5*l[0]/q[0], |
378 | -.5*l[1]/q[1], |
379 | -.5*l[2]/q[2] |
380 | }; |
381 | // Check points where each pair of color channels meets. |
382 | return ( |
383 | hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) || |
384 | hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) || |
385 | hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0]) |
386 | ); |
387 | } |
388 | return false; |
389 | } |
390 | |
391 | template <int N> |
392 | void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) { |
393 | // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels. |
394 | double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length(); |
395 | double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length(); |
396 | double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length(); |
397 | // Inspect all texels. |
398 | for (int y = 0; y < sdf.height; ++y) { |
399 | for (int x = 0; x < sdf.width; ++x) { |
400 | const float *c = sdf(x, y); |
401 | float cm = median(c[0], c[1], c[2]); |
402 | bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0; |
403 | const float *l = NULL, *b = NULL, *r = NULL, *t = NULL; |
404 | // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors. |
405 | *stencil(x, y) |= (byte) (ERROR*( |
406 | (x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, l))) || |
407 | (y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, b))) || |
408 | (x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, r))) || |
409 | (y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, t))) || |
410 | (x > 0 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, b, sdf(x-1, y-1))) || |
411 | (x < sdf.width-1 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, b, sdf(x+1, y-1))) || |
412 | (x > 0 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, t, sdf(x-1, y+1))) || |
413 | (x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, t, sdf(x+1, y+1))) |
414 | )); |
415 | } |
416 | } |
417 | } |
418 | |
419 | template <template <typename> class ContourCombiner, int N> |
420 | void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) { |
421 | // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels. |
422 | double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length(); |
423 | double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length(); |
424 | double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length(); |
425 | #ifdef MSDFGEN_USE_OPENMP |
426 | #pragma omp parallel |
427 | #endif |
428 | { |
429 | ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio); |
430 | bool rightToLeft = false; |
431 | // Inspect all texels. |
432 | #ifdef MSDFGEN_USE_OPENMP |
433 | #pragma omp for |
434 | #endif |
435 | for (int y = 0; y < sdf.height; ++y) { |
436 | int row = shape.inverseYAxis ? sdf.height-y-1 : y; |
437 | for (int col = 0; col < sdf.width; ++col) { |
438 | int x = rightToLeft ? sdf.width-col-1 : col; |
439 | if ((*stencil(x, row)&ERROR)) |
440 | continue; |
441 | const float *c = sdf(x, row); |
442 | shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5)); |
443 | shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5); |
444 | shapeDistanceChecker.msd = c; |
445 | shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0; |
446 | float cm = median(c[0], c[1], c[2]); |
447 | const float *l = NULL, *b = NULL, *r = NULL, *t = NULL; |
448 | // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors. |
449 | *stencil(x, row) |= (byte) (ERROR*( |
450 | (x > 0 && ((l = sdf(x-1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(-1, 0), hSpan), cm, c, l))) || |
451 | (row > 0 && ((b = sdf(x, row-1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, -1), vSpan), cm, c, b))) || |
452 | (x < sdf.width-1 && ((r = sdf(x+1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(+1, 0), hSpan), cm, c, r))) || |
453 | (row < sdf.height-1 && ((t = sdf(x, row+1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, +1), vSpan), cm, c, t))) || |
454 | (x > 0 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, -1), dSpan), cm, c, l, b, sdf(x-1, row-1))) || |
455 | (x < sdf.width-1 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, -1), dSpan), cm, c, r, b, sdf(x+1, row-1))) || |
456 | (x > 0 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, +1), dSpan), cm, c, l, t, sdf(x-1, row+1))) || |
457 | (x < sdf.width-1 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, +1), dSpan), cm, c, r, t, sdf(x+1, row+1))) |
458 | )); |
459 | } |
460 | } |
461 | } |
462 | } |
463 | |
464 | template <int N> |
465 | void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const { |
466 | int texelCount = sdf.width*sdf.height; |
467 | const byte *mask = stencil.pixels; |
468 | float *texel = sdf.pixels; |
469 | for (int i = 0; i < texelCount; ++i) { |
470 | if (*mask&ERROR) { |
471 | // Set all color channels to the median. |
472 | float m = median(texel[0], texel[1], texel[2]); |
473 | texel[0] = m, texel[1] = m, texel[2] = m; |
474 | } |
475 | ++mask; |
476 | texel += N; |
477 | } |
478 | } |
479 | |
480 | BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const { |
481 | return stencil; |
482 | } |
483 | |
484 | template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf); |
485 | template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf); |
486 | template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf); |
487 | template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf); |
488 | template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape); |
489 | template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape); |
490 | template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape); |
491 | template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape); |
492 | template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const; |
493 | template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const; |
494 | |
495 | } |
496 | |