Added MTSDF mode

This commit is contained in:
Viktor Chlumský 2020-03-06 17:09:59 +01:00
parent b9d6f0bb72
commit e93c8d988c
19 changed files with 354 additions and 91 deletions

View File

@ -43,9 +43,10 @@ msdfgen.exe <mode> <input> <options>
where only the input specification is required.
Mode can be one of:
- **sdf** &ndash; generates a conventional monochrome signed distance field.
- **sdf** &ndash; generates a conventional monochrome (true) signed distance field.
- **psdf** &ndash; generates a monochrome signed pseudo-distance field.
- **msdf** (default) &ndash; generates a multi-channel signed distance field using my new method.
- **mtsdf** &ndash; generates a combined multi-channel and true signed distance field in the alpha channel.
The input can be specified as one of:
- **-font \<filename.ttf\> \<character code\>** &ndash; to load a glyph from a font file.

View File

@ -44,6 +44,7 @@ typename SimpleContourCombiner<EdgeSelector>::DistanceType SimpleContourCombiner
template class SimpleContourCombiner<TrueDistanceSelector>;
template class SimpleContourCombiner<PseudoDistanceSelector>;
template class SimpleContourCombiner<MultiDistanceSelector>;
template class SimpleContourCombiner<MultiAndTrueDistanceSelector>;
template <class EdgeSelector>
OverlappingContourCombiner<EdgeSelector>::OverlappingContourCombiner(const Shape &shape) {
@ -118,5 +119,6 @@ typename OverlappingContourCombiner<EdgeSelector>::DistanceType OverlappingConto
template class OverlappingContourCombiner<TrueDistanceSelector>;
template class OverlappingContourCombiner<PseudoDistanceSelector>;
template class OverlappingContourCombiner<MultiDistanceSelector>;
template class OverlappingContourCombiner<MultiAndTrueDistanceSelector>;
}

View File

@ -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;
}
}

View File

@ -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;
};
}

View File

@ -45,7 +45,8 @@ void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vect
#endif
}
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
template <int N>
void scanlineMSDF(Scanline &line, const BitmapConstRef<float, N> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
if (!(sdf.width > 0 && sdf.height > 0))
return line.setIntersections(std::vector<Scanline::Intersection>());
double pixelY = clamp(scale.x*(y+translate.y)-.5, double(sdf.height-1));
@ -123,46 +124,43 @@ void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vect
#endif
}
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y);
}
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y);
}
template <int N>
double estimateSDFErrorInner(const BitmapConstRef<float, N> &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<float, 1> &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<float, 3> &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<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule);
}
}

View File

@ -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<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &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<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
}

View File

@ -30,6 +30,18 @@ public:
}
};
template <>
class DistancePixelConversion<MultiAndTrueDistance> {
public:
typedef BitmapRef<float, 4> 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 <class ContourCombiner>
void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::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<float, 3> &output, const Shape &shape, double
msdfErrorCorrection(output, edgeThreshold/(scale*range));
}
void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
if (overlapSupport)
generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, range, scale, translate);
else
generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(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<float, 3> &output, const Vector2 &threshold) {
template <int N>
void msdfErrorCorrectionInner(const BitmapRef<float, N> &output, const Vector2 &threshold) {
std::vector<std::pair<int, int> > clashes;
int w = output.width, h = output.height;
for (int y = 0; y < h; ++y)
@ -156,6 +178,13 @@ void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &thres
#endif
}
void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
msdfErrorCorrectionInner(output, threshold);
}
void msdfErrorCorrection(const BitmapRef<float, 4> &output, const Vector2 &threshold) {
msdfErrorCorrectionInner(output, threshold);
}
// Legacy version
void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
@ -261,4 +290,62 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
msdfErrorCorrection(output, edgeThreshold/(scale*range));
}
void generateMTSDF_legacy(const BitmapRef<float, 4> &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<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
for (std::vector<EdgeHolder>::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));
}
}

View File

@ -38,7 +38,8 @@ void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape,
}
}
void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
template <int N>
static void multiDistanceSignCorrection(const BitmapRef<float, N> &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<float, 3> &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<float, 3> &sdf, const Shape &shape,
}
}
void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule);
}
void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule);
}
}

View File

@ -13,5 +13,6 @@ void rasterize(const BitmapRef<float, 1> &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<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
}

View File

@ -73,6 +73,29 @@ void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3>
}
}
void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &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<float, 4> &output, const BitmapConstRef<float, 4> &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<float, 1> &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<float, 3> &bitmap) {
*p = pixelByteToFloat(pixelFloatToByte(*p));
}
void simulate8bit(const BitmapRef<float, 4> &bitmap) {
const float *end = bitmap.pixels+4*bitmap.width*bitmap.height;
for (float *p = bitmap.pixels; p < end; ++p)
*p = pixelByteToFloat(pixelFloatToByte(*p));
}
}

View File

@ -11,9 +11,12 @@ void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1>
void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &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<float, 1> &bitmap);
void simulate8bit(const BitmapRef<float, 3> &bitmap);
void simulate8bit(const BitmapRef<float, 4> &bitmap);
}

View File

@ -108,6 +108,11 @@ bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
return !fclose(file);
}
bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
// RGBA not supported by the BMP format
return false;
}
bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
FILE *file = fopen(filename, "wb");
if (!file)
@ -156,4 +161,9 @@ bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
return !fclose(file);
}
bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
// RGBA not supported by the BMP format
return false;
}
}

View File

@ -8,7 +8,9 @@ namespace msdfgen {
/// Saves the bitmap as a BMP file.
bool saveBmp(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename);
}

View File

@ -20,6 +20,11 @@ template <typename T>
static bool writeValue(FILE *file, T value) {
return fwrite(&value, sizeof(T), 1, file) == 1;
}
template <typename T>
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<uint16_t>(file, 0x0102u);
writeValue<uint16_t>(file, 0x0003u);
writeValue<uint32_t>(file, channels);
if (channels == 3)
writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, 32
if (channels > 1)
writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, ...
else {
writeValue<uint16_t>(file, 32);
writeValue<uint16_t>(file, 0);
@ -63,13 +68,13 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
writeValue<uint16_t>(file, 0x0106u);
writeValue<uint16_t>(file, 0x0003u);
writeValue<uint32_t>(file, 1);
writeValue<uint16_t>(file, channels == 3 ? 2 : 1);
writeValue<uint16_t>(file, channels >= 3 ? 2 : 1);
writeValue<uint16_t>(file, 0);
// StripOffsets
writeValue<uint16_t>(file, 0x0111u);
writeValue<uint16_t>(file, 0x0004u);
writeValue<uint32_t>(file, 1);
writeValue<uint32_t>(file, channels == 3 ? 0x00f6u : 0x00d2u); // Offset of pixel data
writeValue<uint32_t>(file, 0x00d2u+(channels > 1)*channels*12); // Offset of pixel data
// SamplesPerPixel
writeValue<uint16_t>(file, 0x0115u);
writeValue<uint16_t>(file, 0x0003u);
@ -90,12 +95,12 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
writeValue<uint16_t>(file, 0x011au);
writeValue<uint16_t>(file, 0x0005u);
writeValue<uint32_t>(file, 1);
writeValue<uint32_t>(file, channels == 3 ? 0x00c8u : 0x00c2u); // Offset of 300, 1
writeValue<uint32_t>(file, 0x00c2u+(channels > 1)*channels*2); // Offset of 300, 1
// YResolution
writeValue<uint16_t>(file, 0x011bu);
writeValue<uint16_t>(file, 0x0005u);
writeValue<uint32_t>(file, 1);
writeValue<uint32_t>(file, channels == 3 ? 0x00d0u : 0x00cau); // Offset of 300, 1
writeValue<uint32_t>(file, 0x00cau+(channels > 1)*channels*2); // Offset of 300, 1
// ResolutionUnit
writeValue<uint16_t>(file, 0x0128u);
writeValue<uint16_t>(file, 0x0003u);
@ -106,8 +111,8 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
writeValue<uint16_t>(file, 0x0153u);
writeValue<uint16_t>(file, 0x0003u);
writeValue<uint32_t>(file, channels);
if (channels == 3)
writeValue<uint32_t>(file, 0x00d8u); // Offset of 3, 3, 3
if (channels > 1)
writeValue<uint32_t>(file, 0x00d2u+channels*2); // Offset of 3, 3, ...
else {
writeValue<uint16_t>(file, 3);
writeValue<uint16_t>(file, 0);
@ -116,46 +121,38 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
writeValue<uint16_t>(file, 0x0154u);
writeValue<uint16_t>(file, 0x000bu);
writeValue<uint32_t>(file, channels);
if (channels == 3)
writeValue<uint32_t>(file, 0x00deu); // Offset of 0.f, 0.f, 0.f
if (channels > 1)
writeValue<uint32_t>(file, 0x00d2u+channels*4); // Offset of 0.f, 0.f, ...
else
writeValue<float>(file, 0.f);
// SMaxSampleValue
writeValue<uint16_t>(file, 0x0155u);
writeValue<uint16_t>(file, 0x000bu);
writeValue<uint32_t>(file, channels);
if (channels == 3)
writeValue<uint32_t>(file, 0x00eau); // Offset of 1.f, 1.f, 1.f
if (channels > 1)
writeValue<uint32_t>(file, 0x00d2u+channels*8); // Offset of 1.f, 1.f, ...
else
writeValue<float>(file, 1.f);
// Offset = 0x00be
writeValue<uint32_t>(file, 0);
if (channels == 3) {
if (channels > 1) {
// 0x00c2 BitsPerSample data
writeValue<uint16_t>(file, 32);
writeValue<uint16_t>(file, 32);
writeValue<uint16_t>(file, 32);
// 0x00c8 XResolution data
writeValueRepeated<uint16_t>(file, 32, channels);
// 0x00c2 + 2*N XResolution data
writeValue<uint32_t>(file, 300);
writeValue<uint32_t>(file, 1);
// 0x00d0 YResolution data
// 0x00ca + 2*N YResolution data
writeValue<uint32_t>(file, 300);
writeValue<uint32_t>(file, 1);
// 0x00d8 SampleFormat data
writeValue<uint16_t>(file, 3);
writeValue<uint16_t>(file, 3);
writeValue<uint16_t>(file, 3);
// 0x00de SMinSampleValue data
writeValue<float>(file, 0.f);
writeValue<float>(file, 0.f);
writeValue<float>(file, 0.f);
// 0x00ea SMaxSampleValue data
writeValue<float>(file, 1.f);
writeValue<float>(file, 1.f);
writeValue<float>(file, 1.f);
// Offset = 0x00f6
// 0x00d2 + 2*N SampleFormat data
writeValueRepeated<uint16_t>(file, 3, channels);
// 0x00d2 + 4*N SMinSampleValue data
writeValueRepeated<float>(file, 0.f, channels);
// 0x00d2 + 8*N SMaxSampleValue data
writeValueRepeated<float>(file, 1.f, channels);
// Offset = 0x00d2 + 12*N
} else {
// 0x00c2 XResolution data
writeValue<uint32_t>(file, 300);
@ -169,24 +166,25 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
return true;
}
template <int N>
bool saveTiffFloat(const BitmapConstRef<float, N> &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<float, 1> &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<float, 3> &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<float, 4> &bitmap, const char *filename) {
return saveTiffFloat(bitmap, filename);
}
}

View File

@ -8,5 +8,6 @@ namespace msdfgen {
/// Saves the bitmap as an uncompressed floating-point TIFF file.
bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename);
bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename);
}

View File

@ -21,6 +21,13 @@ bool savePng(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB);
}
bool savePng(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
std::vector<byte> 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<float, 1> &bitmap, const char *filename) {
std::vector<byte> pixels(bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
@ -42,4 +49,17 @@ bool savePng(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB);
}
bool savePng(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
std::vector<byte> pixels(4*bitmap.width*bitmap.height);
std::vector<byte>::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);
}
}

View File

@ -8,7 +8,9 @@ namespace msdfgen {
/// Saves the bitmap as a PNG file.
bool savePng(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<float, 3> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<float, 4> &bitmap, const char *filename);
}

View File

@ -265,9 +265,10 @@ static const char *helpText =
" <mode> <input specification> <options>\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 <file.svg> or -font <file.ttf/otf> <character code>, 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<float, 1> sdf;
Bitmap<float, 3> msdf;
Bitmap<float, 4> mtsdf;
switch (mode) {
case SINGLE: {
sdf = Bitmap<float, 1>(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<float, 4>(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<float, 4> render(testWidthM, testHeightM);
renderSDF(render, mtsdf, avgScale*range);
if (!savePng(render, testRenderMulti))
puts("Failed to write test render file.");
}
if (testRender) {
Bitmap<float, 1> render(testWidth, testHeight);
renderSDF(render, mtsdf, avgScale*range);
if (!savePng(render, testRender))
ABORT("Failed to write test render file.");
}
break;
default:;
}

View File

@ -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<float, 1> &output, const Shape &shape, double r
void generatePseudoSDF(const BitmapRef<float, 1> &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<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001, bool overlapSupport = true);
void generateMSDF(const BitmapRef<float, 3> &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<float, 4> &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<float, 3> &output, const Vector2 &threshold);
void msdfErrorCorrection(const BitmapRef<float, 4> &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<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001);
void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
}