添加 文本渲染
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -18,6 +18,7 @@ endif ()
|
||||
|
||||
add_subdirectory(Hazel)
|
||||
add_subdirectory(Editor)
|
||||
add_subdirectory(Sandbox)
|
||||
add_subdirectory(Hazel-ScriptCore)
|
||||
add_subdirectory(Editor/SandboxProject)
|
||||
|
||||
|
||||
69
Editor/assets/shaders/Renderer2D_text.glsl
Normal file
69
Editor/assets/shaders/Renderer2D_text.glsl
Normal 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;
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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 ()
|
||||
|
||||
# -------------------------------------------
|
||||
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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();
|
||||
|
||||
158
Hazel/src/Hazel/Renderer/Font.cpp
Normal file
158
Hazel/src/Hazel/Renderer/Font.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
35
Hazel/src/Hazel/Renderer/Font.h
Normal file
35
Hazel/src/Hazel/Renderer/Font.h
Normal 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
|
||||
20
Hazel/src/Hazel/Renderer/MSDFData.h
Normal file
20
Hazel/src/Hazel/Renderer/MSDFData.h
Normal 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
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
26
Hazel/vendor/msdf-atlas-gen/CHANGELOG.md
vendored
Normal file
26
Hazel/vendor/msdf-atlas-gen/CHANGELOG.md
vendored
Normal 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.
|
||||
35
Hazel/vendor/msdf-atlas-gen/CMakeLists.txt
vendored
Normal file
35
Hazel/vendor/msdf-atlas-gen/CMakeLists.txt
vendored
Normal 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
21
Hazel/vendor/msdf-atlas-gen/LICENSE.txt
vendored
Normal 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
4
Hazel/vendor/msdf-atlas-gen/Makefile
vendored
Normal 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
142
Hazel/vendor/msdf-atlas-gen/README.md
vendored
Normal 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.
|
||||
|
||||

|
||||
|
||||
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|
|
||||
|-|-|-|-|-|-|-|
|
||||
| |||||||
|
||||
|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) – sets the input font file.
|
||||
- `-charset <charset.txt>` – 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>` – sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification).
|
||||
- `-fontscale <scale>` – 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>` – sets a name for the font that will be stored in certain output files as metadata.
|
||||
- `-and` – separates multiple inputs to be combined into a single atlas.
|
||||
|
||||
### Bitmap atlas type
|
||||
|
||||
`-type <type>` – see [Atlas types](#atlas-types)
|
||||
|
||||
`<type>` can be one of:
|
||||
|
||||
- `hardmask` – a non-anti-aliased binary image
|
||||
- `softmask` – an anti-aliased image
|
||||
- `sdf` – a true signed distance field (SDF)
|
||||
- `psdf` – a pseudo-distance field
|
||||
- `msdf` (default) – a multi-channel signed distance field (MSDF)
|
||||
- `mtsdf` – a combination of MSDF and true SDF in the alpha channel
|
||||
|
||||
### Atlas image format
|
||||
|
||||
`-format <format>`
|
||||
|
||||
`<format>` can be one of:
|
||||
|
||||
- `png` – a compressed PNG image
|
||||
- `bmp` – an uncompressed BMP image
|
||||
- `tiff` – an uncompressed floating-point TIFF image
|
||||
- `text` – a sequence of pixel values in plain text
|
||||
- `textfloat` – a sequence of floating-point pixel values in plain text
|
||||
- `bin` – a sequence of pixel values encoded as raw bytes of data
|
||||
- `binfloat` – a sequence of pixel values encoded as raw 32-bit floating-point values
|
||||
|
||||
### Atlas dimensions
|
||||
|
||||
`-dimensions <width> <height>` – sets fixed atlas dimensions
|
||||
|
||||
Alternativelly, the minimum possible dimensions may be selected automatically if a dimensions constraint is set instead:
|
||||
|
||||
- `-pots` – a power-of-two square
|
||||
- `-potr` – a power-of-two square or rectangle (2:1)
|
||||
- `-square` – any square dimensions
|
||||
- `-square2` – square with even side length
|
||||
- `-square4` (default) – square with side length divisible by four
|
||||
|
||||
### Outputs
|
||||
|
||||
Any non-empty subset of the following may be specified:
|
||||
|
||||
- `-imageout <filename.*>` – saves the atlas bitmap as a plain image file. Format matches `-format`
|
||||
- `-json <filename.json>` – writes the atlas's layout data as well as other metrics into a structured JSON file
|
||||
- `-csv <filename.csv>` – writes the glyph layout data into a simple CSV file
|
||||
- `-arfont <filename.arfont>` – saves the atlas and its layout data as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file
|
||||
- `-shadronpreview <filename.shadron> <sample text>` – 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>` – sets the size of the glyphs in the atlas in pixels per EM
|
||||
- `-minsize <EM size>` – sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
|
||||
- `-emrange <EM range>` – sets the distance field range in EM's
|
||||
- `-pxrange <pixel range>` (default = 2) – sets the distance field range in output pixels
|
||||
|
||||
### Distance field generator settings
|
||||
|
||||
- `-angle <angle>` – sets the minimum angle between adjacent edges to be considered a corner. Append D for degrees (`msdf` / `mtsdf` only)
|
||||
- `-coloringstrategy <simple / inktrap / distance>` – selects the edge coloring heuristic (`msdf` / `mtsdf` only)
|
||||
- `-errorcorrection <mode>` – selects the error correction algorithm. Use `help` as mode for more information (`msdf` / `mtsdf` only)
|
||||
- `-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)
|
||||
- `-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
|
||||
- `-seed <N>` – sets the initial seed for the edge coloring heuristic
|
||||
- `-threads <N>` – 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
BIN
Hazel/vendor/msdf-atlas-gen/icon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen.aps
vendored
Normal file
BIN
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen.aps
vendored
Normal file
Binary file not shown.
BIN
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen.rc
vendored
Normal file
BIN
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen.rc
vendored
Normal file
Binary file not shown.
42
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/AtlasGenerator.h
vendored
Normal file
42
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/AtlasGenerator.h
vendored
Normal 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 &);
|
||||
|
||||
}
|
||||
37
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/AtlasStorage.h
vendored
Normal file
37
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/AtlasStorage.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
33
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/BitmapAtlasStorage.h
vendored
Normal file
33
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/BitmapAtlasStorage.h
vendored
Normal 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"
|
||||
65
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/BitmapAtlasStorage.hpp
vendored
Normal file
65
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/BitmapAtlasStorage.hpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
39
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Charset.cpp
vendored
Normal file
39
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Charset.cpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
35
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Charset.h
vendored
Normal file
35
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Charset.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
43
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/DynamicAtlas.h
vendored
Normal file
43
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/DynamicAtlas.h
vendored
Normal 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"
|
||||
69
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/DynamicAtlas.hpp
vendored
Normal file
69
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/DynamicAtlas.hpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
185
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/FontGeometry.cpp
vendored
Normal file
185
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/FontGeometry.cpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
86
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/FontGeometry.h
vendored
Normal file
86
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/FontGeometry.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
19
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphBox.h
vendored
Normal file
19
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphBox.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
170
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphGeometry.cpp
vendored
Normal file
170
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphGeometry.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
76
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphGeometry.h
vendored
Normal file
76
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/GlyphGeometry.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
45
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/ImmediateAtlasGenerator.h
vendored
Normal file
45
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/ImmediateAtlasGenerator.h
vendored
Normal 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"
|
||||
77
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/ImmediateAtlasGenerator.hpp
vendored
Normal file
77
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/ImmediateAtlasGenerator.hpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Rectangle.h
vendored
Normal file
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Rectangle.h
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace msdf_atlas {
|
||||
|
||||
struct Rectangle {
|
||||
int x, y, w, h;
|
||||
};
|
||||
|
||||
struct OrientedRectangle : Rectangle {
|
||||
bool rotated;
|
||||
};
|
||||
|
||||
}
|
||||
143
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/RectanglePacker.cpp
vendored
Normal file
143
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/RectanglePacker.cpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
28
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/RectanglePacker.h
vendored
Normal file
28
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/RectanglePacker.h
vendored
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
15
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Remap.h
vendored
Normal file
15
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Remap.h
vendored
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
168
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/TightAtlasPacker.cpp
vendored
Normal file
168
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/TightAtlasPacker.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
71
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/TightAtlasPacker.h
vendored
Normal file
71
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/TightAtlasPacker.h
vendored
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
50
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Workload.cpp
vendored
Normal file
50
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Workload.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
32
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Workload.h
vendored
Normal file
32
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/Workload.h
vendored
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
58
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/bitmap-blit.cpp
vendored
Normal file
58
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/bitmap-blit.cpp
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/bitmap-blit.h
vendored
Normal file
26
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/bitmap-blit.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
250
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/charset-parser.cpp
vendored
Normal file
250
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/charset-parser.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
45
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/csv-export.cpp
vendored
Normal file
45
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/csv-export.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/csv-export.h
vendored
Normal file
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/csv-export.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
52
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/glyph-generators.cpp
vendored
Normal file
52
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/glyph-generators.cpp
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/glyph-generators.h
vendored
Normal file
25
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/glyph-generators.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
63
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-encode.cpp
vendored
Normal file
63
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-encode.cpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
20
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-encode.h
vendored
Normal file
20
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-encode.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
15
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-save.h
vendored
Normal file
15
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-save.h
vendored
Normal 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"
|
||||
172
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-save.hpp
vendored
Normal file
172
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/image-save.hpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
186
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/json-export.cpp
vendored
Normal file
186
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/json-export.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/json-export.h
vendored
Normal file
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/json-export.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
999
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/main.cpp
vendored
Normal file
999
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/main.cpp
vendored
Normal 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
|
||||
41
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/msdf-atlas-gen.h
vendored
Normal file
41
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/msdf-atlas-gen.h
vendored
Normal 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"
|
||||
19
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/rectangle-packing.h
vendored
Normal file
19
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/rectangle-packing.h
vendored
Normal 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"
|
||||
61
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/rectangle-packing.hpp
vendored
Normal file
61
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/rectangle-packing.hpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
155
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/shadron-preview-generator.cpp
vendored
Normal file
155
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/shadron-preview-generator.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/shadron-preview-generator.h
vendored
Normal file
14
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/shadron-preview-generator.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
90
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/size-selectors.cpp
vendored
Normal file
90
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/size-selectors.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
54
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/size-selectors.h
vendored
Normal file
54
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/size-selectors.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
52
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/types.h
vendored
Normal file
52
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/types.h
vendored
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
38
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/utf8.cpp
vendored
Normal file
38
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/utf8.cpp
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/utf8.h
vendored
Normal file
12
Hazel/vendor/msdf-atlas-gen/msdf-atlas-gen/utf8.h
vendored
Normal 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);
|
||||
|
||||
}
|
||||
82
Hazel/vendor/msdf-atlas-gen/msdfgen/CHANGELOG.md
vendored
Normal file
82
Hazel/vendor/msdf-atlas-gen/msdfgen/CHANGELOG.md
vendored
Normal 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.
|
||||
112
Hazel/vendor/msdf-atlas-gen/msdfgen/CMakeLists.txt
vendored
Normal file
112
Hazel/vendor/msdf-atlas-gen/msdfgen/CMakeLists.txt
vendored
Normal 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
|
||||
)
|
||||
21
Hazel/vendor/msdf-atlas-gen/msdfgen/LICENSE.txt
vendored
Normal file
21
Hazel/vendor/msdf-atlas-gen/msdfgen/LICENSE.txt
vendored
Normal 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.
|
||||
BIN
Hazel/vendor/msdf-atlas-gen/msdfgen/Msdfgen.aps
vendored
Normal file
BIN
Hazel/vendor/msdf-atlas-gen/msdfgen/Msdfgen.aps
vendored
Normal file
Binary file not shown.
BIN
Hazel/vendor/msdf-atlas-gen/msdfgen/Msdfgen.rc
vendored
Normal file
BIN
Hazel/vendor/msdf-atlas-gen/msdfgen/Msdfgen.rc
vendored
Normal file
Binary file not shown.
209
Hazel/vendor/msdf-atlas-gen/msdfgen/README.md
vendored
Normal file
209
Hazel/vendor/msdf-atlas-gen/msdfgen/README.md
vendored
Normal 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.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
- 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** – generates a conventional monochrome (true) signed distance field.
|
||||
- **psdf** – generates a monochrome signed pseudo-distance field.
|
||||
- **msdf** (default) – generates a multi-channel signed distance field using my new method.
|
||||
- **mtsdf** – 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\>** – 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\>** – to load an SVG file. Note that only the last vector path in the file will be used.
|
||||
- **-shapedesc \<filename.txt\>**, -defineshape \<definition\>, -stdin – 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\>** – 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\>** – specifies the dimensions of the output distance field (in pixels).
|
||||
- **-range \<range\>**, **-pxrange \<range\>** – 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\>** – sets the scale used to convert shape units to distance field pixels.
|
||||
- **-translate \<x\> \<y\>** – sets the translation of the shape in shape units. Otherwise the origin (0, 0)
|
||||
lies in the bottom left corner.
|
||||
- **-autoframe** – 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\>** – 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** – 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×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.
|
||||
50
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Bitmap.h
vendored
Normal file
50
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Bitmap.h
vendored
Normal 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"
|
||||
117
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Bitmap.hpp
vendored
Normal file
117
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Bitmap.hpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
43
Hazel/vendor/msdf-atlas-gen/msdfgen/core/BitmapRef.hpp
vendored
Normal file
43
Hazel/vendor/msdf-atlas-gen/msdfgen/core/BitmapRef.hpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
90
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Contour.cpp
vendored
Normal file
90
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Contour.cpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
34
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Contour.h
vendored
Normal file
34
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Contour.h
vendored
Normal 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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
18
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeColor.h
vendored
Normal file
18
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeColor.h
vendored
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
77
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeHolder.cpp
vendored
Normal file
77
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeHolder.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
41
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeHolder.h
vendored
Normal file
41
Hazel/vendor/msdf-atlas-gen/msdfgen/core/EdgeHolder.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
495
Hazel/vendor/msdf-atlas-gen/msdfgen/core/MSDFErrorCorrection.cpp
vendored
Normal file
495
Hazel/vendor/msdf-atlas-gen/msdfgen/core/MSDFErrorCorrection.cpp
vendored
Normal 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;
|
||||
|
||||
}
|
||||
56
Hazel/vendor/msdf-atlas-gen/msdfgen/core/MSDFErrorCorrection.h
vendored
Normal file
56
Hazel/vendor/msdf-atlas-gen/msdfgen/core/MSDFErrorCorrection.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
42
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Projection.cpp
vendored
Normal file
42
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Projection.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
37
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Projection.h
vendored
Normal file
37
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Projection.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
125
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Scanline.cpp
vendored
Normal file
125
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Scanline.cpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
55
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Scanline.h
vendored
Normal file
55
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Scanline.h
vendored
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
183
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Shape.cpp
vendored
Normal file
183
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Shape.cpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
55
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Shape.h
vendored
Normal file
55
Hazel/vendor/msdf-atlas-gen/msdfgen/core/Shape.h
vendored
Normal 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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
37
Hazel/vendor/msdf-atlas-gen/msdfgen/core/ShapeDistanceFinder.h
vendored
Normal file
37
Hazel/vendor/msdf-atlas-gen/msdfgen/core/ShapeDistanceFinder.h
vendored
Normal 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"
|
||||
56
Hazel/vendor/msdf-atlas-gen/msdfgen/core/ShapeDistanceFinder.hpp
vendored
Normal file
56
Hazel/vendor/msdf-atlas-gen/msdfgen/core/ShapeDistanceFinder.hpp
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
30
Hazel/vendor/msdf-atlas-gen/msdfgen/core/SignedDistance.cpp
vendored
Normal file
30
Hazel/vendor/msdf-atlas-gen/msdfgen/core/SignedDistance.cpp
vendored
Normal 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
Reference in New Issue
Block a user