Fixed asymmetrical range interaction with scanline and winding reversal, default format in non-PNG build changed to BMP

This commit is contained in:
Chlumsky
2025-09-24 13:52:10 +02:00
parent e36d71409b
commit b25851c859
3 changed files with 81 additions and 70 deletions

View File

@@ -16,26 +16,28 @@ void rasterize(BitmapSection<float, 1> output, const Shape &shape, const Project
}
}
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
sdf.reorient(shape.getYAxisOrientation());
float doubleSdfZeroValue = sdfZeroValue+sdfZeroValue;
Scanline scanline;
for (int y = 0; y < sdf.height; ++y) {
shape.scanline(scanline, projection.unprojectY(y+.5));
for (int x = 0; x < sdf.width; ++x) {
bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule);
float &sd = *sdf(x, y);
if ((sd > .5f) != fill)
sd = 1.f-sd;
if ((sd > sdfZeroValue) != fill)
sd = doubleSdfZeroValue-sd;
}
}
}
template <int N>
static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
int w = sdf.width, h = sdf.height;
if (!(w && h))
return;
sdf.reorient(shape.getYAxisOrientation());
float doubleSdfZeroValue = sdfZeroValue+sdfZeroValue;
Scanline scanline;
bool ambiguous = false;
std::vector<char> matchMap;
@@ -47,17 +49,17 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule);
float *msd = sdf(x, y);
float sd = median(msd[0], msd[1], msd[2]);
if (sd == .5f)
if (sd == sdfZeroValue)
ambiguous = true;
else if ((sd > .5f) != fill) {
msd[0] = 1.f-msd[0];
msd[1] = 1.f-msd[1];
msd[2] = 1.f-msd[2];
else if ((sd > sdfZeroValue) != fill) {
msd[0] = doubleSdfZeroValue-msd[0];
msd[1] = doubleSdfZeroValue-msd[1];
msd[2] = doubleSdfZeroValue-msd[2];
*match = -1;
} else
*match = 1;
if (N >= 4 && (msd[3] > .5f) != fill)
msd[3] = 1.f-msd[3];
if (N >= 4 && (msd[3] > sdfZeroValue) != fill)
msd[3] = doubleSdfZeroValue-msd[3];
++match;
}
}
@@ -74,9 +76,9 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
if (y < h-1) neighborMatch += *(match+w);
if (neighborMatch < 0) {
float *msd = sdf(x, y);
msd[0] = 1.f-msd[0];
msd[1] = 1.f-msd[1];
msd[2] = 1.f-msd[2];
msd[0] = doubleSdfZeroValue-msd[0];
msd[1] = doubleSdfZeroValue-msd[1];
msd[2] = doubleSdfZeroValue-msd[2];
}
}
++match;
@@ -85,12 +87,12 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
}
}
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, projection, fillRule);
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, projection, sdfZeroValue, fillRule);
}
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, projection, fillRule);
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, projection, sdfZeroValue, fillRule);
}
// Legacy API
@@ -99,6 +101,18 @@ void rasterize(const BitmapSection<float, 1> &output, const Shape &shape, const
rasterize(output, shape, Projection(scale, translate), fillRule);
}
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
}
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
}
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
}
void distanceSignCorrection(const BitmapSection<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
distanceSignCorrection(sdf, shape, Projection(scale, translate), fillRule);
}

View File

@@ -12,12 +12,15 @@ namespace msdfgen {
/// Rasterizes the shape into a monochrome bitmap.
void rasterize(BitmapSection<float, 1> output, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
/// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill.
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
// Old version of the function API's kept for backwards compatibility
// Old versions of the function API's kept for backwards compatibility
void rasterize(const BitmapSection<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
void distanceSignCorrection(const BitmapSection<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapSection<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapSection<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);

View File

@@ -35,8 +35,8 @@
#define DEFAULT_IMAGE_EXTENSION "png"
#define SAVE_DEFAULT_IMAGE_FORMAT savePng
#else
#define DEFAULT_IMAGE_EXTENSION "tiff"
#define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
#define DEFAULT_IMAGE_EXTENSION "bmp"
#define SAVE_DEFAULT_IMAGE_FORMAT saveBmp
#endif
using namespace msdfgen;
@@ -193,15 +193,6 @@ static FontHandle *loadVarFont(FreetypeHandle *library, const char *filename) {
#endif
#endif
template <int N>
static void invertColor(const BitmapSection<float, N> &bitmap) {
for (int y = 0; y < bitmap.height; ++y) {
float *p = bitmap(0, y);
for (const float *end = p+N*bitmap.width; p < end; ++p)
*p = 1.f-*p;
}
}
static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows, int rowStride) {
for (int row = 0; row < rows; ++row) {
const float *cur = values;
@@ -452,7 +443,7 @@ static const char *const helpText =
" -format <bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
#endif
"\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
" -guessorder\n"
" -guesswinding\n"
"\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
" -help\n"
"\tDisplays this help.\n"
@@ -483,7 +474,7 @@ static const char *const helpText =
"\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
" -range <range>\n"
"\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
" -reverseorder\n"
" -reversewinding\n"
"\tGenerates the distance field as if the shape's vertices were in reverse order.\n"
" -scale <scale>\n"
"\tSets the scale used to convert shape units to pixels.\n"
@@ -499,10 +490,10 @@ static const char *const helpText =
#if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
"\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
#else
"\tRenders an image preview using the generated distance field and saves it as a TIFF file.\n"
"\tRenders an image preview using the generated distance field and saves it as a BMP file.\n"
#endif
" -testrendermulti <filename." DEFAULT_IMAGE_EXTENSION "> <width> <height>\n"
"\tRenders an image preview without flattening the color channels.\n"
"\tRenders an image preview without resolving the color channels.\n"
" -translate <x> <y>\n"
"\tSets the translation of the shape in shape units.\n"
" -version\n"
@@ -510,7 +501,7 @@ static const char *const helpText =
" -windingpreprocess\n"
"\tAttempts to fix only the contour windings assuming no self-intersections and even-odd fill rule.\n"
" -yflip\n"
"\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
"\tInverts the Y-axis in the output distance field. The default orientation is upward.\n"
"\n";
static const char *errorCorrectionHelpText =
@@ -612,7 +603,7 @@ int main(int argc, const char *const *argv) {
KEEP,
REVERSE,
GUESS
} orientation = KEEP;
} winding = KEEP;
unsigned long long coloringSeed = 0;
void (*edgeColoring)(Shape &, double, unsigned long long) = &edgeColoringSimple;
bool explicitErrorCorrectionMode = false;
@@ -982,16 +973,16 @@ int main(int argc, const char *const *argv) {
estimateError = true;
continue;
}
ARG_CASE("-keeporder", 0) {
orientation = KEEP;
ARG_CASE("-keepwinding" ARG_CASE_OR "-keeporder", 0) {
winding = KEEP;
continue;
}
ARG_CASE("-reverseorder", 0) {
orientation = REVERSE;
ARG_CASE("-reversewinding" ARG_CASE_OR "-reverseorder", 0) {
winding = REVERSE;
continue;
}
ARG_CASE("-guessorder", 0) {
orientation = GUESS;
ARG_CASE("-guesswinding" ARG_CASE_OR "-guessorder", 0) {
winding = GUESS;
continue;
}
ARG_CASE("-seed", 1) {
@@ -1137,9 +1128,20 @@ int main(int argc, const char *const *argv) {
double avgScale = .5*(scale.x+scale.y);
Shape::Bounds bounds = { };
if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS || svgExport)
if (autoFrame || mode == METRICS || printMetrics || winding == GUESS || svgExport)
bounds = shape.getBounds();
if (winding == GUESS) {
// Get sign of signed distance outside bounds
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
winding = distance <= 0 ? KEEP : REVERSE;
}
if (winding == REVERSE) {
for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
contour->reverse();
}
if (outputDistanceShift) {
Range &rangeRef = rangeMode == RANGE_PX ? pxRange : range;
double rangeShift = -outputDistanceShift*(rangeRef.upper-rangeRef.lower);
@@ -1276,39 +1278,19 @@ int main(int argc, const char *const *argv) {
default:;
}
if (orientation == GUESS) {
// Get sign of signed distance outside bounds
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
orientation = distance <= 0 ? KEEP : REVERSE;
}
if (orientation == REVERSE) {
switch (mode) {
case SINGLE:
case PERPENDICULAR:
invertColor<1>(sdf);
break;
case MULTI:
invertColor<3>(msdf);
break;
case MULTI_AND_TRUE:
invertColor<4>(mtsdf);
break;
default:;
}
}
if (scanlinePass) {
float sdfZeroValue = range.lower != range.upper ? float(range.lower/(range.lower-range.upper)) : .5f;
switch (mode) {
case SINGLE:
case PERPENDICULAR:
distanceSignCorrection(sdf, shape, transformation, fillRule);
distanceSignCorrection(sdf, shape, transformation, sdfZeroValue, fillRule);
break;
case MULTI:
distanceSignCorrection(msdf, shape, transformation, fillRule);
distanceSignCorrection(msdf, shape, transformation, sdfZeroValue, fillRule);
msdfErrorCorrection(msdf, shape, transformation, postErrorCorrectionConfig);
break;
case MULTI_AND_TRUE:
distanceSignCorrection(mtsdf, shape, transformation, fillRule);
distanceSignCorrection(mtsdf, shape, transformation, sdfZeroValue, fillRule);
msdfErrorCorrection(mtsdf, shape, transformation, postErrorCorrectionConfig);
break;
default:;
@@ -1344,12 +1326,16 @@ int main(int argc, const char *const *argv) {
if (testRenderMulti) {
Bitmap<float, 3> render(testWidthM, testHeightM);
renderSDF(render, sdf, avgScale*range);
if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
fputs("Failed to write test render file.\n", stderr);
}
if (testRender) {
Bitmap<float, 1> render(testWidth, testHeight);
renderSDF(render, sdf, avgScale*range);
if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
fputs("Failed to write test render file.\n", stderr);
}
@@ -1368,12 +1354,16 @@ int main(int argc, const char *const *argv) {
if (testRenderMulti) {
Bitmap<float, 3> render(testWidthM, testHeightM);
renderSDF(render, msdf, avgScale*range);
if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
fputs("Failed to write test render file.\n", stderr);
}
if (testRender) {
Bitmap<float, 1> render(testWidth, testHeight);
renderSDF(render, msdf, avgScale*range);
if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
fputs("Failed to write test render file.\n", stderr);
}
@@ -1392,12 +1382,16 @@ int main(int argc, const char *const *argv) {
if (testRenderMulti) {
Bitmap<float, 4> render(testWidthM, testHeightM);
renderSDF(render, mtsdf, avgScale*range);
if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
fputs("Failed to write test render file.\n", stderr);
}
if (testRender) {
Bitmap<float, 1> render(testWidth, testHeight);
renderSDF(render, mtsdf, avgScale*range);
if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
fputs("Failed to write test render file.\n", stderr);
}