6#include "fifechan/imagefont.hpp"
19#include "fifechan/platform.hpp"
22#include "fifechan/color.hpp"
23#include "fifechan/exception.hpp"
24#include "fifechan/graphics.hpp"
25#include "fifechan/image.hpp"
26#include "fifechan/rectangle.hpp"
37 int const w = img->getWidth();
38 int const h = img->getHeight();
39 if (w <= 0 || h <= 0) {
40 return Color{0, 0, 0, 255};
46 bool operator<(RGB
const & o)
const
57 std::map<RGB, int> freq;
59 auto count = [&](
int x,
int y) {
60 Color const c = img->getPixel(x, y);
61 ++freq[{.r = c.r, .g = c.g, .b = c.b}];
64 for (
int x = 0; x < w; ++x) {
68 for (
int y = 1; y < h - 1; ++y) {
73 RGB best = {.r = 0, .g = 0, .b = 0};
75 for (
auto const & p : freq) {
76 if (p.second > maxCount) {
81 return Color{best.r, best.g, best.b,
static_cast<uint8_t
>(255)};
86 switch (cfg.strategy) {
87 case SeparatorStrategy::ExplicitColor:
88 return cfg.explicitSeparator;
89 case SeparatorStrategy::BorderDominant:
90 return getBorderDominantColor(img);
91 case SeparatorStrategy::PixelAtOrigin:
92 case SeparatorStrategy::Auto:
94 return img->getPixel(0, 0);
98 bool isSeparator(
Color const & p,
Color const & sep)
100 return p.
r == sep.r && p.g == sep.g && p.b == sep.b;
103 std::vector<Rectangle> scanGlyphs(
104 Image* img,
int expectedCount,
Color const & sep,
int padding,
bool )
106 int const w = img->getWidth();
107 int const h = img->getHeight();
108 std::vector<Rectangle> found;
111 bool foundStart =
false;
112 for (
int y = 0; y < h && !foundStart; ++y) {
113 for (
int x = 0; x < w; ++x) {
114 if (!isSeparator(img->getPixel(x, y), sep)) {
126 int maxRowHeight = 0;
128 while (ycur < h && std::cmp_less(found.size(), expectedCount)) {
130 for (; rowEnd < h; ++rowEnd) {
131 bool hasContent =
false;
132 for (
int x = 0; x < w; ++x) {
133 if (!isSeparator(img->getPixel(x, rowEnd), sep)) {
142 if (rowEnd == ycur) {
146 int const rowH = rowEnd - ycur;
147 maxRowHeight = std::max(maxRowHeight, rowH);
149 auto isSepCol = [&](
int col) {
151 return isSeparator(img->getPixel(col, ycur), sep) &&
152 isSeparator(img->getPixel(col, rowEnd - 1), sep);
156 while (xcur < w && std::cmp_less(found.size(), expectedCount)) {
157 while (xcur < w && isSepCol(xcur)) {
165 while (xcur < w && !isSepCol(xcur)) {
169 int const width = std::max(1, xcur - sx - (padding * 2));
170 int const height = std::max(1, rowH - (padding * 2));
171 found.emplace_back(sx + padding, ycur + padding, width, height);
187 int const expected =
static_cast<int>(glyphs.size());
192 if (config.
strategy == SeparatorStrategy::Auto &&
static_cast<int>(found.size()) < expected * 0.8) {
193 sep = getBorderDominantColor(
mImage);
197 if (std::cmp_less(found.size(), expected)) {
198 std::ostringstream os;
199 os <<
"Image " <<
mFilename <<
" is corrupt or uses wrong separator.\n"
200 <<
"Expected: " << expected <<
" glyphs, Found: " << found.size() <<
"\n"
201 <<
"Detected separator: R:" <<
static_cast<int>(sep.
r) <<
" G:" <<
static_cast<int>(sep.
g)
202 <<
" B:" <<
static_cast<int>(sep.
b) <<
"\n"
203 <<
"Suggestion: Use ExplicitColor strategy with magenta (255,0,255)";
207 for (
size_t i = 0; i < glyphs.size(); ++i) {
208 mGlyph.at(
static_cast<unsigned char>(glyphs.at(i))) = found.at(i);
212 std::cerr <<
"[ImageFont] Loaded '" <<
mFilename <<
"' ExpectedGlyphs=" << expected
213 <<
" Found=" << found.size() <<
" Separator=R:" <<
static_cast<int>(sep.
r)
214 <<
" G:" <<
static_cast<int>(sep.
g) <<
" B:" <<
static_cast<int>(sep.
b) <<
"\n";
215 for (
char const glyph : glyphs) {
216 unsigned char const c =
static_cast<unsigned char>(glyph);
218 std::cerr <<
" glyph '" << glyph <<
"' (" <<
static_cast<int>(c) <<
") -> x=" << r.
x <<
" y=" << r.
y
223 mHeight = std::accumulate(found.begin(), found.end(), 0, [](
int maxHeight,
auto const & r) {
224 return std::max(maxHeight, r.height);
227 mImage->convertToDisplayFormat();
240 for (; startColumn <
mImage->getWidth(); ++startColumn) {
241 if (separator !=
mImage->getPixel(startColumn, 0)) {
247 if (startColumn >=
mImage->getWidth()) {
248 throwException(
"Corrupt image.");
253 for (
int j = 0; j <
mImage->getHeight(); ++j) {
254 if (separator ==
mImage->getPixel(startColumn, j)) {
266 for (
char const glyph : glyphs) {
267 auto const k =
static_cast<unsigned char>(glyph);
274 mImage->convertToDisplayFormat();
283 if (image ==
nullptr) {
291 for (i = 0; i <
mImage->getWidth() && separator ==
mImage->getPixel(i, 0); ++i) {
294 if (i >=
mImage->getWidth()) {
295 throwException(
"Corrupt image.");
299 for (j = 0; j <
mImage->getHeight(); ++j) {
300 if (separator ==
mImage->getPixel(i, j)) {
309 for (i = 0; std::cmp_less(i, glyphs.size()); ++i) {
310 unsigned char const glyph = glyphs.at(i);
318 mImage->convertToDisplayFormat();
326 if (image ==
nullptr) {
331 int const expected =
static_cast<int>(glyphs.size());
336 if (config.
strategy == SeparatorStrategy::Auto &&
static_cast<int>(found.size()) < expected * 0.8) {
337 sep = getBorderDominantColor(
mImage);
341 if (std::cmp_less(found.size(), expected)) {
342 std::ostringstream os;
343 os <<
"Image " <<
mFilename <<
" is corrupt or uses wrong separator.\n"
344 <<
"Expected: " << expected <<
" glyphs, Found: " << found.size() <<
"\n"
345 <<
"Detected separator: R:" <<
static_cast<int>(sep.
r) <<
" G:" <<
static_cast<int>(sep.
g)
346 <<
" B:" <<
static_cast<int>(sep.
b) <<
"\n"
347 <<
"Suggestion: Use ExplicitColor strategy with magenta (255,0,255)";
351 for (
size_t i = 0; i < glyphs.size(); ++i) {
352 mGlyph.at(
static_cast<unsigned char>(glyphs.at(i))) = found.at(i);
355 mHeight = std::accumulate(found.begin(), found.end(), 0, [](
int maxHeight,
auto const & r) {
356 return std::max(maxHeight, r.height);
359 mImage->convertToDisplayFormat();
371 for (i = 0; separator ==
mImage->getPixel(i, 0) && i < mImage->
getWidth(); ++i) {
374 if (i >=
mImage->getWidth()) {
375 throwException(
"Corrupt image.");
379 for (j = 0; j <
mImage->getHeight(); ++j) {
380 if (separator ==
mImage->getPixel(i, j)) {
389 for (i = glyphsFrom; i < glyphsTo + 1; i++) {
396 mImage->convertToDisplayFormat();
403 std::string
const & filename,
404 unsigned char glyphsFrom,
405 unsigned char glyphsTo,
413 int const expected =
static_cast<int>(glyphsTo) -
static_cast<int>(glyphsFrom) + 1;
418 if (config.
strategy == SeparatorStrategy::Auto &&
static_cast<int>(found.size()) < expected * 0.8) {
419 sep = getBorderDominantColor(
mImage);
423 if (std::cmp_less(found.size(), expected)) {
424 std::ostringstream os;
425 os <<
"Image " <<
mFilename <<
" is corrupt or uses wrong separator.\n"
426 <<
"Expected: " << expected <<
" glyphs, Found: " << found.size() <<
"\n"
427 <<
"Detected separator: R:" <<
static_cast<int>(sep.
r) <<
" G:" <<
static_cast<int>(sep.
g)
428 <<
" B:" <<
static_cast<int>(sep.
b) <<
"\n"
429 <<
"Suggestion: Use ExplicitColor strategy with magenta (255,0,255)";
433 for (
int i = 0; i < expected; ++i) {
434 unsigned char const glyph =
static_cast<unsigned char>(
static_cast<int>(glyphsFrom) + i);
435 mGlyph.at(glyph) = found.at(i);
438 mHeight = std::accumulate(found.begin(), found.end(), 0, [](
int maxH,
auto const & r) {
439 return std::max(maxH, r.height);
442 mImage->convertToDisplayFormat();
447 ImageFont::~ImageFont()
454 if (
mGlyph.at(glyph).width == 0) {
472 if (
mGlyph.at(glyph).width == 0) {
476 mGlyph.at(
static_cast<int>(
' ')).width - 1,
477 mGlyph.at(
static_cast<int>(
' ')).height - 2);
496 for (
char const c : text) {
527 bool foundGlyphStart =
false;
529 while (!foundGlyphStart) {
530 if (x >=
mImage->getWidth()) {
534 if (y >=
mImage->getHeight()) {
535 std::ostringstream os;
536 os <<
"Image " <<
mFilename <<
" with font is corrupt near character '" << glyph <<
"'";
541 color =
mImage->getPixel(x, y);
543 foundGlyphStart = (color != separator);
545 if (!foundGlyphStart) {
552 bool foundGlyphEnd =
false;
554 while (!foundGlyphEnd) {
555 if (x + width >=
mImage->getWidth()) {
556 std::ostringstream os;
557 os <<
"Image " <<
mFilename <<
" with font is corrupt near character '" << glyph <<
"'";
561 color =
mImage->getPixel(x + width, y);
563 foundGlyphEnd = (color == separator);
565 if (!foundGlyphEnd) {
579 for (i = 0; i < text.size(); ++i) {
591 for (i = 0; i < text.size(); ++i) {
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
virtual void drawImage(Image const *image, int srcX, int srcY, int dstX, int dstY, int width, int height)=0
Draws a part of an image.
virtual void drawRectangle(Rectangle const &rectangle)=0
Draws a simple, non-filled rectangle with a one pixel width.
virtual int getRowSpacing()
Gets the space between rows in pixels.
int mHeight
Holds the height of the image font.
virtual int drawGlyph(Graphics *graphics, unsigned char glyph, int x, int y)
Draws a glyph.
int getHeight() const override
Gets the height of the glyphs in the font.
int getStringIndexAt(std::string const &text, int x) const override
Gets a string index in a string providing an x coordinate.
Rectangle scanForGlyph(unsigned char glyph, int x, int y, Color const &separator)
Scans for a certain glyph.
Image * mImage
Holds the image with the font data.
std::array< Rectangle, 256 > mGlyph
Holds the glyphs areas in the image.
std::string mFilename
Holds the filename of the image with the font data.
virtual void setRowSpacing(int spacing)
Sets the space between rows in pixels.
ImageFont(std::string const &filename, std::string const &glyphs)
Constructor.
virtual int getWidth(unsigned char glyph) const
Gets a width of a glyph in pixels.
virtual int getGlyphSpacing()
Gets the spacing between letters in pixels.
virtual void setGlyphSpacing(int spacing)
Sets the spacing between glyphs in pixels.
int mGlyphSpacing
Holds the glyph spacing of the image font.
int mRowSpacing
Holds the row spacing of the image font.
void drawString(Graphics *graphics, std::string const &text, int x, int y) override
Draws a string.
Abstract holder for image data.
Represents a rectangular area (X, Y, Width, Height).
int width
Holds the width of the rectangle.
int y
Holds the x coordinate of the rectangle.
int x
Holds the x coordinate of the rectangle.
int height
Holds the height of the rectangle.
Used replacement tokens by configure_file():
void throwException(std::string const &message, std::source_location location=std::source_location::current())
Throw an Exception capturing the current source location.
Configuration struct for ImageFont constructors.
bool verbose
If true, enable verbose debug output while scanning fonts.
SeparatorStrategy strategy
Strategy used to detect separator color in the image.
int glyphPadding
Number of pixels to pad/ignore around detected glyphs.