Skip to content

Commit

Permalink
Skia integration - preprocessing of shape geometry
Browse files Browse the repository at this point in the history
  • Loading branch information
Chlumsky committed Oct 17, 2020
1 parent fe910c8 commit 9d22335
Show file tree
Hide file tree
Showing 294 changed files with 51,516 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
include/** linguist-vendored
lib/** linguist-vendored
freetype/** linguist-vendored
skia/** linguist-vendored
20 changes: 11 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Debug/
Release/
Release OpenMP/
Debug Library/
Release Library/
Release Library OpenMP/
x86/
x64/
/Debug/
/Release/
/Release OpenMP/
/Debug Library/
/Release Library/
/Release Library OpenMP/
/x86/
/x64/
.vs/
*.exe
*.zip
Expand All @@ -17,9 +17,11 @@ x64/
*.suo
*.VC.opendb
*.VC.db
bin/*.lib
/bin/*.lib
output.png
render.png
out/
build/
build_xcode/
skia/win32/
skia/win64/
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@

## Version 1.8 (2020-10-17)

- Integrated the Skia library into the project, which is used to preprocess the shape geometry and eliminate any self-intersections and other irregularities previously unsupported by the software
- The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively.
- The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA`
- Significantly improved performance of the core algorithm by reusing results from previously computed pixels
- Introduced an additional error correction routine which eliminates MSDF artifacts by analytically predicting results of bilinear interpolation
- Added the possibility to load font glyphs by their index rather than a Unicode value (use the prefix `g` before the character code in `-font` argument)
- Added `-distanceshift` argument that can be used to adjust the center of the distance range in the output distance field
- Fixed several errors in the evaluation of curve distances
- Fixed an issue with paths containing convergent corners (those whose inner angle is zero)
- The algorithm for pseudo-distance computation slightly changed, fixing certain rare edge cases and improving consistency
- Added the ability to supply own `FT_Face` handle to the msdfgen library
- Minor refactor of the core algorithm

### Version 1.7.1 (2020-03-09)

- Fixed an edge case bug in scanline rasterization

## Version 1.7 (2020-03-07)

- Added `mtsdf` mode - a combination of `msdf` with `sdf` in the alpha channel
Expand Down
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
cmake_minimum_required(VERSION 3.10)

project(msdfgen VERSION 1.7.1 LANGUAGES CXX)
project(msdfgen VERSION 1.8 LANGUAGES CXX)
option(MSDFGEN_BUILD_MSDFGEN_STANDALONE "Build the msdfgen standalone executable" ON)
option(MSDFGEN_USE_OPENMP "Build with OpenMP support for multithreaded code" OFF)
option(MSDFGEN_USE_CPP11 "Build with C++11 enabled" ON)
option(MSDFGEN_USE_SKIA "Build with the Skia library" OFF)
option(FREETYPE_WITH_PNG "Link libpng and zlib because FreeType is configured to require it" OFF)
option(FREETYPE_WITH_HARFBUZZ "Link HarfBuzz because FreeType is configured to require it" OFF)

Expand Down Expand Up @@ -71,6 +72,12 @@ if(MSDFGEN_USE_OPENMP)
target_compile_definitions(msdfgen PRIVATE MSDFGEN_USE_OPENMP)
endif()

if(MSDFGEN_USE_SKIA)
find_package(Skia REQUIRED)
target_link_libraries(msdfgen-ext PRIVATE Skia::Skia)
target_compile_definitions(msdfgen-ext PUBLIC MSDFGEN_USE_SKIA)
endif()

add_library(msdfgen-ext ${msdfgen-ext_SOURCES} ${msdfgen-ext_PUBLIC_HEADERS} ${msdfgen-ext_PRIVATE_HEADERS} "./msdfgen-ext.h")
add_library(msdfgen::msdfgen-ext ALIAS msdfgen-ext)
set_target_properties(msdfgen-ext PROPERTIES
Expand Down
Binary file added Msdfgen.aps
Binary file not shown.
Binary file modified Msdfgen.rc
Binary file not shown.
74 changes: 38 additions & 36 deletions Msdfgen.vcxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Msdfgen.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<ClInclude Include="core\msdf-edge-artifact-patcher.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="ext\resolve-shape-geometry.h">
<Filter>Extensions</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
Expand Down Expand Up @@ -200,6 +203,9 @@
<ClCompile Include="core\msdf-edge-artifact-patcher.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="ext\resolve-shape-geometry.cpp">
<Filter>Extensions</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Msdfgen.rc">
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ Extensions contain utilities for loading fonts and SVG files, as well as saving
Those are exposed by the [msdfgen-ext.h](msdfgen-ext.h) header. This module uses
[FreeType](http://www.freetype.org/),
[TinyXML2](http://www.grinninglizard.com/tinyxml2/),
and [LodePNG](http://lodev.org/lodepng/).
[LodePNG](http://lodev.org/lodepng/),
and (optionally) [Skia](https://skia.org/).

Additionally, there is the [main.cpp](main.cpp), which wraps the functionality into
a comprehensive standalone console program. To start using the program immediately,
Expand Down
89 changes: 89 additions & 0 deletions ext/resolve-shape-geometry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

#include "resolve-shape-geometry.h"

#ifdef MSDFGEN_USE_SKIA

#include <core/SkPath.h>
#include <pathops/SkPathOps.h>
#include "../core/Vector2.h"
#include "../core/edge-segments.h"
#include "../core/Contour.h"

#ifdef _WIN32
#pragma comment(lib, "skia.lib")
#endif

namespace msdfgen {

SkPoint pointToSkiaPoint(Point2 p) {
return SkPoint::Make((SkScalar) p.x, (SkScalar) p.y);
}

Point2 pointFromSkiaPoint(const SkPoint p) {
return Point2((double) p.x(), (double) p.y());
}

void shapeToSkiaPath(SkPath &skPath, const Shape &shape) {
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
if (!contour->edges.empty()) {
skPath.moveTo(pointToSkiaPoint(contour->edges.front()->point(0)));
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
{
const LinearSegment *linearSegment = dynamic_cast<const LinearSegment *>(&**edge);
if (linearSegment)
skPath.lineTo(pointToSkiaPoint(linearSegment->p[1]));
} {
const QuadraticSegment *quadraticSegment = dynamic_cast<const QuadraticSegment *>(&**edge);
if (quadraticSegment)
skPath.quadTo(pointToSkiaPoint(quadraticSegment->p[1]), pointToSkiaPoint(quadraticSegment->p[2]));
} {
const CubicSegment *cubicSegment = dynamic_cast<const CubicSegment *>(&**edge);
if (cubicSegment)
skPath.cubicTo(pointToSkiaPoint(cubicSegment->p[1]), pointToSkiaPoint(cubicSegment->p[2]), pointToSkiaPoint(cubicSegment->p[3]));
}
}
}
}
}

void shapeFromSkiaPath(Shape &shape, const SkPath &skPath) {
shape.contours.clear();
Contour *contour = &shape.addContour();
SkPath::Iter pathIterator(skPath, true);
SkPoint edgePoints[4];
for (SkPath::Verb op; (op = pathIterator.next(edgePoints)) != SkPath::kDone_Verb;) {
switch (op) {
case SkPath::kMove_Verb:
if (!contour->edges.empty())
contour = &shape.addContour();
break;
case SkPath::kLine_Verb:
contour->addEdge(new LinearSegment(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1])));
break;
case SkPath::kQuad_Verb:
contour->addEdge(new QuadraticSegment(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1]), pointFromSkiaPoint(edgePoints[2])));
break;
case SkPath::kCubic_Verb:
contour->addEdge(new CubicSegment(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1]), pointFromSkiaPoint(edgePoints[2]), pointFromSkiaPoint(edgePoints[3])));
break;
default:;
}
}
if (contour->edges.empty())
shape.contours.pop_back();
}

bool resolveShapeGeometry(Shape &shape) {
SkPath skPath;
shapeToSkiaPath(skPath, shape);
if (!Simplify(skPath, &skPath))
return false;
// Skia's AsWinding doesn't seem to work for unknown reasons
shapeFromSkiaPath(shape, skPath);
shape.orientContours();
return true;
}

}

#endif
15 changes: 15 additions & 0 deletions ext/resolve-shape-geometry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

#pragma once

#include "../core/Shape.h"

#ifdef MSDFGEN_USE_SKIA

namespace msdfgen {

/// Resolves any intersections within the shape by subdividing its contours using the Skia library and makes sure its contours have a consistent winding.
bool resolveShapeGeometry(Shape &shape);

}

#endif
74 changes: 67 additions & 7 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.7 (2020-03-07) - standalone console program
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.8 (2020-10-17) - standalone console program
* --------------------------------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2014 - 2020
*
Expand Down Expand Up @@ -252,10 +252,24 @@ static const char * writeOutput(const BitmapConstRef<float, N> &bitmap, const ch
return NULL;
}

#if defined(MSDFGEN_USE_SKIA) && defined(MSDFGEN_USE_OPENMP)
#define TITLE_SUFFIX " with Skia & OpenMP"
#define EXTRA_UNDERLINE "-------------------"
#elif defined(MSDFGEN_USE_SKIA)
#define TITLE_SUFFIX " with Skia"
#define EXTRA_UNDERLINE "----------"
#elif defined(MSDFGEN_USE_OPENMP)
#define TITLE_SUFFIX " with OpenMP"
#define EXTRA_UNDERLINE "------------"
#else
#define TITLE_SUFFIX
#define EXTRA_UNDERLINE
#endif

static const char *helpText =
"\n"
"Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION "\n"
"---------------------------------------------------------------------\n"
"Multi-channel signed distance field generator by Viktor Chlumsky v" MSDFGEN_VERSION TITLE_SUFFIX "\n"
"---------------------------------------------------------------------" EXTRA_UNDERLINE "\n"
" Usage: msdfgen"
#ifdef _WIN32
".exe"
Expand All @@ -273,14 +287,16 @@ static const char *helpText =
" -defineshape <definition>\n"
"\tDefines input shape using the ad-hoc text definition.\n"
" -font <filename.ttf> <character code>\n"
"\tLoads a single glyph from the specified font file. Format of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
"\tLoads a single glyph from the specified font file.\n"
"\tFormat of character code is '?', 63, 0x3F (Unicode value), or g34 (glyph index).\n"
" -shapedesc <filename.txt>\n"
"\tLoads text shape description from a file.\n"
" -stdin\n"
"\tReads text shape description from the standard input.\n"
" -svg <filename.svg>\n"
"\tLoads the last vector path found in the specified SVG file.\n"
"\n"
// Keep alphabetical order!
"OPTIONS\n"
" -angle <angle>\n"
"\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
Expand Down Expand Up @@ -310,22 +326,35 @@ static const char *helpText =
"\tDisplays this help.\n"
" -legacy\n"
"\tUses the original (legacy) distance field algorithms.\n"
#ifdef MSDFGEN_USE_SKIA
" -nopreprocess\n"
"\tDisables path preprocessing which resolves self-intersections and overlapping contours.\n"
#else
" -nooverlap\n"
"\tDisables resolution of overlapping contours.\n"
" -noscanline\n"
"\tDisables the scanline pass, which corrects the distance field's signs according to the selected fill rule.\n"
#endif
" -o <filename>\n"
"\tSets the output file name. The default value is \"output.png\".\n"
#ifdef MSDFGEN_USE_SKIA
" -overlap\n"
"\tSwitches to distance field generator with support for overlapping contours.\n"
#endif
" -printmetrics\n"
"\tPrints relevant metrics of the shape to the standard output.\n"
" -pxrange <range>\n"
"\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"
"\tGenerates the distance field as if shape vertices were in reverse order.\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"
#ifdef MSDFGEN_USE_SKIA
" -scanline\n"
"\tPerforms an additional scanline pass to fix the signs of the distances.\n"
#endif
" -seed <n>\n"
"\tSets the random seed for edge coloring heuristic.\n"
" -size <width> <height>\n"
Expand Down Expand Up @@ -362,8 +391,15 @@ int main(int argc, const char * const *argv) {
METRICS
} mode = MULTI;
bool legacyMode = false;
bool overlapSupport = true;
bool scanlinePass = true;
bool geometryPreproc = (
#ifdef MSDFGEN_USE_SKIA
true
#else
false
#endif
);
bool overlapSupport = !geometryPreproc;
bool scanlinePass = !geometryPreproc;
FillRule fillRule = FILL_NONZERO;
Format format = AUTO;
const char *input = NULL;
Expand Down Expand Up @@ -477,11 +513,26 @@ int main(int argc, const char * const *argv) {
argPos += 1;
continue;
}
ARG_CASE("-nopreprocess", 0) {
geometryPreproc = false;
argPos += 1;
continue;
}
ARG_CASE("-preprocess", 0) {
geometryPreproc = true;
argPos += 1;
continue;
}
ARG_CASE("-nooverlap", 0) {
overlapSupport = false;
argPos += 1;
continue;
}
ARG_CASE("-overlap", 0) {
overlapSupport = true;
argPos += 1;
continue;
}
ARG_CASE("-noscanline", 0) {
scanlinePass = false;
argPos += 1;
Expand All @@ -493,6 +544,7 @@ int main(int argc, const char * const *argv) {
continue;
}
ARG_CASE("-fillrule", 1) {
scanlinePass = true;
if (!strcmp(argv[argPos+1], "nonzero")) fillRule = FILL_NONZERO;
else if (!strcmp(argv[argPos+1], "evenodd") || !strcmp(argv[argPos+1], "odd")) fillRule = FILL_ODD;
else if (!strcmp(argv[argPos+1], "positive")) fillRule = FILL_POSITIVE;
Expand Down Expand Up @@ -749,6 +801,14 @@ int main(int argc, const char * const *argv) {
// Validate and normalize shape
if (!shape.validate())
ABORT("The geometry of the loaded shape is invalid.");
if (geometryPreproc) {
#ifdef MSDFGEN_USE_SKIA
if (!resolveShapeGeometry(shape))
puts("Shape geometry preprocessing failed, skipping.");
#else
ABORT("Shape geometry preprocessing (-preprocess) is not available in this version because the Skia library is not present.");
#endif
}
shape.normalize();
if (yFlip)
shape.inverseYAxis = !shape.inverseYAxis;
Expand Down
Loading

0 comments on commit 9d22335

Please sign in to comment.