From e93c8d988cb5f71ed7318fa9424b4dda4fb6e58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Chlumsk=C3=BD?= Date: Fri, 6 Mar 2020 17:09:59 +0100 Subject: [PATCH] Added MTSDF mode --- README.md | 3 +- core/contour-combiners.cpp | 2 + core/edge-selectors.cpp | 25 +++++++++++ core/edge-selectors.h | 16 +++++++ core/estimate-sdf-error.cpp | 74 +++++++++++++++--------------- core/estimate-sdf-error.h | 2 + core/msdfgen.cpp | 89 +++++++++++++++++++++++++++++++++++- core/rasterization.cpp | 13 +++++- core/rasterization.h | 1 + core/render-sdf.cpp | 29 ++++++++++++ core/render-sdf.h | 3 ++ core/save-bmp.cpp | 10 +++++ core/save-bmp.h | 2 + core/save-tiff.cpp | 90 ++++++++++++++++++------------------- core/save-tiff.h | 1 + ext/save-png.cpp | 20 +++++++++ ext/save-png.h | 2 + main.cpp | 53 +++++++++++++++++++++- msdfgen.h | 10 ++++- 19 files changed, 354 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 66800d7..2b96881 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,10 @@ msdfgen.exe where only the input specification is required. Mode can be one of: - - **sdf** – generates a conventional monochrome signed distance field. + - **sdf** – generates a conventional monochrome (true) signed distance field. - **psdf** – generates a monochrome signed pseudo-distance field. - **msdf** (default) – generates a multi-channel signed distance field using my new method. + - **mtsdf** – generates a combined multi-channel and true signed distance field in the alpha channel. The input can be specified as one of: - **-font \ \** – to load a glyph from a font file. diff --git a/core/contour-combiners.cpp b/core/contour-combiners.cpp index e52f159..7157231 100644 --- a/core/contour-combiners.cpp +++ b/core/contour-combiners.cpp @@ -44,6 +44,7 @@ typename SimpleContourCombiner::DistanceType SimpleContourCombiner template class SimpleContourCombiner; template class SimpleContourCombiner; template class SimpleContourCombiner; +template class SimpleContourCombiner; template OverlappingContourCombiner::OverlappingContourCombiner(const Shape &shape) { @@ -118,5 +119,6 @@ typename OverlappingContourCombiner::DistanceType OverlappingConto template class OverlappingContourCombiner; template class OverlappingContourCombiner; template class OverlappingContourCombiner; +template class OverlappingContourCombiner; } diff --git a/core/edge-selectors.cpp b/core/edge-selectors.cpp index 1df94d6..f05a11f 100644 --- a/core/edge-selectors.cpp +++ b/core/edge-selectors.cpp @@ -76,6 +76,10 @@ double PseudoDistanceSelectorBase::computeDistance(const Point2 &p) const { return minDistance; } +SignedDistance PseudoDistanceSelectorBase::trueDistance() const { + return minTrueDistance; +} + PseudoDistanceSelector::PseudoDistanceSelector(const Point2 &p) : p(p) { } void PseudoDistanceSelector::addEdge(const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) { @@ -128,4 +132,25 @@ MultiDistanceSelector::DistanceType MultiDistanceSelector::distance() const { return multiDistance; } +SignedDistance MultiDistanceSelector::trueDistance() const { + SignedDistance distance = r.trueDistance(); + if (g.trueDistance() < distance) + distance = g.trueDistance(); + if (b.trueDistance() < distance) + distance = b.trueDistance(); + return distance; +} + +MultiAndTrueDistanceSelector::MultiAndTrueDistanceSelector(const Point2 &p) : MultiDistanceSelector(p) { } + +MultiAndTrueDistanceSelector::DistanceType MultiAndTrueDistanceSelector::distance() const { + MultiDistance multiDistance = MultiDistanceSelector::distance(); + MultiAndTrueDistance mtd; + mtd.r = multiDistance.r; + mtd.g = multiDistance.g; + mtd.b = multiDistance.b; + mtd.a = trueDistance().distance; + return mtd; +} + } diff --git a/core/edge-selectors.h b/core/edge-selectors.h index b474941..8132079 100644 --- a/core/edge-selectors.h +++ b/core/edge-selectors.h @@ -10,6 +10,9 @@ namespace msdfgen { struct MultiDistance { double r, g, b; }; +struct MultiAndTrueDistance : MultiDistance { + double a; +}; /// Selects the nearest edge by its true distance. class TrueDistanceSelector { @@ -38,6 +41,7 @@ public: void addEdgePseudoDistance(const SignedDistance &distance); void merge(const PseudoDistanceSelectorBase &other); double computeDistance(const Point2 &p) const; + SignedDistance trueDistance() const; private: SignedDistance minTrueDistance; @@ -73,6 +77,7 @@ public: void addEdge(const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge); void merge(const MultiDistanceSelector &other); DistanceType distance() const; + SignedDistance trueDistance() const; private: Point2 p; @@ -80,4 +85,15 @@ private: }; +/// Selects the nearest edge for each of the three color channels by its pseudo-distance and by true distance for the alpha channel. +class MultiAndTrueDistanceSelector : public MultiDistanceSelector { + +public: + typedef MultiAndTrueDistance DistanceType; + + explicit MultiAndTrueDistanceSelector(const Point2 &p = Point2()); + DistanceType distance() const; + +}; + } diff --git a/core/estimate-sdf-error.cpp b/core/estimate-sdf-error.cpp index d09c283..4628185 100644 --- a/core/estimate-sdf-error.cpp +++ b/core/estimate-sdf-error.cpp @@ -45,7 +45,8 @@ void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vect #endif } -void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { +template +void scanlineMSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { if (!(sdf.width > 0 && sdf.height > 0)) return line.setIntersections(std::vector()); double pixelY = clamp(scale.x*(y+translate.y)-.5, double(sdf.height-1)); @@ -123,46 +124,43 @@ void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vect #endif } +void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y); +} +void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y); +} + +template +double estimateSDFErrorInner(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { + if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1) + return 0; + double subRowSize = 1./scanlinesPerRow; + double xFrom = .5/scale.x-translate.x; + double xTo = (sdf.width-.5)/scale.x-translate.x; + double overlapFactor = 1/(xTo-xFrom); + double error = 0; + Scanline refScanline, sdfScanline; + for (int row = 0; row < sdf.height-1; ++row) { + for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) { + double bt = (subRow+.5)*subRowSize; + double y = (row+bt+.5)/scale.y-translate.y; + shape.scanline(refScanline, y); + scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y); + error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule); + } + } + return error/((sdf.height-1)*scanlinesPerRow); +} + double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { - if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1) - return 0; - double subRowSize = 1./scanlinesPerRow; - double xFrom = .5/scale.x-translate.x; - double xTo = (sdf.width-.5)/scale.x-translate.x; - double overlapFactor = 1/(xTo-xFrom); - double error = 0; - Scanline refScanline, sdfScanline; - for (int row = 0; row < sdf.height-1; ++row) { - for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) { - double bt = (subRow+.5)*subRowSize; - double y = (row+bt+.5)/scale.y-translate.y; - shape.scanline(refScanline, y); - scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y); - error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule); - } - } - return error/((sdf.height-1)*scanlinesPerRow); + return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule); } - double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { - if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1) - return 0; - double subRowSize = 1./scanlinesPerRow; - double xFrom = .5/scale.x-translate.x; - double xTo = (sdf.width-.5)/scale.x-translate.x; - double overlapFactor = 1/(xTo-xFrom); - double error = 0; - Scanline refScanline, sdfScanline; - for (int row = 0; row < sdf.height-1; ++row) { - for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) { - double bt = (subRow+.5)*subRowSize; - double y = (row+bt+.5)/scale.y-translate.y; - shape.scanline(refScanline, y); - scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y); - error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule); - } - } - return error/((sdf.height-1)*scanlinesPerRow); + return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule); +} +double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) { + return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule); } } diff --git a/core/estimate-sdf-error.h b/core/estimate-sdf-error.h index 671154f..4f793b2 100644 --- a/core/estimate-sdf-error.h +++ b/core/estimate-sdf-error.h @@ -11,9 +11,11 @@ namespace msdfgen { /// Analytically constructs a scanline at y evaluating fill by linear interpolation of the SDF. void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); +void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); /// Estimates the portion of the area that will be filled incorrectly when rendering using the SDF. double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); } diff --git a/core/msdfgen.cpp b/core/msdfgen.cpp index 2dc095c..4acf982 100644 --- a/core/msdfgen.cpp +++ b/core/msdfgen.cpp @@ -30,6 +30,18 @@ public: } }; +template <> +class DistancePixelConversion { +public: + typedef BitmapRef BitmapRefType; + inline static void convert(float *pixels, const MultiAndTrueDistance &distance, double range) { + pixels[0] = float(distance.r/range+.5); + pixels[1] = float(distance.g/range+.5); + pixels[2] = float(distance.b/range+.5); + pixels[3] = float(distance.a/range+.5); + } +}; + template void generateDistanceField(const typename DistancePixelConversion::BitmapRefType &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { #ifdef MSDFGEN_USE_OPENMP @@ -96,6 +108,15 @@ void generateMSDF(const BitmapRef &output, const Shape &shape, double msdfErrorCorrection(output, edgeThreshold/(scale*range)); } +void generateMTSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) { + if (overlapSupport) + generateDistanceField >(output, shape, range, scale, translate); + else + generateDistanceField >(output, shape, range, scale, translate); + if (edgeThreshold > 0) + msdfErrorCorrection(output, edgeThreshold/(scale*range)); +} + inline static bool detectClash(const float *a, const float *b, double threshold) { // Sort channels so that pairs (a0, b0), (a1, b1), (a2, b2) go from biggest to smallest absolute difference float a0 = a[0], a1 = a[1], a2 = a[2]; @@ -118,7 +139,8 @@ inline static bool detectClash(const float *a, const float *b, double threshold) fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge } -void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) { +template +void msdfErrorCorrectionInner(const BitmapRef &output, const Vector2 &threshold) { std::vector > clashes; int w = output.width, h = output.height; for (int y = 0; y < h; ++y) @@ -156,6 +178,13 @@ void msdfErrorCorrection(const BitmapRef &output, const Vector2 &thres #endif } +void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) { + msdfErrorCorrectionInner(output, threshold); +} +void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) { + msdfErrorCorrectionInner(output, threshold); +} + // Legacy version void generateSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { @@ -261,4 +290,62 @@ void generateMSDF_legacy(const BitmapRef &output, const Shape &shape, msdfErrorCorrection(output, edgeThreshold/(scale*range)); } +void generateMTSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) { +#ifdef MSDFGEN_USE_OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; + for (int x = 0; x < output.width; ++x) { + Point2 p = Vector2(x+.5, y+.5)/scale-translate; + + SignedDistance minDistance; + struct { + SignedDistance minDistance; + const EdgeHolder *nearEdge; + double nearParam; + } r, g, b; + r.nearEdge = g.nearEdge = b.nearEdge = NULL; + r.nearParam = g.nearParam = b.nearParam = 0; + + for (std::vector::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + for (std::vector::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) { + double param; + SignedDistance distance = (*edge)->signedDistance(p, param); + if (distance < minDistance) + minDistance = distance; + if ((*edge)->color&RED && distance < r.minDistance) { + r.minDistance = distance; + r.nearEdge = &*edge; + r.nearParam = param; + } + if ((*edge)->color&GREEN && distance < g.minDistance) { + g.minDistance = distance; + g.nearEdge = &*edge; + g.nearParam = param; + } + if ((*edge)->color&BLUE && distance < b.minDistance) { + b.minDistance = distance; + b.nearEdge = &*edge; + b.nearParam = param; + } + } + + if (r.nearEdge) + (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam); + if (g.nearEdge) + (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam); + if (b.nearEdge) + (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam); + output(x, row)[0] = float(r.minDistance.distance/range+.5); + output(x, row)[1] = float(g.minDistance.distance/range+.5); + output(x, row)[2] = float(b.minDistance.distance/range+.5); + output(x, row)[3] = float(minDistance.distance/range+.5); + } + } + + if (edgeThreshold > 0) + msdfErrorCorrection(output, edgeThreshold/(scale*range)); +} + } diff --git a/core/rasterization.cpp b/core/rasterization.cpp index 4aad1da..7d91ff3 100644 --- a/core/rasterization.cpp +++ b/core/rasterization.cpp @@ -38,7 +38,8 @@ void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, } } -void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { +template +static void multiDistanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { int w = sdf.width, h = sdf.height; if (!(w*h)) return; @@ -66,6 +67,8 @@ void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, *match = -1; } else *match = 1; + if (N >= 4 && (msd[3] > .5f) != fill) + msd[3] = 1.f-msd[3]; ++match; } } @@ -94,4 +97,12 @@ void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, } } +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule); +} + +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule); +} + } diff --git a/core/rasterization.h b/core/rasterization.h index 3ce2d5a..18ccb74 100644 --- a/core/rasterization.h +++ b/core/rasterization.h @@ -13,5 +13,6 @@ void rasterize(const BitmapRef &output, const Shape &shape, const Vect /// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill. void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); } diff --git a/core/render-sdf.cpp b/core/render-sdf.cpp index 223d8cf..ad23d58 100644 --- a/core/render-sdf.cpp +++ b/core/render-sdf.cpp @@ -73,6 +73,29 @@ void renderSDF(const BitmapRef &output, const BitmapConstRef } } +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[4]; + sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange); + } +} + +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[4]; + sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + output(x, y)[0] = distVal(sd[0], pxRange); + output(x, y)[1] = distVal(sd[1], pxRange); + output(x, y)[2] = distVal(sd[2], pxRange); + output(x, y)[3] = distVal(sd[3], pxRange); + } +} + void simulate8bit(const BitmapRef &bitmap) { const float *end = bitmap.pixels+1*bitmap.width*bitmap.height; for (float *p = bitmap.pixels; p < end; ++p) @@ -85,4 +108,10 @@ void simulate8bit(const BitmapRef &bitmap) { *p = pixelByteToFloat(pixelFloatToByte(*p)); } +void simulate8bit(const BitmapRef &bitmap) { + const float *end = bitmap.pixels+4*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); +} + } diff --git a/core/render-sdf.h b/core/render-sdf.h index a388e31..9d055f8 100644 --- a/core/render-sdf.h +++ b/core/render-sdf.h @@ -11,9 +11,12 @@ void renderSDF(const BitmapRef &output, const BitmapConstRef void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); /// Snaps the values of the floating-point bitmaps into one of the 256 values representable in a standard 8-bit bitmap. void simulate8bit(const BitmapRef &bitmap); void simulate8bit(const BitmapRef &bitmap); +void simulate8bit(const BitmapRef &bitmap); } diff --git a/core/save-bmp.cpp b/core/save-bmp.cpp index 5ba8920..3ca3674 100644 --- a/core/save-bmp.cpp +++ b/core/save-bmp.cpp @@ -108,6 +108,11 @@ bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { return !fclose(file); } +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { + // RGBA not supported by the BMP format + return false; +} + bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { FILE *file = fopen(filename, "wb"); if (!file) @@ -156,4 +161,9 @@ bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { return !fclose(file); } +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { + // RGBA not supported by the BMP format + return false; +} + } diff --git a/core/save-bmp.h b/core/save-bmp.h index 4fbe703..98f8529 100644 --- a/core/save-bmp.h +++ b/core/save-bmp.h @@ -8,7 +8,9 @@ namespace msdfgen { /// Saves the bitmap as a BMP file. bool saveBmp(const BitmapConstRef &bitmap, const char *filename); bool saveBmp(const BitmapConstRef &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); bool saveBmp(const BitmapConstRef &bitmap, const char *filename); bool saveBmp(const BitmapConstRef &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); } diff --git a/core/save-tiff.cpp b/core/save-tiff.cpp index 441d069..71405e0 100644 --- a/core/save-tiff.cpp +++ b/core/save-tiff.cpp @@ -20,6 +20,11 @@ template static bool writeValue(FILE *file, T value) { return fwrite(&value, sizeof(T), 1, file) == 1; } +template +static void writeValueRepeated(FILE *file, T value, int times) { + for (int i = 0; i < times; ++i) + writeValue(file, value); +} static bool writeTiffHeader(FILE *file, int width, int height, int channels) { #ifdef __BIG_ENDIAN__ @@ -47,8 +52,8 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { writeValue(file, 0x0102u); writeValue(file, 0x0003u); writeValue(file, channels); - if (channels == 3) - writeValue(file, 0x00c2u); // Offset of 32, 32, 32 + if (channels > 1) + writeValue(file, 0x00c2u); // Offset of 32, 32, ... else { writeValue(file, 32); writeValue(file, 0); @@ -63,13 +68,13 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { writeValue(file, 0x0106u); writeValue(file, 0x0003u); writeValue(file, 1); - writeValue(file, channels == 3 ? 2 : 1); + writeValue(file, channels >= 3 ? 2 : 1); writeValue(file, 0); // StripOffsets writeValue(file, 0x0111u); writeValue(file, 0x0004u); writeValue(file, 1); - writeValue(file, channels == 3 ? 0x00f6u : 0x00d2u); // Offset of pixel data + writeValue(file, 0x00d2u+(channels > 1)*channels*12); // Offset of pixel data // SamplesPerPixel writeValue(file, 0x0115u); writeValue(file, 0x0003u); @@ -90,12 +95,12 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { writeValue(file, 0x011au); writeValue(file, 0x0005u); writeValue(file, 1); - writeValue(file, channels == 3 ? 0x00c8u : 0x00c2u); // Offset of 300, 1 + writeValue(file, 0x00c2u+(channels > 1)*channels*2); // Offset of 300, 1 // YResolution writeValue(file, 0x011bu); writeValue(file, 0x0005u); writeValue(file, 1); - writeValue(file, channels == 3 ? 0x00d0u : 0x00cau); // Offset of 300, 1 + writeValue(file, 0x00cau+(channels > 1)*channels*2); // Offset of 300, 1 // ResolutionUnit writeValue(file, 0x0128u); writeValue(file, 0x0003u); @@ -106,8 +111,8 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { writeValue(file, 0x0153u); writeValue(file, 0x0003u); writeValue(file, channels); - if (channels == 3) - writeValue(file, 0x00d8u); // Offset of 3, 3, 3 + if (channels > 1) + writeValue(file, 0x00d2u+channels*2); // Offset of 3, 3, ... else { writeValue(file, 3); writeValue(file, 0); @@ -116,46 +121,38 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { writeValue(file, 0x0154u); writeValue(file, 0x000bu); writeValue(file, channels); - if (channels == 3) - writeValue(file, 0x00deu); // Offset of 0.f, 0.f, 0.f + if (channels > 1) + writeValue(file, 0x00d2u+channels*4); // Offset of 0.f, 0.f, ... else writeValue(file, 0.f); // SMaxSampleValue writeValue(file, 0x0155u); writeValue(file, 0x000bu); writeValue(file, channels); - if (channels == 3) - writeValue(file, 0x00eau); // Offset of 1.f, 1.f, 1.f + if (channels > 1) + writeValue(file, 0x00d2u+channels*8); // Offset of 1.f, 1.f, ... else writeValue(file, 1.f); // Offset = 0x00be writeValue(file, 0); - if (channels == 3) { + if (channels > 1) { // 0x00c2 BitsPerSample data - writeValue(file, 32); - writeValue(file, 32); - writeValue(file, 32); - // 0x00c8 XResolution data + writeValueRepeated(file, 32, channels); + // 0x00c2 + 2*N XResolution data writeValue(file, 300); writeValue(file, 1); - // 0x00d0 YResolution data + // 0x00ca + 2*N YResolution data writeValue(file, 300); writeValue(file, 1); - // 0x00d8 SampleFormat data - writeValue(file, 3); - writeValue(file, 3); - writeValue(file, 3); - // 0x00de SMinSampleValue data - writeValue(file, 0.f); - writeValue(file, 0.f); - writeValue(file, 0.f); - // 0x00ea SMaxSampleValue data - writeValue(file, 1.f); - writeValue(file, 1.f); - writeValue(file, 1.f); - // Offset = 0x00f6 + // 0x00d2 + 2*N SampleFormat data + writeValueRepeated(file, 3, channels); + // 0x00d2 + 4*N SMinSampleValue data + writeValueRepeated(file, 0.f, channels); + // 0x00d2 + 8*N SMaxSampleValue data + writeValueRepeated(file, 1.f, channels); + // Offset = 0x00d2 + 12*N } else { // 0x00c2 XResolution data writeValue(file, 300); @@ -169,24 +166,25 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) { return true; } +template +bool saveTiffFloat(const BitmapConstRef &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + writeTiffHeader(file, bitmap.width, bitmap.height, N); + for (int y = bitmap.height-1; y >= 0; --y) + fwrite(bitmap(0, y), sizeof(float), N*bitmap.width, file); + return !fclose(file); +} + bool saveTiff(const BitmapConstRef &bitmap, const char *filename) { - FILE *file = fopen(filename, "wb"); - if (!file) - return false; - writeTiffHeader(file, bitmap.width, bitmap.height, 1); - for (int y = bitmap.height-1; y >= 0; --y) - fwrite(bitmap(0, y), sizeof(float), bitmap.width, file); - return !fclose(file); + return saveTiffFloat(bitmap, filename); } - bool saveTiff(const BitmapConstRef &bitmap, const char *filename) { - FILE *file = fopen(filename, "wb"); - if (!file) - return false; - writeTiffHeader(file, bitmap.width, bitmap.height, 3); - for (int y = bitmap.height-1; y >= 0; --y) - fwrite(bitmap(0, y), sizeof(float), 3*bitmap.width, file); - return !fclose(file); + return saveTiffFloat(bitmap, filename); +} +bool saveTiff(const BitmapConstRef &bitmap, const char *filename) { + return saveTiffFloat(bitmap, filename); } } diff --git a/core/save-tiff.h b/core/save-tiff.h index f3c7e27..072cd71 100644 --- a/core/save-tiff.h +++ b/core/save-tiff.h @@ -8,5 +8,6 @@ namespace msdfgen { /// Saves the bitmap as an uncompressed floating-point TIFF file. bool saveTiff(const BitmapConstRef &bitmap, const char *filename); bool saveTiff(const BitmapConstRef &bitmap, const char *filename); +bool saveTiff(const BitmapConstRef &bitmap, const char *filename); } diff --git a/ext/save-png.cpp b/ext/save-png.cpp index b8a1759..48e9f5f 100644 --- a/ext/save-png.cpp +++ b/ext/save-png.cpp @@ -21,6 +21,13 @@ bool savePng(const BitmapConstRef &bitmap, const char *filename) { return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB); } +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + std::vector pixels(4*bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[4*bitmap.width*y], bitmap(0, bitmap.height-y-1), 4*bitmap.width); + return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + bool savePng(const BitmapConstRef &bitmap, const char *filename) { std::vector pixels(bitmap.width*bitmap.height); std::vector::iterator it = pixels.begin(); @@ -42,4 +49,17 @@ bool savePng(const BitmapConstRef &bitmap, const char *filename) { return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB); } +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + std::vector pixels(4*bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) { + *it++ = pixelFloatToByte(bitmap(x, y)[0]); + *it++ = pixelFloatToByte(bitmap(x, y)[1]); + *it++ = pixelFloatToByte(bitmap(x, y)[2]); + *it++ = pixelFloatToByte(bitmap(x, y)[3]); + } + return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + } diff --git a/ext/save-png.h b/ext/save-png.h index c0c2743..483b963 100644 --- a/ext/save-png.h +++ b/ext/save-png.h @@ -8,7 +8,9 @@ namespace msdfgen { /// Saves the bitmap as a PNG file. bool savePng(const BitmapConstRef &bitmap, const char *filename); bool savePng(const BitmapConstRef &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); bool savePng(const BitmapConstRef &bitmap, const char *filename); bool savePng(const BitmapConstRef &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); } diff --git a/main.cpp b/main.cpp index 24ebb9e..7b61562 100644 --- a/main.cpp +++ b/main.cpp @@ -265,9 +265,10 @@ static const char *helpText = " \n" "\n" "MODES\n" - " sdf - Generate conventional monochrome signed distance field.\n" + " sdf - Generate conventional monochrome (true) signed distance field.\n" " psdf - Generate monochrome signed pseudo-distance field.\n" " msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n" + " mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n" " metrics - Report shape metrics only.\n" "\n" "INPUT SPECIFICATION\n" @@ -355,6 +356,7 @@ int main(int argc, const char * const *argv) { SINGLE, PSEUDO, MULTI, + MULTI_AND_TRUE, METRICS } mode = MULTI; bool legacyMode = false; @@ -385,7 +387,7 @@ int main(int argc, const char * const *argv) { Vector2 scale = 1; bool scaleSpecified = false; double angleThreshold = 3; - double edgeThreshold = 1.001; + double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD; bool defEdgeAssignment = true; const char *edgeAssignment = NULL; bool yFlip = false; @@ -410,6 +412,7 @@ int main(int argc, const char * const *argv) { ARG_MODE("sdf", SINGLE) ARG_MODE("psdf", PSEUDO) ARG_MODE("msdf", MULTI) + ARG_MODE("mtsdf", MULTI_AND_TRUE) ARG_MODE("metrics", METRICS) ARG_CASE("-svg", 1) { @@ -658,6 +661,8 @@ int main(int argc, const char * const *argv) { double glyphAdvance = 0; if (!inputType || !input) ABORT("No input specified! Use either -svg or -font , or see -help."); + if (mode == MULTI_AND_TRUE && (format == BMP || format == AUTO && output && cmpExtension(output, ".bmp"))) + ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode."); Shape shape; switch (inputType) { case SVG: { @@ -782,6 +787,7 @@ int main(int argc, const char * const *argv) { // Compute output Bitmap sdf; Bitmap msdf; + Bitmap mtsdf; switch (mode) { case SINGLE: { sdf = Bitmap(width, height); @@ -811,6 +817,18 @@ int main(int argc, const char * const *argv) { generateMSDF(msdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport); break; } + case MULTI_AND_TRUE: { + if (!skipColoring) + edgeColoringSimple(shape, angleThreshold, coloringSeed); + if (edgeAssignment) + parseColoring(shape, edgeAssignment); + mtsdf = Bitmap(width, height); + if (legacyMode) + generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold); + else + generateMTSDF(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport); + break; + } default:; } @@ -836,6 +854,9 @@ int main(int argc, const char * const *argv) { case MULTI: invertColor<3>(msdf); break; + case MULTI_AND_TRUE: + invertColor<4>(mtsdf); + break; default:; } } @@ -850,6 +871,11 @@ int main(int argc, const char * const *argv) { if (edgeThreshold > 0) msdfErrorCorrection(msdf, edgeThreshold/(scale*range)); break; + case MULTI_AND_TRUE: + distanceSignCorrection(mtsdf, shape, scale, translate, fillRule); + if (edgeThreshold > 0) + msdfErrorCorrection(mtsdf, edgeThreshold/(scale*range)); + break; default:; } } @@ -912,6 +938,29 @@ int main(int argc, const char * const *argv) { ABORT("Failed to write test render file."); } break; + case MULTI_AND_TRUE: + error = writeOutput<4>(mtsdf, output, format); + if (error) + ABORT(error); + if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError)) + simulate8bit(mtsdf); + if (estimateError) { + double sdfError = estimateSDFError(mtsdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule); + printf("SDF error ~ %e\n", sdfError); + } + if (testRenderMulti) { + Bitmap render(testWidthM, testHeightM); + renderSDF(render, mtsdf, avgScale*range); + if (!savePng(render, testRenderMulti)) + puts("Failed to write test render file."); + } + if (testRender) { + Bitmap render(testWidth, testHeight); + renderSDF(render, mtsdf, avgScale*range); + if (!savePng(render, testRender)) + ABORT("Failed to write test render file."); + } + break; default:; } diff --git a/msdfgen.h b/msdfgen.h index b6a097d..96bbc8c 100644 --- a/msdfgen.h +++ b/msdfgen.h @@ -31,6 +31,7 @@ #include "core/shape-description.h" #define MSDFGEN_VERSION "1.6" +#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001 namespace msdfgen { @@ -41,14 +42,19 @@ void generateSDF(const BitmapRef &output, const Shape &shape, double r void generatePseudoSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); /// Generates a multi-channel signed distance field. Edge colors must be assigned first! (See edgeColoringSimple) -void generateMSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001, bool overlapSupport = true); +void generateMSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true); + +/// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first. +void generateMTSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true); /// Resolves multi-channel signed distance field values that may cause interpolation artifacts. (Already called by generateMSDF) void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold); +void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold); // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours. void generateSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); void generatePseudoSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); -void generateMSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001); +void generateMSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD); +void generateMTSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD); }