| 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 | |