添加 文本渲染

This commit is contained in:
2025-10-30 23:32:20 +08:00
parent 875a77ff5b
commit 3272797873
747 changed files with 328755 additions and 29 deletions

3
.gitmodules vendored
View File

@ -26,3 +26,6 @@
[submodule "Hazel/vendor/box2d"]
path = Hazel/vendor/box2d
url = https://github.com/erincatto/box2d.git
[submodule "Hazel/vendor/freetype"]
path = Hazel/vendor/freetype
url = https://github.com/freetype/freetype.git

View File

@ -18,6 +18,7 @@ endif ()
add_subdirectory(Hazel)
add_subdirectory(Editor)
add_subdirectory(Sandbox)
add_subdirectory(Hazel-ScriptCore)
add_subdirectory(Editor/SandboxProject)

View File

@ -0,0 +1,69 @@
// Basic MSDF Text Texture Shader
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
layout(location = 2) in vec2 a_TexCoord;
layout(location = 3) in int a_EntityID;
layout(std140, binding = 0) uniform Camera
{
mat4 u_ViewProjection;
};
layout (location = 0) out vec4 o_Color;
layout (location = 1) out vec2 o_TexCoord;
layout (location = 2) out flat int v_EntityID;
void main()
{
o_Color = a_Color;
o_TexCoord = a_TexCoord;
v_EntityID = a_EntityID;
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_EntityID;
layout (location = 0) in vec4 i_Color;
layout (location = 1) in vec2 i_TexCoord;
layout (location = 2) in flat int v_EntityID;
layout (binding = 0) uniform sampler2D u_FontAtlas;
float screenPxRange() {
const float pxRange = 2.0; // set to distance field's pixel range
vec2 unitRange = vec2(pxRange)/vec2(textureSize(u_FontAtlas, 0));
vec2 screenTexSize = vec2(1.0)/fwidth(i_TexCoord); // 使用 i_TexCoord
return max(0.5*dot(unitRange, screenTexSize), 1.0);
}
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main()
{
vec4 texColor = i_Color * texture(u_FontAtlas, i_TexCoord); // 使用 i_Color 和 i_TexCoord
vec3 msd = texture(u_FontAtlas, i_TexCoord).rgb; // 使用 i_TexCoord
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange()*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
if (opacity == 0.0)
discard;
vec4 bgColor = vec4(0.0);
o_Color = mix(bgColor, i_Color, opacity); // 使用 i_Color
if (o_Color.a == 0.0)
discard;
o_EntityID = v_EntityID;
}

View File

@ -15,16 +15,18 @@
#include "imgui_internal.h"
#include "Hazel/Project/Project.h"
#include "Hazel/Renderer/Font.h"
#include "Hazel/Scripting/ScriptEngine.h"
namespace Hazel
{
static Ref<Font> s_Font;
EditorLayer::EditorLayer()
: Layer("HazelEditor"), m_CameraController((float)Application::Get().GetWindow().GetWidth() / (float)Application::Get().GetWindow().GetHeight())
{
s_Font = Font::GetDefault();
}
void EditorLayer::OnAttach()

View File

@ -8,6 +8,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
# set
set(VCPKG_TARGET_TRIPLET "x64-windows")
add_subdirectory(vendor/spdlog EXCLUDE_FROM_ALL)
add_subdirectory(vendor/GLAD EXCLUDE_FROM_ALL)
add_subdirectory(vendor/glm EXCLUDE_FROM_ALL)
@ -16,6 +19,7 @@ add_subdirectory(vendor/shaderc EXCLUDE_FROM_ALL)
add_subdirectory(vendor/SPIRV-Cross EXCLUDE_FROM_ALL)
add_subdirectory(vendor/box2d EXCLUDE_FROM_ALL)
add_subdirectory(vendor/SDL EXCLUDE_FROM_ALL)
add_subdirectory(vendor/msdf-atlas-gen)
file(GLOB_RECURSE SOURCES "src/**.cpp")
@ -88,6 +92,7 @@ target_link_libraries(${PROJECT_NAME}-static PUBLIC
ws2_32
bcrypt
assimp::assimp
msdf-atlas-gen
)
@ -121,10 +126,19 @@ target_link_libraries(${PROJECT_NAME}-shared PUBLIC
ws2_32
bcrypt
assimp::assimp
msdf-atlas-gen
)
if(WIN32)
target_compile_definitions(${PROJECT_NAME}-static PUBLIC HZ_PLATFORM_WINDOWS)
target_compile_definitions(${PROJECT_NAME}-shared PUBLIC HZ_PLATFORM_WINDOWS)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()
target_compile_definitions(${PROJECT_NAME}-static PRIVATE HZ_BUILD_STATIC)
target_compile_definitions(${PROJECT_NAME}-shared PRIVATE HZ_BUILD_DLL IMGUI_API=__declspec\(dllexport\))# 编译DLL时定义
target_compile_definitions(${PROJECT_NAME}-static INTERFACE HZ_BUILD_STATIC)
target_compile_definitions(${PROJECT_NAME}-shared PRIVATE HZ_BUILD_SHARED HZ_BUILD_DLL IMGUI_API=__declspec\(dllexport\))# 编译DLL时定义
set_target_properties(${PROJECT_NAME}-static PROPERTIES OUTPUT_NAME ${PROJECT_NAME}-static)
set_target_properties(${PROJECT_NAME}-shared PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
@ -133,11 +147,6 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME}-static PUBLIC HZ_DEBUG)
target_compile_definitions(${PROJECT_NAME}-shared PUBLIC HZ_DEBUG)
endif ()
if(WIN32)
target_compile_definitions(${PROJECT_NAME}-static PUBLIC HZ_PLATFORM_WINDOWS)
target_compile_definitions(${PROJECT_NAME}-shared PUBLIC HZ_PLATFORM_WINDOWS)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()
# -------------------------------------------

View File

@ -1,15 +1,25 @@
#pragma once
#ifdef HZ_PLATFORM_WINDOWS
#if defined(HZ_BUILD_STATIC)
#define HAZEL_API
#elif defined(HZ_BUILD_DLL)
#define HAZEL_API __declspec(dllexport)
#else
#define HAZEL_API __declspec(dllimport)
#endif
/*
#ifdef HZ_BUILD_DLL
#define HAZEL_API __declspec(dllexport)
#else
#define HAZEL_API __declspec(dllimport)
#endif
#ifdef HZ_HAZEL_STATIC
#ifdef HZ_BUILD_STATIC
#define HAZEL_API
#endif
*/
#else
#define HAZEL_API
// #error Hazel only support Windows!

View File

@ -19,6 +19,7 @@ namespace Hazel
static bool IsMouseButtonPressed(MouseButton button);
static std::pair<float, float> GetMousePosition();
static uint32_t GetMouseState(float* x = nullptr, float* y = nullptr);
static float GetMouseX();
static float GetMouseY();

View File

@ -0,0 +1,158 @@
//
// Created by sfd on 25-10-30.
//
#include "Font.h"
#include "Hazel/Core/assert.h"
#include "MSDFData.h"
namespace Hazel
{
template<typename T, typename S, int N, msdf_atlas::GeneratorFunction<S, N> GenFun>
static Ref<Texture2D> CreateAndCacheAtlas(const std::string& fontName, float fontSize, const std::vector<msdf_atlas::GlyphGeometry>& glyphs, const msdf_atlas::FontGeometry& font, uint32_t width, uint32_t height)
{
msdf_atlas::GeneratorAttributes attributes;
attributes.config.overlapSupport = true;
attributes.scanlinePass = true;
msdf_atlas::ImmediateAtlasGenerator<S, N, GenFun, msdf_atlas::BitmapAtlasStorage<T, N>> generator(width, height);
generator.setAttributes(attributes);
generator.setThreadCount(8);
generator.generate(glyphs.data(), (int)glyphs.size());
msdfgen::BitmapConstRef<T, N> bitmap = (msdfgen::BitmapConstRef<T, N>)generator.atlasStorage();
TextureSpecification spec;
spec.Width = width;
spec.Height = height;
spec.Format = ImageFormat::RGB8;
spec.GenerateMips = false;
Ref<Texture2D> texture = Texture2D::Create(spec);
texture->SetData((void*)bitmap.pixels, bitmap.width * bitmap.height * 3);
return texture;
}
Font::Font(const std::filesystem::path& filePath)
: m_Data(new MSDFData())
{
msdfgen::FreetypeHandle* ft = msdfgen::initializeFreetype();
HZ_CORE_ASSERT(ft);
std::string fileString = filePath.string();
msdfgen::FontHandle* font = msdfgen::loadFont(ft, fileString.c_str());
if (!font)
{
HZ_CORE_ASSERT("failed to load font: {}", filePath.string());
return;
}
struct CharsetRange
{
uint32_t Begin, End;
};
// from imgui_draw.cpp
struct CharsetRange charsetranges[] = {
{0x0020, 0x00FF}
};
msdf_atlas::Charset charset;
for (CharsetRange& range : charsetranges)
{
for (uint32_t c = range.Begin; c < range.End; ++c)
{
charset.add(c);
}
}
double fontScale = 1.0f;
m_Data->FontGeometry = msdf_atlas::FontGeometry(&m_Data->Glyphs);
int gyphsloaded = m_Data->FontGeometry.loadCharset(font, fontScale, charset);
HZ_CORE_INFO("loaded {0} glyhps from font (out of {1})", gyphsloaded, charset.size());
double emSize = 40.f;
msdf_atlas::TightAtlasPacker atlasPacker;
atlasPacker.setPixelRange(2.0f);
atlasPacker.setMiterLimit(1.0f);
atlasPacker.setPadding(0);
atlasPacker.setScale(emSize);
int remaining = atlasPacker.pack(m_Data->Glyphs.data(), (int)m_Data->Glyphs.size());
HZ_CORE_ASSERT(remaining == 0);
int width, height;
atlasPacker.getDimensions(width, height);
emSize = atlasPacker.getScale();
// TODO: reading this
#define DEFAULT_ANGLE_THRESHOLD 3.0
#define LCG_MULTIPLIER 6364136223846793005ull
#define LCG_INCREMENT 1442695040888963407ull
#define THREAD_COUNT 8
// if MSTF || MTSDF
uint64_t coloringSeed = 0;
bool expensiveColoring = false;
if (expensiveColoring)
{
msdf_atlas::Workload([&glyphs = m_Data->Glyphs, &coloringSeed] (int i , int threadNo) -> bool
{
unsigned long long glyphSeed = (LCG_MULTIPLIER * (coloringSeed ^ i) + LCG_INCREMENT) * !!coloringSeed;
glyphs[i].edgeColoring(msdfgen::edgeColoringInkTrap, DEFAULT_ANGLE_THRESHOLD, glyphSeed);
return true;
}, m_Data->Glyphs.size()).finish(THREAD_COUNT);
}else
{
unsigned long long glyphSeed = coloringSeed;
for (auto& glyph : m_Data->Glyphs)
{
glyphSeed *= LCG_MULTIPLIER;
glyph.edgeColoring(msdfgen::edgeColoringInkTrap, DEFAULT_ANGLE_THRESHOLD, glyphSeed);
}
}
m_AtlasTexture = CreateAndCacheAtlas<uint8_t, float, 3, msdf_atlas::msdfGenerator>("Test", (int)emSize, m_Data->Glyphs, m_Data->FontGeometry, width, height);
#if 0
msdfgen::Shape shape;
if (msdfgen::loadGlyph(shape, font, 'C'))
{
shape.normalize();
// max. angle
msdfgen::edgeColoringSimple(shape, 3.0f);
msdfgen::Bitmap<float, 3> msdf(32, 32);
msdfgen::generateMSDF(msdf, shape, 4.0f, 1.0f, msdfgen::Vector2(4.0f, 4.0f));
msdfgen::savePng(msdf, "output.png");
}
#endif
msdfgen::destroyFont(font);
msdfgen::deinitializeFreetype(ft);
}
Font::~Font()
{
delete m_Data;
}
Ref<Font> Font::GetDefault()
{
static Ref<Font> defaultFont;
if (!defaultFont)
defaultFont = CreateRef<Font>("assets/fonts/OpenSans/OpenSans-Bold.ttf");
return defaultFont;
}
}

View File

@ -0,0 +1,35 @@
//
// Created by sfd on 25-10-30.
//
#ifndef FONT_H
#define FONT_H
#include <filesystem>
#include "Texture.h"
#include "Hazel/Core/Core.h"
namespace Hazel
{
struct MSDFData;
class HAZEL_API Font {
public:
Font(const std::filesystem::path& filePath);
~Font();
const MSDFData* GetMSDFData() const { return m_Data; }
Ref<Texture2D> GetAtlasTexture() const { return m_AtlasTexture; }
static Ref<Font> GetDefault();
private:
MSDFData* m_Data;
Ref<Texture2D> m_AtlasTexture;
};
}
#endif //FONT_H

View File

@ -0,0 +1,20 @@
//
// Created by sfd on 25-10-30.
//
#ifndef MSDFDATA_H
#define MSDFDATA_H
#include <vector>
#include "msdf-atlas-gen.h"
namespace Hazel
{
struct MSDFData
{
std::vector<msdf_atlas::GlyphGeometry> Glyphs;
msdf_atlas::FontGeometry FontGeometry;
};
}
#endif //MSDFDATA_H

View File

@ -7,6 +7,8 @@
#include <array>
#include <iostream>
#include <Hazel/Debug/Instrumentor.h>
#include "MSDFData.h"
#include "RendererCommand.h"
#include "Shader.h"
#include "UniformBuffer.h"
@ -51,6 +53,15 @@ namespace Hazel
int EntityID;
};
struct TextVertex
{
glm::vec3 Position;
glm::vec4 Color;
glm::vec2 TexCoord;
int EntityID;
};
struct Renderer2DStorage
{
const uint32_t MaxQuad = 10000;
@ -71,6 +82,10 @@ namespace Hazel
Ref<VertexBuffer> LineVertexBuffer;
Ref<Shader> LineShader;
Ref<VertexArray> TextVertexArray;
Ref<VertexBuffer> TextVertexBuffer;
Ref<Shader> TextShader;
uint32_t QuadIndexCount = 0;
QuadVertex* QuadVertexBufferBase = nullptr;
@ -84,11 +99,17 @@ namespace Hazel
LineVertex* LineVertexBufferBase = nullptr;
LineVertex* LineVertexBufferPtr = nullptr;
uint32_t TextIndexCount = 0;
TextVertex* TextVertexBufferBase = nullptr;
TextVertex* TextVertexBufferPtr = nullptr;
float LineWidth = 2.0f;
std::array<Ref<Texture2D>, MaxTextureSlots> TextureSlots;
uint32_t TextureSlotIndex = 1; // 0 use white texture
Ref<Texture2D> FontAtlasTexture;
glm::vec4 QuadVertexPosition[4];
Renderer2D::Statistic Stats;
@ -183,7 +204,25 @@ namespace Hazel
s_Data.LineVertexBufferBase = new LineVertex[s_Data.MaxVertices];
///////////////////////////////////////
s_Data.WhiteTexture = Texture2D::Create(1, 1);
/////////////// text //////////////////
s_Data.TextVertexArray = VertexArray::Create();
s_Data.TextVertexBuffer = VertexBuffer::Create(s_Data.MaxVertices * sizeof(TextVertex));
s_Data.TextVertexBuffer->SetLayout({
{ShaderDataType::Float3, "a_Position"},
{ShaderDataType::Float4, "a_Color"},
{ShaderDataType::Float2, "a_TexCoord"},
{ShaderDataType::Int, "a_EntityID"}
});
s_Data.TextVertexArray->AddVertexBuffer(s_Data.TextVertexBuffer);
s_Data.TextVertexArray->SetIndexBuffer(quadIB);
s_Data.TextVertexBufferBase = new TextVertex[s_Data.MaxVertices];
///////////////////////////////////////
s_Data.WhiteTexture = Texture2D::Create(TextureSpecification());
uint32_t whiteTextureData = 0xffffffff;
s_Data.WhiteTexture->SetData(&whiteTextureData, sizeof(uint32_t));
@ -197,6 +236,7 @@ namespace Hazel
s_Data.QuadShader = Shader::Create("assets/shaders/Renderer2D_quad.glsl");
s_Data.CircleShader = Shader::Create("assets/shaders/Renderer2D_circle.glsl");
s_Data.LineShader = Shader::Create("assets/shaders/Renderer2D_line.glsl");
s_Data.TextShader = Shader::Create("assets/shaders/Renderer2D_text.glsl");
// set all texture slot to 0
s_Data.TextureSlots[0] = s_Data.WhiteTexture;
@ -284,7 +324,6 @@ namespace Hazel
{
const uint32_t datasize = reinterpret_cast<uint8_t*>(s_Data.CircleVertexBufferPtr) - reinterpret_cast<uint8_t*>(s_Data.CircleVertexBufferBase);
s_Data.CircleVertexBuffer->SetData(s_Data.CircleVertexBufferBase, datasize);
auto& a = s_Data;
s_Data.CircleShader->Bind();
RendererCommand::DrawIndexed(s_Data.CircleVertexArray, s_Data.CircleIndexCount);
s_Data.Stats.DrawCalls++;
@ -294,12 +333,23 @@ namespace Hazel
{
const uint32_t datasize = reinterpret_cast<uint8_t*>(s_Data.LineVertexBufferPtr) - reinterpret_cast<uint8_t*>(s_Data.LineVertexBufferBase);
s_Data.LineVertexBuffer->SetData(s_Data.LineVertexBufferBase, datasize);
auto& a = s_Data;
s_Data.LineShader->Bind();
RendererCommand::SetLineWidth(s_Data.LineWidth);
RendererCommand::DrawLines(s_Data.LineVertexArray, s_Data.LineVertexCount);
s_Data.Stats.DrawCalls++;
}
if (s_Data.TextIndexCount)
{
const uint32_t datasize = reinterpret_cast<uint8_t*>(s_Data.TextVertexBufferPtr) - reinterpret_cast<uint8_t*>(s_Data.TextVertexBufferBase);
s_Data.TextVertexBuffer->SetData(s_Data.TextVertexBufferBase, datasize);
s_Data.FontAtlasTexture->Bind(0);
s_Data.TextShader->Bind();
RendererCommand::DrawIndexed(s_Data.TextVertexArray, s_Data.TextIndexCount);
s_Data.Stats.DrawCalls++;
}
}
void Renderer2D::StartBatch()
@ -313,6 +363,9 @@ namespace Hazel
s_Data.LineVertexCount = 0;
s_Data.LineVertexBufferPtr = s_Data.LineVertexBufferBase;
s_Data.TextIndexCount = 0;
s_Data.TextVertexBufferPtr = s_Data.TextVertexBufferBase;
s_Data.TextureSlotIndex = 1;
}
@ -706,6 +759,107 @@ namespace Hazel
DrawLine(lineVectices[3], lineVectices[0], color, entityID);
}
void Renderer2D::DrawString(const std::string& string, const Ref<Font>& font, const glm::mat4& transform,
const glm::vec4& color, int entityID)
{
const auto& fontGeomatry = font->GetMSDFData()->FontGeometry;
const auto& metrics = fontGeomatry.getMetrics();
const Ref<Texture2D> fontAtlas = font->GetAtlasTexture();
s_Data.FontAtlasTexture = fontAtlas;
double x = 0.0;
double fsScale = 1.0 / (metrics.ascenderY - metrics.descenderY);
double y = 0.0;
float lineHeightOffset = 0.0f;
for (size_t i = 0; i < string.size(); i++)
{
char character = string[i];
if (character == '\r')
continue;
if (character == '\n')
{
x = 0;
y -= fsScale * metrics.lineHeight + lineHeightOffset;
continue;
}
auto glphy = fontGeomatry.getGlyph(character);
if (!glphy)
glphy = fontGeomatry.getGlyph('?');
if (!glphy)
return;
if (character == '\t')
glphy = fontGeomatry.getGlyph(' ');
// Atlas bound
double aLeft, aButtom, aRight, aTop;
glphy->getQuadAtlasBounds(aLeft, aButtom, aRight, aTop);
glm::vec2 texCoordMin((float)aLeft, (float)aButtom);
glm::vec2 texCoordMax((float)aRight, (float)aTop);
// PlaneBound
double pLeft, pButtom, pRight, pTop;
glphy->getQuadPlaneBounds(pLeft, pButtom, pRight, pTop);
glm::vec2 quadMin((float)pLeft, (float)pButtom);
glm::vec2 quadMax((float)pRight, (float)pTop);
quadMin *= fsScale, quadMax *= fsScale;
quadMin += glm::vec2(x, y);
quadMax += glm::vec2(x, y);
float texelWidth = 1.0f / fontAtlas->GetWidth();
float texelHeight = 1.0f / fontAtlas->GetHeight();
texCoordMin *= glm::vec2(texelWidth, texelHeight);
texCoordMax *= glm::vec2(texelWidth, texelHeight);
// Render
s_Data.TextVertexBufferPtr->Position = transform * glm::vec4(quadMin, 0.0f, 1.0f);
s_Data.TextVertexBufferPtr->Color = color;
s_Data.TextVertexBufferPtr->TexCoord = texCoordMin;
s_Data.TextVertexBufferPtr->EntityID = entityID;
s_Data.TextVertexBufferPtr++;
s_Data.TextVertexBufferPtr->Position = transform * glm::vec4(quadMin.x, quadMax.y, 0.0f, 1.0f);
s_Data.TextVertexBufferPtr->Color = color;
s_Data.TextVertexBufferPtr->TexCoord = { texCoordMin.x, texCoordMax.y };
s_Data.TextVertexBufferPtr->EntityID = entityID;
s_Data.TextVertexBufferPtr++;
s_Data.TextVertexBufferPtr->Position = transform * glm::vec4(quadMax, 0.0f, 1.0f);
s_Data.TextVertexBufferPtr->Color = color;
s_Data.TextVertexBufferPtr->TexCoord = texCoordMax;
s_Data.TextVertexBufferPtr->EntityID = entityID;
s_Data.TextVertexBufferPtr++;
s_Data.TextVertexBufferPtr->Position = transform * glm::vec4(quadMax.x, quadMin.y, 0.0f, 1.0f);
s_Data.TextVertexBufferPtr->Color = color;
s_Data.TextVertexBufferPtr->TexCoord = {texCoordMax.x, texCoordMin.y};
s_Data.TextVertexBufferPtr->EntityID = entityID;
s_Data.TextVertexBufferPtr++;
s_Data.TextIndexCount += 6;
s_Data.Stats.QuadCount++;
if (i < string.size() - 1)
{
double advance = glphy->getAdvance();
const char nextCharacter = string[i + 1];
fontGeomatry.getAdvance(advance, character, nextCharacter);
float kerningOffset = 0.0f;
x += fsScale * advance + kerningOffset;
}
}
}
float Renderer2D::GetLineWidth()
{
return s_Data.LineWidth;

View File

@ -9,6 +9,7 @@
#include "Camera.h"
#include "EditorCamera.h"
#include "Font.h"
#include "OrthographicCamera.h"
#include "SubTexture2D.h"
#include "Texture.h"
@ -60,6 +61,9 @@ namespace Hazel
static void DrawRect(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID = -1);
static void DrawRect(const glm::mat4& transform, const glm::vec4& color, int entityID = -1);
static void DrawString(const std::string& string, const Ref<Font>& font, const glm::mat4& transform, const glm::vec4& color, int entityID = -1);
static float GetLineWidth();
static void SetLineWidth(float width);

View File

@ -8,6 +8,25 @@
namespace Hazel
{
Ref<Texture2D> Texture2D::Create(const TextureSpecification& spec)
{
switch (Renderer::GetAPI())
{
case RendererAPI::API::NONE:
HZ_CORE_ERROR("NONE is not Support!");
return nullptr;
case RendererAPI::API::OPENGL:
// return std::make_shared<VertexBuffer>(OpenGLVertexBuffer(vertices, size));
return std::make_shared<OpenGLTexture2D>(spec);
break;
default:
break;
}
HZ_CORE_ERROR("Unknown RendererAPI!");
return nullptr;
}
Ref<Texture2D> Texture2D::Create(uint32_t width, uint32_t height)
{
switch (Renderer::GetAPI())

View File

@ -11,10 +11,32 @@
namespace Hazel
{
enum class ImageFormat
{
None = 0,
R8,
RGB8,
RGBA8,
RGBA32F
};
struct TextureSpecification
{
uint32_t Width = 1;
uint32_t Height = 1;
ImageFormat Format = ImageFormat::RGBA8;
bool GenerateMips = true;
};
class HAZEL_API Texture
{
public:
virtual ~Texture() = default;
virtual const TextureSpecification& GetSpecification() const = 0;
virtual const uint32_t GetWidth() const = 0;
virtual const uint32_t GetHeight() const = 0;
virtual const uint32_t GetRendererID() const = 0;
@ -34,6 +56,7 @@ namespace Hazel
public:
virtual ~Texture2D() = default;
static Ref<Texture2D> Create(const TextureSpecification& spec);
static Ref<Texture2D> Create(uint32_t width, uint32_t height);
static Ref<Texture2D> Create(const std::string& path);

View File

@ -223,6 +223,12 @@ namespace Hazel
Renderer2D::DrawCircle(transform.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
}
// Renderer2D::DrawString("hello", Font::GetDefault(), glm::mat4(1.0f), glm::vec4(1.0f), 0);
Renderer2D::DrawString(R"(
Hello World!
)", Font::GetDefault(), glm::mat4(1.0f), glm::vec4(0.2f, 0.5f, 0.3f, 1.0f));
Renderer2D::EndScene();
}

View File

@ -6,6 +6,7 @@
#define SCENE_H
#include <string>
#include <box2d/id.h>
#include <Hazel/Core/TimeStep.h>
#include <Hazel/Core/UUID.h>
@ -14,7 +15,6 @@
#include "entt.hpp"
#include "Hazel/Core/Core.h"
namespace Hazel
{
class Entity;

View File

@ -180,6 +180,7 @@ namespace Hazel
// runtime
Scene* SceneContext = nullptr;
bool IsMonoUsed = false;
};
static ScriptEngineData* s_Data = nullptr;
@ -226,6 +227,7 @@ namespace Hazel
// 1.create an object (and run constructor)
s_Data->EntityClass = ScriptClass("Hazel", "Entity", true);
s_Data->IsMonoUsed = true;
#if 0
// 1.create an object (and run constructor)
// MonoClass* monoClass = mono_class_from_name(s_Data->CoreAssemblyImage, "Hazel", "Entity");
@ -261,7 +263,9 @@ namespace Hazel
void ScriptEngine::Shutdown()
{
MonoShutdown();
if (s_Data != nullptr)
if (s_Data->IsMonoUsed)
MonoShutdown();
delete s_Data;
}
@ -427,7 +431,10 @@ namespace Hazel
const Ref<ScriptInstance> instance = s_Data->EntityInstances[entityUUID];
instance->InvokeOnUpdate(ts);
if (instance)
instance->InvokeOnUpdate(ts);
else
HZ_CORE_ERROR("could not find ScriptInstance for entity {}", (uint64_t)entityUUID);
}
Scene* ScriptEngine::GetSceneContext()

View File

@ -8,10 +8,58 @@
#include <Hazel/Debug/Instrumentor.h>
#include "stb_image.h"
#include "Hazel/Core/assert.h"
namespace Hazel
{
OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height) : m_Width(width), m_Height(height)
namespace Utils
{
static GLenum HazelImageFormatToDataFormat(ImageFormat format)
{
switch (format)
{
case ImageFormat::RGB8: return GL_RGB;
case ImageFormat::RGBA8: return GL_RGBA;
}
HZ_CORE_ASSERT(false);
return 0;
}
static GLenum HazelImageFormatToGLInternalFormal(ImageFormat format)
{
switch (format)
{
case ImageFormat::RGB8: return GL_RGB8;
case ImageFormat::RGBA8: return GL_RGBA8;
}
HZ_CORE_ASSERT(false);
return 0;
}
}
OpenGLTexture2D::OpenGLTexture2D(const TextureSpecification& spec)
:m_Specification(spec), m_Width(m_Specification.Width), m_Height(m_Specification.Height)
{
HZ_PROFILE_FUNCTION();
m_InternalFormat = Utils::HazelImageFormatToGLInternalFormal(m_Specification.Format);
m_DataFormat = Utils::HazelImageFormatToDataFormat(m_Specification.Format);
glCreateTextures(GL_TEXTURE_2D, 1, &m_RendererID);
glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height);
glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height)
: m_Width(width), m_Height(height)
{
HZ_PROFILE_FUNCTION();
m_InternalFormat = GL_RGBA8;

View File

@ -12,10 +12,13 @@ namespace Hazel
class OpenGLTexture2D : public Hazel::Texture2D
{
public:
OpenGLTexture2D(const TextureSpecification& spec);
OpenGLTexture2D(uint32_t width, uint32_t height);
OpenGLTexture2D(const std::string& path);
virtual ~OpenGLTexture2D();
virtual const TextureSpecification& GetSpecification() const override { return m_Specification; }
virtual const uint32_t GetWidth() const override {return m_Width; }
virtual const uint32_t GetHeight() const override { return m_Height; }
virtual const std::string& GetPath() const override {return m_Path; }
@ -31,6 +34,8 @@ namespace Hazel
return m_RendererID == other.GetRendererID();
}
private:
TextureSpecification m_Specification;
std::string m_Path;
uint32_t m_Width, m_Height;
uint32_t m_RendererID;

View File

@ -28,6 +28,16 @@ namespace Hazel
return {xpos, ypos};
}
uint32_t Input::GetMouseState(float* x, float* y)
{
float xpos, ypos;
const uint32_t state = SDL_GetMouseState(&xpos, &ypos);
if (x) *x = xpos;
if (y) *y = ypos;
return state;
}
float Input::GetMouseX()
{
return GetMousePosition().first;

View File

@ -0,0 +1,26 @@
## Version 1.2 (2021-05-29)
- Updated to MSDFgen 1.9.
- Multiple fonts or font sizes can now be compiled into a single atlas.
- Added `-yorigin` option to choose if Y-coordinates increase from bottom to top or from top to bottom.
- Added `-coloringstrategy` option to select MSDF edge coloring heuristic.
- Shadron preview now properly loads floating-point image outputs in full range mode.
## Version 1.1 (2020-10-18)
- Updated to MSDFgen 1.8.
- Glyph geometry is now preprocessed by Skia to resolve irregularities which were previously unsupported and caused artifacts.
- The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively.
- The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA`.
- Glyphs can now also be loaded by glyph index rather than Unicode values. In the standalone version, a set of glyphs can be passed by `-glyphset` in place of `-charset`.
- Glyphs not present in the font should now be correctly skipped instead of producing a placeholder symbol.
- Added `-threads` argument to set the number of concurrent threads used during distance field generation.
### Version 1.0.1 (2020-03-09)
- Updated to MSDFgen 1.7.1.
## Version 1.0 (2020-03-08)
- Initial release.

View File

@ -0,0 +1,35 @@
project(msdf-atlas-gen LANGUAGES CXX)
add_subdirectory(msdfgen)
# 收集源文件
file(GLOB_RECURSE MSDF_ATLAS_GEN_SOURCES
"msdf-atlas-gen/*.cpp"
"msdf-atlas-gen/*.c"
)
# 创建静态库
add_library(msdf-atlas-gen STATIC ${MSDF_ATLAS_GEN_SOURCES})
# 设置包含目录
target_include_directories(msdf-atlas-gen PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/msdf-atlas-gen>
$<INSTALL_INTERFACE:include>
# 显式添加 msdfgen 的包含路径
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../msdfgen/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../msdfgen>
)
# 添加编译定义
target_compile_definitions(msdf-atlas-gen PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
)
# 链接依赖
target_link_libraries(msdf-atlas-gen PUBLIC msdfgen)
# 设置目标属性
set_target_properties(msdf-atlas-gen PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)

21
Hazel/vendor/msdf-atlas-gen/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 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.

4
Hazel/vendor/msdf-atlas-gen/Makefile vendored Normal file
View File

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

142
Hazel/vendor/msdf-atlas-gen/README.md vendored Normal file
View File

@ -0,0 +1,142 @@
# Multi-channel signed distance field atlas generator
This is a utility for generating compact font atlases using [MSDFgen](https://github.com/Chlumsky/msdfgen).
The atlas generator loads a subset of glyphs from a TTF or OTF font file, generates a distance field for each of them, and tightly packs them into an atlas bitmap (example below). The finished atlas and/or its layout metadata can be exported as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file, a plain image file, a CSV sheet or a structured JSON file.
![Atlas example](https://user-images.githubusercontent.com/18639794/76163889-811f2e80-614a-11ea-9b28-1eed54dbb899.png)
A font atlas is typically stored in texture memory and used to draw text in real-time rendering contexts such as video games.
- See what's new in the [changelog](CHANGELOG.md).
## Atlas types
The atlas generator can generate the following six types of atlases.
| |Hard mask|Soft mask|SDF|PSDF|MSDF|MTSDF|
|-|-|-|-|-|-|-|
| |![Hard mask](https://user-images.githubusercontent.com/18639794/76163903-9eec9380-614a-11ea-92cb-d49485bbad31.png)|![Soft mask](https://user-images.githubusercontent.com/18639794/76163904-a1e78400-614a-11ea-912a-b220fed081cb.png)|![SDF](https://user-images.githubusercontent.com/18639794/76163905-a4e27480-614a-11ea-93eb-c80819a44e6e.png)|![PSDF](https://user-images.githubusercontent.com/18639794/76163907-a6ac3800-614a-11ea-8d97-dafc1db6711d.png)|![MSDF](https://user-images.githubusercontent.com/18639794/76163909-a9a72880-614a-11ea-9726-e825ee0dde94.png)|![MTSDF](https://user-images.githubusercontent.com/18639794/76163910-ac098280-614a-11ea-8b6b-811d864cd584.png)|
|Channels:|1 (1-bit)|1|1|1|3|4|
|Anti-aliasing:|-|Yes|Yes|Yes|Yes|Yes|
|Scalability:|-|-|Yes|Yes|Yes|Yes|
|Sharp corners:|-|-|-|-|Yes|Yes|
|Soft effects:|-|-|Yes|-|-|Yes|
|Hard effects:|-|-|-|Yes|Yes|Yes|
Notes:
- *Sharp corners* refers to preservation of corner sharpness when upscaled.
- *Soft effects* refers to the support of effects that use true distance, such as glows, rounded borders, or simplified shadows.
- *Hard effects* refers to the support of effects that use pseudo-distance, such as mitered borders or thickness adjustment.
## Getting started
This project can be used either as a library or as a standalone console program.
To start using the program immediately, there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdf-atlas-gen/releases).
To build the project, you may use the included [Visual Studio solution](msdf-atlas-gen.sln) or the [Unix Makefile](Makefile).
## Command line arguments
Use the following command line arguments for the standalone version of the atlas generator.
### Input
- `-font <fontfile.ttf/otf>` (required) &ndash; sets the input font file.
- `-charset <charset.txt>` &ndash; sets the character set. The ASCII charset will be used if not specified. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`.
- `-glyphset <glyphset.txt>` &ndash; sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification).
- `-fontscale <scale>` &ndash; applies a scaling transformation to the font's glyphs. Mainly to be used to generate multiple sizes in a single atlas, otherwise use [`-size`](#glyph-configuration).
- `-fontname <name>` &ndash; sets a name for the font that will be stored in certain output files as metadata.
- `-and` &ndash; separates multiple inputs to be combined into a single atlas.
### Bitmap atlas type
`-type <type>` &ndash; see [Atlas types](#atlas-types)
`<type>` can be one of:
- `hardmask` &ndash; a non-anti-aliased binary image
- `softmask` &ndash; an anti-aliased image
- `sdf` &ndash; a true signed distance field (SDF)
- `psdf` &ndash; a pseudo-distance field
- `msdf` (default) &ndash; a multi-channel signed distance field (MSDF)
- `mtsdf` &ndash; a combination of MSDF and true SDF in the alpha channel
### Atlas image format
`-format <format>`
`<format>` can be one of:
- `png` &ndash; a compressed PNG image
- `bmp` &ndash; an uncompressed BMP image
- `tiff` &ndash; an uncompressed floating-point TIFF image
- `text` &ndash; a sequence of pixel values in plain text
- `textfloat` &ndash; a sequence of floating-point pixel values in plain text
- `bin` &ndash; a sequence of pixel values encoded as raw bytes of data
- `binfloat` &ndash; a sequence of pixel values encoded as raw 32-bit floating-point values
### Atlas dimensions
`-dimensions <width> <height>` &ndash; sets fixed atlas dimensions
Alternativelly, the minimum possible dimensions may be selected automatically if a dimensions constraint is set instead:
- `-pots` &ndash; a power-of-two square
- `-potr` &ndash; a power-of-two square or rectangle (2:1)
- `-square` &ndash; any square dimensions
- `-square2` &ndash; square with even side length
- `-square4` (default) &ndash; square with side length divisible by four
### Outputs
Any non-empty subset of the following may be specified:
- `-imageout <filename.*>` &ndash; saves the atlas bitmap as a plain image file. Format matches `-format`
- `-json <filename.json>` &ndash; writes the atlas's layout data as well as other metrics into a structured JSON file
- `-csv <filename.csv>` &ndash; writes the glyph layout data into a simple CSV file
- `-arfont <filename.arfont>` &ndash; saves the atlas and its layout data as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file
- `-shadronpreview <filename.shadron> <sample text>` &ndash; generates a [Shadron script](https://www.arteryengine.com/shadron/) that uses the generated atlas to draw a sample text as a preview
### Glyph configuration
- `-size <EM size>` &ndash; sets the size of the glyphs in the atlas in pixels per EM
- `-minsize <EM size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
- `-emrange <EM range>` &ndash; sets the distance field range in EM's
- `-pxrange <pixel range>` (default = 2) &ndash; sets the distance field range in output pixels
### Distance field generator settings
- `-angle <angle>` &ndash; sets the minimum angle between adjacent edges to be considered a corner. Append D for degrees (`msdf` / `mtsdf` only)
- `-coloringstrategy <simple / inktrap / distance>` &ndash; selects the edge coloring heuristic (`msdf` / `mtsdf` only)
- `-errorcorrection <mode>` &ndash; selects the error correction algorithm. Use `help` as mode for more information (`msdf` / `mtsdf` only)
- `-miterlimit <value>` &ndash; sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners (`psdf` / `msdf` / `mtsdf` only)
- `-overlap` &ndash; switches to distance field generator with support for overlapping contours
- `-nopreprocess` &ndash; disables path preprocessing which resolves self-intersections and overlapping contours
- `-scanline` &ndash; performs an additional scanline pass to fix the signs of the distances
- `-seed <N>` &ndash; sets the initial seed for the edge coloring heuristic
- `-threads <N>` &ndash; sets the number of threads for the parallel computation (0 = auto)
Use `-help` for an exhaustive list of options.
## Character set specification syntax
The character set file is a text file with UTF-8 or ASCII encoding.
The characters can be denoted in the following ways:
- Single character: `'A'` (UTF-8 encoded), `65` (decimal Unicode), `0x41` (hexadecimal Unicode)
- Range of characters: `['A', 'Z']`, `[65, 90]`, `[0x41, 0x5a]`
- String of characters: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ"` (UTF-8 encoded)
The entries should be separated by commas or whitespace.
In between quotation marks, backslash is used as the escape character (e.g. `'\''`, `'\\'`, `"!\"#"`).
The order in which characters appear is not taken into consideration.
Additionally, the include directive can be used to include other charset files and combine character sets in a hierarchical way.
It must be written on a separate line:
`@include "base-charset.txt"`
### Glyph set specification
The syntax of the glyph set specification is mostly the same as that of a character set, but only numeric values (decimal and hexadecimal) are allowed.

BIN
Hazel/vendor/msdf-atlas-gen/icon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,42 @@
#pragma once
#include <msdfgen.h>
#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 {
msdfgen::MSDFGeneratorConfig config;
bool scanlinePass = false;
};
/// A function that generates the bitmap for a single glyph
template <typename T, int N>
using GeneratorFunction = void (*)(const msdfgen::BitmapRef<T, N> &, const GlyphGeometry &, const GeneratorAttributes &);
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <msdfgen.h>
#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 <typename T, int N>
void put(int x, int y, const msdfgen::BitmapConstRef<T, N> &subBitmap);
/// Retrieves a subsection at x, y from the atlas storage. May be implemented for only some T, N
template <typename T, int N>
void get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const;
};
}
}

View File

@ -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 <typename T, int N>
class BitmapAtlasStorage {
public:
BitmapAtlasStorage();
BitmapAtlasStorage(int width, int height);
explicit BitmapAtlasStorage(const msdfgen::BitmapConstRef<T, N> &bitmap);
explicit BitmapAtlasStorage(msdfgen::Bitmap<T, N> &&bitmap);
BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height);
BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height, const Remap *remapping, int count);
operator msdfgen::BitmapConstRef<T, N>() const;
operator msdfgen::BitmapRef<T, N>();
operator msdfgen::Bitmap<T, N>() &&;
template <typename S>
void put(int x, int y, const msdfgen::BitmapConstRef<S, N> &subBitmap);
void get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const;
private:
msdfgen::Bitmap<T, N> bitmap;
};
}
#include "BitmapAtlasStorage.hpp"

View File

@ -0,0 +1,65 @@
#include "BitmapAtlasStorage.h"
#include <cstring>
#include <algorithm>
#include "bitmap-blit.h"
namespace msdf_atlas {
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage() { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(int width, int height) : bitmap(width, height) {
memset((T *) bitmap, 0, sizeof(T)*N*width*height);
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const msdfgen::BitmapConstRef<T, N> &bitmap) : bitmap(bitmap) { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(msdfgen::Bitmap<T, N> &&bitmap) : bitmap((msdfgen::Bitmap<T, N> &&) bitmap) { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &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 <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &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 <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::BitmapConstRef<T, N>() const {
return bitmap;
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::BitmapRef<T, N>() {
return bitmap;
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::Bitmap<T, N>() && {
return (msdfgen::Bitmap<T, N> &&) bitmap;
}
template <typename T, int N>
template <typename S>
void BitmapAtlasStorage<T, N>::put(int x, int y, const msdfgen::BitmapConstRef<S, N> &subBitmap) {
blit(bitmap, subBitmap, x, y, 0, 0, subBitmap.width, subBitmap.height);
}
template <typename T, int N>
void BitmapAtlasStorage<T, N>::get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const {
blit(subBitmap, bitmap, 0, 0, x, y, subBitmap.width, subBitmap.height);
}
}

View File

@ -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<unicode_t>::const_iterator Charset::begin() const {
return codepoints.begin();
}
std::set<unicode_t>::const_iterator Charset::end() const {
return codepoints.end();
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <cstdlib>
#include <set>
#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<unicode_t>::const_iterator begin() const;
std::set<unicode_t>::const_iterator end() const;
/// Load character set from a text file with the correct syntax
bool load(const char *filename, bool disableCharLiterals = false);
private:
std::set<unicode_t> codepoints;
};
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <vector>
#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 AtlasGenerator>
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<Rectangle> rectangles;
std::vector<Remap> remapBuffer;
int totalArea;
GeneratorAttributes genAttribs;
int padding;
};
}
#include "DynamicAtlas.hpp"

View File

@ -0,0 +1,69 @@
#include "DynamicAtlas.h"
namespace msdf_atlas {
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { }
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { }
template <class AtlasGenerator>
void DynamicAtlas<AtlasGenerator>::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 <class AtlasGenerator>
AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() {
return generator;
}
template <class AtlasGenerator>
const AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() const {
return generator;
}
}

View File

@ -0,0 +1,185 @@
#include "FontGeometry.h"
namespace msdf_atlas {
FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { }
FontGeometry::GlyphRange::GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { }
size_t FontGeometry::GlyphRange::size() const {
return glyphs->size();
}
bool FontGeometry::GlyphRange::empty() const {
return glyphs->empty();
}
const GlyphGeometry * FontGeometry::GlyphRange::begin() const {
return glyphs->data()+rangeStart;
}
const GlyphGeometry * FontGeometry::GlyphRange::end() const {
return glyphs->data()+rangeEnd;
}
FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { }
FontGeometry::FontGeometry(std::vector<GlyphGeometry> *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(glyphStorage), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { }
int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+glyphset.size());
int loaded = 0;
for (unicode_t index : glyphset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
return loaded;
}
int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+charset.size());
int loaded = 0;
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, cp, preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
return loaded;
}
bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) {
if (!msdfgen::getFontMetrics(metrics, font))
return false;
if (metrics.emSize <= 0)
metrics.emSize = MSDF_ATLAS_DEFAULT_EM_SIZE;
geometryScale = fontScale/metrics.emSize;
metrics.emSize *= geometryScale;
metrics.ascenderY *= geometryScale;
metrics.descenderY *= geometryScale;
metrics.lineHeight *= geometryScale;
metrics.underlineY *= geometryScale;
metrics.underlineThickness *= geometryScale;
return true;
}
bool FontGeometry::addGlyph(const GlyphGeometry &glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back(glyph);
++rangeEnd;
return true;
}
bool FontGeometry::addGlyph(GlyphGeometry &&glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back((GlyphGeometry &&) glyph);
++rangeEnd;
return true;
}
int FontGeometry::loadKerning(msdfgen::FontHandle *font) {
int loaded = 0;
for (size_t i = rangeStart; i < rangeEnd; ++i)
for (size_t j = rangeStart; j < rangeEnd; ++j) {
double advance;
if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex()) && advance) {
kerning[std::make_pair<int, int>((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance;
++loaded;
}
}
return loaded;
}
void FontGeometry::setName(const char *name) {
if (name)
this->name = name;
else
this->name.clear();
}
double FontGeometry::getGeometryScale() const {
return geometryScale;
}
const msdfgen::FontMetrics & FontGeometry::getMetrics() const {
return metrics;
}
GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const {
return preferredIdentifierType;
}
FontGeometry::GlyphRange FontGeometry::getGlyphs() const {
return GlyphRange(glyphs, rangeStart, rangeEnd);
}
const GlyphGeometry * FontGeometry::getGlyph(msdfgen::GlyphIndex index) const {
std::map<int, size_t>::const_iterator it = glyphsByIndex.find(index.getIndex());
if (it != glyphsByIndex.end())
return &(*glyphs)[it->second];
return nullptr;
}
const GlyphGeometry * FontGeometry::getGlyph(unicode_t codepoint) const {
std::map<unicode_t, size_t>::const_iterator it = glyphsByCodepoint.find(codepoint);
if (it != glyphsByCodepoint.end())
return &(*glyphs)[it->second];
return nullptr;
}
bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const {
const GlyphGeometry *glyph1 = getGlyph(index1);
if (!glyph1)
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(index1.getIndex(), index2.getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const {
const GlyphGeometry *glyph1, *glyph2;
if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2))))
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(glyph1->getIndex(), glyph2->getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
const std::map<std::pair<int, int>, double> & FontGeometry::getKerning() const {
return kerning;
}
const char * FontGeometry::getName() const {
if (name.empty())
return nullptr;
return name.c_str();
}
}

View File

@ -0,0 +1,86 @@
#pragma once
#include <utility>
#include <vector>
#include <string>
#include <map>
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "Charset.h"
#define MSDF_ATLAS_DEFAULT_EM_SIZE 32.0
namespace msdf_atlas {
/// Represents the geometry of all glyphs of a given font or font variant
class FontGeometry {
public:
class GlyphRange {
public:
GlyphRange();
GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd);
size_t size() const;
bool empty() const;
const GlyphGeometry * begin() const;
const GlyphGeometry * end() const;
private:
const std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
};
FontGeometry();
explicit FontGeometry(std::vector<GlyphGeometry> *glyphStorage);
/// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs
int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true);
/// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs
int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true);
/// Only loads font metrics and geometry scale from font
bool loadMetrics(msdfgen::FontHandle *font, double fontScale);
/// Adds a loaded glyph
bool addGlyph(const GlyphGeometry &glyph);
bool addGlyph(GlyphGeometry &&glyph);
/// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs
int loadKerning(msdfgen::FontHandle *font);
/// Sets a name to be associated with the font
void setName(const char *name);
/// Returns the geometry scale to be used when loading glyphs
double getGeometryScale() const;
/// Returns the processed font metrics
const msdfgen::FontMetrics & getMetrics() const;
/// Returns the type of identifier that was used to load glyphs
GlyphIdentifierType getPreferredIdentifierType() const;
/// Returns the list of all glyphs
GlyphRange getGlyphs() const;
/// Finds a glyph by glyph index or Unicode codepoint, returns null if not found
const GlyphGeometry * getGlyph(msdfgen::GlyphIndex index) const;
const GlyphGeometry * getGlyph(unicode_t codepoint) const;
/// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure
bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const;
bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const;
/// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values
const std::map<std::pair<int, int>, double> & getKerning() const;
/// Returns the name associated with the font or null if not set
const char * getName() const;
private:
double geometryScale;
msdfgen::FontMetrics metrics;
GlyphIdentifierType preferredIdentifierType;
std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
std::map<int, size_t> glyphsByIndex;
std::map<unicode_t, size_t> glyphsByCodepoint;
std::map<std::pair<int, int>, double> kerning;
std::vector<GlyphGeometry> ownGlyphs;
std::string name;
};
}

View File

@ -0,0 +1,19 @@
#pragma once
namespace msdf_atlas {
/// The glyph box - its bounds in plane and atlas
struct GlyphBox {
int index;
double advance;
struct {
double l, b, r, t;
} bounds;
struct {
int x, y, w, h;
} rect;
};
}

View File

@ -0,0 +1,170 @@
#include "GlyphGeometry.h"
#include <cmath>
#include <core/ShapeDistanceFinder.h>
namespace msdf_atlas {
GlyphGeometry::GlyphGeometry() : index(), codepoint(), geometryScale(), bounds(), advance(), box() { }
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) {
this->index = index.getIndex();
this->geometryScale = geometryScale;
codepoint = 0;
advance *= geometryScale;
#ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
#endif
shape.normalize();
bounds = shape.getBounds();
#ifdef MSDFGEN_USE_SKIA
if (!preprocessGeometry)
#endif
{
// Determine if shape is winded incorrectly and reverse it in that case
msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
if (msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, outerPoint) > 0) {
for (msdfgen::Contour &contour : shape.contours)
contour.reverse();
}
}
return true;
}
return false;
}
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry) {
msdfgen::GlyphIndex index;
if (msdfgen::getGlyphIndex(index, font, codepoint)) {
if (load(font, geometryScale, index, preprocessGeometry)) {
this->codepoint = codepoint;
return true;
}
}
return false;
}
void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed) {
fn(shape, angleThreshold, seed);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) {
scale *= geometryScale;
range /= geometryScale;
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, 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;
}
int GlyphGeometry::getIndex() const {
return index;
}
msdfgen::GlyphIndex GlyphGeometry::getGlyphIndex() const {
return msdfgen::GlyphIndex(index);
}
unicode_t GlyphGeometry::getCodepoint() const {
return codepoint;
}
int GlyphGeometry::getIdentifier(GlyphIdentifierType type) const {
switch (type) {
case GlyphIdentifierType::GLYPH_INDEX:
return index;
case GlyphIdentifierType::UNICODE_CODEPOINT:
return (int) codepoint;
}
return 0;
}
const msdfgen::Shape & GlyphGeometry::getShape() const {
return shape;
}
double GlyphGeometry::getAdvance() const {
return advance;
}
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;
}
msdfgen::Projection GlyphGeometry::getBoxProjection() const {
return msdfgen::Projection(msdfgen::Vector2(box.scale), box.translate);
}
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) {
double invBoxScale = 1/box.scale;
l = geometryScale*(-box.translate.x+.5*invBoxScale);
b = geometryScale*(-box.translate.y+.5*invBoxScale);
r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale);
} 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.index = index;
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;
}
}

View File

@ -0,0 +1,76 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphBox.h"
namespace msdf_atlas {
/// Represents 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, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry = true);
/// Applies edge coloring to glyph shape
void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), 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 index within the font
int getIndex() const;
/// Returns the glyph's index as a msdfgen::GlyphIndex
msdfgen::GlyphIndex getGlyphIndex() const;
/// Returns the Unicode codepoint represented by the glyph or 0 if unknown
unicode_t getCodepoint() const;
/// Returns the glyph's identifier specified by the supplied identifier type
int getIdentifier(GlyphIdentifierType type) const;
/// Returns the glyph's shape
const msdfgen::Shape & getShape() const;
/// Returns the glyph's advance
double getAdvance() 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 projection needed to generate the glyph's bitmap
msdfgen::Projection getBoxProjection() 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:
int index;
unicode_t codepoint;
double geometryScale;
msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds;
double advance;
struct {
struct {
int x, y, w, h;
} rect;
double range;
double scale;
msdfgen::Vector2 translate;
} box;
};
}

View File

@ -0,0 +1,45 @@
#pragma once
#include <vector>
#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 <typename T, int N, GeneratorFunction<T, N> 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<GlyphBox> layout;
std::vector<T> glyphBuffer;
std::vector<byte> errorCorrectionBuffer;
GeneratorAttributes attributes;
int threadCount;
};
}
#include "ImmediateAtlasGenerator.hpp"

View File

@ -0,0 +1,77 @@
#include "ImmediateAtlasGenerator.h"
#include <algorithm>
namespace msdf_atlas {
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator() : threadCount(1) { }
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator(int width, int height) : storage(width, height), threadCount(1) { }
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::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);
if (threadCount*maxBoxArea > (int) errorCorrectionBuffer.size())
errorCorrectionBuffer.resize(threadCount*maxBoxArea);
std::vector<GeneratorAttributes> threadAttributes(threadCount);
for (int i = 0; i < threadCount; ++i) {
threadAttributes[i] = attributes;
threadAttributes[i].config.errorCorrection.buffer = errorCorrectionBuffer.data()+i*maxBoxArea;
}
Workload([this, glyphs, &threadAttributes, 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<T, N> glyphBitmap(glyphBuffer.data()+threadNo*threadBufferSize, w, h);
GEN_FN(glyphBitmap, glyph, threadAttributes[threadNo]);
storage.put(l, b, msdfgen::BitmapConstRef<T, N>(glyphBitmap));
}
return true;
}, count).finish(threadCount);
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::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 <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::resize(int width, int height) {
AtlasStorage newStorage((AtlasStorage &&) storage, width, height);
storage = (AtlasStorage &&) newStorage;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::setAttributes(const GeneratorAttributes &attributes) {
this->attributes = attributes;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::setThreadCount(int threadCount) {
this->threadCount = threadCount;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
const AtlasStorage & ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::atlasStorage() const {
return storage;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
namespace msdf_atlas {
struct Rectangle {
int x, y, w, h;
};
struct OrientedRectangle : Rectangle {
bool rotated;
};
}

View File

@ -0,0 +1,143 @@
#include "RectanglePacker.h"
#include <algorithm>
namespace msdf_atlas {
#define WORST_FIT 0x7fffffff
template <typename T>
static void removeFromUnorderedVector(std::vector<T> &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<int> 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<int> 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();
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <vector>
#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<Rectangle> spaces;
static int rateFit(int w, int h, int sw, int sh);
void splitSpace(int index, int w, int h);
};
}

View File

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

View File

@ -0,0 +1,168 @@
#include "TightAtlasPacker.h"
#include <vector>
#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<Rectangle> rectangles;
std::vector<GlyphGeometry *> 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<int, int> dimensions = std::make_pair(width, height);
switch (dimensionsConstraint) {
case DimensionsConstraint::POWER_OF_TWO_SQUARE:
dimensions = packRectangles<SquarePowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
dimensions = packRectangles<PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
dimensions = packRectangles<SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::EVEN_SQUARE:
dimensions = packRectangles<SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::SQUARE:
dimensions = packRectangles<SquareSizeSelector<> >(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 && ((maxScale = 2*minScale), TRY_PACK(maxScale)))
minScale = maxScale;
} else {
while (minScale > 1e-32 && ((minScale = .5*maxScale), !TRY_PACK(minScale)))
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;
}
}

View File

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

View File

@ -0,0 +1,50 @@
#include "Workload.h"
#include <vector>
#include <thread>
#include <atomic>
#include <algorithm>
namespace msdf_atlas {
Workload::Workload() : chunks(0) { }
Workload::Workload(const std::function<bool(int, int)> &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<int> next(0);
std::function<void(int)> threadWorker = [this, &result, &next](int threadNo) {
for (int i = next++; result && i < chunks; i = next++) {
if (!workerFunction(i, threadNo))
result = false;
}
};
std::vector<std::thread> 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(std::min(threadCount, chunks));
return false;
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <functional>
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<bool(int, int)> &workerFunction, int chunks);
/// Runs the process and returns true if all chunks have been processed
bool finish(int threadCount);
private:
std::function<bool(int, int)> workerFunction;
int chunks;
bool finishSequential();
bool finishParallel(int threadCount);
};
}

View File

@ -0,0 +1,58 @@
#include "bitmap-blit.h"
#include <cstring>
namespace msdf_atlas {
template <typename T, int N>
void blitSameType(const msdfgen::BitmapRef<T, N> &dst, const msdfgen::BitmapConstRef<T, N> &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<T, N> &dst, const msdfgen::BitmapConstRef<T, N> &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<byte, 1> &dst, const msdfgen::BitmapConstRef<float, 1> &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<byte, 3> &dst, const msdfgen::BitmapConstRef<float, 3> &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<byte, 4> &dst, const msdfgen::BitmapConstRef<float, 4> &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]);
}
}
}
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <msdfgen.h>
#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<byte, 1> &dst, const msdfgen::BitmapConstRef<byte, 1> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 3> &dst, const msdfgen::BitmapConstRef<byte, 3> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 4> &dst, const msdfgen::BitmapConstRef<byte, 4> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 1> &dst, const msdfgen::BitmapConstRef<float, 1> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 3> &dst, const msdfgen::BitmapConstRef<float, 3> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 4> &dst, const msdfgen::BitmapConstRef<float, 4> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 1> &dst, const msdfgen::BitmapConstRef<float, 1> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 3> &dst, const msdfgen::BitmapConstRef<float, 3> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 4> &dst, const msdfgen::BitmapConstRef<float, 4> &src, int dx, int dy, int sx, int sy, int w, int h);
}

View File

@ -0,0 +1,250 @@
#include "Charset.h"
#include <cstdio>
#include <string>
#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, bool disableCharLiterals) {
if (FILE *f = fopen(filename, "rb")) {
enum {
CLEAR,
TIGHT,
RANGE_BRACKET,
RANGE_START,
RANGE_SEPARATOR,
RANGE_END
} state = CLEAR;
std::string buffer;
std::vector<unicode_t> 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) || disableCharLiterals)
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();
break;
case '"': // string of UTF-8 characters
if (state != CLEAR || disableCharLiterals)
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;
}
}

View File

@ -0,0 +1,45 @@
#include "csv-export.h"
#include <cstdio>
#include "GlyphGeometry.h"
namespace msdf_atlas {
bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename) {
FILE *f = fopen(filename, "w");
if (!f)
return false;
for (int i = 0; i < fontCount; ++i) {
for (const GlyphGeometry &glyph : fonts[i].getGlyphs()) {
double l, b, r, t;
if (fontCount > 1)
fprintf(f, "%d,", i);
fprintf(f, "%d,%.17g,", glyph.getIdentifier(fonts[i].getPreferredIdentifierType()), glyph.getAdvance());
glyph.getQuadPlaneBounds(l, b, r, t);
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, -t, r, -b);
break;
}
glyph.getQuadAtlasBounds(l, b, r, t);
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, atlasHeight-t, r, atlasHeight-b);
break;
}
}
}
fclose(f);
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "FontGeometry.h"
namespace msdf_atlas {
/**
* Writes the positioning data and atlas layout of the glyphs into a CSV file
* The columns are: font variant index (if fontCount > 1), glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
*/
bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename);
}

View File

@ -0,0 +1,52 @@
#include "glyph-generators.h"
namespace msdf_atlas {
void scanlineGenerator(const msdfgen::BitmapRef<float, 1> &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<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
}
void psdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
}
void msdfGenerator(const msdfgen::BitmapRef<float, 3> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::MSDFGeneratorConfig config = attribs.config;
if (attribs.scanlinePass)
config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
if (attribs.scanlinePass) {
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) {
config.errorCorrection.mode = attribs.config.errorCorrection.mode;
config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
}
}
}
void mtsdfGenerator(const msdfgen::BitmapRef<float, 4> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::MSDFGeneratorConfig config = attribs.config;
if (attribs.scanlinePass)
config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
if (attribs.scanlinePass) {
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) {
config.errorCorrection.mode = attribs.config.errorCorrection.mode;
config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
}
}
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <msdfgen.h>
#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<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a true signed distance field of the glyph
void sdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a signed pseudo-distance field of the glyph
void psdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a multi-channel signed distance field of the glyph
void msdfGenerator(const msdfgen::BitmapRef<float, 3> &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<float, 4> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
}

View File

@ -0,0 +1,63 @@
#include "image-encode.h"
#include <lodepng.h>
namespace msdf_atlas {
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 1> &bitmap) {
std::vector<byte> 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<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 3> &bitmap) {
std::vector<byte> 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<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 4> &bitmap) {
std::vector<byte> pixels(4*bitmap.width*bitmap.height);
for (int y = 0; y < bitmap.height; ++y)
memcpy(&pixels[4*bitmap.width*y], bitmap(0, bitmap.height-y-1), 4*bitmap.width);
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 1> &bitmap) {
std::vector<byte> pixels(bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x)
*it++ = msdfgen::pixelFloatToByte(*bitmap(x, y));
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3> &bitmap) {
std::vector<byte> pixels(3*bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x) {
*it++ = 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<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap) {
std::vector<byte> pixels(4*bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x) {
*it++ = 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);
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <vector>
#include <msdfgen.h>
#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<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 1> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 3> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 4> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 1> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap);
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <msdfgen.h>
#include "types.h"
namespace msdf_atlas {
/// Saves the bitmap as an image file with the specified format
template <typename T, int N>
bool saveImage(const msdfgen::BitmapConstRef<T, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP);
}
#include "image-save.hpp"

View File

@ -0,0 +1,172 @@
#include "image-save.h"
#include <cstdio>
#include <msdfgen-ext.h>
namespace msdf_atlas {
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageBinaryLE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageBinaryBE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<byte, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) {
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, outputYDirection);
case ImageFormat::TEXT_FLOAT:
return false;
case ImageFormat::BINARY:
return saveImageBinary(bitmap, filename, outputYDirection);
case ImageFormat::BINARY_FLOAT:
case ImageFormat::BINARY_FLOAT_BE:
return false;
default:;
}
return false;
}
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<float, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) {
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, outputYDirection);
case ImageFormat::BINARY:
return false;
case ImageFormat::BINARY_FLOAT:
return saveImageBinaryLE(bitmap, filename, outputYDirection);
case ImageFormat::BINARY_FLOAT_BE:
return saveImageBinaryBE(bitmap, filename, outputYDirection);
default:;
}
return false;
}
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
int written = 0;
switch (outputYDirection) {
case YDirection::BOTTOM_UP:
written = fwrite(bitmap.pixels, 1, N*bitmap.width*bitmap.height, f);
break;
case YDirection::TOP_DOWN:
for (int y = bitmap.height-1; y >= 0; --y)
written += fwrite(bitmap.pixels+N*bitmap.width*y, 1, N*bitmap.width, f);
break;
}
success = written == N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
}
template <int N>
bool
#ifdef __BIG_ENDIAN__
saveImageBinaryBE
#else
saveImageBinaryLE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
int written = 0;
switch (outputYDirection) {
case YDirection::BOTTOM_UP:
written = fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f);
break;
case YDirection::TOP_DOWN:
for (int y = bitmap.height-1; y >= 0; --y)
written += fwrite(bitmap.pixels+N*bitmap.width*y, sizeof(float), N*bitmap.width, f);
break;
}
success = written == N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
}
template <int N>
bool
#ifdef __BIG_ENDIAN__
saveImageBinaryLE
#else
saveImageBinaryBE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
int written = 0;
for (int y = 0; y < bitmap.height; ++y) {
const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < bitmap.width; ++x) {
const unsigned char *b = reinterpret_cast<const unsigned char *>(p++);
for (int i = sizeof(float)-1; i >= 0; --i)
written += fwrite(b+i, 1, 1, f);
}
}
success = written == sizeof(float)*N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
}
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
success = true;
for (int y = 0; y < bitmap.height; ++y) {
const byte *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < N*bitmap.width; ++x)
success &= fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++) > 0;
success &= fprintf(f, "\n") > 0;
}
fclose(f);
}
return success;
}
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
success = true;
for (int y = 0; y < bitmap.height; ++y) {
const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < N*bitmap.width; ++x)
success &= fprintf(f, x ? " %g" : "%g", *p++) > 0;
success &= fprintf(f, "\n") > 0;
}
fclose(f);
}
return success;
}
}

View File

@ -0,0 +1,186 @@
#include "json-export.h"
#include <string>
#include "GlyphGeometry.h"
namespace msdf_atlas {
static std::string escapeJsonString(const char *str) {
char uval[7] = "\\u0000";
std::string outStr;
while (*str) {
switch (*str) {
case '\\':
outStr += "\\\\";
break;
case '"':
outStr += "\\\"";
break;
case '\n':
outStr += "\\n";
break;
case '\r':
outStr += "\\r";
break;
case '\t':
outStr += "\\t";
break;
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: /* \\t */ /* \\n */ case 0x0b: case 0x0c: /* \\r */ case 0x0e: case 0x0f:
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
uval[4] = '0'+(*str >= 0x10);
uval[5] = "0123456789abcdef"[*str&0x0f];
outStr += uval;
break;
default:
outStr.push_back(*str);
}
++str;
}
return outStr;
}
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(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning) {
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);
fprintf(f, "\"yOrigin\":\"%s\"", yDirection == YDirection::TOP_DOWN ? "top" : "bottom");
} fputs("},", f);
if (fontCount > 1)
fputs("\"variants\":[", f);
for (int i = 0; i < fontCount; ++i) {
const FontGeometry &font = fonts[i];
if (fontCount > 1)
fputs(i == 0 ? "{" : ",{", f);
// Font name
const char *name = font.getName();
if (name)
fprintf(f, "\"name\":\"%s\",", escapeJsonString(name).c_str());
// Font metrics
fputs("\"metrics\":{", f); {
double yFactor = yDirection == YDirection::TOP_DOWN ? -1 : 1;
const msdfgen::FontMetrics &metrics = font.getMetrics();
fprintf(f, "\"emSize\":%.17g,", metrics.emSize);
fprintf(f, "\"lineHeight\":%.17g,", metrics.lineHeight);
fprintf(f, "\"ascender\":%.17g,", yFactor*metrics.ascenderY);
fprintf(f, "\"descender\":%.17g,", yFactor*metrics.descenderY);
fprintf(f, "\"underlineY\":%.17g,", yFactor*metrics.underlineY);
fprintf(f, "\"underlineThickness\":%.17g", metrics.underlineThickness);
} fputs("},", f);
// Glyph mapping
fputs("\"glyphs\":[", f);
bool firstGlyph = true;
for (const GlyphGeometry &glyph : font.getGlyphs()) {
fputs(firstGlyph ? "{" : ",{", f);
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
fprintf(f, "\"index\":%d,", glyph.getIndex());
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
fprintf(f, "\"unicode\":%u,", glyph.getCodepoint());
break;
}
fprintf(f, "\"advance\":%.17g", glyph.getAdvance());
double l, b, r, t;
glyph.getQuadPlaneBounds(l, b, r, t);
if (l || b || r || t) {
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, -t, r, -b);
break;
}
}
glyph.getQuadAtlasBounds(l, b, r, t);
if (l || b || r || t) {
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, atlasHeight-t, r, atlasHeight-b);
break;
}
}
fputs("}", f);
firstGlyph = false;
} fputs("]", f);
// Kerning pairs
if (kerning) {
fputs(",\"kerning\":[", f);
bool firstPair = true;
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"index1\":%d,", kernPair.first.first);
fprintf(f, "\"index2\":%d,", kernPair.first.second);
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first));
const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second));
if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint());
fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint());
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
}
break;
} fputs("]", f);
}
if (fontCount > 1)
fputs("}", f);
}
if (fontCount > 1)
fputs("]", f);
fputs("}\n", f);
fclose(f);
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file
bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning);
}

View File

@ -0,0 +1,999 @@
/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.2 (2021-05-29) - standalone console program
* --------------------------------------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020 - 2021
*
*/
#ifdef MSDF_ATLAS_STANDALONE
#define _USE_MATH_DEFINES
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cassert>
#include <vector>
#include <algorithm>
#include <thread>
#include "msdf-atlas-gen.h"
using namespace msdf_atlas;
#define DEFAULT_ANGLE_THRESHOLD 3.0
#define DEFAULT_MITER_LIMIT 1.0
#define DEFAULT_PIXEL_RANGE 2.0
#define SDF_ERROR_ESTIMATE_PRECISION 19
#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO
#define LCG_MULTIPLIER 6364136223846793005ull
#define LCG_INCREMENT 1442695040888963407ull
#ifdef MSDFGEN_USE_SKIA
#define TITLE_SUFFIX " & Skia"
#define EXTRA_UNDERLINE "-------"
#else
#define TITLE_SUFFIX
#define EXTRA_UNDERLINE
#endif
static const char * const helpText = R"(
MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION R"( (with MSDFGEN v)" MSDFGEN_VERSION TITLE_SUFFIX R"()
----------------------------------------------------------------)" EXTRA_UNDERLINE R"(
INPUT SPECIFICATION
-font <filename.ttf/otf>
Specifies the input TrueType / OpenType font file. This is required.
-charset <filename>
Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII.
-glyphset <filename>
Specifies the set of input glyphs as glyph indices within the font file.
-fontscale <scale>
Specifies the scale to be applied to the glyph geometry of the font.
-fontname <name>
Specifies a name for the font that will be propagated into the output files as metadata.
-and
Separates multiple inputs to be combined into a single atlas.
ATLAS CONFIGURATION
-type <hardmask / softmask / sdf / psdf / msdf / mtsdf>
Selects the type of atlas to be generated.
-format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>
Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats.
-dimensions <width> <height>
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
-yorigin <bottom / top>
Determines whether the Y-axis is oriented upwards (bottom origin, default) or downwards (top origin).
OUTPUT SPECIFICATION - one or more can be specified
-imageout <filename.*>
Saves the atlas as an image file with the specified format. Layout data must be stored separately.
-json <filename.json>
Writes the atlas's layout data, as well as other metrics into a structured JSON file.
-csv <filename.csv>
Writes the layout data of the glyphs into a simple CSV file.
-arfont <filename.arfont>
Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat.
-shadronpreview <filename.shadron> <sample text>
Generates a Shadron script that uses the generated atlas to draw a sample text as a preview.
GLYPH CONFIGURATION
-size <EM size>
Specifies the size of the glyphs in the atlas bitmap in pixels per EM.
-minsize <EM size>
Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used.
-emrange <EM range>
Specifies the SDF distance range in EM's.
-pxrange <pixel range>
Specifies the SDF distance range in output pixels. The default value is 2.
-nokerning
Disables inclusion of kerning pair table in output files.
DISTANCE FIELD GENERATOR SETTINGS
-angle <angle>
Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only)
-coloringstrategy <simple / inktrap / distance>
Selects the strategy of the edge coloring heuristic.
-errorcorrection <mode>
Changes the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.
-errordeviationratio <ratio>
Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error.
-errorimproveratio <ratio>
Sets the minimum ratio between the pre-correction distance error and the post-correction distance error.
-miterlimit <value>
Sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners. (psdf / msdf / mtsdf only))"
#ifdef MSDFGEN_USE_SKIA
R"(
-overlap
Switches to distance field generator with support for overlapping contours.
-nopreprocess
Disables path preprocessing which resolves self-intersections and overlapping contours.
-scanline
Performs an additional scanline pass to fix the signs of the distances.)"
#else
R"(
-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.)"
#endif
R"(
-seed <N>
Sets the initial seed for the edge coloring heuristic.
-threads <N>
Sets the number of threads for the parallel computation. (0 = auto)
)";
static const char *errorCorrectionHelpText = R"(
ERROR CORRECTION MODES
auto-fast
Detects inversion artifacts and distance errors that do not affect edges by range testing.
auto-full
Detects inversion artifacts and distance errors that do not affect edges by exact distance evaluation.
auto-mixed (default)
Detects inversions by distance evaluation and distance errors that do not affect edges by range testing.
disabled
Disables error correction.
distance-fast
Detects distance errors by range testing. Does not care if edges and corners are affected.
distance-full
Detects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow.
edge-fast
Detects inversion artifacts only by range testing.
edge-full
Detects inversion artifacts only by exact distance evaluation.
help
Displays this help.
)";
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;
}
struct FontInput {
const char *fontFilename;
GlyphIdentifierType glyphIdentifierType;
const char *charsetFilename;
double fontScale;
const char *fontName;
};
struct Configuration {
ImageType imageType;
ImageFormat imageFormat;
YDirection yDirection;
int width, height;
double emSize;
double pxRange;
double angleThreshold;
double miterLimit;
void (*edgeColoring)(msdfgen::Shape &, double, unsigned long long);
bool expensiveColoring;
unsigned long long coloringSeed;
GeneratorAttributes generatorAttributes;
bool preprocessGeometry;
bool kerning;
int threadCount;
const char *arteryFontFilename;
const char *imageFilename;
const char *jsonFilename;
const char *csvFilename;
const char *shadronPreviewFilename;
const char *shadronPreviewText;
};
template <typename T, typename S, int N, GeneratorFunction<S, N> GEN_FN>
static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, const std::vector<FontGeometry> &fonts, const Configuration &config) {
ImmediateAtlasGenerator<S, N, GEN_FN, BitmapAtlasStorage<T, N> > generator(config.width, config.height);
generator.setAttributes(config.generatorAttributes);
generator.setThreadCount(config.threadCount);
generator.generate(glyphs.data(), glyphs.size());
msdfgen::BitmapConstRef<T, N> bitmap = (msdfgen::BitmapConstRef<T, N>) generator.atlasStorage();
bool success = true;
if (config.imageFilename) {
if (saveImage(bitmap, config.imageFormat, config.imageFilename, config.yDirection))
puts("Atlas image file saved.");
else {
success = false;
puts("Failed to save the atlas as an image file.");
}
}
if (config.arteryFontFilename) {
ArteryFontExportProperties arfontProps;
arfontProps.fontSize = config.emSize;
arfontProps.pxRange = config.pxRange;
arfontProps.imageType = config.imageType;
arfontProps.imageFormat = config.imageFormat;
arfontProps.yDirection = config.yDirection;
if (exportArteryFont<float>(fonts.data(), fonts.size(), bitmap, config.arteryFontFilename, arfontProps))
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;
std::vector<FontInput> fontInputs;
FontInput fontInput = { };
Configuration config = { };
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
fontInput.fontScale = -1;
config.imageType = ImageType::MSDF;
config.imageFormat = ImageFormat::UNSPECIFIED;
config.yDirection = YDirection::BOTTOM_UP;
config.edgeColoring = msdfgen::edgeColoringInkTrap;
config.kerning = true;
const char *imageFormatName = nullptr;
int fixedWidth = -1, fixedHeight = -1;
config.preprocessGeometry = (
#ifdef MSDFGEN_USE_SKIA
true
#else
false
#endif
);
config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry;
config.generatorAttributes.scanlinePass = !config.preprocessGeometry;
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 = 0;
// Parse command line
int argPos = 1;
bool suggestHelp = false;
bool explicitErrorCorrectionMode = 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) {
fontInput.fontFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-charset", 1) {
fontInput.charsetFilename = argv[++argPos];
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
++argPos;
continue;
}
ARG_CASE("-glyphset", 1) {
fontInput.charsetFilename = argv[++argPos];
fontInput.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
++argPos;
continue;
}
ARG_CASE("-fontscale", 1) {
double fs;
if (!(parseDouble(fs, argv[++argPos]) && fs > 0))
ABORT("Invalid font scale argument. Use -fontscale <font scale> with a positive real number.");
fontInput.fontScale = fs;
++argPos;
continue;
}
ARG_CASE("-fontname", 1) {
fontInput.fontName = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-and", 0) {
if (!fontInput.fontFilename && !fontInput.charsetFilename && fontInput.fontScale < 0)
ABORT("No font, character set, or font scale specified before -and separator.");
if (!fontInputs.empty() && !memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
ABORT("No changes between subsequent inputs. A different font, character set, or font scale must be set inbetween -and separators.");
fontInputs.push_back(fontInput);
fontInput.fontName = nullptr;
++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 <width> <height> 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("-yorigin", 1) {
arg = argv[++argPos];
if (!strcmp(arg, "bottom"))
config.yDirection = YDirection::BOTTOM_UP;
else if (!strcmp(arg, "top"))
config.yDirection = YDirection::TOP_DOWN;
else
ABORT("Invalid Y-axis origin. Use bottom or top.");
++argPos;
continue;
}
ARG_CASE("-size", 1) {
double s;
if (!(parseDouble(s, argv[++argPos]) && s > 0))
ABORT("Invalid EM size argument. Use -size <EM 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 <EM size> 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 <EM range> 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 <pixel range> 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 <min 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) {
msdfgen::ErrorCorrectionConfig &ec = config.generatorAttributes.config.errorCorrection;
if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) {
ec.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) {
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE;
} else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) {
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) {
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) {
ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) {
ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "edge-fast")) {
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) {
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY;
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
} else if (!strcmp(argv[argPos+1], "help")) {
puts(errorCorrectionHelpText);
return 0;
} else
ABORT("Unknown error correction mode. Use -errorcorrection help for more information.");
explicitErrorCorrectionMode = true;
argPos += 2;
continue;
}
ARG_CASE("-errordeviationratio", 1) {
double edr;
if (!(parseDouble(edr, argv[argPos+1]) && edr > 0))
ABORT("Invalid error deviation ratio. Use -errordeviationratio <ratio> with a positive real number.");
config.generatorAttributes.config.errorCorrection.minDeviationRatio = edr;
argPos += 2;
continue;
}
ARG_CASE("-errorimproveratio", 1) {
double eir;
if (!(parseDouble(eir, argv[argPos+1]) && eir > 0))
ABORT("Invalid error improvement ratio. Use -errorimproveratio <ratio> with a positive real number.");
config.generatorAttributes.config.errorCorrection.minImproveRatio = eir;
argPos += 2;
continue;
}
ARG_CASE("-coloringstrategy", 1) {
if (!strcmp(argv[argPos+1], "simple")) config.edgeColoring = msdfgen::edgeColoringSimple, config.expensiveColoring = false;
else if (!strcmp(argv[argPos+1], "inktrap")) config.edgeColoring = msdfgen::edgeColoringInkTrap, config.expensiveColoring = false;
else if (!strcmp(argv[argPos+1], "distance")) config.edgeColoring = msdfgen::edgeColoringByDistance, config.expensiveColoring = true;
else
puts("Unknown coloring strategy specified.");
argPos += 2;
continue;
}
ARG_CASE("-miterlimit", 1) {
double m;
if (!(parseDouble(m, argv[++argPos]) && m >= 0))
ABORT("Invalid miter limit argument. Use -miterlimit <limit> with a positive real number.");
config.miterLimit = m;
++argPos;
continue;
}
ARG_CASE("-nokerning", 0) {
config.kerning = false;
++argPos;
continue;
}
ARG_CASE("-kerning", 0) {
config.kerning = true;
++argPos;
continue;
}
ARG_CASE("-nopreprocess", 0) {
config.preprocessGeometry = false;
++argPos;
continue;
}
ARG_CASE("-preprocess", 0) {
config.preprocessGeometry = true;
++argPos;
continue;
}
ARG_CASE("-nooverlap", 0) {
config.generatorAttributes.config.overlapSupport = false;
++argPos;
continue;
}
ARG_CASE("-overlap", 0) {
config.generatorAttributes.config.overlapSupport = true;
++argPos;
continue;
}
ARG_CASE("-noscanline", 0) {
config.generatorAttributes.scanlinePass = false;
++argPos;
continue;
}
ARG_CASE("-scanline", 0) {
config.generatorAttributes.scanlinePass = true;
++argPos;
continue;
}
ARG_CASE("-seed", 1) {
if (!parseUnsignedLL(config.coloringSeed, argv[argPos+1]))
ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
argPos += 2;
continue;
}
ARG_CASE("-threads", 1) {
unsigned tc;
if (!parseUnsigned(tc, argv[argPos+1]) || (int) tc < 0)
ABORT("Invalid thread count. Use -threads <N> with N being a non-negative integer.");
config.threadCount = (int) tc;
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 <filename.ttf/otf> -charset <charset> <output specification> <options>\n"
"Use -help for more information.\n"
);
return 0;
}
if (!fontInput.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);
// Finalize font inputs
const FontInput *nextFontInput = &fontInput;
for (std::vector<FontInput>::reverse_iterator it = fontInputs.rbegin(); it != fontInputs.rend(); ++it) {
if (!it->fontFilename && nextFontInput->fontFilename)
it->fontFilename = nextFontInput->fontFilename;
if (!it->charsetFilename && nextFontInput->charsetFilename) {
it->charsetFilename = nextFontInput->charsetFilename;
it->glyphIdentifierType = nextFontInput->glyphIdentifierType;
}
if (it->fontScale < 0 && nextFontInput->fontScale >= 0)
it->fontScale = nextFontInput->fontScale;
nextFontInput = &*it;
}
if (fontInputs.empty() || memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
fontInputs.push_back(fontInput);
// 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 = MSDF_ATLAS_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;
}
if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename))
config.kerning = false;
if (config.threadCount <= 0)
config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1);
if (config.generatorAttributes.scanlinePass) {
if (explicitErrorCorrectionMode && config.generatorAttributes.config.errorCorrection.distanceCheckMode != msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) {
const char *fallbackModeName = "unknown";
switch (config.generatorAttributes.config.errorCorrection.mode) {
case msdfgen::ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break;
case msdfgen::ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break;
case msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break;
case msdfgen::ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break;
}
printf("Selected error correction mode not compatible with scanline mode, falling back to %s.\n", fallbackModeName);
}
config.generatorAttributes.config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
}
// 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
bool floatingPointFormat = (
config.imageFormat == ImageFormat::TIFF ||
config.imageFormat == ImageFormat::TEXT_FLOAT ||
config.imageFormat == ImageFormat::BINARY_FLOAT ||
config.imageFormat == ImageFormat::BINARY_FLOAT_BE
);
// Load fonts
std::vector<GlyphGeometry> glyphs;
std::vector<FontGeometry> fonts;
bool anyCodepointsAvailable = false;
{
class FontHolder {
msdfgen::FreetypeHandle *ft;
msdfgen::FontHandle *font;
const char *fontFilename;
public:
FontHolder() : ft(msdfgen::initializeFreetype()), font(nullptr), fontFilename(nullptr) { }
~FontHolder() {
if (ft) {
if (font)
msdfgen::destroyFont(font);
msdfgen::deinitializeFreetype(ft);
}
}
bool load(const char *fontFilename) {
if (ft && fontFilename) {
if (this->fontFilename && !strcmp(this->fontFilename, fontFilename))
return true;
if (font)
msdfgen::destroyFont(font);
if ((font = msdfgen::loadFont(ft, fontFilename))) {
this->fontFilename = fontFilename;
return true;
}
this->fontFilename = nullptr;
}
return false;
}
operator msdfgen::FontHandle *() const {
return font;
}
} font;
for (FontInput &fontInput : fontInputs) {
if (!font.load(fontInput.fontFilename))
ABORT("Failed to load specified font file.");
if (fontInput.fontScale <= 0)
fontInput.fontScale = 1;
// Load character set
Charset charset;
if (fontInput.charsetFilename) {
if (!charset.load(fontInput.charsetFilename, fontInput.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT))
ABORT(fontInput.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification.");
} else {
charset = Charset::ASCII;
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
}
// Load glyphs
FontGeometry fontGeometry(&glyphs);
int glyphsLoaded = -1;
switch (fontInput.glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
glyphsLoaded = fontGeometry.loadGlyphset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
glyphsLoaded = fontGeometry.loadCharset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
anyCodepointsAvailable |= glyphsLoaded > 0;
break;
}
if (glyphsLoaded < 0)
ABORT("Failed to load glyphs from font.");
printf("Loaded geometry of %d out of %d glyphs", glyphsLoaded, (int) charset.size());
if (fontInputs.size() > 1)
printf(" from font \"%s\"", fontInput.fontFilename);
printf(".\n");
// List missing glyphs
if (glyphsLoaded < (int) charset.size()) {
printf("Missing %d %s", (int) charset.size()-glyphsLoaded, fontInput.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT ? "codepoints" : "glyphs");
bool first = true;
switch (fontInput.glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
for (unicode_t cp : charset)
if (!fontGeometry.getGlyph(msdfgen::GlyphIndex(cp)))
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (unicode_t cp : charset)
if (!fontGeometry.getGlyph(cp))
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
break;
}
printf("\n");
}
if (fontInput.fontName)
fontGeometry.setName(fontInput.fontName);
fonts.push_back((FontGeometry &&) fontGeometry);
}
}
if (glyphs.empty())
ABORT("No glyphs loaded.");
// Determine final atlas dimensions, scale and range, pack glyphs
{
double unitRange = 0, pxRange = 0;
switch (rangeMode) {
case RANGE_EM:
unitRange = rangeValue;
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);
else
atlasPacker.setMinimumScale(minEmSize);
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();
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) {
if (config.expensiveColoring) {
Workload([&glyphs, &config](int i, int threadNo) -> bool {
unsigned long long glyphSeed = (LCG_MULTIPLIER*(config.coloringSeed^i)+LCG_INCREMENT)*!!config.coloringSeed;
glyphs[i].edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed);
return true;
}, glyphs.size()).finish(config.threadCount);
} else {
unsigned long long glyphSeed = config.coloringSeed;
for (GlyphGeometry &glyph : glyphs) {
glyphSeed *= LCG_MULTIPLIER;
glyph.edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed);
}
}
}
bool success = false;
switch (config.imageType) {
case ImageType::HARD_MASK:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, scanlineGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, scanlineGenerator>(glyphs, fonts, config);
break;
case ImageType::SOFT_MASK:
case ImageType::SDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, sdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, sdfGenerator>(glyphs, fonts, config);
break;
case ImageType::PSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, psdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, psdfGenerator>(glyphs, fonts, config);
break;
case ImageType::MSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 3, msdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 3, msdfGenerator>(glyphs, fonts, config);
break;
case ImageType::MTSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 4, mtsdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 4, mtsdfGenerator>(glyphs, fonts, config);
break;
}
if (!success)
result = 1;
}
if (config.csvFilename) {
if (exportCSV(fonts.data(), fonts.size(), config.width, config.height, config.yDirection, config.csvFilename))
puts("Glyph layout written into CSV file.");
else {
result = 1;
puts("Failed to write CSV output file.");
}
}
if (config.jsonFilename) {
if (exportJSON(fonts.data(), fonts.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.yDirection, config.jsonFilename, config.kerning))
puts("Glyph layout and metadata written into JSON file.");
else {
result = 1;
puts("Failed to write JSON output file.");
}
}
if (config.shadronPreviewFilename && config.shadronPreviewText) {
if (anyCodepointsAvailable) {
std::vector<unicode_t> previewText;
utf8Decode(previewText, config.shadronPreviewText);
previewText.push_back(0);
if (generateShadronPreview(fonts.data(), fonts.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, floatingPointFormat, config.shadronPreviewFilename))
puts("Shadron preview script generated.");
else {
result = 1;
puts("Failed to generate Shadron preview file.");
}
} else {
result = 1;
puts("Shadron preview not supported in -glyphset mode.");
}
}
return result;
}
#endif

View File

@ -0,0 +1,41 @@
#pragma once
/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.2 (2021-05-29)
* ---------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020 - 2021
*
* Generates compact bitmap font atlases using MSDFGEN.
*
*/
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "utf8.h"
#include "Rectangle.h"
#include "Charset.h"
#include "GlyphBox.h"
#include "GlyphGeometry.h"
#include "FontGeometry.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 "csv-export.h"
#include "json-export.h"
#include "shadron-preview-generator.h"
#define MSDF_ATLAS_VERSION "1.2"

View File

@ -0,0 +1,19 @@
#pragma once
#include <utility>
#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 <typename RectangleType>
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 <class SizeSelector, typename RectangleType>
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int padding = 0);
}
#include "rectangle-packing.hpp"

View File

@ -0,0 +1,61 @@
#include "rectangle-packing.h"
#include <vector>
#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 <typename RectangleType>
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 <class SizeSelector, typename RectangleType>
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int padding) {
std::vector<RectangleType> 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<int, int> 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;
}
}

View File

@ -0,0 +1,155 @@
#include "shadron-preview-generator.h"
#include <string>
#include <algorithm>
namespace msdf_atlas {
static const char * const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) {
float fill = texture((ATLAS), texCoord).r;
return vec4(vec3(COLOR), fill);
}
)";
static const char * const shadronFillGlyphSdf = R"(
template <ATLAS, RANGE, COLOR>
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 <median>
glsl struct GlyphVertex {
vec2 coord;
vec2 texCoord;
};
template <TEXT_SIZE>
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<TEXT_SIZE>, triangles, VERTEX_LIST), \
fragment(fillGlyph<ATLAS, RANGE, COLOR>), \
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(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
if (fontCount <= 0)
return false;
double texelWidth = 1./atlasWidth;
double texelHeight = 1./atlasHeight;
bool anyGlyphs = false;
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, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear");
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight);
{
msdfgen::FontMetrics fontMetrics = fonts->getMetrics();
for (int i = 1; i < fontCount; ++i) {
fontMetrics.lineHeight = std::max(fontMetrics.lineHeight, fonts[i].getMetrics().lineHeight);
fontMetrics.ascenderY = std::max(fontMetrics.ascenderY, fonts[i].getMetrics().ascenderY);
fontMetrics.descenderY = std::min(fontMetrics.descenderY, fonts[i].getMetrics().descenderY);
}
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 < fontCount; ++i) {
const GlyphGeometry *glyph = fonts[i].getGlyph(*cp);
if (glyph) {
if (!glyph->isWhitespace()) {
double pl, pb, pr, pt;
double il, ib, ir, it;
glyph->getQuadPlaneBounds(pl, pb, pr, pt);
glyph->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
);
}
double advance = glyph->getAdvance();
fonts[i].getAdvance(advance, cp[0], cp[1]);
x += fsScale*advance;
anyGlyphs = true;
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 anyGlyphs;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/// Generates a Shadron script that displays a string using the generated atlas
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
}

View File

@ -0,0 +1,90 @@
#include "size-selectors.h"
#include <cmath>
namespace msdf_atlas {
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE>::SquareSizeSelector(int minArea) : lowerBound(0), upperBound(-1) {
if (minArea > 0)
lowerBound = int(sqrt(minArea-1))/MULTIPLE+1;
updateCurrent();
}
template <int MULTIPLE>
void SquareSizeSelector<MULTIPLE>::updateCurrent() {
if (upperBound < 0)
current = 5*lowerBound/4+16/MULTIPLE;
else
current = lowerBound+(upperBound-lowerBound)/2;
}
template <int MULTIPLE>
bool SquareSizeSelector<MULTIPLE>::operator()(int &width, int &height) const {
width = MULTIPLE*current, height = MULTIPLE*current;
return lowerBound < upperBound || upperBound < 0;
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::operator++() {
lowerBound = current+1;
updateCurrent();
return *this;
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::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;
}
}

View File

@ -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 <int MULTIPLE = 1>
class SquareSizeSelector {
public:
explicit SquareSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
SquareSizeSelector<MULTIPLE> & operator++();
SquareSizeSelector<MULTIPLE> & 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;
};
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
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
};
/// Glyph identification
enum class GlyphIdentifierType {
GLYPH_INDEX,
UNICODE_CODEPOINT
};
/// Direction of the Y-axis
enum class YDirection {
BOTTOM_UP,
TOP_DOWN
};
}

View File

@ -0,0 +1,38 @@
#include "utf8.h"
namespace msdf_atlas {
void utf8Decode(std::vector<unicode_t> &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; ((unsigned char) *c<<block)&0x40 && block < 4; ++block);
if (block < 4) {
cp = (*c&(0x3f>>block))<<(6*block);
rBytes = block;
} else
continue; // error
} else
continue; // error
if (!rBytes) {
if (!(start && cp == 0xfeff)) // BOM
codepoints.push_back(cp);
start = false;
}
}
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include "types.h"
namespace msdf_atlas {
/// Decodes the UTF-8 string into an array of Unicode codepoints
void utf8Decode(std::vector<unicode_t> &codepoints, const char *utf8String);
}

View File

@ -0,0 +1,82 @@
## Version 1.9 (2021-05-28)
- Error correction of multi-channel distance fields has been completely reworked
- Added new edge coloring strategy that optimizes colors based on distances between edges
- Added some minor functions for the library API
- Minor code refactor and optimizations
## Version 1.8 (2020-10-17)
- Integrated the Skia library into the project, which is used to preprocess the shape geometry and eliminate any self-intersections and other irregularities previously unsupported by the software
- The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively.
- The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA`
- Significantly improved performance of the core algorithm by reusing results from previously computed pixels
- Introduced an additional error correction routine which eliminates MSDF artifacts by analytically predicting results of bilinear interpolation
- Added the possibility to load font glyphs by their index rather than a Unicode value (use the prefix `g` before the character code in `-font` argument)
- Added `-distanceshift` argument that can be used to adjust the center of the distance range in the output distance field
- Fixed several errors in the evaluation of curve distances
- Fixed an issue with paths containing convergent corners (those whose inner angle is zero)
- The algorithm for pseudo-distance computation slightly changed, fixing certain rare edge cases and improving consistency
- Added the ability to supply own `FT_Face` handle to the msdfgen library
- Minor refactor of the core algorithm
### Version 1.7.1 (2020-03-09)
- Fixed an edge case bug in scanline rasterization
## Version 1.7 (2020-03-07)
- Added `mtsdf` mode - a combination of `msdf` with `sdf` in the alpha channel
- Distance fields can now be stored as uncompressed TIFF image files with floating point precision
- Bitmap class refactor - template argument split into data type and number of channels, bitmap reference classes introduced
- Added a secondary "ink trap" edge coloring heuristic, can be selected using `-coloringstrategy inktrap`
- Added computation of estimated rendering error for a given SDF
- Added computation of bounding box that includes sharp mitered corners
- The API for bounds computation of the `Shape` class changed for clarity
- Fixed several edge case bugs
## Version 1.6 (2019-04-08)
- Core algorithm rewritten to split up advanced edge selection logic into modular template arguments.
- Pseudo-distance evaluation reworked to eliminate discontinuities at the midpoint between edges.
- MSDF error correction reworked to also fix distances away from edges and consider diagonal pairs. Code simplified.
- Added scanline rasterization support for `Shape`.
- Added a scanline pass in the standalone version, which corrects the signs in the distance field according to the selected fill rule (`-fillrule`). Can be disabled using `-noscanline`.
- Fixed autoframe scaling, which previously caused the output to have unnecessary empty border.
- `-guessorder` switch no longer enabled by default, as the functionality is now provided by the scanline pass.
- Updated FreeType and other libraries, changed to static linkage
- Added 64-bit and static library builds to the Visual Studio solution
## Version 1.5 (2017-07-23)
- Fixed rounding error in cubic curve splitting.
- SVG parser fixes and support for additional path commands.
- Added CMake build script.
## Version 1.4 (2017-02-09)
- Reworked contour combining logic to support overlapping contours. Original algorithm preserved in functions with `_legacy` suffix, which are invoked by the new `-legacy` switch.
- Fixed a severe bug in cubic curve distance computation, where a control point lies at the endpoint.
- Standalone version now automatically detects if the input has the wrong orientation and adjusts the distance field accordingly. Can be disabled by `-keeporder` or `-reverseorder` switch.
- SVG parser fixes and improvements.
## Version 1.3 (2016-12-07)
- Fixed `-reverseorder` switch.
- Fixed glyph loading to use the proper method of acquiring outlines from FreeType.
## Version 1.2 (2016-07-20)
- Added option to specify that shape vertices are listed in reverse order (`-reverseorder`).
- Added option to set a seed for the edge coloring heuristic (-seed \<n\>), which can be used to adjust the output.
- Fixed parsing of glyph contours that start with a curve control point.
## Version 1.1 (2016-05-08)
- Switched to MIT license due to popular demand.
- Fixed SDF rendering anti-aliasing when the output is smaller than the distance field.
## Version 1.0 (2016-04-28)
- Project published.

View File

@ -0,0 +1,112 @@
# Freetype 库
project(freetype LANGUAGES C)
set(FREETYPE_SOURCES
"freetype/src/autofit/autofit.c"
"freetype/src/base/ftbase.c"
"freetype/src/base/ftbbox.c"
"freetype/src/base/ftbdf.c"
"freetype/src/base/ftbitmap.c"
"freetype/src/base/ftcid.c"
"freetype/src/base/ftdebug.c"
"freetype/src/base/ftfstype.c"
"freetype/src/base/ftgasp.c"
"freetype/src/base/ftglyph.c"
"freetype/src/base/ftgxval.c"
"freetype/src/base/ftinit.c"
"freetype/src/base/ftmm.c"
"freetype/src/base/ftotval.c"
"freetype/src/base/ftpatent.c"
"freetype/src/base/ftpfr.c"
"freetype/src/base/ftstroke.c"
"freetype/src/base/ftsynth.c"
"freetype/src/base/ftsystem.c"
"freetype/src/base/fttype1.c"
"freetype/src/base/ftwinfnt.c"
"freetype/src/bdf/bdf.c"
"freetype/src/bzip2/ftbzip2.c"
"freetype/src/cache/ftcache.c"
"freetype/src/cff/cff.c"
"freetype/src/cid/type1cid.c"
"freetype/src/gzip/ftgzip.c"
"freetype/src/lzw/ftlzw.c"
"freetype/src/pcf/pcf.c"
"freetype/src/pfr/pfr.c"
"freetype/src/psaux/psaux.c"
"freetype/src/pshinter/pshinter.c"
"freetype/src/psnames/psnames.c"
"freetype/src/raster/raster.c"
"freetype/src/sdf/sdf.c"
"freetype/src/sfnt/sfnt.c"
"freetype/src/smooth/smooth.c"
"freetype/src/truetype/truetype.c"
"freetype/src/type1/type1.c"
"freetype/src/type42/type42.c"
"freetype/src/winfonts/winfnt.c"
)
# 创建 freetype 静态库
add_library(freetype STATIC ${FREETYPE_SOURCES})
# 设置包含目录
target_include_directories(freetype PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/freetype/include>
$<INSTALL_INTERFACE:include>
./
)
# 添加编译定义
target_compile_definitions(freetype PRIVATE
FT2_BUILD_LIBRARY
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_WARNINGS
)
# MSDFGen 库
project(msdfgen LANGUAGES CXX)
# 收集源文件
file(GLOB_RECURSE MSDFGEN_CORE_SOURCES
"core/*.cpp"
"core/*.c"
)
file(GLOB_RECURSE MSDFGEN_EXT_SOURCES
"ext/*.cpp"
"ext/*.c"
)
file(GLOB_RECURSE MSDFGEN_LIB_SOURCES
"lib/*.cpp"
"lib/*.c"
)
# 创建 msdfgen 静态库
add_library(msdfgen STATIC
${MSDFGEN_CORE_SOURCES}
${MSDFGEN_EXT_SOURCES}
${MSDFGEN_LIB_SOURCES}
)
# 设置包含目录
target_include_directories(msdfgen PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ext>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/freetype/include>
$<INSTALL_INTERFACE:include>
)
# 添加编译定义
target_compile_definitions(msdfgen PRIVATE
MSDFGEN_USE_CPP11
)
# 链接依赖
target_link_libraries(msdfgen PUBLIC freetype)
# 设置目标属性
set_target_properties(msdfgen PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 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.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,209 @@
# Multi-channel signed distance field generator
This is a utility for generating signed distance fields from vector shapes and font glyphs,
which serve as a texture representation that can be used in real-time graphics to efficiently reproduce said shapes.
Although it can also be used to generate conventional signed distance fields best known from
[this Valve paper](https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf)
and pseudo-distance fields, its primary purpose is to generate multi-channel distance fields,
using a method I have developed. Unlike monochrome distance fields, they have the ability
to reproduce sharp corners almost perfectly by utilizing all three color channels.
The following comparison demonstrates the improvement in image quality.
![demo-msdf16](https://user-images.githubusercontent.com/18639794/106391899-e37ebe80-63ef-11eb-988b-4764004bb196.png)
![demo-sdf16](https://user-images.githubusercontent.com/18639794/106391905-e679af00-63ef-11eb-96c3-993176330911.png)
![demo-sdf32](https://user-images.githubusercontent.com/18639794/106391906-e7aadc00-63ef-11eb-8f84-d402d0dd9174.png)
- To learn more about this method, you can read my [Master's thesis](https://github.com/Chlumsky/msdfgen/files/3050967/thesis.pdf).
- Check out my [MSDF-Atlas-Gen](https://github.com/Chlumsky/msdf-atlas-gen) if you want to generate entire glyph atlases for text rendering.
- See what's new in the [changelog](CHANGELOG.md).
## Getting started
The project can be used either as a library or as a console program. It is divided into two parts, **[core](core)**
and **[extensions](ext)**. The core module has no dependencies and only uses bare C++. It contains all
key data structures and algorithms, which can be accessed through the [msdfgen.h](msdfgen.h) header.
Extensions contain utilities for loading fonts and SVG files, as well as saving PNG images.
Those are exposed by the [msdfgen-ext.h](msdfgen-ext.h) header. This module uses
[FreeType](http://www.freetype.org/),
[TinyXML2](http://www.grinninglizard.com/tinyxml2/),
[LodePNG](http://lodev.org/lodepng/),
and (optionally) [Skia](https://skia.org/).
Additionally, there is the [main.cpp](main.cpp), which wraps the functionality into
a comprehensive standalone console program. To start using the program immediately,
there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdfgen/releases).
To build the project, you may use the included [Visual Studio solution](Msdfgen.sln)
or [CMake script](CMakeLists.txt).
## Console commands
The standalone program is executed as
```
msdfgen.exe <mode> <input> <options>
```
where only the input specification is required.
Mode can be one of:
- **sdf** &ndash; generates a conventional monochrome (true) signed distance field.
- **psdf** &ndash; generates a monochrome signed pseudo-distance field.
- **msdf** (default) &ndash; generates a multi-channel signed distance field using my new method.
- **mtsdf** &ndash; generates a combined multi-channel and true signed distance field in the alpha channel.
The input can be specified as one of:
- **-font \<filename.ttf\> \<character code\>** &ndash; to load a glyph from a font file.
Character code can be expressed as either a decimal (63) or hexadecimal (0x3F) Unicode value, or an ASCII character
in single quotes ('?').
- **-svg \<filename.svg\>** &ndash; to load an SVG file. Note that only the last vector path in the file will be used.
- **-shapedesc \<filename.txt\>**, -defineshape \<definition\>, -stdin &ndash; to load a text description of the shape
from either a file, the next argument, or the standard input, respectively. Its syntax is documented further down.
The complete list of available options can be printed with **-help**.
Some of the important ones are:
- **-o \<filename\>** &ndash; specifies the output file name. The desired format will be deduced from the extension
(png, bmp, tif, txt, bin). Otherwise, use -format.
- **-size \<width\> \<height\>** &ndash; specifies the dimensions of the output distance field (in pixels).
- **-range \<range\>**, **-pxrange \<range\>** &ndash; specifies the width of the range around the shape
between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly.
- **-scale \<scale\>** &ndash; sets the scale used to convert shape units to distance field pixels.
- **-translate \<x\> \<y\>** &ndash; sets the translation of the shape in shape units. Otherwise the origin (0, 0)
lies in the bottom left corner.
- **-autoframe** &ndash; automatically frames the shape to fit the distance field. If the output must be precisely aligned,
you should manually position it using -translate and -scale instead.
- **-angle \<angle\>** &ndash; specifies the maximum angle to be considered a corner.
Can be expressed in radians (3.0) or degrees with D at the end (171.9D).
- **-testrender \<filename.png\> \<width\> \<height\>** - tests the generated distance field by using it to render an image
of the original shape into a PNG file with the specified dimensions. Alternatively, -testrendermulti renders
an image without combining the color channels, and may give you an insight in how the multi-channel distance field works.
- **-exportshape \<filename.txt\>** - saves the text description of the shape with edge coloring to the specified file.
This can be later edited and used as input through -shapedesc.
- **-printmetrics** &ndash; prints some useful information about the shape's layout.
For example,
```
msdfgen.exe msdf -font C:\Windows\Fonts\arialbd.ttf 'M' -o msdf.png -size 32 32 -pxrange 4 -autoframe -testrender render.png 1024 1024
```
will take the glyph capital M from the Arial Bold typeface, generate a 32&times;32 multi-channel distance field
with a 4 pixels wide distance range, store it into msdf.png, and create a test render of the glyph as render.png.
**Note:** Do not use `-autoframe` to generate character maps! It is intended as a quick preview only.
## Library API
If you choose to use this utility inside your own program, there are a few simple steps you need to perform
in order to generate a distance field. Please note that all classes and functions are in the `msdfgen` namespace.
- Acquire a `Shape` object. You can either load it via `loadGlyph` or `loadSvgShape`, or construct it manually.
It consists of closed contours, which in turn consist of edges. An edge is represented by a `LinearEdge`, `QuadraticEdge`,
or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points.
- Normalize the shape using its `normalize` method and assign colors to edges if you need a multi-channel SDF.
This can be performed automatically using the `edgeColoringSimple` heuristic, or manually by setting each edge's
`color` member. Keep in mind that at least two color channels must be turned on in each edge, and iff two edges meet
at a sharp corner, they must only have one channel in common.
- Call `generateSDF`, `generatePseudoSDF`, or `generateMSDF` to generate a distance field into a floating point
`Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, or `saveTiff`.
- You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit`
on the distance field beforehand to simulate the standard 8 bits/channel image format.
Example:
```c++
#include "msdfgen.h"
#include "msdfgen-ext.h"
using namespace msdfgen;
int main() {
FreetypeHandle *ft = initializeFreetype();
if (ft) {
FontHandle *font = loadFont(ft, "C:\\Windows\\Fonts\\arialbd.ttf");
if (font) {
Shape shape;
if (loadGlyph(shape, font, 'A')) {
shape.normalize();
// max. angle
edgeColoringSimple(shape, 3.0);
// image width, height
Bitmap<float, 3> msdf(32, 32);
// range, scale, translation
generateMSDF(msdf, shape, 4.0, 1.0, Vector2(4.0, 4.0));
savePng(msdf, "output.png");
}
destroyFont(font);
}
deinitializeFreetype(ft);
}
return 0;
}
```
## Using a multi-channel distance field
Using a multi-channel distance field generated by this program is similarly simple to how a monochrome distance field is used.
The only additional operation is computing the **median** of the three channels inside the fragment shader,
right after sampling the distance field. This signed distance value can then be used the same way as usual.
The following is an example GLSL fragment shader with anti-aliasing:
```glsl
in vec2 texCoord;
out vec4 color;
uniform sampler2D msdf;
uniform vec4 bgColor;
uniform vec4 fgColor;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main() {
vec3 msd = texture(msdf, texCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange()*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
color = mix(bgColor, fgColor, opacity);
}
```
Here, `screenPxRange()` represents the distance field range in output screen pixels. For example, if the pixel range was set to 2
when generating a 32x32 distance field, and it is used to draw a quad that is 72x72 pixels on the screen,
it should return 4.5 (because 72/32 * 2 = 4.5).
**For 2D rendering, this can generally be replaced by a precomputed uniform value.**
For rendering in a **3D perspective only**, where the texture scale varies across the screen,
you may want to implement this function with fragment derivatives in the following way.
I would suggest precomputing `unitRange` as a uniform variable instead of `pxRange` for better performance.
```glsl
uniform float pxRange; // set to distance field's pixel range
float screenPxRange() {
vec2 unitRange = vec2(pxRange)/vec2(textureSize(msdf, 0));
vec2 screenTexSize = vec2(1.0)/fwidth(texCoord);
return max(0.5*dot(unitRange, screenTexSize), 1.0);
}
```
`screenPxRange()` must never be lower than 1. If it is lower than 2, there is a high probability that the anti-aliasing will fail
and you may want to re-generate your distance field with a wider range.
## Shape description syntax
The text shape description has the following syntax.
- Each closed contour is enclosed by braces: `{ <contour 1> } { <contour 2> }`
- Each point (and control point) is written as two real numbers separated by a comma.
- Points in a contour are separated with semicolons.
- The last point of each contour must be equal to the first, or the symbol `#` can be used, which represents the first point.
- There can be an edge segment specification between any two points, also separated by semicolons.
This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses.
For example,
```
{ -1, -1; m; -1, +1; y; +1, +1; m; +1, -1; y; # }
```
would represent a square with magenta and yellow edges,
```
{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # }
```
is a teardrop shape formed by a single cubic Bézier curve.

View File

@ -0,0 +1,50 @@
#pragma once
#include "BitmapRef.hpp"
namespace msdfgen {
/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class.
template <typename T, int N = 1>
class Bitmap {
public:
Bitmap();
Bitmap(int width, int height);
Bitmap(const BitmapConstRef<T, N> &orig);
Bitmap(const Bitmap<T, N> &orig);
#ifdef MSDFGEN_USE_CPP11
Bitmap(Bitmap<T, N> &&orig);
#endif
~Bitmap();
Bitmap<T, N> & operator=(const BitmapConstRef<T, N> &orig);
Bitmap<T, N> & operator=(const Bitmap<T, N> &orig);
#ifdef MSDFGEN_USE_CPP11
Bitmap<T, N> & operator=(Bitmap<T, N> &&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<T, N>();
operator BitmapConstRef<T, N>() const;
private:
T *pixels;
int w, h;
};
}
#include "Bitmap.hpp"

View File

@ -0,0 +1,117 @@
#include "Bitmap.h"
#include <cstdlib>
#include <cstring>
namespace msdfgen {
template <typename T, int N>
Bitmap<T, N>::Bitmap() : pixels(NULL), w(0), h(0) { }
template <typename T, int N>
Bitmap<T, N>::Bitmap(int width, int height) : w(width), h(height) {
pixels = new T[N*w*h];
}
template <typename T, int N>
Bitmap<T, N>::Bitmap(const BitmapConstRef<T, N> &orig) : w(orig.width), h(orig.height) {
pixels = new T[N*w*h];
memcpy(pixels, orig.pixels, sizeof(T)*N*w*h);
}
template <typename T, int N>
Bitmap<T, N>::Bitmap(const Bitmap<T, N> &orig) : w(orig.w), h(orig.h) {
pixels = new T[N*w*h];
memcpy(pixels, orig.pixels, sizeof(T)*N*w*h);
}
#ifdef MSDFGEN_USE_CPP11
template <typename T, int N>
Bitmap<T, N>::Bitmap(Bitmap<T, N> &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) {
orig.pixels = NULL;
orig.w = 0, orig.h = 0;
}
#endif
template <typename T, int N>
Bitmap<T, N>::~Bitmap() {
delete [] pixels;
}
template <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(const BitmapConstRef<T, N> &orig) {
if (pixels != orig.pixels) {
delete [] pixels;
w = orig.width, h = orig.height;
pixels = new T[N*w*h];
memcpy(pixels, orig.pixels, sizeof(T)*N*w*h);
}
return *this;
}
template <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(const Bitmap<T, N> &orig) {
if (this != &orig) {
delete [] pixels;
w = orig.w, h = orig.h;
pixels = new T[N*w*h];
memcpy(pixels, orig.pixels, sizeof(T)*N*w*h);
}
return *this;
}
#ifdef MSDFGEN_USE_CPP11
template <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(Bitmap<T, N> &&orig) {
if (this != &orig) {
delete [] pixels;
pixels = orig.pixels;
w = orig.w, h = orig.h;
orig.pixels = NULL;
}
return *this;
}
#endif
template <typename T, int N>
int Bitmap<T, N>::width() const {
return w;
}
template <typename T, int N>
int Bitmap<T, N>::height() const {
return h;
}
template <typename T, int N>
T * Bitmap<T, N>::operator()(int x, int y) {
return pixels+N*(w*y+x);
}
template <typename T, int N>
const T * Bitmap<T, N>::operator()(int x, int y) const {
return pixels+N*(w*y+x);
}
template <typename T, int N>
Bitmap<T, N>::operator T *() {
return pixels;
}
template <typename T, int N>
Bitmap<T, N>::operator const T *() const {
return pixels;
}
template <typename T, int N>
Bitmap<T, N>::operator BitmapRef<T, N>() {
return BitmapRef<T, N>(pixels, w, h);
}
template <typename T, int N>
Bitmap<T, N>::operator BitmapConstRef<T, N>() const {
return BitmapConstRef<T, N>(pixels, w, h);
}
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <cstdlib>
namespace msdfgen {
typedef unsigned char byte;
/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object.
template <typename T, int N = 1>
struct BitmapRef {
T *pixels;
int width, height;
inline BitmapRef() : pixels(NULL), width(0), height(0) { }
inline BitmapRef(T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { }
inline T * operator()(int x, int y) const {
return pixels+N*(width*y+x);
}
};
/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object.
template <typename T, int N = 1>
struct BitmapConstRef {
const T *pixels;
int width, height;
inline BitmapConstRef() : pixels(NULL), width(0), height(0) { }
inline BitmapConstRef(const T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { }
inline BitmapConstRef(const BitmapRef<T, N> &orig) : pixels(orig.pixels), width(orig.width), height(orig.height) { }
inline const T * operator()(int x, int y) const {
return pixels+N*(width*y+x);
}
};
}

View File

@ -0,0 +1,90 @@
#include "Contour.h"
#include "arithmetics.hpp"
namespace msdfgen {
static double shoelace(const Point2 &a, const Point2 &b) {
return (b.x-a.x)*(a.y+b.y);
}
void Contour::addEdge(const EdgeHolder &edge) {
edges.push_back(edge);
}
#ifdef MSDFGEN_USE_CPP11
void Contour::addEdge(EdgeHolder &&edge) {
edges.push_back((EdgeHolder &&) edge);
}
#endif
EdgeHolder & Contour::addEdge() {
edges.resize(edges.size()+1);
return edges.back();
}
static void boundPoint(double &l, double &b, double &r, double &t, Point2 p) {
if (p.x < l) l = p.x;
if (p.y < b) b = p.y;
if (p.x > r) r = p.x;
if (p.y > t) t = p.y;
}
void Contour::bound(double &l, double &b, double &r, double &t) const {
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge)
(*edge)->bound(l, b, r, t);
}
void Contour::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const {
if (edges.empty())
return;
Vector2 prevDir = edges.back()->direction(1).normalize(true);
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) {
Vector2 dir = -(*edge)->direction(0).normalize(true);
if (polarity*crossProduct(prevDir, dir) >= 0) {
double miterLength = miterLimit;
double q = .5*(1-dotProduct(prevDir, dir));
if (q > 0)
miterLength = min(1/sqrt(q), miterLimit);
Point2 miter = (*edge)->point(0)+border*miterLength*(prevDir+dir).normalize(true);
boundPoint(l, b, r, t, miter);
}
prevDir = (*edge)->direction(1).normalize(true);
}
}
int Contour::winding() const {
if (edges.empty())
return 0;
double total = 0;
if (edges.size() == 1) {
Point2 a = edges[0]->point(0), b = edges[0]->point(1/3.), c = edges[0]->point(2/3.);
total += shoelace(a, b);
total += shoelace(b, c);
total += shoelace(c, a);
} else if (edges.size() == 2) {
Point2 a = edges[0]->point(0), b = edges[0]->point(.5), c = edges[1]->point(0), d = edges[1]->point(.5);
total += shoelace(a, b);
total += shoelace(b, c);
total += shoelace(c, d);
total += shoelace(d, a);
} else {
Point2 prev = edges.back()->point(0);
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) {
Point2 cur = (*edge)->point(0);
total += shoelace(prev, cur);
prev = cur;
}
}
return sign(total);
}
void Contour::reverse() {
for (int i = (int) edges.size()/2; i > 0; --i)
EdgeHolder::swap(edges[i-1], edges[edges.size()-i]);
for (std::vector<EdgeHolder>::iterator edge = edges.begin(); edge != edges.end(); ++edge)
(*edge)->reverse();
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <vector>
#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<EdgeHolder> 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 &l, double &b, double &r, double &t) const;
/// Adjusts the bounding box to fit the contour border's mitered corners.
void boundMiters(double &l, double &b, double &r, double &t, 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();
};
}

View File

@ -0,0 +1,18 @@
#pragma once
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
};
}

View File

@ -0,0 +1,77 @@
#include "EdgeHolder.h"
namespace msdfgen {
void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) {
EdgeSegment *tmp = a.edgeSegment;
a.edgeSegment = b.edgeSegment;
b.edgeSegment = tmp;
}
EdgeHolder::EdgeHolder() : edgeSegment(NULL) { }
EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { }
EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor) : edgeSegment(new LinearSegment(p0, p1, edgeColor)) { }
EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : edgeSegment(new QuadraticSegment(p0, p1, p2, edgeColor)) { }
EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : edgeSegment(new CubicSegment(p0, p1, p2, p3, edgeColor)) { }
EdgeHolder::EdgeHolder(const EdgeHolder &orig) : edgeSegment(orig.edgeSegment ? orig.edgeSegment->clone() : NULL) { }
#ifdef MSDFGEN_USE_CPP11
EdgeHolder::EdgeHolder(EdgeHolder &&orig) : edgeSegment(orig.edgeSegment) {
orig.edgeSegment = NULL;
}
#endif
EdgeHolder::~EdgeHolder() {
delete edgeSegment;
}
EdgeHolder & EdgeHolder::operator=(const EdgeHolder &orig) {
if (this != &orig) {
delete edgeSegment;
edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL;
}
return *this;
}
#ifdef MSDFGEN_USE_CPP11
EdgeHolder & EdgeHolder::operator=(EdgeHolder &&orig) {
if (this != &orig) {
delete edgeSegment;
edgeSegment = orig.edgeSegment;
orig.edgeSegment = NULL;
}
return *this;
}
#endif
EdgeSegment & EdgeHolder::operator*() {
return *edgeSegment;
}
const EdgeSegment & EdgeHolder::operator*() const {
return *edgeSegment;
}
EdgeSegment * EdgeHolder::operator->() {
return edgeSegment;
}
const EdgeSegment * EdgeHolder::operator->() const {
return edgeSegment;
}
EdgeHolder::operator EdgeSegment *() {
return edgeSegment;
}
EdgeHolder::operator const EdgeSegment *() const {
return edgeSegment;
}
}

View File

@ -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);
EdgeHolder();
EdgeHolder(EdgeSegment *segment);
EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE);
EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE);
EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE);
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;
};
}

View File

@ -0,0 +1,495 @@
#include "MSDFErrorCorrection.h"
#include <cstring>
#include "arithmetics.hpp"
#include "equation-solver.h"
#include "EdgeColor.h"
#include "bitmap-interpolation.hpp"
#include "edge-selectors.h"
#include "contour-combiners.h"
#include "ShapeDistanceFinder.h"
#include "generator-config.h"
namespace msdfgen {
#define ARTIFACT_T_EPSILON .01
#define PROTECTION_RADIUS_TOLERANCE 1.001
#define CLASSIFIER_FLAG_CANDIDATE 0x01
#define CLASSIFIER_FLAG_ARTIFACT 0x02
const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111;
const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111;
/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone.
class BaseArtifactClassifier {
public:
inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { }
/// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact.
inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const {
// For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries.
if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) {
double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span;
// Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b.
if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan))
return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT;
return CLASSIFIER_FLAG_CANDIDATE;
}
return 0;
}
/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
inline bool evaluate(double t, float m, int flags) const {
return (flags&2) != 0;
}
private:
double span;
bool protectedFlag;
};
/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost.
template <template <typename> class ContourCombiner, int N>
class ShapeDistanceChecker {
public:
class ArtifactClassifier : public BaseArtifactClassifier {
public:
inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { }
/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
inline bool evaluate(double t, float m, int flags) const {
if (flags&CLASSIFIER_FLAG_CANDIDATE) {
// Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier.
if (flags&CLASSIFIER_FLAG_ARTIFACT)
return true;
Vector2 tVector = t*direction;
float oldMSD[N], newMSD[3];
// Compute the color that would be currently interpolated at the artifact candidate's position.
Point2 sdfCoord = parent->sdfCoord+tVector;
interpolate(oldMSD, parent->sdf, sdfCoord);
// Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel.
double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y));
float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]);
newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0]));
newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1]));
newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2]));
// Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.
float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);
float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);
float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5);
// Compare the differences of the exact distance and the before and after distances.
return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));
}
return false;
}
private:
ShapeDistanceChecker *parent;
Vector2 direction;
};
Point2 shapeCoord, sdfCoord;
const float *msd;
bool protectedFlag;
inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) {
texelSize = projection.unprojectVector(Vector2(1));
}
inline ArtifactClassifier classifier(const Vector2 &direction, double span) {
return ArtifactClassifier(this, direction, span);
}
private:
ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder;
BitmapConstRef<float, N> sdf;
double invRange;
Vector2 texelSize;
double minImproveRatio;
};
MSDFErrorCorrection::MSDFErrorCorrection() { }
MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) {
invRange = 1/range;
minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;
minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;
memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
}
void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) {
this->minDeviationRatio = minDeviationRatio;
}
void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) {
this->minImproveRatio = minImproveRatio;
}
void MSDFErrorCorrection::protectCorners(const Shape &shape) {
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
if (!contour->edges.empty()) {
const EdgeSegment *prevEdge = contour->edges.back();
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
int commonColor = prevEdge->color&(*edge)->color;
// If the color changes from prevEdge to edge, this is a corner.
if (!(commonColor&(commonColor-1))) {
// Find the four texels that envelop the corner and mark them as protected.
Point2 p = projection.project((*edge)->point(0));
if (shape.inverseYAxis)
p.y = stencil.height-p.y;
int l = (int) floor(p.x-.5);
int b = (int) floor(p.y-.5);
int r = l+1;
int t = b+1;
// Check that the positions are within bounds.
if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) {
if (l >= 0 && b >= 0)
*stencil(l, b) |= (byte) PROTECTED;
if (r < stencil.width && b >= 0)
*stencil(r, b) |= (byte) PROTECTED;
if (l >= 0 && t < stencil.height)
*stencil(l, t) |= (byte) PROTECTED;
if (r < stencil.width && t < stencil.height)
*stencil(r, t) |= (byte) PROTECTED;
}
}
prevEdge = *edge;
}
}
}
/// Determines if the channel contributes to an edge between the two texels a, b.
static bool edgeBetweenTexelsChannel(const float *a, const float *b, int channel) {
// Find interpolation ratio t (0 < t < 1) where an edge is expected (mix(a[channel], b[channel], t) == 0.5).
double t = (a[channel]-.5)/(a[channel]-b[channel]);
if (t > 0 && t < 1) {
// Interpolate channel values at t.
float c[3] = {
mix(a[0], b[0], t),
mix(a[1], b[1], t),
mix(a[2], b[2], t)
};
// This is only an edge if the zero-distance channel is the median.
return median(c[0], c[1], c[2]) == c[channel];
}
return false;
}
/// Returns a bit mask of which channels contribute to an edge between the two texels a, b.
static int edgeBetweenTexels(const float *a, const float *b) {
return (
RED*edgeBetweenTexelsChannel(a, b, 0)+
GREEN*edgeBetweenTexelsChannel(a, b, 1)+
BLUE*edgeBetweenTexelsChannel(a, b, 2)
);
}
/// Marks texel as protected if one of its non-median channels is present in the channel mask.
static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) {
if (
(mask&RED && msd[0] != m) ||
(mask&GREEN && msd[1] != m) ||
(mask&BLUE && msd[2] != m)
)
*stencil |= (byte) MSDFErrorCorrection::PROTECTED;
}
template <int N>
void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
float radius;
// Horizontal texel pairs
radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length());
for (int y = 0; y < sdf.height; ++y) {
const float *left = sdf(0, y);
const float *right = sdf(1, y);
for (int x = 0; x < sdf.width-1; ++x) {
float lm = median(left[0], left[1], left[2]);
float rm = median(right[0], right[1], right[2]);
if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) {
int mask = edgeBetweenTexels(left, right);
protectExtremeChannels(stencil(x, y), left, lm, mask);
protectExtremeChannels(stencil(x+1, y), right, rm, mask);
}
left += N, right += N;
}
}
// Vertical texel pairs
radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length());
for (int y = 0; y < sdf.height-1; ++y) {
const float *bottom = sdf(0, y);
const float *top = sdf(0, y+1);
for (int x = 0; x < sdf.width; ++x) {
float bm = median(bottom[0], bottom[1], bottom[2]);
float tm = median(top[0], top[1], top[2]);
if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) {
int mask = edgeBetweenTexels(bottom, top);
protectExtremeChannels(stencil(x, y), bottom, bm, mask);
protectExtremeChannels(stencil(x, y+1), top, tm, mask);
}
bottom += N, top += N;
}
}
// Diagonal texel pairs
radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length());
for (int y = 0; y < sdf.height-1; ++y) {
const float *lb = sdf(0, y);
const float *rb = sdf(1, y);
const float *lt = sdf(0, y+1);
const float *rt = sdf(1, y+1);
for (int x = 0; x < sdf.width-1; ++x) {
float mlb = median(lb[0], lb[1], lb[2]);
float mrb = median(rb[0], rb[1], rb[2]);
float mlt = median(lt[0], lt[1], lt[2]);
float mrt = median(rt[0], rt[1], rt[2]);
if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) {
int mask = edgeBetweenTexels(lb, rt);
protectExtremeChannels(stencil(x, y), lb, mlb, mask);
protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask);
}
if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) {
int mask = edgeBetweenTexels(rb, lt);
protectExtremeChannels(stencil(x+1, y), rb, mrb, mask);
protectExtremeChannels(stencil(x, y+1), lt, mlt, mask);
}
lb += N, rb += N, lt += N, rt += N;
}
}
}
void MSDFErrorCorrection::protectAll() {
byte *end = stencil.pixels+stencil.width*stencil.height;
for (byte *mask = stencil.pixels; mask < end; ++mask)
*mask |= (byte) PROTECTED;
}
/// Returns the median of the linear interpolation of texels a, b at t.
static float interpolatedMedian(const float *a, const float *b, double t) {
return median(
mix(a[0], b[0], t),
mix(a[1], b[1], t),
mix(a[2], b[2], t)
);
}
/// Returns the median of the bilinear interpolation with the given constant, linear, and quadratic terms at t.
static float interpolatedMedian(const float *a, const float *l, const float *q, double t) {
return float(median(
t*(t*q[0]+l[0])+a[0],
t*(t*q[1]+l[1])+a[1],
t*(t*q[2]+l[2])+a[2]
));
}
/// Determines if the interpolated median xm is an artifact.
static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) {
return (
// For protected texels, only report an artifact if it would cause fill inversion (change between positive and negative distance).
(!isProtected || (am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f)) &&
// This is an artifact if the interpolated median is outside the range of possible values based on its distance from a, b.
!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)
);
}
/// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
template <class ArtifactClassifier>
static bool hasLinearArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float bm, const float *a, const float *b, float dA, float dB) {
// Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0).
double t = (double) dA/(dA-dB);
if (t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON) {
// Interpolate median at t and let the classifier decide if its value indicates an artifact.
float xm = interpolatedMedian(a, b, t);
return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm));
}
return false;
}
/// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
template <class ArtifactClassifier>
static bool hasDiagonalArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) {
// Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal.
double t[2];
int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA);
for (int i = 0; i < solutions; ++i) {
// Solutions t[i] == 0 and t[i] == 1 are singularities and occur very often because two channels are usually equal at texels.
if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) {
// Interpolate median xm at t.
float xm = interpolatedMedian(a, l, q, t[i]);
// Determine if xm deviates too much from medians of a, d.
int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm);
// Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.
double tEnd[2];
float em[2];
// tEx0
if (tEx0 > 0 && tEx0 < 1) {
tEnd[0] = 0, tEnd[1] = 1;
em[0] = am, em[1] = dm;
tEnd[tEx0 > t[i]] = tEx0;
em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0);
rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm);
}
// tEx1
if (tEx1 > 0 && tEx1 < 1) {
tEnd[0] = 0, tEnd[1] = 1;
em[0] = am, em[1] = dm;
tEnd[tEx1 > t[i]] = tEx1;
em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1);
rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm);
}
if (artifactClassifier.evaluate(t[i], xm, rangeFlags))
return true;
}
}
return false;
}
/// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b.
template <class ArtifactClassifier>
static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) {
float bm = median(b[0], b[1], b[2]);
return (
// Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
fabsf(am-.5f) >= fabsf(bm-.5f) && (
// Check points where each pair of color channels meets.
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) ||
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) ||
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2])
)
);
}
/// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal).
template <class ArtifactClassifier>
static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) {
float dm = median(d[0], d[1], d[2]);
// Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
if (fabsf(am-.5f) >= fabsf(dm-.5f)) {
float abc[3] = {
a[0]-b[0]-c[0],
a[1]-b[1]-c[1],
a[2]-b[2]-c[2]
};
// Compute the linear terms for bilinear interpolation.
float l[3] = {
-a[0]-abc[0],
-a[1]-abc[1],
-a[2]-abc[2]
};
// Compute the quadratic terms for bilinear interpolation.
float q[3] = {
d[0]+abc[0],
d[1]+abc[1],
d[2]+abc[2]
};
// Compute interpolation ratios tEx (0 < tEx[i] < 1) for the local extremes of each color channel (the derivative 2*q[i]*tEx[i]+l[i] == 0).
double tEx[3] = {
-.5*l[0]/q[0],
-.5*l[1]/q[1],
-.5*l[2]/q[2]
};
// Check points where each pair of color channels meets.
return (
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) ||
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) ||
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0])
);
}
return false;
}
template <int N>
void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
// Inspect all texels.
for (int y = 0; y < sdf.height; ++y) {
for (int x = 0; x < sdf.width; ++x) {
const float *c = sdf(x, y);
float cm = median(c[0], c[1], c[2]);
bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0;
const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
// Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
*stencil(x, y) |= (byte) (ERROR*(
(x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, l))) ||
(y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, b))) ||
(x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, r))) ||
(y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, t))) ||
(x > 0 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, b, sdf(x-1, y-1))) ||
(x < sdf.width-1 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, b, sdf(x+1, y-1))) ||
(x > 0 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, t, sdf(x-1, y+1))) ||
(x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, t, sdf(x+1, y+1)))
));
}
}
}
template <template <typename> class ContourCombiner, int N>
void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {
// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
#ifdef MSDFGEN_USE_OPENMP
#pragma omp parallel
#endif
{
ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio);
bool rightToLeft = false;
// Inspect all texels.
#ifdef MSDFGEN_USE_OPENMP
#pragma omp for
#endif
for (int y = 0; y < sdf.height; ++y) {
int row = shape.inverseYAxis ? sdf.height-y-1 : y;
for (int col = 0; col < sdf.width; ++col) {
int x = rightToLeft ? sdf.width-col-1 : col;
if ((*stencil(x, row)&ERROR))
continue;
const float *c = sdf(x, row);
shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5));
shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);
shapeDistanceChecker.msd = c;
shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;
float cm = median(c[0], c[1], c[2]);
const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
// Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
*stencil(x, row) |= (byte) (ERROR*(
(x > 0 && ((l = sdf(x-1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(-1, 0), hSpan), cm, c, l))) ||
(row > 0 && ((b = sdf(x, row-1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, -1), vSpan), cm, c, b))) ||
(x < sdf.width-1 && ((r = sdf(x+1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(+1, 0), hSpan), cm, c, r))) ||
(row < sdf.height-1 && ((t = sdf(x, row+1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, +1), vSpan), cm, c, t))) ||
(x > 0 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, -1), dSpan), cm, c, l, b, sdf(x-1, row-1))) ||
(x < sdf.width-1 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, -1), dSpan), cm, c, r, b, sdf(x+1, row-1))) ||
(x > 0 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, +1), dSpan), cm, c, l, t, sdf(x-1, row+1))) ||
(x < sdf.width-1 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, +1), dSpan), cm, c, r, t, sdf(x+1, row+1)))
));
}
}
}
}
template <int N>
void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
int texelCount = sdf.width*sdf.height;
const byte *mask = stencil.pixels;
float *texel = sdf.pixels;
for (int i = 0; i < texelCount; ++i) {
if (*mask&ERROR) {
// Set all color channels to the median.
float m = median(texel[0], texel[1], texel[2]);
texel[0] = m, texel[1] = m, texel[2] = m;
}
++mask;
texel += N;
}
}
BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const {
return stencil;
}
template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf);
template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf);
template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf);
template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf);
template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const;
template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "Projection.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 BitmapRef<byte, 1> &stencil, const Projection &projection, double range);
/// 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 <int N>
void protectEdges(const BitmapConstRef<float, N> &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 <int N>
void findErrors(const BitmapConstRef<float, N> &sdf);
/// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF and comparison with the exact shape distance.
template <template <typename> class ContourCombiner, int N>
void findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape);
/// Modifies the MSDF so that all texels with the error flag are converted to single-channel.
template <int N>
void apply(const BitmapRef<float, N> &sdf) const;
/// Returns the stencil in its current state (see Flags).
BitmapConstRef<byte, 1> getStencil() const;
private:
BitmapRef<byte, 1> stencil;
Projection projection;
double invRange;
double minDeviationRatio;
double minImproveRatio;
};
}

View File

@ -0,0 +1,42 @@
#include "Projection.h"
namespace msdfgen {
Projection::Projection() : scale(1), translate(0) { }
Projection::Projection(const Vector2 &scale, const Vector2 &translate) : scale(scale), translate(translate) { }
Point2 Projection::project(const Point2 &coord) const {
return scale*(coord+translate);
}
Point2 Projection::unproject(const Point2 &coord) const {
return coord/scale-translate;
}
Vector2 Projection::projectVector(const Vector2 &vector) const {
return scale*vector;
}
Vector2 Projection::unprojectVector(const Vector2 &vector) const {
return vector/scale;
}
double Projection::projectX(double x) const {
return scale.x*(x+translate.x);
}
double Projection::projectY(double y) const {
return scale.y*(y+translate.y);
}
double Projection::unprojectX(double x) const {
return x/scale.x-translate.x;
}
double Projection::unprojectY(double y) const {
return y/scale.y-translate.y;
}
}

View File

@ -0,0 +1,37 @@
#pragma once
#include "Vector2.h"
namespace msdfgen {
/// A transformation from shape coordinates to pixel coordinates.
class Projection {
public:
Projection();
Projection(const Vector2 &scale, const Vector2 &translate);
/// Converts the shape coordinate to pixel coordinate.
Point2 project(const Point2 &coord) const;
/// Converts the pixel coordinate to shape coordinate.
Point2 unproject(const Point2 &coord) const;
/// Converts the vector to pixel coordinate space.
Vector2 projectVector(const Vector2 &vector) const;
/// Converts the vector from pixel coordinate space.
Vector2 unprojectVector(const Vector2 &vector) const;
/// Converts the X-coordinate from shape to pixel coordinate space.
double projectX(double x) const;
/// Converts the Y-coordinate from shape to pixel coordinate space.
double projectY(double y) const;
/// Converts the X-coordinate from pixel to shape coordinate space.
double unprojectX(double x) const;
/// Converts the Y-coordinate from pixel to shape coordinate space.
double unprojectY(double y) const;
private:
Vector2 scale;
Vector2 translate;
};
}

View File

@ -0,0 +1,125 @@
#include "Scanline.h"
#include <algorithm>
#include "arithmetics.hpp"
namespace msdfgen {
static int compareIntersections(const void *a, const void *b) {
return sign(reinterpret_cast<const Scanline::Intersection *>(a)->x-reinterpret_cast<const Scanline::Intersection *>(b)->x);
}
bool interpretFillRule(int intersections, FillRule fillRule) {
switch (fillRule) {
case FILL_NONZERO:
return intersections != 0;
case FILL_ODD:
return intersections&1;
case FILL_POSITIVE:
return intersections > 0;
case FILL_NEGATIVE:
return intersections < 0;
}
return false;
}
double Scanline::overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule) {
double total = 0;
bool aInside = false, bInside = false;
int ai = 0, bi = 0;
double ax = !a.intersections.empty() ? a.intersections[ai].x : xTo;
double bx = !b.intersections.empty() ? b.intersections[bi].x : xTo;
while (ax < xFrom || bx < xFrom) {
double xNext = min(ax, bx);
if (ax == xNext && ai < (int) a.intersections.size()) {
aInside = interpretFillRule(a.intersections[ai].direction, fillRule);
ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo;
}
if (bx == xNext && bi < (int) b.intersections.size()) {
bInside = interpretFillRule(b.intersections[bi].direction, fillRule);
bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo;
}
}
double x = xFrom;
while (ax < xTo || bx < xTo) {
double xNext = min(ax, bx);
if (aInside == bInside)
total += xNext-x;
if (ax == xNext && ai < (int) a.intersections.size()) {
aInside = interpretFillRule(a.intersections[ai].direction, fillRule);
ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo;
}
if (bx == xNext && bi < (int) b.intersections.size()) {
bInside = interpretFillRule(b.intersections[bi].direction, fillRule);
bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo;
}
x = xNext;
}
if (aInside == bInside)
total += xTo-x;
return total;
}
Scanline::Scanline() : lastIndex(0) { }
void Scanline::preprocess() {
lastIndex = 0;
if (!intersections.empty()) {
qsort(&intersections[0], intersections.size(), sizeof(Intersection), compareIntersections);
int totalDirection = 0;
for (std::vector<Intersection>::iterator intersection = intersections.begin(); intersection != intersections.end(); ++intersection) {
totalDirection += intersection->direction;
intersection->direction = totalDirection;
}
}
}
void Scanline::setIntersections(const std::vector<Intersection> &intersections) {
this->intersections = intersections;
preprocess();
}
#ifdef MSDFGEN_USE_CPP11
void Scanline::setIntersections(std::vector<Intersection> &&intersections) {
this->intersections = (std::vector<Intersection> &&) intersections;
preprocess();
}
#endif
int Scanline::moveTo(double x) const {
if (intersections.empty())
return -1;
int index = lastIndex;
if (x < intersections[index].x) {
do {
if (index == 0) {
lastIndex = 0;
return -1;
}
--index;
} while (x < intersections[index].x);
} else {
while (index < (int) intersections.size()-1 && x >= intersections[index+1].x)
++index;
}
lastIndex = index;
return index;
}
int Scanline::countIntersections(double x) const {
return moveTo(x)+1;
}
int Scanline::sumIntersections(double x) const {
int index = moveTo(x);
if (index >= 0)
return intersections[index].direction;
return 0;
}
bool Scanline::filled(double x, FillRule fillRule) const {
return interpretFillRule(sumIntersections(x), fillRule);
}
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <vector>
namespace msdfgen {
/// Fill rule dictates how intersection total is interpreted during rasterization.
enum FillRule {
FILL_NONZERO,
FILL_ODD, // "even-odd"
FILL_POSITIVE,
FILL_NEGATIVE
};
/// Resolves the number of intersection into a binary fill value based on fill rule.
bool interpretFillRule(int intersections, FillRule fillRule);
/// Represents a horizontal scanline intersecting a shape.
class Scanline {
public:
/// An intersection with the scanline.
struct Intersection {
/// X coordinate.
double x;
/// Normalized Y direction of the oriented edge at the point of intersection.
int direction;
};
static double overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule);
Scanline();
/// Populates the intersection list.
void setIntersections(const std::vector<Intersection> &intersections);
#ifdef MSDFGEN_USE_CPP11
void setIntersections(std::vector<Intersection> &&intersections);
#endif
/// Returns the number of intersections left of x.
int countIntersections(double x) const;
/// Returns the total sign of intersections left of x.
int sumIntersections(double x) const;
/// Decides whether the scanline is filled at x based on fill rule.
bool filled(double x, FillRule fillRule) const;
private:
std::vector<Intersection> intersections;
mutable int lastIndex;
void preprocess();
int moveTo(double x) const;
};
}

View File

@ -0,0 +1,183 @@
#include "Shape.h"
#include <algorithm>
#include "arithmetics.hpp"
namespace msdfgen {
Shape::Shape() : inverseYAxis(false) { }
void Shape::addContour(const Contour &contour) {
contours.push_back(contour);
}
#ifdef MSDFGEN_USE_CPP11
void Shape::addContour(Contour &&contour) {
contours.push_back((Contour &&) contour);
}
#endif
Contour & Shape::addContour() {
contours.resize(contours.size()+1);
return contours.back();
}
bool Shape::validate() const {
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) {
if (!contour->edges.empty()) {
Point2 corner = contour->edges.back()->point(1);
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
if (!*edge)
return false;
if ((*edge)->point(0) != corner)
return false;
corner = (*edge)->point(1);
}
}
}
return true;
}
static void deconvergeEdge(EdgeHolder &edgeHolder, int param) {
{
const QuadraticSegment *quadraticSegment = dynamic_cast<const QuadraticSegment *>(&*edgeHolder);
if (quadraticSegment)
edgeHolder = quadraticSegment->convertToCubic();
}
{
CubicSegment *cubicSegment = dynamic_cast<CubicSegment *>(&*edgeHolder);
if (cubicSegment)
cubicSegment->deconverge(param, MSDFGEN_DECONVERGENCE_FACTOR);
}
}
void Shape::normalize() {
for (std::vector<Contour>::iterator contour = contours.begin(); contour != contours.end(); ++contour) {
if (contour->edges.size() == 1) {
EdgeSegment *parts[3] = { };
contour->edges[0]->splitInThirds(parts[0], parts[1], parts[2]);
contour->edges.clear();
contour->edges.push_back(EdgeHolder(parts[0]));
contour->edges.push_back(EdgeHolder(parts[1]));
contour->edges.push_back(EdgeHolder(parts[2]));
} else {
EdgeHolder *prevEdge = &contour->edges.back();
for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
Vector2 prevDir = (*prevEdge)->direction(1).normalize();
Vector2 curDir = (*edge)->direction(0).normalize();
if (dotProduct(prevDir, curDir) < MSDFGEN_CORNER_DOT_EPSILON-1) {
deconvergeEdge(*prevEdge, 1);
deconvergeEdge(*edge, 0);
}
prevEdge = &*edge;
}
}
}
}
void Shape::bound(double &l, double &b, double &r, double &t) const {
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
contour->bound(l, b, r, t);
}
void Shape::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const {
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
contour->boundMiters(l, b, r, t, border, miterLimit, polarity);
}
Shape::Bounds Shape::getBounds(double border, double miterLimit, int polarity) const {
static const double LARGE_VALUE = 1e240;
Shape::Bounds bounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE };
bound(bounds.l, bounds.b, bounds.r, bounds.t);
if (border > 0) {
bounds.l -= border, bounds.b -= border;
bounds.r += border, bounds.t += border;
if (miterLimit > 0)
boundMiters(bounds.l, bounds.b, bounds.r, bounds.t, border, miterLimit, polarity);
}
return bounds;
}
void Shape::scanline(Scanline &line, double y) const {
std::vector<Scanline::Intersection> intersections;
double x[3];
int dy[3];
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) {
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
int n = (*edge)->scanlineIntersections(x, dy, y);
for (int i = 0; i < n; ++i) {
Scanline::Intersection intersection = { x[i], dy[i] };
intersections.push_back(intersection);
}
}
}
#ifdef MSDFGEN_USE_CPP11
line.setIntersections((std::vector<Scanline::Intersection> &&) intersections);
#else
line.setIntersections(intersections);
#endif
}
int Shape::edgeCount() const {
int total = 0;
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
total += (int) contour->edges.size();
return total;
}
void Shape::orientContours() {
struct Intersection {
double x;
int direction;
int contourIndex;
static int compare(const void *a, const void *b) {
return sign(reinterpret_cast<const Intersection *>(a)->x-reinterpret_cast<const Intersection *>(b)->x);
}
};
const double ratio = .5*(sqrt(5)-1); // an irrational number to minimize chance of intersecting a corner or other point of interest
std::vector<int> orientations(contours.size());
std::vector<Intersection> intersections;
for (int i = 0; i < (int) contours.size(); ++i) {
if (!orientations[i] && !contours[i].edges.empty()) {
// Find an Y that crosses the contour
double y0 = contours[i].edges.front()->point(0).y;
double y1 = y0;
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
y1 = (*edge)->point(1).y;
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
y1 = (*edge)->point(ratio).y; // in case all endpoints are in a horizontal line
double y = mix(y0, y1, ratio);
// Scanline through whole shape at Y
double x[3];
int dy[3];
for (int j = 0; j < (int) contours.size(); ++j) {
for (std::vector<EdgeHolder>::const_iterator edge = contours[j].edges.begin(); edge != contours[j].edges.end(); ++edge) {
int n = (*edge)->scanlineIntersections(x, dy, y);
for (int k = 0; k < n; ++k) {
Intersection intersection = { x[k], dy[k], j };
intersections.push_back(intersection);
}
}
}
qsort(&intersections[0], intersections.size(), sizeof(Intersection), &Intersection::compare);
// Disqualify multiple intersections
for (int j = 1; j < (int) intersections.size(); ++j)
if (intersections[j].x == intersections[j-1].x)
intersections[j].direction = intersections[j-1].direction = 0;
// Inspect scanline and deduce orientations of intersected contours
for (int j = 0; j < (int) intersections.size(); ++j)
if (intersections[j].direction)
orientations[intersections[j].contourIndex] += 2*((j&1)^(intersections[j].direction > 0))-1;
intersections.clear();
}
}
// Reverse contours that have the opposite orientation
for (int i = 0; i < (int) contours.size(); ++i)
if (orientations[i] < 0)
contours[i].reverse();
}
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <vector>
#include "Contour.h"
#include "Scanline.h"
namespace msdfgen {
// Threshold of the dot product of adjacent edge directions to be considered convergent.
#define MSDFGEN_CORNER_DOT_EPSILON .000001
// The proportional amount by which a curve's control point will be adjusted to eliminate convergent corners.
#define MSDFGEN_DECONVERGENCE_FACTOR .000001
/// Vector shape representation.
class Shape {
public:
struct Bounds {
double l, b, r, t;
};
/// The list of contours the shape consists of.
std::vector<Contour> contours;
/// Specifies whether the shape uses bottom-to-top (false) or top-to-bottom (true) Y coordinates.
bool inverseYAxis;
Shape();
/// Adds a contour.
void addContour(const Contour &contour);
#ifdef MSDFGEN_USE_CPP11
void addContour(Contour &&contour);
#endif
/// Adds a blank contour and returns its reference.
Contour & addContour();
/// Normalizes the shape geometry for distance field generation.
void normalize();
/// Performs basic checks to determine if the object represents a valid shape.
bool validate() const;
/// Adjusts the bounding box to fit the shape.
void bound(double &l, double &b, double &r, double &t) const;
/// Adjusts the bounding box to fit the shape border's mitered corners.
void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const;
/// Computes the minimum bounding box that fits the shape, optionally with a (mitered) border.
Bounds getBounds(double border = 0, double miterLimit = 0, int polarity = 0) const;
/// Outputs the scanline that intersects the shape at y.
void scanline(Scanline &line, double y) const;
/// Returns the total number of edge segments
int edgeCount() const;
/// Assumes its contours are unoriented (even-odd fill rule). Attempts to orient them to conform to the non-zero winding rule.
void orientContours();
};
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <vector>
#include "Vector2.h"
#include "edge-selectors.h"
#include "contour-combiners.h"
namespace msdfgen {
/// Finds the distance between a point and a Shape. ContourCombiner dictates the distance metric and its data type.
template <class ContourCombiner>
class ShapeDistanceFinder {
public:
typedef typename ContourCombiner::DistanceType DistanceType;
// Passed shape object must persist until the distance finder is destroyed!
explicit ShapeDistanceFinder(const Shape &shape);
/// Finds the distance from origin. Not thread-safe! Is fastest when subsequent queries are close together.
DistanceType distance(const Point2 &origin);
/// Finds the distance between shape and origin. Does not allocate result cache used to optimize performance of multiple queries.
static DistanceType oneShotDistance(const Shape &shape, const Point2 &origin);
private:
const Shape &shape;
ContourCombiner contourCombiner;
std::vector<typename ContourCombiner::EdgeSelectorType::EdgeCache> shapeEdgeCache;
};
typedef ShapeDistanceFinder<SimpleContourCombiner<TrueDistanceSelector> > SimpleTrueShapeDistanceFinder;
}
#include "ShapeDistanceFinder.hpp"

View File

@ -0,0 +1,56 @@
#include "ShapeDistanceFinder.h"
namespace msdfgen {
template <class ContourCombiner>
ShapeDistanceFinder<ContourCombiner>::ShapeDistanceFinder(const Shape &shape) : shape(shape), contourCombiner(shape), shapeEdgeCache(shape.edgeCount()) { }
template <class ContourCombiner>
typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::distance(const Point2 &origin) {
contourCombiner.reset(origin);
typename ContourCombiner::EdgeSelectorType::EdgeCache *edgeCache = &shapeEdgeCache[0];
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
if (!contour->edges.empty()) {
typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin()));
const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin();
const EdgeSegment *curEdge = contour->edges.back();
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
const EdgeSegment *nextEdge = *edge;
edgeSelector.addEdge(*edgeCache++, prevEdge, curEdge, nextEdge);
prevEdge = curEdge;
curEdge = nextEdge;
}
}
}
return contourCombiner.distance();
}
template <class ContourCombiner>
typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::oneShotDistance(const Shape &shape, const Point2 &origin) {
ContourCombiner contourCombiner(shape);
contourCombiner.reset(origin);
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
if (!contour->edges.empty()) {
typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin()));
const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin();
const EdgeSegment *curEdge = contour->edges.back();
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
const EdgeSegment *nextEdge = *edge;
typename ContourCombiner::EdgeSelectorType::EdgeCache dummy;
edgeSelector.addEdge(dummy, prevEdge, curEdge, nextEdge);
prevEdge = curEdge;
curEdge = nextEdge;
}
}
}
return contourCombiner.distance();
}
}

View File

@ -0,0 +1,30 @@
#include "SignedDistance.h"
#include <cmath>
namespace msdfgen {
const SignedDistance SignedDistance::INFINITE(-1e240, 1);
SignedDistance::SignedDistance() : distance(-1e240), dot(1) { }
SignedDistance::SignedDistance(double dist, double d) : distance(dist), dot(d) { }
bool operator<(SignedDistance a, SignedDistance b) {
return fabs(a.distance) < fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot < b.dot);
}
bool operator>(SignedDistance a, SignedDistance b) {
return fabs(a.distance) > fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot > b.dot);
}
bool operator<=(SignedDistance a, SignedDistance b) {
return fabs(a.distance) < fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot <= b.dot);
}
bool operator>=(SignedDistance a, SignedDistance b) {
return fabs(a.distance) > fabs(b.distance) || (fabs(a.distance) == fabs(b.distance) && a.dot >= b.dot);
}
}

Some files were not shown because too many files have changed in this diff Show More