diff --git a/Content/Editor/Fonts/Inconsolata-Regular.flax b/Content/Editor/Fonts/Inconsolata-Regular.flax index a2fdf0626..d85ef610a 100644 --- a/Content/Editor/Fonts/Inconsolata-Regular.flax +++ b/Content/Editor/Fonts/Inconsolata-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36995fa880bf47331618b1e96f613c6925e72484fe76e98d6e44c1985ecab017 -size 93015 +oid sha256:139e2f54a268f4febc753aaa9807a33a6eb2dd84e788c5b8b4f50ca8683d2b3b +size 93116 diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax index 39964e6c5..8f8f00157 100644 --- a/Content/Editor/Fonts/NotoSansSC-Regular.flax +++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304 -size 10560899 +oid sha256:2ddc7af5fab1dd4ad858ad2abf11c6c243d908345720892cd56242fbc9c33b02 +size 10560900 diff --git a/Content/Editor/Fonts/Roboto-Regular.flax b/Content/Editor/Fonts/Roboto-Regular.flax index a1e416f77..01251cac6 100644 --- a/Content/Editor/Fonts/Roboto-Regular.flax +++ b/Content/Editor/Fonts/Roboto-Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:574b86d8e7439e88c3d7b4265cbc5ad7089c49368c47661b2c6f4c997cb53d86 -size 172093 +oid sha256:c0584b3a3c186364464de155edece5cb6a8c46000676ac82a45f951f3a67fac5 +size 172194 diff --git a/Content/Editor/Fonts/SegMDL2.flax b/Content/Editor/Fonts/SegMDL2.flax index d06bac811..b627849d9 100644 --- a/Content/Editor/Fonts/SegMDL2.flax +++ b/Content/Editor/Fonts/SegMDL2.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c4a17c412fce35275af5be883c784b57d192eb188fb3568990b0ae8e0e6d34e -size 204049 +oid sha256:f7a9d723d6342b8a484dfb2ee784cef506b3311a6092be0e9de35f941d698ff5 +size 204150 diff --git a/Content/Editor/Fonts/Segoe Media Center Regular.flax b/Content/Editor/Fonts/Segoe Media Center Regular.flax index e9038cd65..6c17d96bf 100644 --- a/Content/Editor/Fonts/Segoe Media Center Regular.flax +++ b/Content/Editor/Fonts/Segoe Media Center Regular.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d277b4abbdc1c91f056dd0d838631135242643abb41ce5751b1be3aaca72401 -size 72697 +oid sha256:c21443a111daede658cb634f56b4ae0838dbb2eaa75d4ba8ba74a4c77df44de8 +size 72798 diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax index d5e3f59fa..3e0ccc18f 100644 --- a/Content/Shaders/GUI.flax +++ b/Content/Shaders/GUI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fed6a05104322f61a77f6cf69b64478518b829165a2a3ab7fc151098dcd48be -size 4526 +oid sha256:b816ba638bc37b8a8288e4c397ff5b708153057650fa6b626a109ba42d189694 +size 4721 diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index 3f4a7a681..e08a87b05 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -21,6 +21,10 @@ namespace FlaxEditor.Windows.Assets /// private sealed class PropertiesProxy { + [DefaultValue(FontRasterMode.Bitmap)] + [EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")] + public FontRasterMode RasterMode; + [DefaultValue(FontHinting.Default)] [EditorOrder(10), EditorDisplay("Properties"), Tooltip("The font hinting used when rendering characters.")] public FontHinting Hinting; @@ -41,7 +45,8 @@ namespace FlaxEditor.Windows.Assets { options = new FontOptions { - Hinting = Hinting + Hinting = Hinting, + RasterMode = RasterMode, }; if (AntiAliasing) options.Flags |= FontFlags.AntiAliasing; @@ -57,6 +62,7 @@ namespace FlaxEditor.Windows.Assets AntiAliasing = (options.Flags & FontFlags.AntiAliasing) == FontFlags.AntiAliasing; Bold = (options.Flags & FontFlags.Bold) == FontFlags.Bold; Italic = (options.Flags & FontFlags.Italic) == FontFlags.Italic; + RasterMode = options.RasterMode; } } diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index 5574919c4..12cde01e8 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -3,7 +3,6 @@ #include "Audio.h" #include "AudioBackend.h" #include "AudioSettings.h" -#include "FlaxEngine.Gen.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Level/Level.h" diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h index 80c2e6c39..4ca6e6d73 100644 --- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h +++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h @@ -5,6 +5,7 @@ #if USE_EDITOR #include "BinaryAssetUpgrader.h" +#include "Engine/Render2D/FontAsset.h" /// /// Font Asset Upgrader @@ -17,10 +18,33 @@ public: { const Upgrader upgraders[] = { - {}, + { 3, 4, &Upgrade_3_To_4 }, }; setup(upgraders, ARRAY_COUNT(upgraders)); } + +private: + struct FontOptionsOld + { + FontHinting Hinting; + FontFlags Flags; + }; + + static bool Upgrade_3_To_4(AssetMigrationContext& context) + { + ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4); + + FontOptionsOld optionsOld; + Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptionsOld)); + + FontOptions options; + options.Hinting = optionsOld.Hinting; + options.Flags = optionsOld.Flags; + options.RasterMode = FontRasterMode::Bitmap; + context.Output.CustomData.Copy(&options); + + return CopyChunk(context, 0); + } }; #endif diff --git a/Source/Engine/ContentImporters/ImportFont.cpp b/Source/Engine/ContentImporters/ImportFont.cpp index c7dc01fe6..964f3907e 100644 --- a/Source/Engine/ContentImporters/ImportFont.cpp +++ b/Source/Engine/ContentImporters/ImportFont.cpp @@ -12,12 +12,13 @@ CreateAssetResult ImportFont::Import(CreateAssetContext& context) { // Base - IMPORT_SETUP(FontAsset, 3); + IMPORT_SETUP(FontAsset, 4); // Setup header FontOptions options; options.Hinting = FontHinting::Default; options.Flags = FontFlags::AntiAliasing; + options.RasterMode = FontRasterMode::Bitmap; context.Data.CustomData.Copy(&options); // Open the file diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 15b3a1932..b53b69741 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -596,7 +596,7 @@ void EngineImpl::InitLog() LOG(Info, "Compiled for Dev Environment"); #endif #if defined(FLAXENGINE_BRANCH) && defined(FLAXENGINE_COMMIT) - LOG(Info, "Version " FLAXENGINE_VERSION_TEXT ", " FLAXENGINE_BRANCH ", " FLAXENGINE_COMMIT); + LOG(Info, "Version " FLAXENGINE_VERSION_TEXT ", {}, {}", StringAsUTF16<>(FLAXENGINE_BRANCH).Get(), StringAsUTF16<>(FLAXENGINE_COMMIT).Get()); #else LOG(Info, "Version " FLAXENGINE_VERSION_TEXT); #endif diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index abf62aef0..8123142aa 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -5,6 +5,7 @@ #include "Level.h" #include "SceneQuery.h" #include "SceneObjectsFactory.h" +#include "FlaxEngine.Gen.h" #include "Scene/Scene.h" #include "Prefabs/Prefab.h" #include "Prefabs/PrefabManager.h" diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index a46ee52f6..adea4575b 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -5,6 +5,7 @@ #include "LargeWorlds.h" #include "SceneQuery.h" #include "SceneObjectsFactory.h" +#include "FlaxEngine.Gen.h" #include "Scene/Scene.h" #include "Engine/Content/Content.h" #include "Engine/Content/Deprecated.h" diff --git a/Source/Engine/Render2D/FontAsset.cs b/Source/Engine/Render2D/FontAsset.cs index 72520db5d..624daab5b 100644 --- a/Source/Engine/Render2D/FontAsset.cs +++ b/Source/Engine/Render2D/FontAsset.cs @@ -1,5 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. +using System; + namespace FlaxEngine { partial struct FontOptions @@ -11,7 +13,7 @@ namespace FlaxEngine /// true if this object has the same value as ; otherwise, false public bool Equals(FontOptions other) { - return Hinting == other.Hinting && Flags == other.Flags; + return Hinting == other.Hinting && Flags == other.Flags && RasterMode == other.RasterMode; } /// @@ -23,10 +25,7 @@ namespace FlaxEngine /// public override int GetHashCode() { - unchecked - { - return ((int)Hinting * 397) ^ (int)Flags; - } + return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode); } /// @@ -37,7 +36,7 @@ namespace FlaxEngine /// true if has the same value as ; otherwise, false. public static bool operator ==(FontOptions left, FontOptions right) { - return left.Hinting == right.Hinting && left.Flags == right.Flags; + return left.Hinting == right.Hinting && left.Flags == right.Flags && left.RasterMode == right.RasterMode; } /// @@ -48,7 +47,7 @@ namespace FlaxEngine /// true if has a different value than ; otherwise,false. public static bool operator !=(FontOptions left, FontOptions right) { - return left.Hinting != right.Hinting || left.Flags != right.Flags; + return left.Hinting != right.Hinting || left.Flags != right.Flags || left.RasterMode != right.RasterMode; } } } diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index b1ea87e0a..56cc3d655 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -66,6 +66,22 @@ API_ENUM(Attributes="Flags") enum class FontFlags : byte Italic = 4, }; +/// +/// The font rasterization mode. +/// +API_ENUM() enum class FontRasterMode : byte +{ + /// + /// Use the default FreeType rasterizer to render font atlases. + /// + Bitmap, + + /// + /// Use the Multi-channel Signed Distance Field (MSDF) generator to render font atlases. Need to be rendered with a compatible material. + /// + MSDF, +}; + DECLARE_ENUM_OPERATORS(FontFlags); /// @@ -76,7 +92,7 @@ API_STRUCT() struct FontOptions DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions); /// - /// The hinting. + /// The font hinting used when rendering characters. /// API_FIELD() FontHinting Hinting; @@ -84,6 +100,11 @@ API_STRUCT() struct FontOptions /// The flags. /// API_FIELD() FontFlags Flags; + + /// + /// The font rasterization mode. + /// + API_FIELD() FontRasterMode RasterMode; }; /// @@ -91,7 +112,7 @@ API_STRUCT() struct FontOptions /// API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset { - DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); + DECLARE_BINARY_ASSET_HEADER(FontAsset, 4); friend Font; private: diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index 0375814ae..d58394bb3 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -9,6 +9,7 @@ #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" +#include "Engine/Render2D/MSDFGenerator.h" #include "IncludeFreeType.h" #include #include @@ -125,7 +126,11 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Set load flags uint32 glyphFlags = FT_LOAD_NO_BITMAP; const bool useAA = EnumHasAnyFlags(options.Flags, FontFlags::AntiAliasing); - if (useAA) + if (options.RasterMode == FontRasterMode::MSDF) + { + glyphFlags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; + } + else if (useAA) { switch (options.Hinting) { @@ -185,75 +190,109 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) FT_GlyphSlot_Oblique(face->glyph); } - // Render glyph to the bitmap - FT_GlyphSlot glyph = face->glyph; - FT_Render_Glyph(glyph, useAA ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); - - FT_Bitmap* bitmap = &glyph->bitmap; - FT_Bitmap tmpBitmap; - if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY) + int32 glyphWidth = 0; + int32 glyphHeight = 0; + if (options.RasterMode == FontRasterMode::Bitmap) { - // Convert the bitmap to 8bpp grayscale - FT_Bitmap_New(&tmpBitmap); - FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4); - bitmap = &tmpBitmap; - } - ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); + // Render glyph to the bitmap + FT_GlyphSlot glyph = face->glyph; + FT_Render_Glyph(glyph, useAA ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); - // Fill the character data - entry.AdvanceX = Convert26Dot6ToRoundedPixel(glyph->advance.x); - entry.OffsetY = glyph->bitmap_top; - entry.OffsetX = glyph->bitmap_left; - entry.IsValid = true; - entry.BearingY = Convert26Dot6ToRoundedPixel(glyph->metrics.horiBearingY); - entry.Height = Convert26Dot6ToRoundedPixel(glyph->metrics.height); + FT_Bitmap* bitmap = &glyph->bitmap; + FT_Bitmap tmpBitmap; + if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY) + { + // Convert the bitmap to 8bpp grayscale + FT_Bitmap_New(&tmpBitmap); + FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4); + bitmap = &tmpBitmap; + } + ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); - // Allocate memory - const int32 glyphWidth = bitmap->width; - const int32 glyphHeight = bitmap->rows; - GlyphImageData.Clear(); - GlyphImageData.Resize(glyphWidth * glyphHeight); + // Fill the character data + entry.AdvanceX = Convert26Dot6ToRoundedPixel(glyph->advance.x); + entry.OffsetY = glyph->bitmap_top; + entry.OffsetX = glyph->bitmap_left; + entry.IsValid = true; + entry.BearingY = Convert26Dot6ToRoundedPixel(glyph->metrics.horiBearingY); + entry.Height = Convert26Dot6ToRoundedPixel(glyph->metrics.height); - // End for empty glyphs - if (GlyphImageData.IsEmpty()) - { - entry.TextureIndex = MAX_uint8; + // Allocate memory + glyphWidth = bitmap->width; + glyphHeight = bitmap->rows; + GlyphImageData.Clear(); + GlyphImageData.Resize(glyphWidth * glyphHeight); + + // End for empty glyphs + if (GlyphImageData.IsEmpty()) + { + entry.TextureIndex = MAX_uint8; + if (bitmap == &tmpBitmap) + { + FT_Bitmap_Done(Library, bitmap); + bitmap = nullptr; + } + return false; + } + + // Copy glyph data after rasterization (row by row) + for (int32 row = 0; row < glyphHeight; row++) + { + Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth); + } + + // Normalize gray scale images not using 256 colors + if (bitmap->num_grays != 256) + { + const int32 scale = 255 / (bitmap->num_grays - 1); + for (byte& pixel : GlyphImageData) + pixel *= scale; + } + + // Free temporary bitmap if used if (bitmap == &tmpBitmap) { FT_Bitmap_Done(Library, bitmap); bitmap = nullptr; } - return false; } - - // Copy glyph data after rasterization (row by row) - for (int32 row = 0; row < glyphHeight; row++) + else { - Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth); - } + // Generate bitmap for MSDF + FT_GlyphSlot glyph = face->glyph; - // Normalize gray scale images not using 256 colors - if (bitmap->num_grays != 256) - { - const int32 scale = 255 / (bitmap->num_grays - 1); - for (byte& pixel : GlyphImageData) + // Set advance in advance + entry.AdvanceX = Convert26Dot6ToRoundedPixel(glyph->advance.x); + + int16 msdf_top = 0; + int16 msdf_left = 0; + MSDFGenerator::GenerateMSDF(glyph, GlyphImageData, glyphWidth, glyphHeight, msdf_top, msdf_left); + + // End for empty glyphs + if (GlyphImageData.IsEmpty()) { - pixel *= scale; + entry.TextureIndex = MAX_uint8; + return false; } - } - // Free temporary bitmap if used - if (bitmap == &tmpBitmap) - { - FT_Bitmap_Done(Library, bitmap); - bitmap = nullptr; + // Fill the remaining character data + entry.OffsetY = msdf_top; + entry.OffsetX = msdf_left; + entry.IsValid = true; + entry.BearingY = Convert26Dot6ToRoundedPixel(glyph->metrics.horiBearingY); + entry.Height = Convert26Dot6ToRoundedPixel(glyph->metrics.height); } // Find atlas for the character texture + PixelFormat atlasFormat = options.RasterMode == FontRasterMode::MSDF ? PixelFormat::R8G8B8A8_UNorm : PixelFormat::R8_UNorm; int32 atlasIndex = 0; const FontTextureAtlasSlot* slot = nullptr; for (; atlasIndex < Atlases.Count() && slot == nullptr; atlasIndex++) + { + if (Atlases[atlasIndex]->GetFormat() != atlasFormat) + continue; slot = Atlases[atlasIndex]->AddEntry(glyphWidth, glyphHeight, GlyphImageData); + } atlasIndex--; // Check if there is no atlas for this character @@ -261,7 +300,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) { // Create new atlas auto atlas = Content::CreateVirtualAsset(); - atlas->Setup(PixelFormat::R8_UNorm, FontTextureAtlas::PaddingStyle::PadWithZero); + atlas->Setup(atlasFormat, FontTextureAtlas::PaddingStyle::PadWithZero); Atlases.Add(atlas); atlasIndex++; diff --git a/Source/Engine/Render2D/FontTextureAtlas.h b/Source/Engine/Render2D/FontTextureAtlas.h index fe92ed67c..f688644ab 100644 --- a/Source/Engine/Render2D/FontTextureAtlas.h +++ b/Source/Engine/Render2D/FontTextureAtlas.h @@ -105,6 +105,14 @@ public: return _height; } + /// + /// Gets the atlas pixel format. + /// + FORCE_INLINE PixelFormat GetFormat() const + { + return _format; + } + /// /// Gets the atlas size. /// @@ -186,8 +194,8 @@ public: /// /// Returns glyph's bitmap data of the slot. /// - /// The slot in atlas. - /// The width of the slot. + /// The slot in atlas. + /// The width of the slot. /// The height of the slot. /// The stride of the slot. /// The pointer to the bitmap data of the given slot. diff --git a/Source/Engine/Render2D/MSDFGenerator.h b/Source/Engine/Render2D/MSDFGenerator.h new file mode 100644 index 000000000..e1add4dfd --- /dev/null +++ b/Source/Engine/Render2D/MSDFGenerator.h @@ -0,0 +1,142 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Math/Math.h" +#include +#include +#include + +class MSDFGenerator +{ + using Point2 = msdfgen::Point2; + using Shape = msdfgen::Shape; + using Contour = msdfgen::Contour; + using EdgeHolder = msdfgen::EdgeHolder; + + static Point2 ftPoint2(const FT_Vector& vector, double scale) + { + return Point2(scale * vector.x, scale * vector.y); + } + + struct FtContext + { + double scale; + Point2 position; + Shape* shape; + Contour* contour; + }; + + static int ftMoveTo(const FT_Vector* to, void* user) + { + FtContext* context = reinterpret_cast(user); + if (!(context->contour && context->contour->edges.empty())) + context->contour = &context->shape->addContour(); + context->position = ftPoint2(*to, context->scale); + return 0; + } + + static int ftLineTo(const FT_Vector* to, void* user) + { + FtContext* context = reinterpret_cast(user); + Point2 endpoint = ftPoint2(*to, context->scale); + if (endpoint != context->position) + { + context->contour->addEdge(EdgeHolder(context->position, endpoint)); + context->position = endpoint; + } + return 0; + } + + static int ftConicTo(const FT_Vector* control, const FT_Vector* to, void* user) + { + FtContext* context = reinterpret_cast(user); + Point2 endpoint = ftPoint2(*to, context->scale); + if (endpoint != context->position) + { + context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control, context->scale), endpoint)); + context->position = endpoint; + } + return 0; + } + + static int ftCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user) + { + FtContext* context = reinterpret_cast(user); + Point2 endpoint = ftPoint2(*to, context->scale); + if (endpoint != context->position || msdfgen::crossProduct(ftPoint2(*control1, context->scale) - endpoint, ftPoint2(*control2, context->scale) - endpoint)) + { + context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1, context->scale), ftPoint2(*control2, context->scale), endpoint)); + context->position = endpoint; + } + return 0; + } + + static void correctWinding(Shape& shape, Shape::Bounds& bounds) + { + Point2 p(bounds.l - (bounds.r - bounds.l) - 1, bounds.b - (bounds.t - bounds.b) - 1); + double distance = msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p); + if (distance > 0) + { + for (auto& contour : shape.contours) + contour.reverse(); + } + } + +public: + static void GenerateMSDF(FT_GlyphSlot glyph, Array& output, int32& outputWidth, int32& outputHeight, int16& top, int16& left) + { + Shape shape; + shape.contours.clear(); + shape.inverseYAxis = true; + + FtContext context = { }; + context.scale = 1.0 / 64.0; + context.shape = &shape; + FT_Outline_Funcs ftFunctions; + ftFunctions.move_to = &ftMoveTo; + ftFunctions.line_to = &ftLineTo; + ftFunctions.conic_to = &ftConicTo; + ftFunctions.cubic_to = &ftCubicTo; + ftFunctions.shift = 0; + ftFunctions.delta = 0; + FT_Outline_Decompose(&glyph->outline, &ftFunctions, &context); + + shape.normalize(); + edgeColoringSimple(shape, 3.0); + + // Todo: make this configurable + // Also hard-coded in material: MSDFFontMaterial + const double pxRange = 4.0; + + Shape::Bounds bounds = shape.getBounds(); + int32 width = static_cast(Math::CeilToInt(bounds.r - bounds.l + pxRange)); + int32 height = static_cast(Math::CeilToInt(bounds.t - bounds.b + pxRange)); + msdfgen::Bitmap msdf(width, height); + + auto transform = msdfgen::Vector2(Math::Ceil(-bounds.l + pxRange / 2.0), Math::Ceil(-bounds.b + pxRange / 2.0)); + + msdfgen::SDFTransformation t(msdfgen::Projection(1.0, transform), msdfgen::Range(pxRange)); + correctWinding(shape, bounds); + generateMTSDF(msdf, shape, t); + + output.Resize(width * height * 4); + + const msdfgen::BitmapConstRef& bitmap = msdf; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + output[(y * width + x) * 4] = msdfgen::pixelFloatToByte(bitmap(x, y)[0]); + output[(y * width + x) * 4 + 1] = msdfgen::pixelFloatToByte(bitmap(x, y)[1]); + output[(y * width + x) * 4 + 2] = msdfgen::pixelFloatToByte(bitmap(x, y)[2]); + output[(y * width + x) * 4 + 3] = msdfgen::pixelFloatToByte(bitmap(x, y)[3]); + } + } + outputWidth = width; + outputHeight = height; + top = height - static_cast(transform.y); + left = -static_cast(transform.x); + } +}; diff --git a/Source/Engine/Render2D/Render2D.Build.cs b/Source/Engine/Render2D/Render2D.Build.cs index f0be43983..a8f09270c 100644 --- a/Source/Engine/Render2D/Render2D.Build.cs +++ b/Source/Engine/Render2D/Render2D.Build.cs @@ -14,6 +14,7 @@ public class Render2D : EngineModule base.Setup(options); options.PrivateDependencies.Add("freetype"); + options.PrivateDependencies.Add("msdfgen"); options.PrivateDefinitions.Add("RENDER2D_USE_LINE_AA"); } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 689c1a3c1..d9e052e6e 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -2,6 +2,7 @@ #include "Render2D.h" #include "Font.h" +#include "FontAsset.h" #include "FontManager.h" #include "FontTextureAtlas.h" #include "RotatedRectangle.h" @@ -74,6 +75,7 @@ enum class DrawCallType : byte FillTexture, FillTexturePoint, DrawChar, + DrawCharMSDF, DrawCharMaterial, Custom, Material, @@ -164,6 +166,7 @@ struct CachedPSO GPUPipelineState* PS_Color_NoAlpha; GPUPipelineState* PS_Font; + GPUPipelineState* PS_FontMSDF; GPUPipelineState* PS_BlurH; GPUPipelineState* PS_BlurV; @@ -455,6 +458,7 @@ CanDrawCallCallback CanDrawCallBatch[] = CanDrawCallCallbackTexture, // FillTexture, CanDrawCallCallbackTexture, // FillTexturePoint, CanDrawCallCallbackChar, // DrawChar, + CanDrawCallCallbackChar, // DrawCharMSDF, CanDrawCallCallbackCharMaterial, // DrawCharMaterial, CanDrawCallCallbackFalse, // Custom, CanDrawCallCallbackMaterial, // Material, @@ -517,6 +521,12 @@ bool CachedPSO::Init(GPUShader* shader, bool useDepth) if (PS_Font->Init(desc)) return true; // + desc.BlendMode = BlendingMode::AlphaBlend; + desc.PS = shader->GetPS("PS_FontMSDF"); + PS_FontMSDF = GPUDevice::Instance->CreatePipelineState(); + if (PS_FontMSDF->Init(desc)) + return true; + // desc.PS = shader->GetPS("PS_LineAA"); PS_LineAA = GPUDevice::Instance->CreatePipelineState(); if (PS_LineAA->Init(desc)) @@ -994,6 +1004,10 @@ void DrawBatch(int32 startIndex, int32 count) Context->BindSR(0, d.AsChar.Tex); Context->SetState(CurrentPso->PS_Font); break; + case DrawCallType::DrawCharMSDF: + Context->BindSR(0, d.AsChar.Tex); + Context->SetState(CurrentPso->PS_FontMSDF); + break; case DrawCallType::DrawCharMaterial: { // Apply and bind material @@ -1186,7 +1200,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } else { - drawCall.Type = DrawCallType::DrawChar; + drawCall.Type = font->GetAsset()->GetOptions().RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar; drawCall.AsChar.Mat = nullptr; } Float2 pointer = location; @@ -1305,7 +1319,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } else { - drawCall.Type = DrawCallType::DrawChar; + drawCall.Type = font->GetAsset()->GetOptions().RasterMode == FontRasterMode::MSDF ? DrawCallType::DrawCharMSDF : DrawCallType::DrawChar; drawCall.AsChar.Mat = nullptr; } for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++) diff --git a/Source/Engine/Renderer/AtmospherePreCompute.h b/Source/Engine/Renderer/AtmospherePreCompute.h index e1fd3fa93..bf72f276a 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.h +++ b/Source/Engine/Renderer/AtmospherePreCompute.h @@ -2,8 +2,6 @@ #pragma once -#include "FlaxEngine.Gen.h" - class GPUTexture; /// diff --git a/Source/Engine/Serialization/ISerializeModifier.h b/Source/Engine/Serialization/ISerializeModifier.h index 8815ac2ea..0c49e24f1 100644 --- a/Source/Engine/Serialization/ISerializeModifier.h +++ b/Source/Engine/Serialization/ISerializeModifier.h @@ -4,7 +4,6 @@ #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Types/Guid.h" -#include "FlaxEngine.Gen.h" /// /// Object serialization modification base class. Allows to extend the serialization process by custom effects like object ids mapping. @@ -12,17 +11,18 @@ class FLAXENGINE_API ISerializeModifier { public: - /// /// Number of engine build when data was serialized. Useful to upgrade data from the older storage format. /// - uint32 EngineBuild = FLAXENGINE_VERSION_BUILD; + uint32 EngineBuild; // Utility for scene deserialization to track currently mapped in Prefab Instance object IDs into IdsMapping. - int32 CurrentInstance = -1; + int32 CurrentInstance; /// /// The object IDs mapping. Key is a serialized object id, value is mapped value to use. /// Dictionary IdsMapping; + + ISerializeModifier(); }; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 1eb6b0181..ec02fd65f 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -25,6 +25,13 @@ #include "Engine/Content/Asset.h" #include "Engine/Level/SceneObject.h" #include "Engine/Utilities/Encryption.h" +#include "FlaxEngine.Gen.h" + +ISerializeModifier::ISerializeModifier() +{ + EngineBuild = FLAXENGINE_VERSION_BUILD; + CurrentInstance = -1; +} void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier) { diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index 4c9b94042..bbf57ea30 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -5,6 +5,7 @@ #include "JsonWriters.h" #include "JsonSerializer.h" #include "MemoryReadStream.h" +#include "FlaxEngine.Gen.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Core/Collections/Dictionary.h" diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index 6f85fb950..048661617 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -148,7 +148,6 @@ void ShadowsOfMordor::Builder::Dispose() #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Scripting/Scripting.h" -#include "FlaxEngine.Gen.h" namespace ShadowsOfMordor { diff --git a/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libmsdfgen-core.a b/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libmsdfgen-core.a new file mode 100644 index 000000000..274ba3a52 --- /dev/null +++ b/Source/Platforms/Android/Binaries/ThirdParty/ARM64/libmsdfgen-core.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c34b3e22db047130c0f0a425197f4e27f345c458fbadb73104c7af69d0f9f2cd +size 4560474 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/msdfgen-core.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/msdfgen-core.lib new file mode 100644 index 000000000..3b2aeffc3 --- /dev/null +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/msdfgen-core.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb19678e7b094a07f035bf9438fde6ec56bc0d68a192a2e719a41aee4089e03 +size 1097914 diff --git a/Source/Shaders/GUI.shader b/Source/Shaders/GUI.shader index e9eedd4a6..a8f9452e9 100644 --- a/Source/Shaders/GUI.shader +++ b/Source/Shaders/GUI.shader @@ -92,7 +92,17 @@ float4 PS_Font(VS2PS input) : SV_Target0 PerformClipping(input); float4 color = input.Color; - color.a *= Image.Sample(SamplerLinearClamp, input.TexCoord).r; + color.a *= SampleFont(Image, input.TexCoord); + return color; +} + +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_FontMSDF(VS2PS input) : SV_Target0 +{ + PerformClipping(input); + + float4 color = input.Color; + color.a *= SampleFontMSDF(Image, input.TexCoord); return color; } diff --git a/Source/Shaders/GUICommon.hlsl b/Source/Shaders/GUICommon.hlsl index 14c8b1327..07420389c 100644 --- a/Source/Shaders/GUICommon.hlsl +++ b/Source/Shaders/GUICommon.hlsl @@ -54,4 +54,52 @@ void PerformClipping(VS2PS input) PerformClipping(input.ClipOriginAndPos.xy, input.ClipOriginAndPos.zw, input.ClipExtents); } +float SampleFont(Texture2D font, float2 uv) +{ + return font.Sample(SamplerLinearClamp, uv).r; +} + +float GetFontMSDFMedian(Texture2D font, float2 uv) +{ + float4 msd = font.Sample(SamplerLinearClamp, uv); + return max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); +} + +float GetFontMSDFMedian(float4 msd) +{ + return max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); +} + +float GetFontMSDFPixelRange(Texture2D font, float2 uv) +{ + uint width, height; + font.GetDimensions(width, height); + float pxRange = 4.0f; // Must match C++ code + float unitRange = float2(pxRange, pxRange) / float2(width, height); + + float2 dx = ddx(uv); + float2 dy = ddy(uv); + float2 screenTexSize = rsqrt(dx * dx + dy * dy); + return max(0.5f * dot(screenTexSize, unitRange), 1.0f); +} + +float SampleFontMSDF(Texture2D font, float2 uv) +{ + float sd = GetFontMSDFMedian(font, uv); + float screenPxRange = GetFontMSDFPixelRange(font, uv); + float screenPxDist = screenPxRange * (sd - 0.5f); + return saturate(screenPxDist + 0.5f); +} + +float SampleFontMSDFOutline(Texture2D font, float2 uv, float thickness) +{ + float4 msd = font.Sample(SamplerLinearClamp, uv); + float sd = max(min(msd.r, msd.g), min(max(msd.r, msd.g), msd.b)); + float screenPxRange = GetFontMSDFPixelRange(font, uv); + float thick = clamp(thickness, 0.0, screenPxRange * 0.5 - 1.0) / screenPxRange; + float outline = saturate((min(sd, msd.a) - 0.5 + thick) * screenPxRange + 0.5); + outline *= 1 - saturate(screenPxRange * (sd - 0.5f) + 0.5f); + return outline; +} + #endif diff --git a/Source/ThirdParty/msdfgen/LICENSE.txt b/Source/ThirdParty/msdfgen/LICENSE.txt new file mode 100644 index 000000000..fbea359b2 --- /dev/null +++ b/Source/ThirdParty/msdfgen/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014 - 2025 Viktor Chlumsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Source/ThirdParty/msdfgen/msdfgen.Build.cs b/Source/ThirdParty/msdfgen/msdfgen.Build.cs new file mode 100644 index 000000000..f798152b1 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen.Build.cs @@ -0,0 +1,45 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System.IO; +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// https://github.com/Chlumsky/msdfgen +/// +public class msdfgen : DepsModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.MIT; + LicenseFilePath = "LICENSE.txt"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + var depsRoot = options.DepsFolder; + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + options.OutputFiles.Add(Path.Combine(depsRoot, "msdfgen-core.lib")); + break; + case TargetPlatform.Linux: + case TargetPlatform.Mac: + case TargetPlatform.Android: + options.OutputFiles.Add(Path.Combine(depsRoot, "libmsdfgen-core.a")); + break; + default: throw new InvalidPlatformException(options.Platform.Target); + } + + options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/ThirdParty/msdfgen")); + } +} diff --git a/Source/ThirdParty/msdfgen/msdfgen.h b/Source/ThirdParty/msdfgen/msdfgen.h new file mode 100644 index 000000000..a0d69e2a5 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen.h @@ -0,0 +1,4 @@ + +#pragma once + +#include "msdfgen/msdfgen.h" diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h new file mode 100644 index 000000000..05c305d64 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.h @@ -0,0 +1,60 @@ + +#pragma once + +#include "YAxisOrientation.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class. +template +class Bitmap { + +public: + Bitmap(); + Bitmap(int width, int height, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION); + explicit Bitmap(const BitmapConstRef &orig); + explicit Bitmap(const BitmapConstSection &orig); + Bitmap(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap(Bitmap &&orig); +#endif + ~Bitmap(); + Bitmap &operator=(const BitmapConstRef &orig); + Bitmap &operator=(const BitmapConstSection &orig); + Bitmap &operator=(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap &operator=(Bitmap &&orig); +#endif + /// Bitmap width in pixels. + int width() const; + /// Bitmap height in pixels. + int height() const; + T *operator()(int x, int y); + const T *operator()(int x, int y) const; +#ifdef MSDFGEN_USE_CPP11 + explicit operator T *(); + explicit operator const T *() const; +#else + operator T *(); + operator const T *() const; +#endif + operator BitmapRef(); + operator BitmapConstRef() const; + operator BitmapSection(); + operator BitmapConstSection() const; + /// Returns a reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + BitmapSection getSection(int xMin, int yMin, int xMax, int yMax); + /// Returns a constant reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + BitmapConstSection getConstSection(int xMin, int yMin, int xMax, int yMax) const; + +private: + T *pixels; + int w, h; + YAxisOrientation yOrientation; + +}; + +} + +#include "Bitmap.hpp" diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp new file mode 100644 index 000000000..afb53942f --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Bitmap.hpp @@ -0,0 +1,172 @@ + +#include "Bitmap.h" + +#include +#include + +namespace msdfgen { + +template +Bitmap::Bitmap() : pixels(NULL), w(0), h(0), yOrientation(MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) { } + +template +Bitmap::Bitmap(int width, int height, YAxisOrientation yOrientation) : w(width), h(height), yOrientation(yOrientation) { + pixels = new T[N*w*h]; +} + +template +Bitmap::Bitmap(const BitmapConstRef &orig) : w(orig.width), h(orig.height), yOrientation(orig.yOrientation) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +template +Bitmap::Bitmap(const BitmapConstSection &orig) : w(orig.width), h(orig.height), yOrientation(orig.yOrientation) { + pixels = new T[N*w*h]; + T *dst = pixels; + const T *src = orig.pixels; + int rowLength = N*w; + for (int y = 0; y < h; ++y) { + memcpy(dst, src, sizeof(T)*rowLength); + dst += rowLength; + src += orig.rowStride; + } +} + +template +Bitmap::Bitmap(const Bitmap &orig) : w(orig.w), h(orig.h), yOrientation(orig.yOrientation) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap::Bitmap(Bitmap &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h), yOrientation(orig.yOrientation) { + orig.pixels = NULL; + orig.w = 0, orig.h = 0; +} +#endif + +template +Bitmap::~Bitmap() { + delete[] pixels; +} + +template +Bitmap &Bitmap::operator=(const BitmapConstRef &orig) { + if (pixels != orig.pixels) { + delete[] pixels; + w = orig.width, h = orig.height; + yOrientation = orig.yOrientation; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +template +Bitmap &Bitmap::operator=(const BitmapConstSection &orig) { + if (orig.pixels && orig.pixels >= pixels && orig.pixels < pixels+N*w*h) + return *this = Bitmap(orig); + delete[] pixels; + w = orig.width, h = orig.height; + yOrientation = orig.yOrientation; + pixels = new T[N*w*h]; + T *dst = pixels; + const T *src = orig.pixels; + int rowLength = N*w; + for (int y = 0; y < h; ++y) { + memcpy(dst, src, sizeof(T)*rowLength); + dst += rowLength; + src += orig.rowStride; + } + return *this; +} + +template +Bitmap &Bitmap::operator=(const Bitmap &orig) { + if (this != &orig) { + delete[] pixels; + w = orig.w, h = orig.h; + yOrientation = orig.yOrientation; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap &Bitmap::operator=(Bitmap &&orig) { + if (this != &orig) { + delete[] pixels; + pixels = orig.pixels; + w = orig.w, h = orig.h; + yOrientation = orig.yOrientation; + orig.pixels = NULL; + } + return *this; +} +#endif + +template +int Bitmap::width() const { + return w; +} + +template +int Bitmap::height() const { + return h; +} + +template +T *Bitmap::operator()(int x, int y) { + return pixels+N*(w*y+x); +} + +template +const T *Bitmap::operator()(int x, int y) const { + return pixels+N*(w*y+x); +} + +template +Bitmap::operator T *() { + return pixels; +} + +template +Bitmap::operator const T *() const { + return pixels; +} + +template +Bitmap::operator BitmapRef() { + return BitmapRef(pixels, w, h, yOrientation); +} + +template +Bitmap::operator BitmapConstRef() const { + return BitmapConstRef(pixels, w, h, yOrientation); +} + +template +Bitmap::operator BitmapSection() { + return BitmapSection(pixels, w, h, yOrientation); +} + +template +Bitmap::operator BitmapConstSection() const { + return BitmapConstSection(pixels, w, h, yOrientation); +} + +template +BitmapSection Bitmap::getSection(int xMin, int yMin, int xMax, int yMax) { + return BitmapSection(pixels+N*(w*yMin+xMin), xMax-xMin, yMax-yMin, N*w, yOrientation); +} + +template +BitmapConstSection Bitmap::getConstSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapConstSection(pixels+N*(w*yMin+xMin), xMax-xMin, yMax-yMin, N*w, yOrientation); +} + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp b/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp new file mode 100644 index 000000000..6e93fe983 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/BitmapRef.hpp @@ -0,0 +1,154 @@ + +#pragma once + +#include "YAxisOrientation.h" + +namespace msdfgen { + +/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapRef; +/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapConstRef; +/// Reference to a 2D image bitmap with non-contiguous rows of pixels. Pixel storage not owned or managed by the object. Can represent e.g. a section of a larger bitmap, bitmap with padded rows, or vertically flipped bitmap (rowStride can be negative). +template +struct BitmapSection; +/// Constant reference to a 2D image bitmap with non-contiguous rows of pixels. Pixel storage not owned or managed by the object. Can represent e.g. a section of a larger bitmap, bitmap with padded rows, or vertically flipped bitmap (rowStride can be negative). +template +struct BitmapConstSection; + +template +struct BitmapRef { + + T *pixels; + int width, height; + YAxisOrientation yOrientation; + + inline BitmapRef() : pixels(NULL), width(0), height(0), yOrientation(MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) { } + inline BitmapRef(T *pixels, int width, int height, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), yOrientation(yOrientation) { } + + inline T *operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + + /// Returns a reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapSection getSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapSection(pixels+N*(width*yMin+xMin), xMax-xMin, yMax-yMin, N*width, yOrientation); + } + + /// Returns a constant reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getConstSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapConstSection(pixels+N*(width*yMin+xMin), xMax-xMin, yMax-yMin, N*width, yOrientation); + } + +}; + +template +struct BitmapConstRef { + + const T *pixels; + int width, height; + YAxisOrientation yOrientation; + + inline BitmapConstRef() : pixels(NULL), width(0), height(0), yOrientation(MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) { } + inline BitmapConstRef(const T *pixels, int width, int height, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), yOrientation(yOrientation) { } + inline BitmapConstRef(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height), yOrientation(orig.yOrientation) { } + + inline const T *operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + + /// Returns a constant reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapConstSection(pixels+N*(width*yMin+xMin), xMax-xMin, yMax-yMin, N*width, yOrientation); + } + + /// Returns a constant reference to a rectangular section of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getConstSection(int xMin, int yMin, int xMax, int yMax) const { + return getSection(xMin, yMin, xMax, yMax); + } + +}; + +template +struct BitmapSection { + + T *pixels; + int width, height; + /// Specifies the difference between the beginnings of adjacent pixel rows as the number of T elements, can be negative. + int rowStride; + YAxisOrientation yOrientation; + + inline BitmapSection() : pixels(NULL), width(0), height(0), rowStride(0), yOrientation(MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) { } + inline BitmapSection(T *pixels, int width, int height, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), rowStride(N*width), yOrientation(yOrientation) { } + inline BitmapSection(T *pixels, int width, int height, int rowStride, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), rowStride(rowStride), yOrientation(yOrientation) { } + inline BitmapSection(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height), rowStride(N*orig.width), yOrientation(orig.yOrientation) { } + + inline T *operator()(int x, int y) const { + return pixels+rowStride*y+N*x; + } + + /// Returns a reference to a rectangular subsection of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapSection getSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapSection(pixels+rowStride*yMin+N*xMin, xMax-xMin, yMax-yMin, rowStride, yOrientation); + } + + /// Returns a constant reference to a rectangular subsection of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getConstSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapConstSection(pixels+rowStride*yMin+N*xMin, xMax-xMin, yMax-yMin, rowStride, yOrientation); + } + + /// Makes sure that the section's Y-axis orientation matches the argument by potentially reordering its rows. + inline void reorient(YAxisOrientation newYAxisOrientation) { + if (yOrientation != newYAxisOrientation) { + pixels += rowStride*(height-1); + rowStride = -rowStride; + yOrientation = newYAxisOrientation; + } + } + +}; + +template +struct BitmapConstSection { + + const T *pixels; + int width, height; + /// Specifies the difference between the beginnings of adjacent pixel rows as the number of T elements, can be negative. + int rowStride; + YAxisOrientation yOrientation; + + inline BitmapConstSection() : pixels(NULL), width(0), height(0), rowStride(0), yOrientation(MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) { } + inline BitmapConstSection(const T *pixels, int width, int height, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), rowStride(N*width), yOrientation(yOrientation) { } + inline BitmapConstSection(const T *pixels, int width, int height, int rowStride, YAxisOrientation yOrientation = MSDFGEN_Y_AXIS_DEFAULT_ORIENTATION) : pixels(pixels), width(width), height(height), rowStride(rowStride), yOrientation(yOrientation) { } + inline BitmapConstSection(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height), rowStride(N*orig.width), yOrientation(orig.yOrientation) { } + inline BitmapConstSection(const BitmapConstRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height), rowStride(N*orig.width), yOrientation(orig.yOrientation) { } + inline BitmapConstSection(const BitmapSection &orig) : pixels(orig.pixels), width(orig.width), height(orig.height), rowStride(orig.rowStride), yOrientation(orig.yOrientation) { } + + inline const T *operator()(int x, int y) const { + return pixels+rowStride*y+N*x; + } + + /// Returns a constant reference to a rectangular subsection of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getSection(int xMin, int yMin, int xMax, int yMax) const { + return BitmapConstSection(pixels+rowStride*yMin+N*xMin, xMax-xMin, yMax-yMin, rowStride, yOrientation); + } + + /// Returns a constant reference to a rectangular subsection of the bitmap specified by bounds (excluding xMax, yMax). + inline BitmapConstSection getConstSection(int xMin, int yMin, int xMax, int yMax) const { + return getSection(xMin, yMin, xMax, yMax); + } + + /// Makes sure that the section's Y-axis orientation matches the argument by potentially reordering its rows. + inline void reorient(YAxisOrientation newYAxisOrientation) { + if (yOrientation != newYAxisOrientation) { + pixels += rowStride*(height-1); + rowStride = -rowStride; + yOrientation = newYAxisOrientation; + } + } + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h b/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h new file mode 100644 index 000000000..53ceb8eda --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/Contour.h @@ -0,0 +1,34 @@ + +#pragma once + +#include +#include "EdgeHolder.h" + +namespace msdfgen { + +/// A single closed contour of a shape. +class Contour { + +public: + /// The sequence of edges that make up the contour. + std::vector edges; + + /// Adds an edge to the contour. + void addEdge(const EdgeHolder &edge); +#ifdef MSDFGEN_USE_CPP11 + void addEdge(EdgeHolder &&edge); +#endif + /// Creates a new edge in the contour and returns its reference. + EdgeHolder &addEdge(); + /// Adjusts the bounding box to fit the contour. + void bound(double &xMin, double &yMin, double &xMax, double &yMax) const; + /// Adjusts the bounding box to fit the contour border's mitered corners. + void boundMiters(double &xMin, double &yMin, double &xMax, double &yMax, double border, double miterLimit, int polarity) const; + /// Computes the winding of the contour. Returns 1 if positive, -1 if negative. + int winding() const; + /// Reverses the sequence of edges on the contour. + void reverse(); + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h b/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h new file mode 100644 index 000000000..fadbefa54 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/DistanceMapping.h @@ -0,0 +1,36 @@ + +#pragma once + +#include "Range.hpp" + +namespace msdfgen { + +/// Linear transformation of signed distance values. +class DistanceMapping { + +public: + /// Explicitly designates value as distance delta rather than an absolute distance. + class Delta { + public: + double value; + inline explicit Delta(double distanceDelta) : value(distanceDelta) { } + inline operator double() const { return value; } + }; + + static DistanceMapping inverse(Range range); + + DistanceMapping(); + DistanceMapping(Range range); + double operator()(double d) const; + double operator()(Delta d) const; + DistanceMapping inverse() const; + +private: + double scale; + double translate; + + inline DistanceMapping(double scale, double translate) : scale(scale), translate(translate) { } + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h new file mode 100644 index 000000000..5d3730c9a --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeColor.h @@ -0,0 +1,20 @@ + +#pragma once + +#include "base.h" + +namespace msdfgen { + +/// Edge color specifies which color channels an edge belongs to. +enum EdgeColor { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7 +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h new file mode 100644 index 000000000..5ae2dc231 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/EdgeHolder.h @@ -0,0 +1,41 @@ + +#pragma once + +#include "edge-segments.h" + +namespace msdfgen { + +/// Container for a single edge of dynamic type. +class EdgeHolder { + +public: + /// Swaps the edges held by a and b. + static void swap(EdgeHolder &a, EdgeHolder &b); + + inline EdgeHolder() : edgeSegment() { } + inline EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { } + inline EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, edgeColor)) { } + inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, edgeColor)) { } + inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, p3, edgeColor)) { } + EdgeHolder(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder(EdgeHolder &&orig); +#endif + ~EdgeHolder(); + EdgeHolder &operator=(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder &operator=(EdgeHolder &&orig); +#endif + EdgeSegment &operator*(); + const EdgeSegment &operator*() const; + EdgeSegment *operator->(); + const EdgeSegment *operator->() const; + operator EdgeSegment *(); + operator const EdgeSegment *() const; + +private: + EdgeSegment *edgeSegment; + +}; + +} diff --git a/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h b/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h new file mode 100644 index 000000000..995b1d614 --- /dev/null +++ b/Source/ThirdParty/msdfgen/msdfgen/core/MSDFErrorCorrection.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "SDFTransformation.h" +#include "Shape.h" +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// Performs error correction on a computed MSDF to eliminate interpolation artifacts. This is a low-level class, you may want to use the API in msdf-error-correction.h instead. +class MSDFErrorCorrection { + +public: + /// Stencil flags. + enum Flags { + /// Texel marked as potentially causing interpolation errors. + ERROR = 1, + /// Texel marked as protected. Protected texels are only given the error flag if they cause inversion artifacts. + PROTECTED = 2 + }; + + MSDFErrorCorrection(); + explicit MSDFErrorCorrection(const BitmapSection &stencil, const SDFTransformation &transformation); + /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error. + void setMinDeviationRatio(double minDeviationRatio); + /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error. + void setMinImproveRatio(double minImproveRatio); + /// Flags all texels that are interpolated at corners as protected. + void protectCorners(const Shape &shape); + /// Flags all texels that contribute to edges as protected. + template + void protectEdges(const BitmapConstSection &sdf); + /// Flags all texels as protected. + void protectAll(); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF only. + template + void findErrors(const BitmapConstSection &sdf); + /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF and comparison with the exact shape distance. + template