From cad221afcd438842ed73814cc1ee1c3a7f4fe300 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Sun, 8 Mar 2020 11:26:47 +0100 Subject: [PATCH] Version 1.0 --- Makefile | 4 + icon.ico | Bin 0 -> 32038 bytes msdf-atlas-gen.rc | Bin 0 -> 3250 bytes msdf-atlas-gen.sln | 61 ++ msdf-atlas-gen.vcxproj | 360 ++++++++++ msdf-atlas-gen.vcxproj.filters | 178 +++++ msdf-atlas-gen/AtlasGenerator.h | 43 ++ msdf-atlas-gen/AtlasStorage.h | 37 + msdf-atlas-gen/BitmapAtlasStorage.h | 33 + msdf-atlas-gen/BitmapAtlasStorage.hpp | 65 ++ msdf-atlas-gen/Charset.cpp | 39 ++ msdf-atlas-gen/Charset.h | 35 + msdf-atlas-gen/DynamicAtlas.h | 43 ++ msdf-atlas-gen/DynamicAtlas.hpp | 69 ++ msdf-atlas-gen/GlyphBox.h | 21 + msdf-atlas-gen/GlyphGeometry.cpp | 133 ++++ msdf-atlas-gen/GlyphGeometry.h | 71 ++ msdf-atlas-gen/ImmediateAtlasGenerator.h | 44 ++ msdf-atlas-gen/ImmediateAtlasGenerator.hpp | 70 ++ msdf-atlas-gen/Rectangle.h | 14 + msdf-atlas-gen/RectanglePacker.cpp | 143 ++++ msdf-atlas-gen/RectanglePacker.h | 28 + msdf-atlas-gen/Remap.h | 15 + msdf-atlas-gen/TightAtlasPacker.cpp | 168 +++++ msdf-atlas-gen/TightAtlasPacker.h | 71 ++ msdf-atlas-gen/Workload.cpp | 49 ++ msdf-atlas-gen/Workload.h | 32 + msdf-atlas-gen/artery-font-export.cpp | 149 ++++ msdf-atlas-gen/artery-font-export.h | 15 + msdf-atlas-gen/bitmap-blit.cpp | 58 ++ msdf-atlas-gen/bitmap-blit.h | 26 + msdf-atlas-gen/charset-parser.cpp | 251 +++++++ msdf-atlas-gen/csv-export.cpp | 27 + msdf-atlas-gen/csv-export.h | 14 + msdf-atlas-gen/glyph-generators.cpp | 51 ++ msdf-atlas-gen/glyph-generators.h | 25 + msdf-atlas-gen/image-encode.cpp | 63 ++ msdf-atlas-gen/image-encode.h | 20 + msdf-atlas-gen/image-save.h | 15 + msdf-atlas-gen/image-save.hpp | 151 ++++ msdf-atlas-gen/json-export.cpp | 95 +++ msdf-atlas-gen/json-export.h | 14 + msdf-atlas-gen/main.cpp | 688 +++++++++++++++++++ msdf-atlas-gen/msdf-atlas-gen.h | 41 ++ msdf-atlas-gen/rectangle-packing.h | 19 + msdf-atlas-gen/rectangle-packing.hpp | 61 ++ msdf-atlas-gen/shadron-preview-generator.cpp | 148 ++++ msdf-atlas-gen/shadron-preview-generator.h | 14 + msdf-atlas-gen/size-selectors.cpp | 90 +++ msdf-atlas-gen/size-selectors.h | 54 ++ msdf-atlas-gen/types.h | 40 ++ msdf-atlas-gen/utf8.cpp | 38 + msdf-atlas-gen/utf8.h | 12 + resource.h | Bin 0 -> 916 bytes 54 files changed, 4005 insertions(+) create mode 100644 Makefile create mode 100644 icon.ico create mode 100644 msdf-atlas-gen.rc create mode 100644 msdf-atlas-gen.sln create mode 100644 msdf-atlas-gen.vcxproj create mode 100644 msdf-atlas-gen.vcxproj.filters create mode 100644 msdf-atlas-gen/AtlasGenerator.h create mode 100644 msdf-atlas-gen/AtlasStorage.h create mode 100644 msdf-atlas-gen/BitmapAtlasStorage.h create mode 100644 msdf-atlas-gen/BitmapAtlasStorage.hpp create mode 100644 msdf-atlas-gen/Charset.cpp create mode 100644 msdf-atlas-gen/Charset.h create mode 100644 msdf-atlas-gen/DynamicAtlas.h create mode 100644 msdf-atlas-gen/DynamicAtlas.hpp create mode 100644 msdf-atlas-gen/GlyphBox.h create mode 100644 msdf-atlas-gen/GlyphGeometry.cpp create mode 100644 msdf-atlas-gen/GlyphGeometry.h create mode 100644 msdf-atlas-gen/ImmediateAtlasGenerator.h create mode 100644 msdf-atlas-gen/ImmediateAtlasGenerator.hpp create mode 100644 msdf-atlas-gen/Rectangle.h create mode 100644 msdf-atlas-gen/RectanglePacker.cpp create mode 100644 msdf-atlas-gen/RectanglePacker.h create mode 100644 msdf-atlas-gen/Remap.h create mode 100644 msdf-atlas-gen/TightAtlasPacker.cpp create mode 100644 msdf-atlas-gen/TightAtlasPacker.h create mode 100644 msdf-atlas-gen/Workload.cpp create mode 100644 msdf-atlas-gen/Workload.h create mode 100644 msdf-atlas-gen/artery-font-export.cpp create mode 100644 msdf-atlas-gen/artery-font-export.h create mode 100644 msdf-atlas-gen/bitmap-blit.cpp create mode 100644 msdf-atlas-gen/bitmap-blit.h create mode 100644 msdf-atlas-gen/charset-parser.cpp create mode 100644 msdf-atlas-gen/csv-export.cpp create mode 100644 msdf-atlas-gen/csv-export.h create mode 100644 msdf-atlas-gen/glyph-generators.cpp create mode 100644 msdf-atlas-gen/glyph-generators.h create mode 100644 msdf-atlas-gen/image-encode.cpp create mode 100644 msdf-atlas-gen/image-encode.h create mode 100644 msdf-atlas-gen/image-save.h create mode 100644 msdf-atlas-gen/image-save.hpp create mode 100644 msdf-atlas-gen/json-export.cpp create mode 100644 msdf-atlas-gen/json-export.h create mode 100644 msdf-atlas-gen/main.cpp create mode 100644 msdf-atlas-gen/msdf-atlas-gen.h create mode 100644 msdf-atlas-gen/rectangle-packing.h create mode 100644 msdf-atlas-gen/rectangle-packing.hpp create mode 100644 msdf-atlas-gen/shadron-preview-generator.cpp create mode 100644 msdf-atlas-gen/shadron-preview-generator.h create mode 100644 msdf-atlas-gen/size-selectors.cpp create mode 100644 msdf-atlas-gen/size-selectors.h create mode 100644 msdf-atlas-gen/types.h create mode 100644 msdf-atlas-gen/utf8.cpp create mode 100644 msdf-atlas-gen/utf8.h create mode 100644 resource.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88acefb --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ + +all: + mkdir -p bin + g++ -I /usr/local/include/freetype2 -I /usr/include/freetype2 -I artery-font-format -I msdfgen/include -I msdfgen -D MSDFGEN_USE_CPP11 -D MSDF_ATLAS_STANDALONE -std=c++11 -pthread -O2 -o bin/msdf-atlas-gen msdfgen/core/*.cpp msdfgen/lib/*.cpp msdfgen/ext/*.cpp msdf-atlas-gen/*.cpp -lfreetype diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09e1a936e1b5df1fa2d35af3986f7b345047d42c GIT binary patch literal 32038 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 + + + + Debug Library + Win32 + + + Debug Library + x64 + + + Debug + Win32 + + + Release Library + Win32 + + + Release Library + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {223EDB94-5B35-45F2-A584-273DE6E45F6F} + msdfatlasgen + 8.1 + + + + Application + true + v140 + MultiByte + + + StaticLibrary + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + Application + true + v140 + MultiByte + + + StaticLibrary + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + msdf-atlas-gen + $(Configuration)\ + + + msdf-atlas-gen + $(Configuration)\ + + + msdf-atlas-gen + bin\ + + + msdf-atlas-gen + bin\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win32;msdfgen\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win32;$(SolutionDir)$(Configuration) Library;%(AdditionalLibraryDirectories) + + + MachineX86 + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win64;msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win32;msdfgen\bin;%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win32;$(SolutionDir)bin;%(AdditionalLibraryDirectories) + + + MachineX86 + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win64;msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msdf-atlas-gen.vcxproj.filters b/msdf-atlas-gen.vcxproj.filters new file mode 100644 index 0000000..e105908 --- /dev/null +++ b/msdf-atlas-gen.vcxproj.filters @@ -0,0 +1,178 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {ee785f45-c1cf-48ae-b864-f27237b077c1} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/msdf-atlas-gen/AtlasGenerator.h b/msdf-atlas-gen/AtlasGenerator.h new file mode 100644 index 0000000..71e5513 --- /dev/null +++ b/msdf-atlas-gen/AtlasGenerator.h @@ -0,0 +1,43 @@ + +#pragma once + +#include +#include "Remap.h" +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +namespace { + +/** Prototype of an atlas generator class. + * An atlas generator maintains the atlas bitmap (AtlasStorage) and its layout and facilitates + * generation of bitmap representation of glyphs. The layout of the atlas is given by the caller. + */ +class AtlasGenerator { + +public: + AtlasGenerator(); + AtlasGenerator(int width, int height); + /// Generates bitmap representation for the supplied array of glyphs + void generate(const GlyphGeometry *glyphs, int count); + /// Resizes the atlas and rearranges the generated pixels according to the remapping array + void rearrange(int width, int height, const Remap *remapping, int count); + /// Resizes the atlas and keeps the generated pixels in place + void resize(int width, int height); + +}; + +} + +/// Configuration of signed distance field generator +struct GeneratorAttributes { + bool overlapSupport = true; + bool scanlinePass = true; + double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD; +}; + +/// A function that generates the bitmap for a single glyph +template +using GeneratorFunction = void (*)(const msdfgen::BitmapRef &, const GlyphGeometry &, const GeneratorAttributes &); + +} diff --git a/msdf-atlas-gen/AtlasStorage.h b/msdf-atlas-gen/AtlasStorage.h new file mode 100644 index 0000000..a51f46a --- /dev/null +++ b/msdf-atlas-gen/AtlasStorage.h @@ -0,0 +1,37 @@ + +#pragma once + +#include +#include "Remap.h" + +namespace msdf_atlas { + +namespace { + +/** Prototype of an atlas storage class. + * An atlas storage physically holds the pixels of the atlas + * and allows to read and write subsections represented as bitmaps. + * Can be implemented using a simple bitmap (BitmapAtlasStorage), + * as texture memory, or any other way. + */ +class AtlasStorage { + +public: + AtlasStorage(); + AtlasStorage(int width, int height); + /// Creates a copy with different dimensions + AtlasStorage(const AtlasStorage &orig, int width, int height); + /// Creates a copy with different dimensions and rearranges the pixels according to the remapping array + AtlasStorage(const AtlasStorage &orig, int width, int height, const Remap *remapping, int count); + /// Stores a subsection at x, y into the atlas storage. May be implemented for only some T, N + template + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + /// Retrieves a subsection at x, y from the atlas storage. May be implemented for only some T, N + template + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +}; + +} + +} diff --git a/msdf-atlas-gen/BitmapAtlasStorage.h b/msdf-atlas-gen/BitmapAtlasStorage.h new file mode 100644 index 0000000..f0f7c99 --- /dev/null +++ b/msdf-atlas-gen/BitmapAtlasStorage.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "AtlasStorage.h" + +namespace msdf_atlas { + +/// An implementation of AtlasStorage represented by a bitmap in memory (msdfgen::Bitmap) +template +class BitmapAtlasStorage { + +public: + BitmapAtlasStorage(); + BitmapAtlasStorage(int width, int height); + explicit BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap); + explicit BitmapAtlasStorage(msdfgen::Bitmap &&bitmap); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height, const Remap *remapping, int count); + operator msdfgen::BitmapConstRef() const; + operator msdfgen::BitmapRef(); + operator msdfgen::Bitmap() &&; + template + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +private: + msdfgen::Bitmap bitmap; + +}; + +} + +#include "BitmapAtlasStorage.hpp" diff --git a/msdf-atlas-gen/BitmapAtlasStorage.hpp b/msdf-atlas-gen/BitmapAtlasStorage.hpp new file mode 100644 index 0000000..602a352 --- /dev/null +++ b/msdf-atlas-gen/BitmapAtlasStorage.hpp @@ -0,0 +1,65 @@ + +#include "BitmapAtlasStorage.h" + +#include +#include +#include "bitmap-blit.h" + +namespace msdf_atlas { + +template +BitmapAtlasStorage::BitmapAtlasStorage() { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(int width, int height) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); +} + +template +BitmapAtlasStorage::BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap) : bitmap(bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(msdfgen::Bitmap &&bitmap) : bitmap((msdfgen::Bitmap &&) bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); + blit(bitmap, orig.bitmap, 0, 0, 0, 0, std::min(width, orig.bitmap.width()), std::min(height, orig.bitmap.height())); +} + +template +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height, const Remap *remapping, int count) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); + for (int i = 0; i < count; ++i) { + const Remap &remap = remapping[i]; + blit(bitmap, orig.bitmap, remap.target.x, remap.target.y, remap.source.x, remap.source.y, remap.width, remap.height); + } +} + +template +BitmapAtlasStorage::operator msdfgen::BitmapConstRef() const { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::BitmapRef() { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::Bitmap() && { + return (msdfgen::Bitmap() &&) bitmap; +} + +template +template +void BitmapAtlasStorage::put(int x, int y, const msdfgen::BitmapConstRef &subBitmap) { + blit(bitmap, subBitmap, x, y, 0, 0, subBitmap.width, subBitmap.height); +} + +template +void BitmapAtlasStorage::get(int x, int y, const msdfgen::BitmapRef &subBitmap) const { + blit(subBitmap, bitmap, 0, 0, x, y, subBitmap.width, subBitmap.height); +} + +} diff --git a/msdf-atlas-gen/Charset.cpp b/msdf-atlas-gen/Charset.cpp new file mode 100644 index 0000000..69c93ee --- /dev/null +++ b/msdf-atlas-gen/Charset.cpp @@ -0,0 +1,39 @@ + +#include "Charset.h" + +namespace msdf_atlas { + +static Charset createAsciiCharset() { + Charset ascii; + for (unicode_t cp = 0x20; cp < 0x7f; ++cp) + ascii.add(cp); + return ascii; +} + +const Charset Charset::ASCII = createAsciiCharset(); + +void Charset::add(unicode_t cp) { + codepoints.insert(cp); +} + +void Charset::remove(unicode_t cp) { + codepoints.erase(cp); +} + +size_t Charset::size() const { + return codepoints.size(); +} + +bool Charset::empty() const { + return codepoints.empty(); +} + +std::set::const_iterator Charset::begin() const { + return codepoints.begin(); +} + +std::set::const_iterator Charset::end() const { + return codepoints.end(); +} + +} diff --git a/msdf-atlas-gen/Charset.h b/msdf-atlas-gen/Charset.h new file mode 100644 index 0000000..6b107b3 --- /dev/null +++ b/msdf-atlas-gen/Charset.h @@ -0,0 +1,35 @@ + +#pragma once + +#include +#include +#include "types.h" + +namespace msdf_atlas { + +/// Represents a set of Unicode codepoints (characters) +class Charset { + +public: + /// The set of the 95 printable ASCII characters + static const Charset ASCII; + + /// Adds a codepoint + void add(unicode_t cp); + /// Removes a codepoint + void remove(unicode_t cp); + + size_t size() const; + bool empty() const; + std::set::const_iterator begin() const; + std::set::const_iterator end() const; + + /// Load character set from a text file with the correct syntax + bool load(const char *filename); + +private: + std::set codepoints; + +}; + +} diff --git a/msdf-atlas-gen/DynamicAtlas.h b/msdf-atlas-gen/DynamicAtlas.h new file mode 100644 index 0000000..e164097 --- /dev/null +++ b/msdf-atlas-gen/DynamicAtlas.h @@ -0,0 +1,43 @@ + +#pragma once + +#include +#include "RectanglePacker.h" +#include "AtlasGenerator.h" + +namespace msdf_atlas { + +/** + * This class can be used to produce a dynamic atlas to which more glyphs are added over time. + * It takes care of laying out and enlarging the atlas as necessary and delegates the actual work + * to the specified AtlasGenerator, which may e.g. do the work asynchronously. + */ +template +class DynamicAtlas { + +public: + DynamicAtlas(); + /// Creates with a configured generator. The generator must not contain any prior glyphs! + explicit DynamicAtlas(AtlasGenerator &&generator); + /// Adds a batch of glyphs. Adding more than one glyph at a time may improve packing efficiency + void add(GlyphGeometry *glyphs, int count); + /// Allows access to generator. Do not add glyphs to the generator directly! + AtlasGenerator & atlasGenerator(); + const AtlasGenerator & atlasGenerator() const; + +private: + AtlasGenerator generator; + RectanglePacker packer; + int glyphCount; + int side; + std::vector rectangles; + std::vector remapBuffer; + int totalArea; + GeneratorAttributes genAttribs; + int padding; + +}; + +} + +#include "DynamicAtlas.hpp" diff --git a/msdf-atlas-gen/DynamicAtlas.hpp b/msdf-atlas-gen/DynamicAtlas.hpp new file mode 100644 index 0000000..51a712a --- /dev/null +++ b/msdf-atlas-gen/DynamicAtlas.hpp @@ -0,0 +1,69 @@ + +#include "DynamicAtlas.h" + +namespace msdf_atlas { + +template +DynamicAtlas::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +DynamicAtlas::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +void DynamicAtlas::add(GlyphGeometry *glyphs, int count) { + int start = rectangles.size(); + for (int i = 0; i < count; ++i) { + if (!glyphs[i].isWhitespace()) { + int w, h; + glyphs[i].getBoxSize(w, h); + Rectangle rect = { 0, 0, w+padding, h+padding }; + rectangles.push_back(rect); + Remap remapEntry = { }; + remapEntry.index = glyphCount+i; + remapEntry.width = w; + remapEntry.height = h; + remapBuffer.push_back(remapEntry); + totalArea += (w+padding)*(h+padding); + } + } + if ((int) rectangles.size() > start) { + int oldSide = side; + int packerStart = start; + while (packer.pack(rectangles.data()+packerStart, rectangles.size()-packerStart) > 0) { + side = side+!side<<1; + while (side*side < totalArea) + side <<= 1; + packer = RectanglePacker(side+padding, side+padding); + packerStart = 0; + } + if (packerStart < start) { + for (int i = 0; i < start; ++i) { + Remap &remap = remapBuffer[i]; + remap.source = remap.target; + remap.target.x = rectangles[i].x; + remap.target.y = rectangles[i].y; + } + generator.rearrange(side, side, remapBuffer.data(), start); + } else if (side != oldSide) + generator.resize(side, side); + for (int i = start; i < (int) rectangles.size(); ++i) { + remapBuffer[i].target.x = rectangles[i].x; + remapBuffer[i].target.y = rectangles[i].y; + glyphs[remapBuffer[i].index-glyphCount].placeBox(rectangles[i].x, rectangles[i].y); + } + } + generator.generate(glyphs, count, genAttribs); + glyphCount += count; +} + +template +AtlasGenerator & DynamicAtlas::atlasGenerator() { + return generator; +} + +template +const AtlasGenerator & DynamicAtlas::atlasGenerator() const { + return generator; +} + +} diff --git a/msdf-atlas-gen/GlyphBox.h b/msdf-atlas-gen/GlyphBox.h new file mode 100644 index 0000000..a6d1c6a --- /dev/null +++ b/msdf-atlas-gen/GlyphBox.h @@ -0,0 +1,21 @@ + +#pragma once + +#include "types.h" + +namespace msdf_atlas { + +/// The glyph box - its bounds in plane and atlas +struct GlyphBox { + unicode_t codepoint; + double advance; + struct { + double l, b, r, t; + } bounds; + struct { + int x, y, w, h; + } rect; + +}; + +} diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp new file mode 100644 index 0000000..e6a8a67 --- /dev/null +++ b/msdf-atlas-gen/GlyphGeometry.cpp @@ -0,0 +1,133 @@ + +#include "GlyphGeometry.h" + +#include + +namespace msdf_atlas { + +GlyphGeometry::GlyphGeometry() : codepoint(), bounds(), reverseWinding(), advance(), box() { } + +double GlyphGeometry::simpleSignedDistance(const msdfgen::Point2 &p) const { + double dummy; + msdfgen::SignedDistance minDistance; + for (const msdfgen::Contour &contour : shape.contours) + for (const msdfgen::EdgeHolder &edge : contour.edges) { + msdfgen::SignedDistance distance = edge->signedDistance(p, dummy); + if (distance < minDistance) + minDistance = distance; + } + return minDistance.distance; +} + +bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint) { + if (font && msdfgen::loadGlyph(shape, font, codepoint, &advance) && shape.validate()) { + this->codepoint = codepoint; + shape.normalize(); + bounds = shape.getBounds(); + msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1); + reverseWinding = simpleSignedDistance(outerPoint) > 0; + return true; + } + return false; +} + +void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) { + msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed); +} + +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) { + box.range = range; + box.scale = scale; + if (bounds.l < bounds.r && bounds.b < bounds.t) { + double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; + l -= .5*range, b -= .5*range; + r += .5*range, t += .5*range; + if (miterLimit > 0) + shape.boundMiters(l, b, r, t, .5*range, miterLimit, reverseWinding ? -1 : +1); + double w = scale*(r-l); + double h = scale*(t-b); + box.rect.w = (int) ceil(w)+1; + box.rect.h = (int) ceil(h)+1; + box.translate.x = -l+.5*(box.rect.w-w)/scale; + box.translate.y = -b+.5*(box.rect.h-h)/scale; + } else { + box.rect.w = 0, box.rect.h = 0; + box.translate = msdfgen::Vector2(); + } +} + +void GlyphGeometry::placeBox(int x, int y) { + box.rect.x = x, box.rect.y = y; +} + +unicode_t GlyphGeometry::getCodepoint() const { + return codepoint; +} + +const msdfgen::Shape & GlyphGeometry::getShape() const { + return shape; +} + +double GlyphGeometry::getAdvance() const { + return advance; +} + +bool GlyphGeometry::isWindingReverse() const { + return reverseWinding; +} + +void GlyphGeometry::getBoxRect(int &x, int &y, int &w, int &h) const { + x = box.rect.x, y = box.rect.y; + w = box.rect.w, h = box.rect.h; +} + +void GlyphGeometry::getBoxSize(int &w, int &h) const { + w = box.rect.w, h = box.rect.h; +} + +double GlyphGeometry::getBoxRange() const { + return box.range; +} + +double GlyphGeometry::getBoxScale() const { + return box.scale; +} + +msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const { + return box.translate; +} + +void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const { + if (box.rect.w > 0 && box.rect.h > 0) { + l = -box.translate.x+.5/box.scale; + b = -box.translate.y+.5/box.scale; + r = -box.translate.x+(box.rect.w-.5)/box.scale; + t = -box.translate.y+(box.rect.h-.5)/box.scale; + } else + l = 0, b = 0, r = 0, t = 0; +} + +void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const { + if (box.rect.w > 0 && box.rect.h > 0) { + l = box.rect.x+.5; + b = box.rect.y+.5; + r = box.rect.x+box.rect.w-.5; + t = box.rect.y+box.rect.h-.5; + } else + l = 0, b = 0, r = 0, t = 0; +} + +bool GlyphGeometry::isWhitespace() const { + return shape.contours.empty(); +} + +GlyphGeometry::operator GlyphBox() const { + GlyphBox box; + box.codepoint = codepoint; + box.advance = advance; + getQuadPlaneBounds(box.bounds.l, box.bounds.b, box.bounds.r, box.bounds.t); + box.rect.x = this->box.rect.x, box.rect.y = this->box.rect.y, box.rect.w = this->box.rect.w, box.rect.h = this->box.rect.h; + return box; +} + +} diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h new file mode 100644 index 0000000..a0f3dda --- /dev/null +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -0,0 +1,71 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "GlyphBox.h" + +namespace msdf_atlas { + +/// Represent's the shape geometry of a single glyph as well as its configuration +class GlyphGeometry { + +public: + GlyphGeometry(); + /// Loads glyph geometry from font + bool load(msdfgen::FontHandle *font, unicode_t codepoint); + /// Applies edge coloring to glyph shape + void edgeColoring(double angleThreshold, unsigned long long seed); + /// Computes the dimensions of the glyph's box as well as the transformation for the generator function + void wrapBox(double scale, double range, double miterLimit); + /// Sets the glyph's box's position in the atlas + void placeBox(int x, int y); + /// Returns the glyph's Unicode index + unicode_t getCodepoint() const; + /// Returns the glyph's shape + const msdfgen::Shape & getShape() const; + /// Returns the glyph's advance + double getAdvance() const; + /// Returns true if the shape has reverse winding + bool isWindingReverse() const; + /// Outputs the position and dimensions of the glyph's box in the atlas + void getBoxRect(int &x, int &y, int &w, int &h) const; + /// Outputs the dimensions of the glyph's box in the atlas + void getBoxSize(int &w, int &h) const; + /// Returns the range needed to generate the glyph's SDF + double getBoxRange() const; + /// Returns the scale needed to generate the glyph's bitmap + double getBoxScale() const; + /// Returns the translation vector needed to generate the glyph's bitmap + msdfgen::Vector2 getBoxTranslate() const; + /// Outputs the bounding box of the glyph as it should be placed on the baseline + void getQuadPlaneBounds(double &l, double &b, double &r, double &t) const; + /// Outputs the bounding box of the glyph in the atlas + void getQuadAtlasBounds(double &l, double &b, double &r, double &t) const; + /// Returns true if the glyph is a whitespace and has no geometry + bool isWhitespace() const; + /// Simplifies to GlyphBox + operator GlyphBox() const; + +private: + unicode_t codepoint; + msdfgen::Shape shape; + msdfgen::Shape::Bounds bounds; + bool reverseWinding; + double advance; + struct { + struct { + int x, y, w, h; + } rect; + double range; + double scale; + msdfgen::Vector2 translate; + } box; + + /// Computes the signed distance from point p in a naive way + double simpleSignedDistance(const msdfgen::Point2 &p) const; + +}; + +} diff --git a/msdf-atlas-gen/ImmediateAtlasGenerator.h b/msdf-atlas-gen/ImmediateAtlasGenerator.h new file mode 100644 index 0000000..2dc789e --- /dev/null +++ b/msdf-atlas-gen/ImmediateAtlasGenerator.h @@ -0,0 +1,44 @@ + +#pragma once + +#include +#include "GlyphBox.h" +#include "Workload.h" +#include "AtlasGenerator.h" + +namespace msdf_atlas { + +/** + * An implementation of AtlasGenerator that uses the specified generator function + * and AtlasStorage class and generates glyph bitmaps immediately + * (does not return until all submitted work is finished), + * but may use multiple threads (setThreadCount). + */ +template GEN_FN, class AtlasStorage> +class ImmediateAtlasGenerator { + +public: + ImmediateAtlasGenerator(); + ImmediateAtlasGenerator(int width, int height); + void generate(const GlyphGeometry *glyphs, int count); + void rearrange(int width, int height, const Remap *remapping, int count); + void resize(int width, int height); + /// Sets attributes for the generator function + void setAttributes(const GeneratorAttributes &attributes); + /// Sets the number of threads to be run by generate + void setThreadCount(int threadCount); + /// Allows access to the underlying AtlasStorage + const AtlasStorage & atlasStorage() const; + +private: + AtlasStorage storage; + std::vector layout; + std::vector glyphBuffer; + GeneratorAttributes attributes; + int threadCount; + +}; + +} + +#include "ImmediateAtlasGenerator.hpp" diff --git a/msdf-atlas-gen/ImmediateAtlasGenerator.hpp b/msdf-atlas-gen/ImmediateAtlasGenerator.hpp new file mode 100644 index 0000000..cdfd705 --- /dev/null +++ b/msdf-atlas-gen/ImmediateAtlasGenerator.hpp @@ -0,0 +1,70 @@ + +#include "ImmediateAtlasGenerator.h" + +#include + +namespace msdf_atlas { + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator() : threadCount(1) { } + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator(int width, int height) : storage(width, height), threadCount(1) { } + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::generate(const GlyphGeometry *glyphs, int count) { + int maxBoxArea = 0; + for (int i = 0; i < count; ++i) { + GlyphBox box = glyphs[i]; + maxBoxArea = std::max(maxBoxArea, box.rect.w*box.rect.h); + layout.push_back((GlyphBox &&) box); + } + int threadBufferSize = N*maxBoxArea; + if (threadCount*threadBufferSize > (int) glyphBuffer.size()) + glyphBuffer.resize(threadCount*threadBufferSize); + + Workload([this, &glyphs, threadBufferSize](int i, int threadNo) -> bool { + const GlyphGeometry &glyph = glyphs[i]; + if (!glyph.isWhitespace()) { + int l, b, w, h; + glyph.getBoxRect(l, b, w, h); + msdfgen::BitmapRef glyphBitmap(glyphBuffer.data()+threadNo*threadBufferSize, w, h); + GEN_FN(glyphBitmap, glyph, attributes); + storage.put(l, b, msdfgen::BitmapConstRef(glyphBitmap)); + } + return true; + }, count).finish(threadCount); +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::rearrange(int width, int height, const Remap *remapping, int count) { + for (int i = 0; i < count; ++i) { + layout[remapping[i].index].rect.x = remapping[i].target.x; + layout[remapping[i].index].rect.y = remapping[i].target.y; + } + AtlasStorage newStorage((AtlasStorage &&) storage, width, height, remapping, count); + storage = (AtlasStorage &&) newStorage; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::resize(int width, int height) { + AtlasStorage newStorage((AtlasStorage &&) storage, width, height); + storage = (AtlasStorage &&) newStorage; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setAttributes(const GeneratorAttributes &attributes) { + this->attributes = attributes; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setThreadCount(int threadCount) { + this->threadCount = threadCount; +} + +template GEN_FN, class AtlasStorage> +const AtlasStorage & ImmediateAtlasGenerator::atlasStorage() const { + return storage; +} + +} diff --git a/msdf-atlas-gen/Rectangle.h b/msdf-atlas-gen/Rectangle.h new file mode 100644 index 0000000..8221be4 --- /dev/null +++ b/msdf-atlas-gen/Rectangle.h @@ -0,0 +1,14 @@ + +#pragma once + +namespace msdf_atlas { + +struct Rectangle { + int x, y, w, h; +}; + +struct OrientedRectangle : Rectangle { + bool rotated; +}; + +} diff --git a/msdf-atlas-gen/RectanglePacker.cpp b/msdf-atlas-gen/RectanglePacker.cpp new file mode 100644 index 0000000..b85d697 --- /dev/null +++ b/msdf-atlas-gen/RectanglePacker.cpp @@ -0,0 +1,143 @@ + +#include "RectanglePacker.h" + +#include + +namespace msdf_atlas { + +#define WORST_FIT 0x7fffffff + +template +static void removeFromUnorderedVector(std::vector &vector, size_t index) { + if (index != vector.size()-1) + std::swap(vector[index], vector.back()); + vector.pop_back(); +} + +int RectanglePacker::rateFit(int w, int h, int sw, int sh) { + return std::min(sw-w, sh-h); +} + +RectanglePacker::RectanglePacker() : RectanglePacker(0, 0) { } + +RectanglePacker::RectanglePacker(int width, int height) { + if (width > 0 && height > 0) + spaces.push_back(Rectangle { 0, 0, width, height }); +} + +void RectanglePacker::splitSpace(int index, int w, int h) { + Rectangle space = spaces[index]; + removeFromUnorderedVector(spaces, index); + Rectangle a = { space.x, space.y+h, w, space.h-h }; + Rectangle b = { space.x+w, space.y, space.w-w, h }; + if (w*(space.h-h) <= h*(space.w-w)) + a.w = space.w; + else + b.h = space.h; + if (a.w > 0 && a.h > 0) + spaces.push_back(a); + if (b.w > 0 && b.h > 0) + spaces.push_back(b); +} + +int RectanglePacker::pack(Rectangle *rectangles, int count) { + std::vector remainingRects(count); + for (int i = 0; i < count; ++i) + remainingRects[i] = i; + while (!remainingRects.empty()) { + int bestFit = WORST_FIT; + int bestSpace = -1; + int bestRect = -1; + for (size_t i = 0; i < spaces.size(); ++i) { + const Rectangle &space = spaces[i]; + for (size_t j = 0; j < remainingRects.size(); ++j) { + const Rectangle &rect = rectangles[remainingRects[j]]; + if (rect.w == space.w && rect.h == space.h) { + bestSpace = i; + bestRect = j; + goto BEST_FIT_FOUND; + } + if (rect.w <= space.w && rect.h <= space.h) { + int fit = rateFit(rect.w, rect.h, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestFit = fit; + } + } + } + } + if (bestSpace < 0 || bestRect < 0) + break; + BEST_FIT_FOUND: + Rectangle &rect = rectangles[remainingRects[bestRect]]; + rect.x = spaces[bestSpace].x; + rect.y = spaces[bestSpace].y; + splitSpace(bestSpace, rect.w, rect.h); + removeFromUnorderedVector(remainingRects, bestRect); + } + return (int) remainingRects.size(); +} + +int RectanglePacker::pack(OrientedRectangle *rectangles, int count) { + std::vector remainingRects(count); + for (int i = 0; i < count; ++i) + remainingRects[i] = i; + while (!remainingRects.empty()) { + int bestFit = WORST_FIT; + int bestSpace = -1; + int bestRect = -1; + bool bestRotated = false; + for (size_t i = 0; i < spaces.size(); ++i) { + const Rectangle &space = spaces[i]; + for (size_t j = 0; j < remainingRects.size(); ++j) { + const OrientedRectangle &rect = rectangles[remainingRects[j]]; + if (rect.w == space.w && rect.h == space.h) { + bestSpace = i; + bestRect = j; + bestRotated = false; + goto BEST_FIT_FOUND; + } + if (rect.h == space.w && rect.w == space.h) { + bestSpace = i; + bestRect = j; + bestRotated = true; + goto BEST_FIT_FOUND; + } + if (rect.w <= space.w && rect.h <= space.h) { + int fit = rateFit(rect.w, rect.h, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestRotated = false; + bestFit = fit; + } + } + if (rect.h <= space.w && rect.w <= space.h) { + int fit = rateFit(rect.h, rect.w, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestRotated = true; + bestFit = fit; + } + } + } + } + if (bestSpace < 0 || bestRect < 0) + break; + BEST_FIT_FOUND: + OrientedRectangle &rect = rectangles[remainingRects[bestRect]]; + rect.x = spaces[bestSpace].x; + rect.y = spaces[bestSpace].y; + rect.rotated = bestRotated; + if (bestRotated) + splitSpace(bestSpace, rect.h, rect.w); + else + splitSpace(bestSpace, rect.w, rect.h); + removeFromUnorderedVector(remainingRects, bestRect); + } + return (int) remainingRects.size(); +} + +} diff --git a/msdf-atlas-gen/RectanglePacker.h b/msdf-atlas-gen/RectanglePacker.h new file mode 100644 index 0000000..10e4c19 --- /dev/null +++ b/msdf-atlas-gen/RectanglePacker.h @@ -0,0 +1,28 @@ + +#pragma once + +#include +#include "Rectangle.h" + +namespace msdf_atlas { + +/// Guillotine 2D single bin packer +class RectanglePacker { + +public: + RectanglePacker(); + RectanglePacker(int width, int height); + /// Packs the rectangle array, returns how many didn't fit (0 on success) + int pack(Rectangle *rectangles, int count); + int pack(OrientedRectangle *rectangles, int count); + +private: + std::vector spaces; + + static int rateFit(int w, int h, int sw, int sh); + + void splitSpace(int index, int w, int h); + +}; + +} diff --git a/msdf-atlas-gen/Remap.h b/msdf-atlas-gen/Remap.h new file mode 100644 index 0000000..4a69889 --- /dev/null +++ b/msdf-atlas-gen/Remap.h @@ -0,0 +1,15 @@ + +#pragma once + +namespace msdf_atlas { + +/// Represents the repositioning of a subsection of the atlas +struct Remap { + int index; + struct { + int x, y; + } source, target; + int width, height; +}; + +} diff --git a/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp new file mode 100644 index 0000000..fe59411 --- /dev/null +++ b/msdf-atlas-gen/TightAtlasPacker.cpp @@ -0,0 +1,168 @@ + +#include "TightAtlasPacker.h" + +#include +#include "Rectangle.h" +#include "rectangle-packing.h" +#include "size-selectors.h" + +namespace msdf_atlas { + +int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit) { + // Wrap glyphs into boxes + std::vector rectangles; + std::vector rectangleGlyphs; + rectangles.reserve(count); + rectangleGlyphs.reserve(count); + for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { + if (!glyph->isWhitespace()) { + Rectangle rect = { }; + glyph->wrapBox(scale, range, miterLimit); + glyph->getBoxSize(rect.w, rect.h); + if (rect.w > 0 && rect.h > 0) { + rectangles.push_back(rect); + rectangleGlyphs.push_back(glyph); + } + } + } + // No non-zero size boxes? + if (rectangles.empty()) { + if (width < 0 || height < 0) + width = 0, height = 0; + return 0; + } + // Box rectangle packing + if (width < 0 || height < 0) { + std::pair dimensions = std::make_pair(width, height); + switch (dimensionsConstraint) { + case DimensionsConstraint::POWER_OF_TWO_SQUARE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::POWER_OF_TWO_RECTANGLE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::EVEN_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + } + if (!(dimensions.first > 0 && dimensions.second > 0)) + return -1; + width = dimensions.first, height = dimensions.second; + } else { + if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, padding)) + return result; + } + // Set glyph box placement + for (size_t i = 0; i < rectangles.size(); ++i) + rectangleGlyphs[i]->placeBox(rectangles[i].x, height-(rectangles[i].y+rectangles[i].h)); + return 0; +} + +double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance) { + bool lastResult = false; + #define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, DimensionsConstraint(), width, height, padding, (scale), unitRange+pxRange/(scale), miterLimit)) + double minScale = 1, maxScale = 1; + if (TRY_PACK(1)) { + while (maxScale < 1e+32 && TRY_PACK(maxScale = 2*minScale)) + minScale = maxScale; + } else { + while (minScale > 1e-32 && !TRY_PACK(minScale = .5*maxScale)) + maxScale = minScale; + } + if (minScale == maxScale) + return 0; + while (minScale/maxScale < 1-tolerance) { + double midScale = .5*(minScale+maxScale); + if (TRY_PACK(midScale)) + minScale = midScale; + else + maxScale = midScale; + } + if (!lastResult) + TRY_PACK(minScale); + return minScale; +} + +TightAtlasPacker::TightAtlasPacker() : + width(-1), height(-1), + padding(0), + dimensionsConstraint(DimensionsConstraint::POWER_OF_TWO_SQUARE), + scale(-1), + minScale(1), + unitRange(0), + pxRange(0), + miterLimit(0), + scaleMaximizationTolerance(.001) +{ } + +int TightAtlasPacker::pack(GlyphGeometry *glyphs, int count) { + double initialScale = scale > 0 ? scale : minScale; + if (initialScale > 0) { + if (int remaining = tryPack(glyphs, count, dimensionsConstraint, width, height, padding, initialScale, unitRange+pxRange/initialScale, miterLimit)) + return remaining; + } else if (width < 0 || height < 0) + return -1; + if (scale <= 0) + scale = packAndScale(glyphs, count, width, height, padding, unitRange, pxRange, miterLimit, scaleMaximizationTolerance); + if (scale <= 0) + return -1; + pxRange += scale*unitRange; + unitRange = 0; + return 0; +} + +void TightAtlasPacker::setDimensions(int width, int height) { + this->width = width, this->height = height; +} + +void TightAtlasPacker::unsetDimensions() { + width = -1, height = -1; +} + +void TightAtlasPacker::setDimensionsConstraint(DimensionsConstraint dimensionsConstraint) { + this->dimensionsConstraint = dimensionsConstraint; +} + +void TightAtlasPacker::setPadding(int padding) { + this->padding = padding; +} + +void TightAtlasPacker::setScale(double scale) { + this->scale = scale; +} + +void TightAtlasPacker::setMinimumScale(double minScale) { + this->minScale = minScale; +} + +void TightAtlasPacker::setUnitRange(double unitRange) { + this->unitRange = unitRange; +} + +void TightAtlasPacker::setPixelRange(double pxRange) { + this->pxRange = pxRange; +} + +void TightAtlasPacker::setMiterLimit(double miterLimit) { + this->miterLimit = miterLimit; +} + +void TightAtlasPacker::getDimensions(int &width, int &height) const { + width = this->width, height = this->height; +} + +double TightAtlasPacker::getScale() const { + return scale; +} + +double TightAtlasPacker::getPixelRange() const { + return pxRange; +} + +} diff --git a/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h new file mode 100644 index 0000000..35bb2a9 --- /dev/null +++ b/msdf-atlas-gen/TightAtlasPacker.h @@ -0,0 +1,71 @@ + +#pragma once + +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/** + * This class computes the layout of a static atlas and may optionally + * also find the minimum required dimensions and/or the maximum glyph scale + */ +class TightAtlasPacker { + +public: + /// Constraints for the atlas's dimensions - see size selectors for more info + enum class DimensionsConstraint { + POWER_OF_TWO_SQUARE, + POWER_OF_TWO_RECTANGLE, + MULTIPLE_OF_FOUR_SQUARE, + EVEN_SQUARE, + SQUARE + }; + + TightAtlasPacker(); + + /// Computes the layout for the array of glyphs. Returns 0 on success + int pack(GlyphGeometry *glyphs, int count); + + /// Sets the atlas's dimensions to be fixed + void setDimensions(int width, int height); + /// Sets the atlas's dimensions to be determined during pack + void unsetDimensions(); + /// Sets the constraint to be used when determining dimensions + void setDimensionsConstraint(DimensionsConstraint dimensionsConstraint); + /// Sets the padding between glyph boxes + void setPadding(int padding); + /// Sets fixed glyph scale + void setScale(double scale); + /// Sets the minimum glyph scale + void setMinimumScale(double minScale); + /// Sets the unit component of the total distance range + void setUnitRange(double unitRange); + /// Sets the pixel component of the total distance range + void setPixelRange(double pxRange); + /// Sets the miter limit for bounds computation + void setMiterLimit(double miterLimit); + + /// Outputs the atlas's final dimensions + void getDimensions(int &width, int &height) const; + /// Returns the final glyph scale + double getScale() const; + /// Returns the final combined pixel range (including converted unit range) + double getPixelRange() const; + +private: + int width, height; + int padding; + DimensionsConstraint dimensionsConstraint; + double scale; + double minScale; + double unitRange; + double pxRange; + double miterLimit; + double scaleMaximizationTolerance; + + static int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit); + static double packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance); + +}; + +} diff --git a/msdf-atlas-gen/Workload.cpp b/msdf-atlas-gen/Workload.cpp new file mode 100644 index 0000000..e9cd5d4 --- /dev/null +++ b/msdf-atlas-gen/Workload.cpp @@ -0,0 +1,49 @@ + +#include "Workload.h" + +#include +#include +#include + +namespace msdf_atlas { + +Workload::Workload() : chunks(0) { } + +Workload::Workload(const std::function &workerFunction, int chunks) : workerFunction(workerFunction), chunks(chunks) { } + +bool Workload::finishSequential() { + for (int i = 0; i < chunks; ++i) + if (!workerFunction(i, 0)) + return false; + return true; +} + +bool Workload::finishParallel(int threadCount) { + bool result = true; + std::atomic next(0); + std::function threadWorker = [this, &result, &next](int threadNo) { + for (int i = next++; result && i < chunks; i = next++) { + if (!workerFunction(i, threadNo)) + result = false; + } + }; + std::vector threads; + threads.reserve(threadCount); + for (int i = 0; i < threadCount; ++i) + threads.emplace_back(threadWorker, i); + for (std::thread &thread : threads) + thread.join(); + return result; +} + +bool Workload::finish(int threadCount) { + if (!chunks) + return true; + if (threadCount == 1 || chunks == 1) + return finishSequential(); + if (threadCount > 1) + return finishParallel(threadCount); + return false; +} + +} diff --git a/msdf-atlas-gen/Workload.h b/msdf-atlas-gen/Workload.h new file mode 100644 index 0000000..cee0c9d --- /dev/null +++ b/msdf-atlas-gen/Workload.h @@ -0,0 +1,32 @@ + +#pragma once + +#include + +namespace msdf_atlas { + +/** + * This function allows to split a workload into multiple threads. + * The worker function: + * bool FN(int chunk, int threadNo); + * should process the given chunk (out of chunks) and return true. + * If false is returned, the process is interrupted. + */ +class Workload { + +public: + Workload(); + Workload(const std::function &workerFunction, int chunks); + /// Runs the process and returns true if all chunks have been processed + bool finish(int threadCount); + +private: + std::function workerFunction; + int chunks; + + bool finishSequential(); + bool finishParallel(int threadCount); + +}; + +} diff --git a/msdf-atlas-gen/artery-font-export.cpp b/msdf-atlas-gen/artery-font-export.cpp new file mode 100644 index 0000000..6e151ef --- /dev/null +++ b/msdf-atlas-gen/artery-font-export.cpp @@ -0,0 +1,149 @@ + +#include "artery-font-export.h" + +#include +#include +#include "image-encode.h" + +namespace msdf_atlas { + +static artery_font::ImageType convertImageType(ImageType imageType) { + switch (imageType) { + case ImageType::HARD_MASK: + case ImageType::SOFT_MASK: + return artery_font::IMAGE_LINEAR_MASK; + case ImageType::SDF: + return artery_font::IMAGE_SDF; + case ImageType::PSDF: + return artery_font::IMAGE_PSDF; + case ImageType::MSDF: + return artery_font::IMAGE_MSDF; + case ImageType::MTSDF: + return artery_font::IMAGE_MTSDF; + } + return artery_font::IMAGE_NONE; +} + +template +static bool encodeTiff(std::vector &output, const msdfgen::BitmapConstRef &atlas) { + // TODO + return false; +} + +template +static artery_font::PixelFormat getPixelFormat(); + +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_UNSIGNED8; +} +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_FLOAT32; +} + +template +bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename) { + artery_font::StdArteryFont arfont = { }; + arfont.metadataFormat = artery_font::METADATA_NONE; + + if (glyphCount > 0) { + msdfgen::FontMetrics fontMetrics; + if (!msdfgen::getFontMetrics(fontMetrics, font)) + return false; + double fsScale = 1/fontMetrics.emSize; + artery_font::StdFontVariant fontVariant = { }; + fontVariant.codepointType = artery_font::CP_UNICODE; + fontVariant.imageType = convertImageType(imageType); + fontVariant.metrics.fontSize = REAL(fontSize); + if (imageType != ImageType::HARD_MASK) + fontVariant.metrics.distanceRange = REAL(pxRange); + fontVariant.metrics.emSize = REAL(fsScale*fontMetrics.emSize); + fontVariant.metrics.ascender = REAL(fsScale*fontMetrics.ascenderY); + fontVariant.metrics.descender = REAL(fsScale*fontMetrics.descenderY); + fontVariant.metrics.lineHeight = REAL(fsScale*fontMetrics.lineHeight); + fontVariant.metrics.underlineY = REAL(fsScale*fontMetrics.underlineY); + fontVariant.metrics.underlineThickness = REAL(fsScale*fontMetrics.underlineThickness); + fontVariant.glyphs = artery_font::StdList >(glyphCount); + for (int i = 0; i < glyphCount; ++i) { + artery_font::Glyph &glyph = fontVariant.glyphs[i]; + glyph.codepoint = glyphs[i].getCodepoint(); + glyph.image = 0; + double l, b, r, t; + glyphs[i].getQuadPlaneBounds(l, b, r, t); + glyph.planeBounds.l = REAL(fsScale*l); + glyph.planeBounds.b = REAL(fsScale*b); + glyph.planeBounds.r = REAL(fsScale*r); + glyph.planeBounds.t = REAL(fsScale*t); + glyphs[i].getQuadAtlasBounds(l, b, r, t); + glyph.imageBounds.l = REAL(l); + glyph.imageBounds.b = REAL(b); + glyph.imageBounds.r = REAL(r); + glyph.imageBounds.t = REAL(t); + glyph.advance.h = REAL(fsScale*glyphs[i].getAdvance()); + glyph.advance.v = REAL(0); + for (int j = 0; j < glyphCount; ++j) { + double kerning; + if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = glyphs[i].getCodepoint(); + kernPair.codepoint2 = glyphs[j].getCodepoint(); + kernPair.advance.h = REAL(fsScale*kerning); + fontVariant.kernPairs.vector.push_back((artery_font::KernPair &&) kernPair); + } + } + } + arfont.variants.vector.push_back((artery_font::StdFontVariant &&) fontVariant); + } + + { + artery_font::StdImage image = { }; + image.width = atlas.width; + image.height = atlas.height; + image.channels = N; + image.imageType = convertImageType(imageType); + switch (imageFormat) { + case ImageFormat::PNG: + image.encoding = artery_font::IMAGE_PNG; + image.pixelFormat = artery_font::PIXEL_UNSIGNED8; + if (!encodePng(image.data.vector, atlas)) + return false; + break; + case ImageFormat::TIFF: + image.encoding = artery_font::IMAGE_TIFF; + image.pixelFormat = artery_font::PIXEL_FLOAT32; + if (!encodeTiff(image.data.vector, atlas)) + return false; + break; + case ImageFormat::BINARY: + image.pixelFormat = artery_font::PIXEL_UNSIGNED8; + goto BINARY_EITHER; + case ImageFormat::BINARY_FLOAT: + image.pixelFormat = artery_font::PIXEL_FLOAT32; + goto BINARY_EITHER; + BINARY_EITHER: + if (image.pixelFormat != getPixelFormat()) + return false; + image.encoding = artery_font::IMAGE_RAW_BINARY; + image.rawBinaryFormat.rowLength = N*sizeof(T)*atlas.width; + image.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP; + image.data = artery_font::StdByteArray(N*sizeof(T)*atlas.width*atlas.height); + memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height); + break; + default: + return false; + } + arfont.images.vector.push_back((artery_font::StdImage &&) image); + } + + return artery_font::writeFile(arfont, filename); +} + +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); +template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); + +} diff --git a/msdf-atlas-gen/artery-font-export.h b/msdf-atlas-gen/artery-font-export.h new file mode 100644 index 0000000..fe2abdc --- /dev/null +++ b/msdf-atlas-gen/artery-font-export.h @@ -0,0 +1,15 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file +template +bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); + +} diff --git a/msdf-atlas-gen/bitmap-blit.cpp b/msdf-atlas-gen/bitmap-blit.cpp new file mode 100644 index 0000000..c1e6afa --- /dev/null +++ b/msdf-atlas-gen/bitmap-blit.cpp @@ -0,0 +1,58 @@ + +#include "bitmap-blit.h" + +#include + +namespace msdf_atlas { + +template +void blitSameType(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) + memcpy(dst(dx, dy+y), src(sx, sy+y), sizeof(T)*N*w); +} + +#define BLIT_SAME_TYPE_IMPL(T, N) void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { blitSameType(dst, src, dx, dy, sx, sy, w, h); } + +BLIT_SAME_TYPE_IMPL(byte, 1) +BLIT_SAME_TYPE_IMPL(byte, 3) +BLIT_SAME_TYPE_IMPL(byte, 4) +BLIT_SAME_TYPE_IMPL(float, 1) +BLIT_SAME_TYPE_IMPL(float, 3) +BLIT_SAME_TYPE_IMPL(float, 4) + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(*srcPixel); + } + } +} + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]); + } + } +} + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[3]); + } + } +} + +} diff --git a/msdf-atlas-gen/bitmap-blit.h b/msdf-atlas-gen/bitmap-blit.h new file mode 100644 index 0000000..b186d12 --- /dev/null +++ b/msdf-atlas-gen/bitmap-blit.h @@ -0,0 +1,26 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/* + * Copies a rectangular section from source bitmap to destination bitmap. + * Width and height are not checked and must not exceed bitmap bounds! + */ + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +} diff --git a/msdf-atlas-gen/charset-parser.cpp b/msdf-atlas-gen/charset-parser.cpp new file mode 100644 index 0000000..44c511b --- /dev/null +++ b/msdf-atlas-gen/charset-parser.cpp @@ -0,0 +1,251 @@ + +#include "Charset.h" + +#include +#include +#include "utf8.h" + +namespace msdf_atlas { + +static char escapedChar(char c) { + switch (c) { + case '0': + return '\0'; + case 'n': case 'N': + return '\n'; + case 'r': case 'R': + return '\r'; + case 's': case 'S': + return ' '; + case 't': case 'T': + return '\t'; + case '\\': case '"': case '\'': + default: + return c; + } +} + +static int readWord(std::string &str, FILE *f) { + while (true) { + int c = fgetc(f); + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') + str.push_back((char) c); + else + return c; + } +} + +static bool readString(std::string &str, FILE *f, char terminator) { + bool escape = false; + while (true) { + int c = fgetc(f); + if (c < 0) + return false; + if (escape) { + str.push_back(escapedChar((char) c)); + escape = false; + } else { + if (c == terminator) + return true; + else if (c == '\\') + escape = true; + else + str.push_back((char) c); + } + } +} + +static bool parseInt(int &i, const char *str) { + i = 0; + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { // hex + str += 2; + for (; *str; ++str) { + if (*str >= '0' && *str <= '9') { + i <<= 4; + i += *str-'0'; + } else if (*str >= 'A' && *str <= 'F') { + i <<= 4; + i += *str-'A'+10; + } else if (*str >= 'a' && *str <= 'f') { + i <<= 4; + i += *str-'a'+10; + } else + return false; + } + } else { // dec + for (; *str; ++str) { + if (*str >= '0' && *str <= '9') { + i *= 10; + i += *str-'0'; + } else + return false; + } + } + return true; +} + +static std::string combinePath(const char *basePath, const char *relPath) { + if (relPath[0] == '/' || (relPath[0] && relPath[1] == ':')) // absolute path? + return relPath; + int lastSlash = -1; + for (int i = 0; basePath[i]; ++i) + if (basePath[i] == '/' || basePath[i] == '\\') + lastSlash = i; + if (lastSlash < 0) + return relPath; + return std::string(basePath, lastSlash+1)+relPath; +} + +bool Charset::load(const char *filename) { + + if (FILE *f = fopen(filename, "rb")) { + + enum { + CLEAR, + TIGHT, + RANGE_BRACKET, + RANGE_START, + RANGE_SEPARATOR, + RANGE_END + } state = CLEAR; + + std::string buffer; + std::vector unicodeBuffer; + unicode_t rangeStart = 0; + for (int c = fgetc(f), start = true; c >= 0; start = false) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // number + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR)) + goto FAIL; + buffer.push_back((char) c); + c = readWord(buffer, f); + { + int cp; + if (!parseInt(cp, buffer.c_str())) + goto FAIL; + switch (state) { + case CLEAR: + if (cp > 0) + add((unicode_t) cp); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = (unicode_t) cp; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; (int) u <= cp; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } + buffer.clear(); + continue; // next character already read + case '\'': // single UTF-8 character + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR)) + goto FAIL; + if (!readString(buffer, f, '\'')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + if (unicodeBuffer.size() == 1) { + switch (state) { + case CLEAR: + if (unicodeBuffer[0] > 0) + add(unicodeBuffer[0]); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = unicodeBuffer[0]; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; u <= unicodeBuffer[0]; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } else + goto FAIL; + unicodeBuffer.clear(); + buffer.clear(); + state = TIGHT; + break; + case '"': // string of UTF-8 characters + if (state != CLEAR) + goto FAIL; + if (!readString(buffer, f, '"')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + for (unicode_t cp : unicodeBuffer) + add(cp); + unicodeBuffer.clear(); + buffer.clear(); + state = TIGHT; + break; + case '[': // character range start + if (state != CLEAR) + goto FAIL; + state = RANGE_BRACKET; + break; + case ']': // character range end + if (state == RANGE_END) + state = TIGHT; + else + goto FAIL; + break; + case '@': // annotation + if (state != CLEAR) + goto FAIL; + c = readWord(buffer, f); + if (buffer == "include") { + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') + c = fgetc(f); + if (c != '"') + goto FAIL; + buffer.clear(); + if (!readString(buffer, f, '"')) + goto FAIL; + load(combinePath(filename, buffer.c_str()).c_str()); + state = TIGHT; + } else + goto FAIL; + buffer.clear(); + break; + case ',': case ';': // separator + if (!(state == CLEAR || state == TIGHT)) { + if (state == RANGE_START) + state = RANGE_SEPARATOR; + else + goto FAIL; + } // else treat as whitespace + case ' ': case '\n': case '\r': case '\t': // whitespace + if (state == TIGHT) + state = CLEAR; + break; + case 0xef: // UTF-8 byte order mark + if (start) { + if (!(fgetc(f) == 0xbb && fgetc(f) == 0xbf)) + goto FAIL; + break; + } + default: // unexpected character + goto FAIL; + } + c = fgetc(f); + } + + fclose(f); + return state == CLEAR || state == TIGHT; + + FAIL: + fclose(f); + return false; + } + + return false; +} + +} diff --git a/msdf-atlas-gen/csv-export.cpp b/msdf-atlas-gen/csv-export.cpp new file mode 100644 index 0000000..e48fbe1 --- /dev/null +++ b/msdf-atlas-gen/csv-export.cpp @@ -0,0 +1,27 @@ + +#include "csv-export.h" + +#include + +namespace msdf_atlas { + +bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename) { + FILE *f = fopen(filename, "w"); + if (!f) + return false; + + double fsScale = 1/emSize; + for (int i = 0; i < glyphCount; ++i) { + double l, b, r, t; + fprintf(f, "%u,%.17g,", glyphs[i].getCodepoint(), fsScale*glyphs[i].getAdvance()); + glyphs[i].getQuadPlaneBounds(l, b, r, t); + fprintf(f, "%.17g,%.17g,%.17g,%.17g,", fsScale*l, fsScale*b, fsScale*r, fsScale*t); + glyphs[i].getQuadAtlasBounds(l, b, r, t); + fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t); + } + + fclose(f); + return true; +} + +} diff --git a/msdf-atlas-gen/csv-export.h b/msdf-atlas-gen/csv-export.h new file mode 100644 index 0000000..492c50f --- /dev/null +++ b/msdf-atlas-gen/csv-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/** + * Writes the positioning data and atlas layout of the glyphs into a CSV file + * The columns are: Unicode index, horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t) + */ +bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename); + +} diff --git a/msdf-atlas-gen/glyph-generators.cpp b/msdf-atlas-gen/glyph-generators.cpp new file mode 100644 index 0000000..c09adc8 --- /dev/null +++ b/msdf-atlas-gen/glyph-generators.cpp @@ -0,0 +1,51 @@ + +#include "glyph-generators.h" + +namespace msdf_atlas { + +template +static void invertColor(const msdfgen::BitmapRef &bitmap) { + const float *end = bitmap.pixels+N*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = 1.f-*p; +} + +void scanlineGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::rasterize(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void sdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport); + if (glyph.isWindingReverse()) + invertColor(output); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport); + if (glyph.isWindingReverse()) + invertColor(output); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), 0, attribs.overlapSupport); + if (glyph.isWindingReverse()) + invertColor(output); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); + msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange())); +} + +void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), 0, attribs.overlapSupport); + if (glyph.isWindingReverse()) + invertColor(output); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); + msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange())); +} + +} diff --git a/msdf-atlas-gen/glyph-generators.h b/msdf-atlas-gen/glyph-generators.h new file mode 100644 index 0000000..1df7c24 --- /dev/null +++ b/msdf-atlas-gen/glyph-generators.h @@ -0,0 +1,25 @@ + +#pragma once + +#include +#include "GlyphGeometry.h" +#include "AtlasGenerator.h" + +#define MSDF_ATLAS_GLYPH_FILL_RULE msdfgen::FILL_NONZERO + +namespace msdf_atlas { + +// Glyph bitmap generator functions + +/// Generates non-anti-aliased binary image of the glyph using scanline rasterization +void scanlineGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a true signed distance field of the glyph +void sdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a signed pseudo-distance field of the glyph +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a multi-channel signed distance field of the glyph +void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a multi-channel and alpha-encoded true signed distance field of the glyph +void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); + +} diff --git a/msdf-atlas-gen/image-encode.cpp b/msdf-atlas-gen/image-encode.cpp new file mode 100644 index 0000000..b34fc5a --- /dev/null +++ b/msdf-atlas-gen/image-encode.cpp @@ -0,0 +1,63 @@ + +#include "image-encode.h" + +#include + +namespace msdf_atlas { + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[bitmap.width*y], bitmap(0, bitmap.height-y-1), bitmap.width); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(3*bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[3*bitmap.width*y], bitmap(0, bitmap.height-y-1), 3*bitmap.width); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + 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(output, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(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++ = msdfgen::pixelFloatToByte(*bitmap(x, y)); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(3*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++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]); + } + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + 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++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[3]); + } + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + +} diff --git a/msdf-atlas-gen/image-encode.h b/msdf-atlas-gen/image-encode.h new file mode 100644 index 0000000..59a8fc5 --- /dev/null +++ b/msdf-atlas-gen/image-encode.h @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include +#include "types.h" + +namespace msdf_atlas { + +// Functions to encode an image as a sequence of bytes in memory +// Only PNG format available currently + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); + +} diff --git a/msdf-atlas-gen/image-save.h b/msdf-atlas-gen/image-save.h new file mode 100644 index 0000000..0e73fa8 --- /dev/null +++ b/msdf-atlas-gen/image-save.h @@ -0,0 +1,15 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Saves the bitmap as an image file with the specified format +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename); + +} + +#include "image-save.hpp" diff --git a/msdf-atlas-gen/image-save.hpp b/msdf-atlas-gen/image-save.hpp new file mode 100644 index 0000000..cd3dc7e --- /dev/null +++ b/msdf-atlas-gen/image-save.hpp @@ -0,0 +1,151 @@ + +#include "image-save.h" + +#include +#include + +namespace msdf_atlas { + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename); +template +bool saveImageBinaryLE(const msdfgen::BitmapConstRef &bitmap, const char *filename); +template +bool saveImageBinaryBE(const msdfgen::BitmapConstRef &bitmap, const char *filename); + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename); +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename); + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename) { + switch (format) { + case ImageFormat::PNG: + return msdfgen::savePng(bitmap, filename); + case ImageFormat::BMP: + return msdfgen::saveBmp(bitmap, filename); + case ImageFormat::TIFF: + return false; + case ImageFormat::TEXT: + return saveImageText(bitmap, filename); + case ImageFormat::TEXT_FLOAT: + return false; + case ImageFormat::BINARY: + return saveImageBinary(bitmap, filename); + case ImageFormat::BINARY_FLOAT: + case ImageFormat::BINARY_FLOAT_BE: + return false; + default:; + } + return false; +} + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename) { + switch (format) { + case ImageFormat::PNG: + return msdfgen::savePng(bitmap, filename); + case ImageFormat::BMP: + return msdfgen::saveBmp(bitmap, filename); + case ImageFormat::TIFF: + return msdfgen::saveTiff(bitmap, filename); + case ImageFormat::TEXT: + return false; + case ImageFormat::TEXT_FLOAT: + return saveImageText(bitmap, filename); + case ImageFormat::BINARY: + return false; + case ImageFormat::BINARY_FLOAT: + return saveImageBinaryLE(bitmap, filename); + case ImageFormat::BINARY_FLOAT_BE: + return saveImageBinaryBE(bitmap, filename); + default:; + } + return false; +} + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + success = fwrite(bitmap.pixels, 1, N*bitmap.width*bitmap.height, f) == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryBE + #else + saveImageBinaryLE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + success = fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f) == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryLE + #else + saveImageBinaryBE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + const float *p = bitmap.pixels; + int count = N*bitmap.width*bitmap.height; + int written = 0; + for (int i = 0; i < count; ++i) { + const unsigned char *b = reinterpret_cast(p++); + for (int i = sizeof(float)-1; i >= 0; --i) + written += fwrite(b+i, 1, 1, f); + } + success = written == sizeof(float)*count; + fclose(f); + } + return success; +} + + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + const byte *p = bitmap.pixels; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < N*bitmap.width; ++x) { + fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++); + } + fprintf(f, "\n"); + } + fclose(f); + } + return success; +} + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + const float *p = bitmap.pixels; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < N*bitmap.width; ++x) { + fprintf(f, x ? " %g" : "%g", *p++); + } + fprintf(f, "\n"); + } + fclose(f); + } + return success; +} + +} diff --git a/msdf-atlas-gen/json-export.cpp b/msdf-atlas-gen/json-export.cpp new file mode 100644 index 0000000..ec6c332 --- /dev/null +++ b/msdf-atlas-gen/json-export.cpp @@ -0,0 +1,95 @@ + +#include "json-export.h" + +namespace msdf_atlas { + +static const char * imageTypeString(ImageType type) { + switch (type) { + case ImageType::HARD_MASK: + return "hardmask"; + case ImageType::SOFT_MASK: + return "softmask"; + case ImageType::SDF: + return "sdf"; + case ImageType::PSDF: + return "psdf"; + case ImageType::MSDF: + return "msdf"; + case ImageType::MTSDF: + return "mtsdf"; + } + return nullptr; +} + +bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename) { + msdfgen::FontMetrics fontMetrics; + if (!msdfgen::getFontMetrics(fontMetrics, font)) + return false; + double fsScale = 1/fontMetrics.emSize; + + FILE *f = fopen(filename, "w"); + if (!f) + return false; + fputs("{", f); + + // Atlas properties + fputs("\"atlas\":{", f); { + fprintf(f, "\"type\":\"%s\",", imageTypeString(imageType)); + if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF) + fprintf(f, "\"distanceRange\":%.17g,", pxRange); + fprintf(f, "\"size\":%.17g,", fontSize); + fprintf(f, "\"width\":%d,", atlasWidth); + fprintf(f, "\"height\":%d,", atlasHeight); + fputs("\"yOrigin\":\"bottom\"", f); + } fputs("},", f); + + // Font metrics + fputs("\"metrics\":{", f); { + fprintf(f, "\"lineHeight\":%.17g,", fsScale*fontMetrics.lineHeight); + fprintf(f, "\"ascender\":%.17g,", fsScale*fontMetrics.ascenderY); + fprintf(f, "\"descender\":%.17g,", fsScale*fontMetrics.descenderY); + fprintf(f, "\"underlineY\":%.17g,", fsScale*fontMetrics.underlineY); + fprintf(f, "\"underlineThickness\":%.17g", fsScale*fontMetrics.underlineThickness); + } fputs("},", f); + + // Glyph mapping + fputs("\"glyphs\":[", f); + for (int i = 0; i < glyphCount; ++i) { + fputs(i == 0 ? "{" : ",{", f); + fprintf(f, "\"unicode\":%u,", glyphs[i].getCodepoint()); + fprintf(f, "\"advance\":%.17g", fsScale*glyphs[i].getAdvance()); + double l, b, r, t; + glyphs[i].getQuadPlaneBounds(l, b, r, t); + if (l || b || r || t) + fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", fsScale*l, fsScale*b, fsScale*r, fsScale*t); + glyphs[i].getQuadAtlasBounds(l, b, r, t); + if (l || b || r || t) + fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t); + fputs("}", f); + } + fputs("],", f); + + // Kerning pairs + fputs("\"kerning\":[", f); + bool firstPair = true; + for (int i = 0; i < glyphCount; ++i) { + for (int j = 0; j < glyphCount; ++j) { + double kerning; + if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"unicode1\":%u,", glyphs[i].getCodepoint()); + fprintf(f, "\"unicode2\":%u,", glyphs[j].getCodepoint()); + fprintf(f, "\"advance\":%.17g", fsScale*kerning); + fputs("}", f); + firstPair = false; + } + } + } + fputs("]", f); + + fputs("}\n", f); + fclose(f); + return true; +} + +} diff --git a/msdf-atlas-gen/json-export.h b/msdf-atlas-gen/json-export.h new file mode 100644 index 0000000..9299274 --- /dev/null +++ b/msdf-atlas-gen/json-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file +bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename); + +} diff --git a/msdf-atlas-gen/main.cpp b/msdf-atlas-gen/main.cpp new file mode 100644 index 0000000..fb21ee1 --- /dev/null +++ b/msdf-atlas-gen/main.cpp @@ -0,0 +1,688 @@ + +/* +* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.0 (2020-03-08) - standalone console program +* -------------------------------------------------------------------------------------------------- +* A utility by Viktor Chlumsky, (c) 2020 +* +*/ + +#ifdef MSDF_ATLAS_STANDALONE + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include + +#include "msdf-atlas-gen.h" + +using namespace msdf_atlas; + +#define DEFAULT_ANGLE_THRESHOLD 3.0 +#define DEFAULT_MITER_LIMIT 1.0 +#define DEFAULT_EM_SIZE 32.0 +#define DEFAULT_PIXEL_RANGE 2.0 +#define SDF_ERROR_ESTIMATE_PRECISION 19 +#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO +#define MCG_MULTIPLIER 6364136223846793005ull + +static const char * const helpText = R"( +MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION R"( (with MSDFGEN v)" MSDFGEN_VERSION R"() +---------------------------------------------------------------- + +INPUT SPECIFICATION + -font + Specifies the input TrueType / OpenType font file. This is required. + -charset + Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII. + +ATLAS CONFIGURATION + -type + Selects the type of atlas to be generated. + -format + Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats. + -dimensions + Sets the atlas to have fixed dimensions (width x height). + -pots / -potr / -square / -square2 / -square4 + Picks the minimum atlas dimensions that fit all glyphs and satisfy the selected constraint: + power of two square / ... rectangle / any square / square with side divisible by 2 / ... 4 + +OUTPUT SPECIFICATION - one or more can be specified + -imageout + Saves the atlas as an image file with the specified format. Layout data must be stored separately. + -json + Writes the atlas's layout data, as well as other metrics into a structured JSON file. + -csv + Writes the layout data of the glyphs into a simple CSV file. + -arfont + Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat. + -shadronpreview + Generates a Shadron script that uses the generated atlas to draw a sample text as a preview. + +GLYPH CONFIGURATION + -size + Specified the size of the glyphs in the atlas bitmap in pixels per EM. + -minsize + Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used. + -emrange + Specifies the SDF distance range in EM's. + -pxrange + Specifies the SDF distance range in output pixels. The default value is 2. + +DISTANCE FIELD GENERATOR SETTINGS + -angle + Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only) + -errorcorrection + Changes the threshold used to detect and correct potential artifacts. 0 disables error correction. (msdf / mtsdf only) + -miterlimit + Sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners. (psdf / msdf / mtsdf only) + -nooverlap + Disables resolution of overlapping contours. + -noscanline + Disables the scanline pass, which corrects the distance field's signs according to the non-zero fill rule. + -seed + Sets the initial seed for the edge coloring heuristic. +)"; + +static char toupper(char c) { + return c >= 'a' && c <= 'z' ? c-'a'+'A' : c; +} + +static bool parseUnsigned(unsigned &value, const char *arg) { + static char c; + return sscanf(arg, "%u%c", &value, &c) == 1; +} + +static bool parseUnsignedLL(unsigned long long &value, const char *arg) { + static char c; + return sscanf(arg, "%llu%c", &value, &c) == 1; +} + +static bool parseDouble(double &value, const char *arg) { + static char c; + return sscanf(arg, "%lf%c", &value, &c) == 1; +} + +static bool parseAngle(double &value, const char *arg) { + char c1, c2; + int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2); + if (result == 1) + return true; + if (result == 2 && (c1 == 'd' || c1 == 'D')) { + value *= M_PI/180; + return true; + } + return false; +} + +static bool cmpExtension(const char *path, const char *ext) { + for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b) + if (a < path || toupper(*a) != toupper(*b)) + return false; + return true; +} + +static void loadGlyphs(std::vector &glyphs, msdfgen::FontHandle *font, const Charset &charset) { + glyphs.clear(); + glyphs.reserve(charset.size()); + for (unicode_t cp : charset) { + GlyphGeometry glyph; + if (glyph.load(font, cp)) + glyphs.push_back((GlyphGeometry &&) glyph); + else + printf("Glyph for codepoint 0x%X missing\n", cp); + } +} + +struct Configuration { + ImageType imageType; + ImageFormat imageFormat; + int width, height; + double emSize; + double pxRange; + double angleThreshold; + double miterLimit; + unsigned long long coloringSeed; + GeneratorAttributes generatorAttributes; + int threadCount; + const char *arteryFontFilename; + const char *imageFilename; + const char *jsonFilename; + const char *csvFilename; + const char *shadronPreviewFilename; + const char *shadronPreviewText; +}; + +template GEN_FN> +static bool makeAtlas(const std::vector &glyphs, msdfgen::FontHandle *font, const Configuration &config) { + ImmediateAtlasGenerator > generator(config.width, config.height); + generator.setAttributes(config.generatorAttributes); + generator.setThreadCount(config.threadCount); + generator.generate(glyphs.data(), glyphs.size()); + msdfgen::BitmapConstRef bitmap = (msdfgen::BitmapConstRef) generator.atlasStorage(); + + bool success = true; + + if (config.imageFilename) { + if (saveImage(bitmap, config.imageFormat, config.imageFilename)) + puts("Atlas image file saved."); + else { + success = false; + puts("Failed to save the atlas as an image file."); + } + } + + if (config.arteryFontFilename) { + if (exportArteryFont(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, bitmap, config.imageType, config.imageFormat, config.arteryFontFilename)) + puts("Artery Font file generated."); + else { + success = false; + puts("Failed to generate Artery Font file."); + } + } + + return success; +} + +int main(int argc, const char * const *argv) { + #define ABORT(msg) { puts(msg); return 1; } + + int result = 0; + Configuration config = { }; + const char *fontFilename = nullptr; + const char *charsetFilename = nullptr; + config.imageType = ImageType::MSDF; + config.imageFormat = ImageFormat::UNSPECIFIED; + const char *imageFormatName = nullptr; + int fixedWidth = -1, fixedHeight = -1; + config.generatorAttributes.overlapSupport = true; + config.generatorAttributes.scanlinePass = true; + config.generatorAttributes.errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD; + double minEmSize = 0; + enum { + /// Range specified in EMs + RANGE_EM, + /// Range specified in output pixels + RANGE_PIXEL, + } rangeMode = RANGE_PIXEL; + double rangeValue = 0; + TightAtlasPacker::DimensionsConstraint atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE; + config.angleThreshold = DEFAULT_ANGLE_THRESHOLD; + config.miterLimit = DEFAULT_MITER_LIMIT; + config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1); + + // Parse command line + int argPos = 1; + bool suggestHelp = false; + while (argPos < argc) { + const char *arg = argv[argPos]; + #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc) + + ARG_CASE("-type", 1) { + arg = argv[++argPos]; + if (!strcmp(arg, "hardmask")) + config.imageType = ImageType::HARD_MASK; + else if (!strcmp(arg, "softmask")) + config.imageType = ImageType::SOFT_MASK; + else if (!strcmp(arg, "sdf")) + config.imageType = ImageType::SDF; + else if (!strcmp(arg, "psdf")) + config.imageType = ImageType::PSDF; + else if (!strcmp(arg, "msdf")) + config.imageType = ImageType::MSDF; + else if (!strcmp(arg, "mtsdf")) + config.imageType = ImageType::MTSDF; + else + ABORT("Invalid atlas type. Valid types are: hardmask, softmask, sdf, psdf, msdf, mtsdf"); + ++argPos; + continue; + } + ARG_CASE("-format", 1) { + arg = argv[++argPos]; + if (!strcmp(arg, "png")) + config.imageFormat = ImageFormat::PNG; + else if (!strcmp(arg, "bmp")) + config.imageFormat = ImageFormat::BMP; + else if (!strcmp(arg, "tiff")) + config.imageFormat = ImageFormat::TIFF; + else if (!strcmp(arg, "text")) + config.imageFormat = ImageFormat::TEXT; + else if (!strcmp(arg, "textfloat")) + config.imageFormat = ImageFormat::TEXT_FLOAT; + else if (!strcmp(arg, "bin")) + config.imageFormat = ImageFormat::BINARY; + else if (!strcmp(arg, "binfloat")) + config.imageFormat = ImageFormat::BINARY_FLOAT; + else if (!strcmp(arg, "binfloatbe")) + config.imageFormat = ImageFormat::BINARY_FLOAT_BE; + else + ABORT("Invalid image format. Valid formats are: png, bmp, tiff, text, textfloat, bin, binfloat"); + imageFormatName = arg; + ++argPos; + continue; + } + ARG_CASE("-font", 1) { + fontFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-charset", 1) { + charsetFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-arfont", 1) { + config.arteryFontFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-imageout", 1) { + config.imageFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-json", 1) { + config.jsonFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-csv", 1) { + config.csvFilename = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-shadronpreview", 2) { + config.shadronPreviewFilename = argv[++argPos]; + config.shadronPreviewText = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-dimensions", 2) { + unsigned w, h; + if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h)) + ABORT("Invalid atlas dimensions. Use -dimensions with two positive integers."); + fixedWidth = w, fixedHeight = h; + argPos += 3; + continue; + } + ARG_CASE("-pots", 0) { + atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_SQUARE; + fixedWidth = -1, fixedHeight = -1; + ++argPos; + continue; + } + ARG_CASE("-potr", 0) { + atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_RECTANGLE; + fixedWidth = -1, fixedHeight = -1; + ++argPos; + continue; + } + ARG_CASE("-square", 0) { + atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::SQUARE; + fixedWidth = -1, fixedHeight = -1; + ++argPos; + continue; + } + ARG_CASE("-square2", 0) { + atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::EVEN_SQUARE; + fixedWidth = -1, fixedHeight = -1; + ++argPos; + continue; + } + ARG_CASE("-square4", 0) { + atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE; + fixedWidth = -1, fixedHeight = -1; + ++argPos; + continue; + } + ARG_CASE("-size", 1) { + double s; + if (!(parseDouble(s, argv[++argPos]) && s > 0)) + ABORT("Invalid EM size argument. Use -size with a positive real number."); + config.emSize = s; + ++argPos; + continue; + } + ARG_CASE("-minsize", 1) { + double s; + if (!(parseDouble(s, argv[++argPos]) && s > 0)) + ABORT("Invalid minimum EM size argument. Use -minsize with a positive real number."); + minEmSize = s; + ++argPos; + continue; + } + ARG_CASE("-emrange", 1) { + double r; + if (!(parseDouble(r, argv[++argPos]) && r >= 0)) + ABORT("Invalid range argument. Use -emrange with a positive real number."); + rangeMode = RANGE_EM; + rangeValue = r; + ++argPos; + continue; + } + ARG_CASE("-pxrange", 1) { + double r; + if (!(parseDouble(r, argv[++argPos]) && r >= 0)) + ABORT("Invalid range argument. Use -pxrange with a positive real number."); + rangeMode = RANGE_PIXEL; + rangeValue = r; + ++argPos; + continue; + } + ARG_CASE("-angle", 1) { + double at; + if (!parseAngle(at, argv[argPos+1])) + ABORT("Invalid angle threshold. Use -angle with a positive real number less than PI or a value in degrees followed by 'd' below 180d."); + config.angleThreshold = at; + argPos += 2; + continue; + } + ARG_CASE("-errorcorrection", 1) { + double ect; + if (!parseDouble(ect, argv[argPos+1]) || ect < 0) + ABORT("Invalid error correction threshold. Use -errorcorrection with a real number larger or equal to 1."); + config.generatorAttributes.errorCorrectionThreshold = ect; + argPos += 2; + continue; + } + ARG_CASE("-miterlimit", 1) { + double m; + if (!(parseDouble(m, argv[++argPos]) && m >= 0)) + ABORT("Invalid miter limit argument. Use -miterlimit with a positive real number."); + config.miterLimit = m; + ++argPos; + continue; + } + ARG_CASE("-nooverlap", 0) { + config.generatorAttributes.overlapSupport = false; + argPos += 1; + continue; + } + ARG_CASE("-noscanline", 0) { + config.generatorAttributes.scanlinePass = false; + argPos += 1; + continue; + } + ARG_CASE("-scanline", 0) { + config.generatorAttributes.scanlinePass = true; + argPos += 1; + continue; + } + ARG_CASE("-seed", 1) { + if (!parseUnsignedLL(config.coloringSeed, argv[argPos+1])) + ABORT("Invalid seed. Use -seed with N being a non-negative integer."); + argPos += 2; + continue; + } + ARG_CASE("-help", 0) { + puts(helpText); + return 0; + } + printf("Unknown setting or insufficient parameters: %s\n", arg); + suggestHelp = true; + ++argPos; + } + if (suggestHelp) + printf("Use -help for more information.\n"); + + // Nothing to do? + if (argc == 1) { + printf( + "Usage: msdf-atlas-gen" + #ifdef _WIN32 + ".exe" + #endif + " -font -charset \n" + "Use -help for more information.\n" + ); + return 0; + } + if (!fontFilename) + ABORT("No font specified."); + if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename)) { + puts("No output specified."); + return 0; + } + bool layoutOnly = !(config.arteryFontFilename || config.imageFilename); + + // Fix up configuration based on related values + if (!(config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF)) + config.miterLimit = 0; + if (config.emSize > minEmSize) + minEmSize = config.emSize; + if (!(fixedWidth > 0 && fixedHeight > 0) && !(minEmSize > 0)) { + puts("Neither atlas size nor glyph size selected, using default..."); + minEmSize = DEFAULT_EM_SIZE; + } + if (!(config.imageType == ImageType::SDF || config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF)) { + rangeMode = RANGE_PIXEL; + rangeValue = (double) (config.imageType == ImageType::SOFT_MASK); + } else if (rangeValue <= 0) { + rangeMode = RANGE_PIXEL; + rangeValue = DEFAULT_PIXEL_RANGE; + } + + // Finalize image format + ImageFormat imageExtension = ImageFormat::UNSPECIFIED; + if (config.imageFilename) { + if (cmpExtension(config.imageFilename, ".png")) imageExtension = ImageFormat::PNG; + else if (cmpExtension(config.imageFilename, ".bmp")) imageExtension = ImageFormat::BMP; + else if (cmpExtension(config.imageFilename, ".tif") || cmpExtension(config.imageFilename, ".tiff")) imageExtension = ImageFormat::TIFF; + else if (cmpExtension(config.imageFilename, ".txt")) imageExtension = ImageFormat::TEXT; + else if (cmpExtension(config.imageFilename, ".bin")) imageExtension = ImageFormat::BINARY; + } + if (config.imageFormat == ImageFormat::UNSPECIFIED) { + config.imageFormat = ImageFormat::PNG; + imageFormatName = "png"; + // If image format is not specified and -imageout is the only image output, infer format from its extension + if (imageExtension != ImageFormat::UNSPECIFIED && !config.arteryFontFilename) + config.imageFormat = imageExtension; + } + if (config.imageType == ImageType::MTSDF && config.imageFormat == ImageFormat::BMP) + ABORT("Atlas type not compatible with image format. MTSDF requires a format with alpha channel."); + if (config.arteryFontFilename && !(config.imageFormat == ImageFormat::PNG || config.imageFormat == ImageFormat::BINARY || config.imageFormat == ImageFormat::BINARY_FLOAT)) { + config.arteryFontFilename = nullptr; + result = 1; + puts("Error: Unable to create an Artery Font file with the specified image format!"); + // Recheck whether there is anything else to do + if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename)) + return result; + layoutOnly = !(config.arteryFontFilename || config.imageFilename); + } + if (imageExtension != ImageFormat::UNSPECIFIED) { + // Warn if image format mismatches -imageout extension + bool mismatch = false; + switch (config.imageFormat) { + case ImageFormat::TEXT: case ImageFormat::TEXT_FLOAT: + mismatch = imageExtension != ImageFormat::TEXT; + break; + case ImageFormat::BINARY: case ImageFormat::BINARY_FLOAT: case ImageFormat::BINARY_FLOAT_BE: + mismatch = imageExtension != ImageFormat::BINARY; + break; + default: + mismatch = imageExtension != config.imageFormat; + } + if (mismatch) + printf("Warning: Output image file extension does not match the image's actual format (%s)!\n", imageFormatName); + } + imageFormatName = nullptr; // No longer consistent with imageFormat + + // Load font + class FontHolder { + msdfgen::FreetypeHandle *ft; + msdfgen::FontHandle *font; + public: + explicit FontHolder(const char *fontFilename) : ft(nullptr), font(nullptr) { + if ((ft = msdfgen::initializeFreetype())) + font = msdfgen::loadFont(ft, fontFilename); + } + ~FontHolder() { + if (ft) { + if (font) + msdfgen::destroyFont(font); + msdfgen::deinitializeFreetype(ft); + } + } + operator msdfgen::FontHandle *() const { + return font; + } + } font(fontFilename); + if (!font) + ABORT("Failed to load specified font file."); + msdfgen::FontMetrics fontMetrics = { }; + msdfgen::getFontMetrics(fontMetrics, font); + if (fontMetrics.emSize <= 0) + fontMetrics.emSize = DEFAULT_EM_SIZE; + + // Load character set + Charset charset; + if (charsetFilename) { + if (!charset.load(charsetFilename)) + ABORT("Failed to load character set specification."); + } else + charset = Charset::ASCII; + if (charset.empty()) + ABORT("No character set loaded."); + + // Load glyphs + std::vector glyphs; + loadGlyphs(glyphs, font, charset); + printf("Loaded geometry of %d out of %d characters.\n", (int) glyphs.size(), (int) charset.size()); + + // Determine final atlas dimensions, scale and range, pack glyphs + { + double unitRange = 0, pxRange = 0; + switch (rangeMode) { + case RANGE_EM: + unitRange = rangeValue*fontMetrics.emSize; + break; + case RANGE_PIXEL: + pxRange = rangeValue; + break; + } + bool fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0; + bool fixedScale = config.emSize > 0; + TightAtlasPacker atlasPacker; + if (fixedDimensions) + atlasPacker.setDimensions(fixedWidth, fixedHeight); + else + atlasPacker.setDimensionsConstraint(atlasSizeConstraint); + atlasPacker.setPadding(config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF ? 0 : -1); + // TODO: In this case (if padding is -1), the border pixels of each glyph are black, but still computed. For floating-point output, this may play a role. + if (fixedScale) + atlasPacker.setScale(config.emSize/fontMetrics.emSize); + else + atlasPacker.setMinimumScale(minEmSize/fontMetrics.emSize); + atlasPacker.setPixelRange(pxRange); + atlasPacker.setUnitRange(unitRange); + atlasPacker.setMiterLimit(config.miterLimit); + if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { + if (remaining < 0) { + ABORT("Failed to pack glyphs into atlas."); + } else { + printf("Error: Could not fit %d out of %d glyphs into the atlas.\n", remaining, (int) glyphs.size()); + return 1; + } + } + atlasPacker.getDimensions(config.width, config.height); + if (!(config.width > 0 && config.height > 0)) + ABORT("Unable to determine atlas size."); + config.emSize = atlasPacker.getScale()*fontMetrics.emSize; + config.pxRange = atlasPacker.getPixelRange(); + if (!fixedScale) + printf("Glyph size: %.9g pixels/EM\n", config.emSize); + if (!fixedDimensions) + printf("Atlas dimensions: %d x %d\n", config.width, config.height); + } + + // Generate atlas bitmap + if (!layoutOnly) { + + // Edge coloring + if (config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF) { + unsigned long long glyphSeed = config.coloringSeed; + for (GlyphGeometry &glyph : glyphs) { + glyphSeed *= MCG_MULTIPLIER; + glyph.edgeColoring(config.angleThreshold, glyphSeed); + } + } + + bool floatingPoint = ( + config.imageFormat == ImageFormat::TIFF || + config.imageFormat == ImageFormat::TEXT_FLOAT || + config.imageFormat == ImageFormat::BINARY_FLOAT || + config.imageFormat == ImageFormat::BINARY_FLOAT_BE + ); + + bool success = false; + switch (config.imageType) { + case ImageType::HARD_MASK: + if (floatingPoint) + success = makeAtlas(glyphs, font, config); + else + success = makeAtlas(glyphs, font, config); + break; + case ImageType::SOFT_MASK: + case ImageType::SDF: + if (floatingPoint) + success = makeAtlas(glyphs, font, config); + else + success = makeAtlas(glyphs, font, config); + break; + case ImageType::PSDF: + if (floatingPoint) + success = makeAtlas(glyphs, font, config); + else + success = makeAtlas(glyphs, font, config); + break; + case ImageType::MSDF: + if (floatingPoint) + success = makeAtlas(glyphs, font, config); + else + success = makeAtlas(glyphs, font, config); + break; + case ImageType::MTSDF: + if (floatingPoint) + success = makeAtlas(glyphs, font, config); + else + success = makeAtlas(glyphs, font, config); + break; + } + if (!success) + result = 1; + } + + if (config.csvFilename) { + if (exportCSV(glyphs.data(), glyphs.size(), fontMetrics.emSize, config.csvFilename)) + puts("Glyph layout written into CSV file."); + else { + result = 1; + puts("Failed to write CSV output file."); + } + } + if (config.jsonFilename) { + if (exportJSON(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename)) + puts("Glyph layout and metadata written into JSON file."); + else { + result = 1; + puts("Failed to write JSON output file."); + } + } + + if (config.shadronPreviewFilename && config.shadronPreviewText) { + std::vector previewText; + utf8Decode(previewText, config.shadronPreviewText); + previewText.push_back(0); + if (generateShadronPreview(font, glyphs.data(), glyphs.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, config.shadronPreviewFilename)) + puts("Shadron preview script generated."); + else { + result = 1; + puts("Failed to generate Shadron preview file."); + } + } + + return result; +} + +#endif diff --git a/msdf-atlas-gen/msdf-atlas-gen.h b/msdf-atlas-gen/msdf-atlas-gen.h new file mode 100644 index 0000000..6c9ebb5 --- /dev/null +++ b/msdf-atlas-gen/msdf-atlas-gen.h @@ -0,0 +1,41 @@ + +#pragma once + +/* + * MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.0 (2020-03-08) + * --------------------------------------------------------------------- + * A utility by Viktor Chlumsky, (c) 2020 + * + * Generates compact bitmap font atlases using MSDFGEN. + * + */ + +#include +#include + +#include "types.h" +#include "utf8.h" +#include "Rectangle.h" +#include "Charset.h" +#include "GlyphBox.h" +#include "GlyphGeometry.h" +#include "RectanglePacker.h" +#include "rectangle-packing.h" +#include "Workload.h" +#include "size-selectors.h" +#include "bitmap-blit.h" +#include "AtlasStorage.h" +#include "BitmapAtlasStorage.h" +#include "TightAtlasPacker.h" +#include "AtlasGenerator.h" +#include "ImmediateAtlasGenerator.h" +#include "DynamicAtlas.h" +#include "glyph-generators.h" +#include "image-encode.h" +#include "image-save.h" +#include "artery-font-export.h" +#include "csv-export.h" +#include "json-export.h" +#include "shadron-preview-generator.h" + +#define MSDF_ATLAS_VERSION "1.0" diff --git a/msdf-atlas-gen/rectangle-packing.h b/msdf-atlas-gen/rectangle-packing.h new file mode 100644 index 0000000..4bcc6a8 --- /dev/null +++ b/msdf-atlas-gen/rectangle-packing.h @@ -0,0 +1,19 @@ + +#pragma once + +#include +#include "Rectangle.h" + +namespace msdf_atlas { + +/// Packs the rectangle array into an atlas with fixed dimensions, returns how many didn't fit (0 on success) +template +int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding = 0); + +/// Packs the rectangle array into an atlas of unknown size, returns the minimum required dimensions constrained by SizeSelector +template +std::pair packRectangles(RectangleType *rectangles, int count, int padding = 0); + +} + +#include "rectangle-packing.hpp" diff --git a/msdf-atlas-gen/rectangle-packing.hpp b/msdf-atlas-gen/rectangle-packing.hpp new file mode 100644 index 0000000..56c7a84 --- /dev/null +++ b/msdf-atlas-gen/rectangle-packing.hpp @@ -0,0 +1,61 @@ + +#include "rectangle-packing.h" + +#include +#include "RectanglePacker.h" + +namespace msdf_atlas { + +static void copyRectanglePlacement(Rectangle &dst, const Rectangle &src) { + dst.x = src.x; + dst.y = src.y; +} + +static void copyRectanglePlacement(OrientedRectangle &dst, const OrientedRectangle &src) { + dst.x = src.x; + dst.y = src.y; + dst.rotated = src.rotated; +} + +template +int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding) { + if (padding) + for (int i = 0; i < count; ++i) { + rectangles[i].w += padding; + rectangles[i].h += padding; + } + int result = RectanglePacker(width+padding, height+padding).pack(rectangles, count); + if (padding) + for (int i = 0; i < count; ++i) { + rectangles[i].w -= padding; + rectangles[i].h -= padding; + } + return result; +} + +template +std::pair packRectangles(RectangleType *rectangles, int count, int padding) { + std::vector rectanglesCopy(count); + int totalArea = 0; + for (int i = 0; i < count; ++i) { + rectanglesCopy[i].w = rectangles[i].w+padding; + rectanglesCopy[i].h = rectangles[i].h+padding; + totalArea += rectangles[i].w*rectangles[i].h; + } + std::pair dimensions; + SizeSelector sizeSelector(totalArea); + int width, height; + while (sizeSelector(width, height)) { + if (!RectanglePacker(width+padding, height+padding).pack(rectanglesCopy.data(), count)) { + dimensions.first = width; + dimensions.second = height; + for (int i = 0; i < count; ++i) + copyRectanglePlacement(rectangles[i], rectanglesCopy[i]); + --sizeSelector; + } else + ++sizeSelector; + } + return dimensions; +} + +} diff --git a/msdf-atlas-gen/shadron-preview-generator.cpp b/msdf-atlas-gen/shadron-preview-generator.cpp new file mode 100644 index 0000000..807a3da --- /dev/null +++ b/msdf-atlas-gen/shadron-preview-generator.cpp @@ -0,0 +1,148 @@ + +#include "shadron-preview-generator.h" + +#include +#include + +namespace msdf_atlas { + +static const char * const shadronFillGlyphMask = R"( +template +glsl vec4 fillGlyph(vec2 texCoord) { + float fill = texture((ATLAS), texCoord).r; + return vec4(vec3(COLOR), fill); +} +)"; + +static const char * const shadronFillGlyphSdf = R"( +template +glsl vec4 fillGlyph(vec2 texCoord) { + vec3 s = texture((ATLAS), texCoord).rgb; + float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-0.5); + float fill = clamp(sd+0.5, 0.0, 1.0); + return vec4(vec3(COLOR), fill); +} +)"; + +static const char * const shadronPreviewPreamble = R"( +#include + +glsl struct GlyphVertex { + vec2 coord; + vec2 texCoord; +}; + +template +glsl vec4 projectVertex(out vec2 texCoord, in GlyphVertex vertex) { + vec2 coord = vertex.coord; + float scale = 2.0/max((TEXT_SIZE).x, shadron_Aspect*(TEXT_SIZE).y); + scale *= exp(0.0625*shadron_Mouse.z); + coord += vec2(-0.5, 0.5)*vec2(TEXT_SIZE); + coord *= scale*vec2(1.0, shadron_Aspect); + texCoord = vertex.texCoord; + return vec4(coord, 0.0, 1.0); +} +%s +#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \ + vertex_data(GlyphVertex), \ + fragment_data(vec2), \ + vertex(projectVertex, triangles, VERTEX_LIST), \ + fragment(fillGlyph), \ + depth(false), \ + blend(transparency), \ + background(vec4(vec3(COLOR), 0.0)), \ + dimensions(DIMENSIONS), \ + resizable(true) + +)"; + +static std::string relativizePath(const char *base, const char *target) { + if (target[0] == '/' || (target[0] && target[1] == ':')) // absolute path? + return target; + int commonPrefix = 0; + for (int i = 0; base[i] && target[i] && base[i] == target[i]; ++i) { + if (base[i] == '/' || base[i] == '\\') + commonPrefix = i+1; + } + base += commonPrefix; + target += commonPrefix; + int baseNesting = 0; + for (int i = 0; base[i]; ++i) + if (base[i] == '/' || base[i] == '\\') + ++baseNesting; + std::string output; + for (int i = 0; i < baseNesting; ++i) + output += "../"; + output += target; + return output; +} + +bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, const char *outputFilename) { + double texelWidth = 1./atlasWidth; + double texelHeight = 1./atlasHeight; + FILE *file = fopen(outputFilename, "w"); + if (!file) + return false; + fprintf(file, shadronPreviewPreamble, atlasType == ImageType::HARD_MASK || atlasType == ImageType::SOFT_MASK ? shadronFillGlyphMask : shadronFillGlyphSdf); + if (imageFilename) + fprintf(file, "image Atlas = file(\"%s\")", relativizePath(outputFilename, imageFilename).c_str()); + else + fprintf(file, "image Atlas = file()"); + fprintf(file, " : filter(%s), map(repeat);\n", atlasType == ImageType::HARD_MASK ? "nearest" : "linear"); + fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight); + { + msdfgen::FontMetrics fontMetrics; + if (!msdfgen::getFontMetrics(fontMetrics, font)) + return false; + double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY); + fputs("vertex_list GlyphVertex textQuadVertices = {\n", file); + double x = 0, y = -fsScale*fontMetrics.ascenderY; + double textWidth = 0; + for (const unicode_t *cp = text; *cp; ++cp) { + if (*cp == '\r') + continue; + if (*cp == '\n') { + textWidth = std::max(textWidth, x); + x = 0; + y -= fsScale*fontMetrics.lineHeight; + continue; + } + for (int i = 0; i < glyphCount; ++i) { + if (glyphs[i].getCodepoint() == *cp) { + if (!glyphs[i].isWhitespace()) { + double pl, pb, pr, pt; + double il, ib, ir, it; + glyphs[i].getQuadPlaneBounds(pl, pb, pr, pt); + glyphs[i].getQuadAtlasBounds(il, ib, ir, it); + pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale; + pl += x, pb += y, pr += x, pt += y; + il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight; + fprintf(file, " %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g,\n", + pl, pb, il, ib, + pr, pb, ir, ib, + pl, pt, il, it, + pr, pt, ir, it, + pl, pt, il, it, + pr, pb, ir, ib + ); + } + x += fsScale*glyphs[i].getAdvance(); + double kerning; + if (msdfgen::getKerning(kerning, font, cp[0], cp[1])) + x += fsScale*kerning; + break; + } + } + } + textWidth = std::max(textWidth, x); + y += fsScale*fontMetrics.descenderY; + fputs("};\n", file); + fprintf(file, "const vec2 textSize = vec2(%.9g, %.9g);\n\n", textWidth, -y); + } + fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file); + fputs("export png(Preview, \"preview.png\");\n", file); + fclose(file); + return true; +} + +} diff --git a/msdf-atlas-gen/shadron-preview-generator.h b/msdf-atlas-gen/shadron-preview-generator.h new file mode 100644 index 0000000..240fe72 --- /dev/null +++ b/msdf-atlas-gen/shadron-preview-generator.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/// Generates a Shadron script that displays a string using the generated atlas +bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, const char *outputFilename); + +} diff --git a/msdf-atlas-gen/size-selectors.cpp b/msdf-atlas-gen/size-selectors.cpp new file mode 100644 index 0000000..ef2f21b --- /dev/null +++ b/msdf-atlas-gen/size-selectors.cpp @@ -0,0 +1,90 @@ + +#include "size-selectors.h" + +#include + +namespace msdf_atlas { + +template +SquareSizeSelector::SquareSizeSelector(int minArea) : lowerBound(0), upperBound(-1) { + if (minArea > 0) + lowerBound = int(sqrt(minArea-1))/MULTIPLE+1; + updateCurrent(); +} + +template +void SquareSizeSelector::updateCurrent() { + if (upperBound < 0) + current = 5*lowerBound/4+16/MULTIPLE; + else + current = lowerBound+(upperBound-lowerBound)/2; +} + +template +bool SquareSizeSelector::operator()(int &width, int &height) const { + width = MULTIPLE*current, height = MULTIPLE*current; + return lowerBound < upperBound || upperBound < 0; +} + +template +SquareSizeSelector & SquareSizeSelector::operator++() { + lowerBound = current+1; + updateCurrent(); + return *this; +} + +template +SquareSizeSelector & SquareSizeSelector::operator--() { + upperBound = current; + updateCurrent(); + return *this; +} + +template class SquareSizeSelector<1>; +template class SquareSizeSelector<2>; +template class SquareSizeSelector<4>; + +SquarePowerOfTwoSizeSelector::SquarePowerOfTwoSizeSelector(int minArea) : side(1) { + while (side*side < minArea) + side <<= 1; +} + +bool SquarePowerOfTwoSizeSelector::operator()(int &width, int &height) const { + width = side, height = side; + return side > 0; +} + +SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator++() { + side <<= 1; + return *this; +} + +SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator--() { + side = 0; + return *this; +} + +PowerOfTwoSizeSelector::PowerOfTwoSizeSelector(int minArea) : w(1), h(1) { + while (w*h < minArea) + ++*this; +} + +bool PowerOfTwoSizeSelector::operator()(int &width, int &height) const { + width = w, height = h; + return w > 0 && h > 0; +} + +PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator++() { + if (w == h) + w <<= 1; + else + h = w; + return *this; +} + +PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator--() { + w = 0, h = 0; + return *this; +} + +} diff --git a/msdf-atlas-gen/size-selectors.h b/msdf-atlas-gen/size-selectors.h new file mode 100644 index 0000000..b75fc3a --- /dev/null +++ b/msdf-atlas-gen/size-selectors.h @@ -0,0 +1,54 @@ + +#pragma once + +namespace msdf_atlas { + +// The size selector classes are used to select the minimum dimensions of the atlas fitting a given constraint. + +/// Selects square dimensions which are also a multiple of MULTIPLE +template +class SquareSizeSelector { + +public: + explicit SquareSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + SquareSizeSelector & operator++(); + SquareSizeSelector & operator--(); + +private: + int lowerBound, upperBound; + int current; + + void updateCurrent(); + +}; + +/// Selects square power-of-two dimensions +class SquarePowerOfTwoSizeSelector { + +public: + explicit SquarePowerOfTwoSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + SquarePowerOfTwoSizeSelector & operator++(); + SquarePowerOfTwoSizeSelector & operator--(); + +private: + int side; + +}; + +/// Selects square or rectangular (2:1) power-of-two dimensions +class PowerOfTwoSizeSelector { + +public: + explicit PowerOfTwoSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + PowerOfTwoSizeSelector & operator++(); + PowerOfTwoSizeSelector & operator--(); + +private: + int w, h; + +}; + +} diff --git a/msdf-atlas-gen/types.h b/msdf-atlas-gen/types.h new file mode 100644 index 0000000..6f45c95 --- /dev/null +++ b/msdf-atlas-gen/types.h @@ -0,0 +1,40 @@ + +#pragma once + +#include + +namespace msdf_atlas { + +typedef unsigned char byte; +typedef uint32_t unicode_t; + +/// Type of atlas image contents +enum class ImageType { + /// Rendered glyphs without anti-aliasing (two colors only) + HARD_MASK, + /// Rendered glyphs with anti-aliasing + SOFT_MASK, + /// Signed (true) distance field + SDF, + /// Signed pseudo-distance field + PSDF, + /// Multi-channel signed distance field + MSDF, + /// Multi-channel & true signed distance field + MTSDF +}; + +/// Atlas image encoding +enum class ImageFormat { + UNSPECIFIED, + PNG, + BMP, + TIFF, + TEXT, + TEXT_FLOAT, + BINARY, + BINARY_FLOAT, + BINARY_FLOAT_BE +}; + +} diff --git a/msdf-atlas-gen/utf8.cpp b/msdf-atlas-gen/utf8.cpp new file mode 100644 index 0000000..7e81c57 --- /dev/null +++ b/msdf-atlas-gen/utf8.cpp @@ -0,0 +1,38 @@ + +#include "utf8.h" + +namespace msdf_atlas { + +void utf8Decode(std::vector &codepoints, const char *utf8String) { + bool start = true; + int rBytes = 0; + unicode_t cp = 0; + + for (const char *c = utf8String; *c; ++c) { + if (rBytes > 0) { + --rBytes; + if ((*c&0xc0) == 0x80) + cp |= (*c&0x3f)<<(6*rBytes); + // else error + } else if (!(*c&0x80)) { + cp = *c; + rBytes = 0; + } else if (*c&0x40) { + int block; + for (block = 0; (*c<>block))<<(6*block); + rBytes = block; + } else + continue; // error + } else + continue; // error + if (!rBytes) { + if (!(start && cp == 0xfeff)) // BOM + codepoints.push_back(cp); + start = false; + } + } +} + +} diff --git a/msdf-atlas-gen/utf8.h b/msdf-atlas-gen/utf8.h new file mode 100644 index 0000000..740c2fb --- /dev/null +++ b/msdf-atlas-gen/utf8.h @@ -0,0 +1,12 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Decodes the UTF-8 string into an array of Unicode codepoints +void utf8Decode(std::vector &codepoints, const char *utf8String); + +} diff --git a/resource.h b/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..d0ca059fb29154229292d8b3bdb6f4fab890847b GIT binary patch literal 916 zcmb7?-A=+l5QWdRiSN*aOCyN!1!~I$5vsA2=v5ELhMSN?N%PESiWaL8piDLolpgrCDC1R=xU+@