code refactoring

This commit is contained in:
ohyzha
2025-01-03 12:16:59 +02:00
parent cfb613d6bb
commit 9ff67815d0
4 changed files with 135 additions and 117 deletions

View File

@@ -44,7 +44,7 @@ namespace OpenVulkano::Image
if (channels == 3) if (channels == 3)
{ {
result.data = OpenVulkano::Array<uint8_t>(width * height * 4); result.data = OpenVulkano::Array<uint8_t>(width * height * 4);
for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) for (size_t srcPos = 0, dstPos = 0; dstPos < result.data.Size(); srcPos += 3, dstPos += 4)
{ {
result.data[dstPos] = pixelData[srcPos]; result.data[dstPos] = pixelData[srcPos];
result.data[dstPos + 1] = pixelData[srcPos + 1]; result.data[dstPos + 1] = pixelData[srcPos + 1];

View File

@@ -7,88 +7,6 @@
#include "BitmapFontAtlasGenerator.hpp" #include "BitmapFontAtlasGenerator.hpp"
#include "Base/Logger.hpp" #include "Base/Logger.hpp"
namespace
{
using namespace OpenVulkano;
struct GlyphForPacking
{
uint32_t code;
size_t firstGlyphByteInAtlas;
Math::Vector2d atlasPos;
Math::Vector2i wh;
};
struct Shelf
{
Shelf(uint32_t width, uint32_t height) : m_width(width), m_height(height), m_remainingWidth(width) {}
bool HasSpaceForGlyph(uint32_t glyphWidth, uint32_t glyphHeight) const
{
return m_remainingWidth >= glyphWidth && m_height >= glyphHeight;
}
uint32_t GetWidth() const { return m_width; }
uint32_t GetHeight() const { return m_height; }
uint32_t GetNextGlyphPos() const { return m_nextGlyphPos; };
uint32_t GetOccupiedSize() const { return ((m_width - m_remainingWidth) * m_height); }
std::optional<uint32_t> AddGlyph(uint32_t glyphWidth, uint32_t glyphHeight)
{
if (!HasSpaceForGlyph(glyphWidth, glyphHeight))
{
return {};
}
uint32_t insertionPos = m_nextGlyphPos;
m_nextGlyphPos += glyphWidth;
m_remainingWidth -= glyphWidth;
return insertionPos;
}
private:
uint32_t m_width;
uint32_t m_height;
uint32_t m_remainingWidth;
uint32_t m_nextGlyphPos = 0;
};
std::vector<Shelf> CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs, const Scene::FtFaceRecPtr& face)
{
std::vector<Shelf> shelves;
for (GlyphForPacking& glyph : glyphs)
{
FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
if (error)
{
continue;
}
FT_GlyphSlot slot = face->glyph;
bool needNewShelf = true;
uint32_t totalPrevShelvesHeight = 0;
for (Shelf& shelf : shelves)
{
if (std::optional<uint32_t> insertionPosX = shelf.AddGlyph(glyph.wh.x, glyph.wh.y))
{
glyph.firstGlyphByteInAtlas = *insertionPosX + (totalPrevShelvesHeight * atlasWidth);
glyph.atlasPos.x = *insertionPosX;
glyph.atlasPos.y = totalPrevShelvesHeight;
needNewShelf = false;
break;
}
totalPrevShelvesHeight += shelf.GetHeight();
}
if (needNewShelf)
{
shelves.emplace_back(atlasWidth, glyph.wh.y);
shelves.back().AddGlyph(glyph.wh.x, glyph.wh.y);
glyph.firstGlyphByteInAtlas = totalPrevShelvesHeight * atlasWidth;
glyph.atlasPos.x = 0;
glyph.atlasPos.y = totalPrevShelvesHeight;
}
}
return shelves;
}
}
namespace OpenVulkano::Scene namespace OpenVulkano::Scene
{ {
void BitmapFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset, void BitmapFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
@@ -125,33 +43,12 @@ namespace OpenVulkano::Scene
FT_Set_Pixel_Sizes(face.get(), 0, pixelSize); FT_Set_Pixel_Sizes(face.get(), 0, pixelSize);
} }
FT_Error error = 0; auto [allGlyphs, area] = InitGlyphsForPacking(chset, face);
double area = 0; const double atlasWidth = ceil(sqrt(area));
std::vector<GlyphForPacking> allGlyphs; std::vector<Shelf> shelves = Shelf::CreateShelves(atlasWidth, allGlyphs, face);
allGlyphs.reserve(chset.size());
for (uint32_t codepoint : chset)
{
error = FT_Load_Char(face.get(), codepoint, FT_LOAD_RENDER);
if (error)
{
Logger::APP->error("FT_Load_Char for codepoint {} has failed. {}", codepoint, GetFreetypeErrorDescription(error));
continue;
}
FT_GlyphSlot slot = face->glyph;
unsigned int h = slot->bitmap.rows;
unsigned int w = slot->bitmap.width;
GlyphForPacking& glyph = allGlyphs.emplace_back();
glyph.code = codepoint;
glyph.wh = { slot->bitmap.width, slot->bitmap.rows };
area += h * w;
}
const double sq = ceil(sqrt(area));
std::sort(allGlyphs.begin(), allGlyphs.end(), [](const GlyphForPacking& a, const GlyphForPacking& b) { return a.wh.y > b.wh.y; });
std::vector<Shelf> shelves = ::CreateShelves(sq, allGlyphs, face);
uint32_t atlasHeight = 0; uint32_t atlasHeight = 0;
std::for_each(shelves.begin(), shelves.end(), [&](const Shelf& shelf) { atlasHeight += shelf.GetHeight(); }); std::for_each(shelves.begin(), shelves.end(), [&](const Shelf& shelf) { atlasHeight += shelf.GetHeight(); });
const Math::Vector2ui atlasResolution = { sq, atlasHeight }; const Math::Vector2ui atlasResolution = { atlasWidth, atlasHeight };
// same as in msdfgen lib by default. see import-font.h for reference // same as in msdfgen lib by default. see import-font.h for reference
// TODO: probably also support keeping coordinates as the integer values native to the font file // TODO: probably also support keeping coordinates as the integer values native to the font file
@@ -163,10 +60,10 @@ namespace OpenVulkano::Scene
size_t loadedGlyphs = 0; size_t loadedGlyphs = 0;
for (const GlyphForPacking& glyph : allGlyphs) for (const GlyphForPacking& glyph : allGlyphs)
{ {
error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER); FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
if (error) if (error)
{ {
Logger::APP->error("FT_Load_Char for codepoint {} has failed. {}", glyph.code, Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", glyph.code,
GetFreetypeErrorDescription(error)); GetFreetypeErrorDescription(error));
continue; continue;
} }
@@ -174,11 +71,9 @@ namespace OpenVulkano::Scene
FT_GlyphSlot slot = face->glyph; FT_GlyphSlot slot = face->glyph;
for (int row = 0; row < slot->bitmap.rows; row++) for (int row = 0; row < slot->bitmap.rows; row++)
{ {
for (int col = 0; col < slot->bitmap.width; col++) std::memcpy(&m_atlasData->img->data[glyph.firstGlyphByteInAtlas + row * atlasResolution.x],
{ &slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch],
m_atlasData->img->data[glyph.firstGlyphByteInAtlas + row * atlasResolution.x + col] = slot->bitmap.width);
slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch + col];
}
} }
GlyphInfo& glyphInfo = m_atlasData->glyphs[glyph.code]; GlyphInfo& glyphInfo = m_atlasData->glyphs[glyph.code];
@@ -195,6 +90,31 @@ namespace OpenVulkano::Scene
{ {
SavePng(*pngOutput); SavePng(*pngOutput);
} }
Logger::APP->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs); Logger::SCENE->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs);
}
std::pair<std::vector<GlyphForPacking>, double>
BitmapFontAtlasGenerator::InitGlyphsForPacking(const std::set<uint32_t>& chset, const FtFaceRecPtr& face)
{
FT_Error error = 0;
double area = 0;
std::vector<GlyphForPacking> allGlyphs;
allGlyphs.reserve(chset.size());
for (uint32_t codepoint : chset)
{
error = FT_Load_Char(face.get(), codepoint, FT_LOAD_RENDER);
if (error)
{
Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", codepoint,
GetFreetypeErrorDescription(error));
continue;
}
FT_GlyphSlot slot = face->glyph;
GlyphForPacking& glyph = allGlyphs.emplace_back(codepoint, Math::Vector2ui(slot->bitmap.width, slot->bitmap.rows));
area += slot->bitmap.rows * slot->bitmap.width;
}
std::sort(allGlyphs.begin(), allGlyphs.end(),
[](const GlyphForPacking& a, const GlyphForPacking& b) { return a.size.y > b.size.y; });
return { allGlyphs, area };
} }
} }

View File

@@ -7,6 +7,7 @@
#pragma once #pragma once
#include "FontAtlasGeneratorBase.hpp" #include "FontAtlasGeneratorBase.hpp"
#include "Shelf.hpp"
namespace OpenVulkano::Scene namespace OpenVulkano::Scene
{ {
@@ -27,6 +28,7 @@ namespace OpenVulkano::Scene
const std::optional<std::string>& pngOutput = std::nullopt) override; const std::optional<std::string>& pngOutput = std::nullopt) override;
private: private:
void Generate(const std::variant<std::string, Array<char>>& source, const std::set<uint32_t>& chset, const std::optional<std::string>& pngOutput); void Generate(const std::variant<std::string, Array<char>>& source, const std::set<uint32_t>& chset, const std::optional<std::string>& pngOutput);
std::pair<std::vector<GlyphForPacking>, double> InitGlyphsForPacking(const std::set<uint32_t>& chset, const FtFaceRecPtr& face);
private: private:
FontPixelSizeConfig m_pixelSizeConfig; FontPixelSizeConfig m_pixelSizeConfig;
}; };

View File

@@ -0,0 +1,96 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Math/Math.hpp"
#include "Scene/FreetypeHelper.hpp"
#include <vector>
#include <optional>
namespace OpenVulkano::Scene
{
struct GlyphForPacking
{
uint32_t code;
Math::Vector2i size;
Math::Vector2d atlasPos;
size_t firstGlyphByteInAtlas;
};
struct Shelf
{
inline static std::vector<Shelf> CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
const FtFaceRecPtr& face);
Shelf(uint32_t width, uint32_t height) : m_width(width), m_height(height), m_remainingWidth(width) {}
bool HasSpaceForGlyph(uint32_t glyphWidth, uint32_t glyphHeight) const
{
return m_remainingWidth >= glyphWidth && m_height >= glyphHeight;
}
uint32_t GetWidth() const { return m_width; }
uint32_t GetHeight() const { return m_height; }
uint32_t GetNextGlyphPos() const { return m_nextGlyphPos; };
uint32_t GetOccupiedSize() const { return ((m_width - m_remainingWidth) * m_height); }
std::optional<uint32_t> AddGlyph(uint32_t glyphWidth, uint32_t glyphHeight)
{
if (!HasSpaceForGlyph(glyphWidth, glyphHeight))
{
return {};
}
uint32_t insertionPos = m_nextGlyphPos;
m_nextGlyphPos += glyphWidth;
m_remainingWidth -= glyphWidth;
return insertionPos;
}
private:
uint32_t m_width;
uint32_t m_height;
uint32_t m_remainingWidth;
uint32_t m_nextGlyphPos = 0;
};
std::vector<Shelf> Shelf::CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
const FtFaceRecPtr& face)
{
std::vector<Shelf> shelves;
for (GlyphForPacking& glyph : glyphs)
{
FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
if (error)
{
continue;
}
FT_GlyphSlot slot = face->glyph;
bool needNewShelf = true;
uint32_t totalPrevShelvesHeight = 0;
for (Shelf& shelf : shelves)
{
if (std::optional<uint32_t> insertionPosX = shelf.AddGlyph(glyph.size.x, glyph.size.y))
{
glyph.firstGlyphByteInAtlas = *insertionPosX + (totalPrevShelvesHeight * atlasWidth);
glyph.atlasPos.x = *insertionPosX;
glyph.atlasPos.y = totalPrevShelvesHeight;
needNewShelf = false;
break;
}
totalPrevShelvesHeight += shelf.GetHeight();
}
if (needNewShelf)
{
shelves.emplace_back(atlasWidth, glyph.size.y);
shelves.back().AddGlyph(glyph.size.x, glyph.size.y);
glyph.firstGlyphByteInAtlas = totalPrevShelvesHeight * atlasWidth;
glyph.atlasPos.x = 0;
glyph.atlasPos.y = totalPrevShelvesHeight;
}
}
return shelves;
}
}