Skip to content

Commit 2b3236a

Browse files
committed
initial commit
1 parent 80d14bc commit 2b3236a

14 files changed

+5046
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@
2626
*.exe
2727
*.out
2828
*.app
29+
30+
# Build environment
31+
build
32+
img2bbs

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
2+
project(img2bbs CXX)
3+
find_package(OpenCV REQUIRED)
4+
find_package(Freetype REQUIRED)
5+
set(CMAKE_BUILD_TYPE Release)
6+
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
7+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto")
8+
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed -flto")
9+
add_subdirectory(src)

build.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
[ -d build ] || mkdir build
4+
cd build
5+
cmake .. && make -j && cp -fv src/img2bbs ..

src/B2U.txt

Lines changed: 4096 additions & 0 deletions
Large diffs are not rendered by default.

src/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
include_directories(${FREETYPE_INCLUDE_DIRS})
2+
add_executable(img2bbs
3+
img2bbs.cpp
4+
5+
error.cpp
6+
library.cpp
7+
face.cpp
8+
screen.cpp
9+
)
10+
target_compile_features(img2bbs PRIVATE
11+
cxx_auto_type
12+
cxx_constexpr
13+
cxx_decltype
14+
cxx_defaulted_functions
15+
cxx_defaulted_move_initializers
16+
cxx_deleted_functions
17+
cxx_explicit_conversions
18+
cxx_func_identifier
19+
cxx_lambdas
20+
cxx_noexcept
21+
cxx_nullptr
22+
cxx_range_for
23+
cxx_rvalue_references
24+
cxx_variadic_macros
25+
cxx_variadic_templates
26+
cxx_template_template_parameters
27+
)
28+
target_link_libraries(img2bbs ${OpenCV_LIBS} ${FREETYPE_LIBRARIES})

src/error.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "error.hpp"
2+
3+
namespace FT {
4+
5+
const char * Error::what() const noexcept {
6+
switch (m_error) {
7+
8+
#undef __FTERRORS_H__
9+
#define FT_ERRORDEF(e, v, s) \
10+
case (e): return (s);
11+
#define FT_ERROR_START_LIST
12+
#define FT_ERROR_END_LIST
13+
#include FT_ERRORS_H
14+
15+
default: return "unknown";
16+
}
17+
}
18+
19+
} // namespace FT

src/error.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <ft2build.h>
4+
#include FT_FREETYPE_H
5+
#include <exception>
6+
7+
namespace FT {
8+
9+
class Error: public std::exception {
10+
public:
11+
Error(FT_Error error) noexcept: m_error(error) {}
12+
virtual const char * what() const noexcept;
13+
private:
14+
FT_Error m_error;
15+
};
16+
17+
} // namespace FT

src/face.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <iostream>
2+
#include "face.hpp"
3+
#include FT_GLYPH_H
4+
5+
namespace FT {
6+
7+
void Face::putText(cv::Mat & img, cv::Point pos, const std::string & text) {
8+
FT_Vector pen = {pos.x * 64, pos.y * 64};
9+
for (const auto & c: text) {
10+
FT_Vector small = {pen.x % 64, pen.y % 64};
11+
FT_Set_Transform(m_face, nullptr, &small);
12+
auto glyph_index = FT_Get_Char_Index(m_face, c);
13+
auto error = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);
14+
if (error) {throw Error(error);}
15+
error = FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_NORMAL);
16+
if (error) {throw Error(error);}
17+
const auto & glyph = *m_face->glyph;
18+
const auto & bitmap = glyph.bitmap;
19+
cv::Mat glyph_img(bitmap.rows, bitmap.width, CV_8UC1, bitmap.buffer, bitmap.pitch);
20+
cvtColor(glyph_img, glyph_img, CV_GRAY2RGB);
21+
glyph_img.convertTo(glyph_img, img.type());
22+
23+
cv::Mat color = cv::Mat::zeros(bitmap.rows, bitmap.width, img.type());
24+
color = cv::Scalar(255, 255, 255);
25+
26+
cv::Rect roi(cv::Size(pen.x / 64 + glyph.bitmap_left, pen.y / 64 - glyph.bitmap_top), glyph_img.size());
27+
28+
img(roi) = color.mul(glyph_img, 1.0 / 255.0) + img(roi).mul(cv::Scalar(255, 255, 255) - glyph_img, 1.0 / 255.0);
29+
30+
pen.x += glyph.advance.x;
31+
pen.y += glyph.advance.y;
32+
}
33+
}
34+
35+
void Face::putChar(cv::Mat & img, const cv::Rect & pos, const cv::Point & offset,
36+
const cv::Scalar & fgcolor, const cv::Scalar & bgcolor, uint32_t unicode) {
37+
38+
auto glyph_index = FT_Get_Char_Index(m_face, unicode);
39+
auto error = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);
40+
if (error) {throw Error(error);}
41+
42+
FT_Glyph glyph;
43+
error = FT_Get_Glyph(m_face->glyph, &glyph);
44+
if (error) {throw Error(error);}
45+
46+
FT_BBox box;
47+
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_UNSCALED, &box);
48+
FT_Done_Glyph(glyph);
49+
50+
FT_Pos x = (pos.x + offset.x + pos.width / 2) * 64 - (box.xMax - box.xMin) / 2 - box.xMin;
51+
FT_Pos y = (pos.y + offset.y + pos.height * 5 / 6) * 64;
52+
FT_Vector small = {x % 64, y % 64};
53+
FT_Set_Transform(m_face, nullptr, &small);
54+
55+
// render
56+
error = FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_NORMAL);
57+
if (error) {throw Error(error);}
58+
const auto & bitmap = m_face->glyph->bitmap;
59+
cv::Mat glyph_img(bitmap.rows, bitmap.width, CV_8UC1, bitmap.buffer, bitmap.pitch);
60+
cv::Rect roi(cv::Size(x / 64 + m_face->glyph->bitmap_left, y / 64 - m_face->glyph->bitmap_top), glyph_img.size());
61+
cv::Rect shrink1 = roi & pos;
62+
cv::Rect shrink2(shrink1.tl() - roi.tl(), cv::Point2i(glyph_img.size()) + shrink1.br() - roi.br());
63+
64+
// crop
65+
if (shrink2.area() > 0) {
66+
cvtColor(glyph_img(shrink2), glyph_img, CV_GRAY2RGB);
67+
glyph_img.convertTo(glyph_img, img.type());
68+
}
69+
70+
cv::Mat bg = cv::Mat::zeros(pos.size(), img.type());
71+
bg = bgcolor;
72+
bg.copyTo(img(pos));
73+
74+
if (shrink1.area() > 0) {
75+
cv::Mat fg = cv::Mat::zeros(glyph_img.rows, glyph_img.cols, img.type());
76+
fg = fgcolor;
77+
img(shrink1) = fg.mul(glyph_img, 1.0 / 255.0) + img(shrink1).mul(cv::Scalar(255, 255, 255) - glyph_img, 1.0 / 255.0);
78+
}
79+
}
80+
81+
void Face::putChar(cv::Mat & img, uint32_t unicode) {
82+
auto glyph_index = FT_Get_Char_Index(m_face, unicode);
83+
auto error = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);
84+
if (error) {throw Error(error);}
85+
86+
FT_Glyph glyph;
87+
error = FT_Get_Glyph(m_face->glyph, &glyph);
88+
if (error) {throw Error(error);}
89+
90+
FT_BBox box;
91+
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_UNSCALED, &box);
92+
FT_Done_Glyph(glyph);
93+
94+
FT_Pos x = (img.cols / 2) * 64 - (box.xMax - box.xMin) / 2 - box.xMin;
95+
FT_Pos y = (img.rows * 5 / 6) * 64;
96+
FT_Vector small = {x % 64, y % 64};
97+
FT_Set_Transform(m_face, nullptr, &small);
98+
99+
// render
100+
error = FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_NORMAL);
101+
if (error) {throw Error(error);}
102+
const auto & bitmap = m_face->glyph->bitmap;
103+
104+
cv::Mat glyph_img(bitmap.rows, bitmap.width, CV_8UC1, bitmap.buffer, bitmap.pitch);
105+
106+
cv::Rect roi(cv::Point(x / 64 + m_face->glyph->bitmap_left, y / 64 - m_face->glyph->bitmap_top), glyph_img.size());
107+
cv::Rect shrink1 = roi & cv::Rect(0, 0, img.cols, img.rows);
108+
cv::Rect shrink2(shrink1.tl() - roi.tl(), cv::Point(glyph_img.size()) + shrink1.br() - roi.br());
109+
110+
// crop
111+
if (shrink2.area() == 0) {
112+
return;
113+
}
114+
cvtColor(glyph_img(shrink2), glyph_img, CV_GRAY2RGB);
115+
glyph_img.convertTo(img(shrink1), img.type());
116+
}
117+
118+
} // namespace FT

src/face.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <memory>
5+
#include <opencv2/imgproc.hpp>
6+
#include "error.hpp"
7+
8+
namespace FT {
9+
10+
class Face: public std::shared_ptr<Face> {
11+
public:
12+
Face(FT_Face face) noexcept(false): m_face(face) {
13+
auto error = FT_Set_Char_Size(m_face, 0, 20 * 64, 96, 96);
14+
if (error) {throw Error(error);}
15+
}
16+
~Face() noexcept {FT_Done_Face(m_face);}
17+
void putText(cv::Mat & img, cv::Point org, const std::string & text);
18+
void putChar(cv::Mat & img, const cv::Rect & pos, const cv::Point & offset,
19+
const cv::Scalar & fgcolor, const cv::Scalar & bgcolor, uint32_t unicode);
20+
void putChar(cv::Mat & img, uint32_t unicode);
21+
private:
22+
FT_Face m_face;
23+
};
24+
25+
} // namespace FT

src/img2bbs.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <iostream>
2+
#include <fstream>
3+
#include <string>
4+
#include <opencv2/imgcodecs.hpp>
5+
#include <opencv2/imgcodecs/imgcodecs_c.h>
6+
#include <opencv2/imgproc.hpp>
7+
#include "library.hpp"
8+
#include "screen.hpp"
9+
10+
namespace {
11+
12+
static thread_local std::mt19937 rd(time(NULL));
13+
static thread_local std::uniform_int_distribution<size_t> gen_idx(0, 99);
14+
15+
cv::Mat normalize(cv::Mat &src) {
16+
src.convertTo(src, CV_64FC3);
17+
cv::Mat dst = cv::Mat::zeros(23 * kCharH, 80 * kCharW, src.type());
18+
cv::Rect roi;
19+
if (src.cols * dst.rows > src.rows * dst.cols) {
20+
roi.width = dst.cols;
21+
roi.x = 0;
22+
roi.height = src.rows * dst.cols / src.cols;
23+
roi.y = (dst.rows - roi.height) / 2;
24+
} else if (src.cols * dst.rows < src.rows * dst.cols) {
25+
roi.height = dst.rows;
26+
roi.y = 0;
27+
roi.width = src.cols * dst.rows / src.rows;
28+
roi.x = (dst.cols - roi.width) / 2;
29+
} else {
30+
roi.x = 0;
31+
roi.y = 0;
32+
roi.width = dst.cols;
33+
roi.height = dst.rows;
34+
}
35+
resize(src, dst(roi), roi.size(), 0, 0, cv::INTER_LANCZOS4);
36+
return std::move(dst);
37+
}
38+
39+
} // namespace
40+
41+
int main(int argc, char *argv[]) {
42+
if (argc != 4) return -1;
43+
44+
cv::Mat src = cv::imread(argv[1]);
45+
src = normalize(src);
46+
47+
Screen::init();
48+
49+
auto s = std::make_shared<Screen>(src);
50+
//s->sample();
51+
s->best();
52+
cv::Mat dst;
53+
s->render(dst);
54+
55+
std::cout << *s << std::endl;
56+
std::cout << s->psnr << std::endl;
57+
58+
imwrite(argv[2], dst);
59+
60+
std::ofstream fs(argv[3], std::ios::binary);
61+
fs << s->to_string();
62+
return 0;
63+
}

src/library.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include "face.hpp"
2+
#include "library.hpp"
3+
4+
namespace FT {
5+
6+
std::shared_ptr<Face> Library::new_face(const char * filepathname, FT_Long face_index) noexcept(false) {
7+
FT_Face face;
8+
auto error = FT_New_Face(m_lib, filepathname, face_index, &face);
9+
if (error) {
10+
throw Error(error);
11+
}
12+
return std::make_shared<Face>(face);
13+
}
14+
15+
} // namespace FT

src/library.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include "error.hpp"
5+
6+
namespace FT {
7+
8+
class Face;
9+
10+
class Library {
11+
public:
12+
Library() noexcept(false) {
13+
auto error = FT_Init_FreeType(&m_lib);
14+
if (error) {
15+
throw Error(error);
16+
}
17+
}
18+
~Library() noexcept {FT_Done_FreeType(m_lib);}
19+
Library(const Library&) = delete;
20+
Library& operator=(const Library&) = delete;
21+
std::shared_ptr<Face> new_face(const char * filepathname, FT_Long face_index) noexcept(false);
22+
private:
23+
FT_Library m_lib;
24+
};
25+
26+
} // namespace FT

0 commit comments

Comments
 (0)