Path: blob/master/thirdparty/msdfgen/core/MSDFErrorCorrection.cpp
9903 views
1#include "MSDFErrorCorrection.h"23#include <cstring>4#include "arithmetics.hpp"5#include "equation-solver.h"6#include "EdgeColor.h"7#include "bitmap-interpolation.hpp"8#include "edge-selectors.h"9#include "contour-combiners.h"10#include "ShapeDistanceFinder.h"11#include "generator-config.h"1213namespace msdfgen {1415#define ARTIFACT_T_EPSILON .0116#define PROTECTION_RADIUS_TOLERANCE 1.0011718#define CLASSIFIER_FLAG_CANDIDATE 0x0119#define CLASSIFIER_FLAG_ARTIFACT 0x022021MSDFGEN_PUBLIC const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111;22MSDFGEN_PUBLIC const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111;2324/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone.25class BaseArtifactClassifier {26public:27inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { }28/// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact.29inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const {30// 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.31if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) {32double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span;33// Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b.34if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan))35return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT;36return CLASSIFIER_FLAG_CANDIDATE;37}38return 0;39}40/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.41inline bool evaluate(double t, float m, int flags) const {42return (flags&2) != 0;43}44private:45double span;46bool protectedFlag;47};4849/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost.50template <template <typename> class ContourCombiner, int N>51class ShapeDistanceChecker {52public:53class ArtifactClassifier : public BaseArtifactClassifier {54public:55inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { }56/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.57inline bool evaluate(double t, float m, int flags) const {58if (flags&CLASSIFIER_FLAG_CANDIDATE) {59// Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier.60if (flags&CLASSIFIER_FLAG_ARTIFACT)61return true;62Vector2 tVector = t*direction;63float oldMSD[N], newMSD[3];64// Compute the color that would be currently interpolated at the artifact candidate's position.65Point2 sdfCoord = parent->sdfCoord+tVector;66interpolate(oldMSD, parent->sdf, sdfCoord);67// Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel.68double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y));69float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]);70newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0]));71newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1]));72newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2]));73// Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.74float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);75float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);76float refPSD = float(parent->distanceMapping(parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)));77// Compare the differences of the exact distance and the before and after distances.78return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));79}80return false;81}82private:83ShapeDistanceChecker *parent;84Vector2 direction;85};86Point2 shapeCoord, sdfCoord;87const float *msd;88bool protectedFlag;89inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, DistanceMapping distanceMapping, double minImproveRatio) : distanceFinder(shape), sdf(sdf), distanceMapping(distanceMapping), minImproveRatio(minImproveRatio) {90texelSize = projection.unprojectVector(Vector2(1));91if (shape.inverseYAxis)92texelSize.y = -texelSize.y;93}94inline ArtifactClassifier classifier(const Vector2 &direction, double span) {95return ArtifactClassifier(this, direction, span);96}97private:98ShapeDistanceFinder<ContourCombiner<PerpendicularDistanceSelector> > distanceFinder;99BitmapConstRef<float, N> sdf;100DistanceMapping distanceMapping;101Vector2 texelSize;102double minImproveRatio;103};104105MSDFErrorCorrection::MSDFErrorCorrection() { }106107MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation) : stencil(stencil), transformation(transformation) {108minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;109minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;110memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);111}112113void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) {114this->minDeviationRatio = minDeviationRatio;115}116117void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) {118this->minImproveRatio = minImproveRatio;119}120121void MSDFErrorCorrection::protectCorners(const Shape &shape) {122for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)123if (!contour->edges.empty()) {124const EdgeSegment *prevEdge = contour->edges.back();125for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {126int commonColor = prevEdge->color&(*edge)->color;127// If the color changes from prevEdge to edge, this is a corner.128if (!(commonColor&(commonColor-1))) {129// Find the four texels that envelop the corner and mark them as protected.130Point2 p = transformation.project((*edge)->point(0));131int l = (int) floor(p.x-.5);132int b = (int) floor(p.y-.5);133if (shape.inverseYAxis)134b = stencil.height-b-2;135int r = l+1;136int t = b+1;137// Check that the positions are within bounds.138if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) {139if (l >= 0 && b >= 0)140*stencil(l, b) |= (byte) PROTECTED;141if (r < stencil.width && b >= 0)142*stencil(r, b) |= (byte) PROTECTED;143if (l >= 0 && t < stencil.height)144*stencil(l, t) |= (byte) PROTECTED;145if (r < stencil.width && t < stencil.height)146*stencil(r, t) |= (byte) PROTECTED;147}148}149prevEdge = *edge;150}151}152}153154/// Determines if the channel contributes to an edge between the two texels a, b.155static 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).157double t = (a[channel]-.5)/(a[channel]-b[channel]);158if (t > 0 && t < 1) {159// Interpolate channel values at t.160float c[3] = {161mix(a[0], b[0], t),162mix(a[1], b[1], t),163mix(a[2], b[2], t)164};165// This is only an edge if the zero-distance channel is the median.166return median(c[0], c[1], c[2]) == c[channel];167}168return false;169}170171/// Returns a bit mask of which channels contribute to an edge between the two texels a, b.172static int edgeBetweenTexels(const float *a, const float *b) {173return (174RED*edgeBetweenTexelsChannel(a, b, 0)+175GREEN*edgeBetweenTexelsChannel(a, b, 1)+176BLUE*edgeBetweenTexelsChannel(a, b, 2)177);178}179180/// Marks texel as protected if one of its non-median channels is present in the channel mask.181static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) {182if (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}189190template <int N>191void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {192float radius;193// Horizontal texel pairs194radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length());195for (int y = 0; y < sdf.height; ++y) {196const float *left = sdf(0, y);197const float *right = sdf(1, y);198for (int x = 0; x < sdf.width-1; ++x) {199float lm = median(left[0], left[1], left[2]);200float rm = median(right[0], right[1], right[2]);201if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) {202int mask = edgeBetweenTexels(left, right);203protectExtremeChannels(stencil(x, y), left, lm, mask);204protectExtremeChannels(stencil(x+1, y), right, rm, mask);205}206left += N, right += N;207}208}209// Vertical texel pairs210radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length());211for (int y = 0; y < sdf.height-1; ++y) {212const float *bottom = sdf(0, y);213const float *top = sdf(0, y+1);214for (int x = 0; x < sdf.width; ++x) {215float bm = median(bottom[0], bottom[1], bottom[2]);216float tm = median(top[0], top[1], top[2]);217if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) {218int mask = edgeBetweenTexels(bottom, top);219protectExtremeChannels(stencil(x, y), bottom, bm, mask);220protectExtremeChannels(stencil(x, y+1), top, tm, mask);221}222bottom += N, top += N;223}224}225// Diagonal texel pairs226radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length());227for (int y = 0; y < sdf.height-1; ++y) {228const float *lb = sdf(0, y);229const float *rb = sdf(1, y);230const float *lt = sdf(0, y+1);231const float *rt = sdf(1, y+1);232for (int x = 0; x < sdf.width-1; ++x) {233float mlb = median(lb[0], lb[1], lb[2]);234float mrb = median(rb[0], rb[1], rb[2]);235float mlt = median(lt[0], lt[1], lt[2]);236float mrt = median(rt[0], rt[1], rt[2]);237if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) {238int mask = edgeBetweenTexels(lb, rt);239protectExtremeChannels(stencil(x, y), lb, mlb, mask);240protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask);241}242if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) {243int mask = edgeBetweenTexels(rb, lt);244protectExtremeChannels(stencil(x+1, y), rb, mrb, mask);245protectExtremeChannels(stencil(x, y+1), lt, mlt, mask);246}247lb += N, rb += N, lt += N, rt += N;248}249}250}251252void MSDFErrorCorrection::protectAll() {253byte *end = stencil.pixels+stencil.width*stencil.height;254for (byte *mask = stencil.pixels; mask < end; ++mask)255*mask |= (byte) PROTECTED;256}257258/// Returns the median of the linear interpolation of texels a, b at t.259static float interpolatedMedian(const float *a, const float *b, double t) {260return median(261mix(a[0], b[0], t),262mix(a[1], b[1], t),263mix(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.267static float interpolatedMedian(const float *a, const float *l, const float *q, double t) {268return float(median(269t*(t*q[0]+l[0])+a[0],270t*(t*q[1]+l[1])+a[1],271t*(t*q[2]+l[2])+a[2]272));273}274275/// Determines if the interpolated median xm is an artifact.276static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) {277return (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}284285/// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.286template <class ArtifactClassifier>287static 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).289double t = (double) dA/(dA-dB);290if (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.292float xm = interpolatedMedian(a, b, t);293return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm));294}295return false;296}297298/// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.299template <class ArtifactClassifier>300static 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.302double t[2];303int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA);304for (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.306if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) {307// Interpolate median xm at t.308float xm = interpolatedMedian(a, l, q, t[i]);309// Determine if xm deviates too much from medians of a, d.310int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm);311// Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.312double tEnd[2];313float em[2];314// tEx0315if (tEx0 > 0 && tEx0 < 1) {316tEnd[0] = 0, tEnd[1] = 1;317em[0] = am, em[1] = dm;318tEnd[tEx0 > t[i]] = tEx0;319em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0);320rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], em[0], em[1], xm);321}322// tEx1323if (tEx1 > 0 && tEx1 < 1) {324tEnd[0] = 0, tEnd[1] = 1;325em[0] = am, em[1] = dm;326tEnd[tEx1 > t[i]] = tEx1;327em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1);328rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], em[0], em[1], xm);329}330if (artifactClassifier.evaluate(t[i], xm, rangeFlags))331return true;332}333}334return false;335}336337/// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b.338template <class ArtifactClassifier>339static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) {340float bm = median(b[0], b[1], b[2]);341return (342// Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.343fabsf(am-.5f) >= fabsf(bm-.5f) && (344// Check points where each pair of color channels meets.345hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) ||346hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) ||347hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2])348)349);350}351352/// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal).353template <class ArtifactClassifier>354static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) {355float 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.357if (fabsf(am-.5f) >= fabsf(dm-.5f)) {358float abc[3] = {359a[0]-b[0]-c[0],360a[1]-b[1]-c[1],361a[2]-b[2]-c[2]362};363// Compute the linear terms for bilinear interpolation.364float 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.370float q[3] = {371d[0]+abc[0],372d[1]+abc[1],373d[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).376double 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.382return (383hasDiagonalArtifactInner(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]) ||384hasDiagonalArtifactInner(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]) ||385hasDiagonalArtifactInner(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}388return false;389}390391template <int N>392void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {393// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.394double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();395double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();396double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();397// Inspect all texels.398for (int y = 0; y < sdf.height; ++y) {399for (int x = 0; x < sdf.width; ++x) {400const float *c = sdf(x, y);401float cm = median(c[0], c[1], c[2]);402bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0;403const 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}418419template <template <typename> class ContourCombiner, int N>420void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {421// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.422double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();423double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();424double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();425#ifdef MSDFGEN_USE_OPENMP426#pragma omp parallel427#endif428{429ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, transformation, transformation.distanceMapping, minImproveRatio);430bool rightToLeft = false;431// Inspect all texels.432#ifdef MSDFGEN_USE_OPENMP433#pragma omp for434#endif435for (int y = 0; y < sdf.height; ++y) {436int row = shape.inverseYAxis ? sdf.height-y-1 : y;437for (int col = 0; col < sdf.width; ++col) {438int x = rightToLeft ? sdf.width-col-1 : col;439if ((*stencil(x, row)&ERROR))440continue;441const float *c = sdf(x, row);442shapeDistanceChecker.shapeCoord = transformation.unproject(Point2(x+.5, y+.5));443shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);444shapeDistanceChecker.msd = c;445shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;446float cm = median(c[0], c[1], c[2]);447const 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}463464template <int N>465void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {466int texelCount = sdf.width*sdf.height;467const byte *mask = stencil.pixels;468float *texel = sdf.pixels;469for (int i = 0; i < texelCount; ++i) {470if (*mask&ERROR) {471// Set all color channels to the median.472float m = median(texel[0], texel[1], texel[2]);473texel[0] = m, texel[1] = m, texel[2] = m;474}475++mask;476texel += N;477}478}479480BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const {481return stencil;482}483484template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf);485template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf);486template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf);487template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf);488template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);489template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);490template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);491template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);492template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const;493template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;494495}496497498