Compare commits

..

2 Commits

13 changed files with 469 additions and 2 deletions

View File

@ -3,5 +3,7 @@ cmake_minimum_required(VERSION 3.20)
project(all)
enable_testing()
add_subdirectory(applications)
add_subdirectory(bmrshared)
add_subdirectory(bmrshared-freetype)
add_subdirectory(bmrshared-magic)

View File

@ -0,0 +1 @@
add_subdirectory(text2image)

View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.20)
find_package(Boost 1.84.0 REQUIRED COMPONENTS program_options headers CONFIG)
find_package(JPEG REQUIRED)
project(text2image)
add_executable(
${PROJECT_NAME}
./src/main.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_link_libraries(
${PROJECT_NAME}
PRIVATE
bmrshared-freetype
Boost::program_options
Boost::headers
JPEG::JPEG
)
install(
TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)

View File

@ -0,0 +1,102 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#include <bmrshared/freetype_face.hpp>
#include <bmrshared/freetype_lib.hpp>
#include <bmrshared/freetype_utils.hpp>
#include <boost/gil/extension/io/jpeg.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
namespace bpo = boost::program_options;
int main(int argc, char** argv)
{
std::string font_file_str;
std::string output_file_str;
std::string text;
int font_size;
bpo::options_description desc("Options");
// clang-format off
desc.add_options()
("text,t", bpo::value<std::string>(&text), "The text to render.")
("font,f", bpo::value<std::string>(&font_file_str), "TTF font file to use for rendering.")
("size,s", bpo::value<int>(&font_size)->default_value(16), "Font size in pixels.")
("output,o", bpo::value<std::string>(&output_file_str), "Output file path.")
("help,h", "Show help information.");
// clang-format on
std::filesystem::path font_file;
std::filesystem::path output_file;
try
{
bpo::variables_map vm;
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
bpo::notify(vm);
if (vm.count("help"))
{
std::cout << desc << std::endl;
return 1;
}
font_file = font_file_str;
if (!std::filesystem::is_regular_file(font_file))
{
throw std::runtime_error("TTF font file path is invalid.");
}
output_file = output_file_str;
if (!std::filesystem::is_directory(output_file.parent_path()))
{
throw std::runtime_error("Output directory is invalid.");
}
}
catch (const std::exception& e)
{
std::cout << "Error while processing commandline arguments.\n" << e.what() << "\n" << desc << std::endl;
return 1;
}
std::cout << "text=" << text << "\nfont=" << font_file << "\nsize=" << font_size << "\noutput=" << output_file;
auto ftLib = std::make_shared<bmrshared::freetype_lib>();
auto ftFace = std::make_shared<bmrshared::freetype_face>(ftLib, font_file, 0, font_size, true);
const auto dim = bmrshared::freetype_calculate_dimensions(*ftFace, text);
const int pixel_width = dim.width / 64;
const int pixel_height = dim.height / 64;
const int bearingY = dim.vertOriginOffsetY / 64;
boost::gil::gray8_image_t image(pixel_width, pixel_height);
auto view = boost::gil::view(image);
boost::gil::fill_pixels(view, boost::gil::gray8_pixel_t(0));
auto painter = [&](int x, int y, const uint8_t level) -> void
{
const auto& _x = x;
const auto _y = y + bearingY;
if (_x >= 0 && _y >= 0 && _x < pixel_width && _y < pixel_height)
{
const auto iter = view.at({_x, _y});
boost::gil::color_convert(boost::gil::gray8_pixel_t(level), *iter);
}
};
bmrshared::freetype_paint(*ftFace, text, painter);
boost::gil::write_view(output_file,
boost::gil::view(image),
boost::gil::image_write_info<boost::gil::jpeg_tag>(95));
return 0;
}

View File

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.20)
find_package(freetype REQUIRED)
find_package(Boost 1.84.0 REQUIRED COMPONENTS headers CONFIG)
project(bmrshared-freetype)
add_library(
${PROJECT_NAME}
./lib/freetype_lib.cpp
./lib/freetype_face.cpp
./lib/freetype_utils.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_include_directories(
${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(
${PROJECT_NAME}
PUBLIC
Freetype::Freetype
Boost::headers
)

View File

@ -0,0 +1,57 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#pragma once
#include <memory>
#include <functional>
#include <string>
#include <boost/gil/image.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
namespace bmrshared
{
class freetype_lib;
class freetype_face final : public std::enable_shared_from_this<freetype_face>
{
using character_index = FT_ULong;
using pixel_size = FT_UInt;
public:
freetype_face(std::shared_ptr<freetype_lib> lib,
const std::string& font_path,
pixel_size pixel_width,
pixel_size pixel_height,
bool render_monochrome
);
~freetype_face();
FT_BBox get_boundbox() const;
FT_Glyph_Metrics get_dimensions(character_index character_index) const;
void render(
character_index character_index,
std::function<void(int x,
int y,
const uint8_t level
)> painter);
private:
FT_Int32 get_load_flags() const;
FT_Render_Mode get_render_mode() const;
private:
std::shared_ptr<freetype_lib> m_lib;
FT_Face m_face;
bool m_renderMonochrome;
};
}

View File

@ -0,0 +1,26 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#pragma once
#include <memory>
#include <ft2build.h>
#include FT_FREETYPE_H
namespace bmrshared
{
class freetype_lib final : public std::enable_shared_from_this<freetype_lib>
{
public:
freetype_lib();
~freetype_lib();
FT_Library get_ft_library();
private:
FT_Library m_library;
};
}

View File

@ -0,0 +1,14 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#pragma once
namespace bmrshared
{
}

View File

@ -0,0 +1,23 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#pragma once
#include <bmrshared/freetype_face.hpp>
#include <string_view>
namespace bmrshared
{
struct dimensions final
{
FT_Pos width;
FT_Pos height;
FT_Pos vertOriginOffsetY;
};
dimensions freetype_calculate_dimensions(freetype_face& face, std::string_view text);
void freetype_paint(freetype_face& face, std::string_view text, const std::function<void(int x, int y, uint8_t level)>& paintfn);
}

View File

@ -0,0 +1,102 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#include <bmrshared/freetype_face.hpp>
#include <bmrshared/freetype_lib.hpp>
#include <algorithm>
using namespace bmrshared;
freetype_face::freetype_face(
std::shared_ptr<freetype_lib> lib,
const std::string& font_path,
pixel_size pixel_width,
pixel_size pixel_height,
bool render_monochrome)
: m_lib(lib)
, m_renderMonochrome(render_monochrome)
{
FT_New_Face(
m_lib->get_ft_library(),
font_path.c_str(),
0,
&m_face);
FT_Set_Pixel_Sizes(
m_face,
pixel_width,
pixel_height);
}
freetype_face::~freetype_face()
{
FT_Done_Face(m_face);
}
FT_BBox freetype_face::get_boundbox() const
{
return m_face->bbox;
}
FT_Glyph_Metrics freetype_face::get_dimensions(
character_index character_index) const
{
// Load glyph and retrieve metrics.
const auto glyph_index = FT_Get_Char_Index(m_face, character_index);
FT_Load_Glyph(m_face, glyph_index, get_load_flags());
FT_GlyphSlot& glyphSlot = m_face->glyph;
return glyphSlot->metrics;
}
void freetype_face::render(
FT_ULong character_index,
std::function<void(int x,
int y,
uint8_t level
)> painter)
{
// Load glyph and retrieve metrics.
const auto glyph_index = FT_Get_Char_Index(m_face, character_index);
FT_Load_Glyph(m_face, glyph_index, get_load_flags());
const FT_GlyphSlot& glyphSlot = m_face->glyph;
// render glyph
FT_Render_Glyph(glyphSlot, get_render_mode());
const FT_Bitmap& bitmap = glyphSlot->bitmap;
FT_Bitmap target;
FT_Bitmap_Init(&target);
FT_Bitmap_Convert(m_lib->get_ft_library(), &bitmap, &target, 1);
for(unsigned int x = 0; x < target.width; ++x)
{
for(unsigned int y = 0; y < target.rows; ++y)
{
const uint8_t& value = 0xff * target.buffer[x + target.width * y];
painter(x,y,value);
}
}
FT_Bitmap_Done(m_lib->get_ft_library(), &target);
}
FT_Int32 freetype_face::get_load_flags() const
{
FT_Int32 loadFlags = FT_LOAD_TARGET_NORMAL;
if (m_renderMonochrome)
{
loadFlags = FT_LOAD_TARGET_MONO;
}
return loadFlags;
}
FT_Render_Mode freetype_face::get_render_mode() const
{
return FT_RENDER_MODE_MONO;
}

View File

@ -0,0 +1,25 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#include <bmrshared/freetype_lib.hpp>
using bmrshared::freetype_lib;
freetype_lib::freetype_lib()
{
FT_Init_FreeType(&m_library);
}
freetype_lib::~freetype_lib()
{
FT_Done_FreeType(m_library);
}
FT_Library freetype_lib::get_ft_library()
{
return m_library;
}

View File

@ -0,0 +1,56 @@
// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#include <bmrshared/freetype_utils.hpp>
#include <algorithm>
#include <functional>
namespace bmrshared
{
dimensions freetype_calculate_dimensions(freetype_face& face, std::string_view text)
{
FT_Pos line_width = 0;
FT_Pos line_above_origin = 0;
FT_Pos line_below_origin = 0;
for (const char& c : text)
{
const FT_Glyph_Metrics& dim = face.get_dimensions(c);
line_width += dim.horiAdvance;
const auto char_above_origin = dim.horiBearingY;
const auto char_below_origin = dim.height - dim.horiBearingY;
line_above_origin = std::max(line_above_origin, char_above_origin);
line_below_origin = std::max(line_below_origin, char_below_origin);
}
return {.width = line_width, .height = (line_above_origin + line_below_origin), .vertOriginOffsetY = line_above_origin};
}
void freetype_paint(freetype_face& face, std::string_view text, const std::function<void(int x, int y, uint8_t level)>& paintfn)
{
FT_Pos line_x = 0;
FT_Pos char_offset_x = 0;
FT_Pos char_offset_y = 0;
auto painter_char = [&](int x, int y, uint8_t level)
{
paintfn((char_offset_x + x), (y - char_offset_y), level);
};
for( const char& c : text)
{
const FT_Glyph_Metrics& dim = face.get_dimensions(c);
char_offset_x = (line_x + (dim.horiAdvance - dim.width)/2)/64;
char_offset_y = dim.horiBearingY/64;
face.render(c, painter_char);
line_x += dim.horiAdvance;
}
}
}

View File

@ -5,7 +5,7 @@ from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps
class HelloConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "boost/1.84.0", "gtest/1.14.0", "libmagic/5.45"
requires = "boost/1.84.0", "gtest/1.14.0", "libmagic/5.45", "freetype/2.13.3", "libjpeg/9f"
generators = "CMakeDeps"
build_policy = "*"