Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/ext/native/tools/atlastool.cpp
5664 views
1
// Sprite packing method borrowed from glorp engine and heavily modified.
2
// For license safety, just run this as a build tool, don't build it into your game/program.
3
// https://github.com/zorbathut/glorp
4
5
// Horrible build instructions:
6
// * Download freetype, put in ppsspp/ext as freetype/
7
// * Open tools.sln
8
// * In Code Generation on freetype, change from Multithreaded DLL to Multithreaded.
9
// * Build
10
// * Move exe file to ext/native/tools/build
11
12
// data we need to provide:
13
// sx, sy
14
// dx, dy
15
// ox, oy
16
// wx
17
18
// line height
19
// dist-per-pixel
20
21
#include <cstdio>
22
#include <assert.h>
23
#include <cstring>
24
#include <set>
25
#include <vector>
26
#include <map>
27
#include <string>
28
#include "ft2build.h"
29
#include "freetype/ftbitmap.h"
30
#include "zstd.h"
31
32
#include "Common/Render/AtlasGen.h"
33
#include "Common/Render/TextureAtlas.h"
34
#include "Common/Data/Encoding/Utf8.h"
35
#include "Common/StringUtils.h"
36
#include "Common/Common.h"
37
#include "Common/CommonTypes.h"
38
#include "Common/Data/Format/ZIMSave.h"
39
#include "kanjifilter.h"
40
41
42
constexpr int supersample = 16;
43
constexpr int distmult = 64 * 3; // this is "one pixel in the final version equals 64 difference". reduce this number to increase the "blur" radius, increase it to make things "sharper"
44
constexpr int maxsearch = (128 * supersample + distmult - 1) / distmult;
45
46
// extracted only JIS Kanji on the CJK Unified Ideographs of UCS2. Cannot reading BlockAllocator. (texture size over)
47
//#define USE_KANJI KANJI_STANDARD | KANJI_RARELY_USED | KANJI_LEVEL4
48
// daily-use character only. However, it is too enough this.
49
//#define USE_KANJI KANJI_STANDARD (texture size over)
50
// Shift-JIS filtering. (texture size over)
51
//#define USE_KANJI KANJI_SJIS_L1 | KANJI_SJIS_L2
52
// more conpact daily-use character. but, not enough this.
53
// if when you find the unintelligible sequence of characters,
54
// add kanjiFilter Array with KANJI_LEARNING_ORDER_ADDTIONAL.
55
#define USE_KANJI KANJI_LEARNING_ORDER_ALL
56
57
using namespace std;
58
59
struct ImageDesc {
60
std::string name;
61
std::string fileName;
62
int result_index;
63
};
64
65
struct CharRange : public AtlasCharRange {
66
std::set<u16> filter;
67
};
68
69
CharRange range(int start, int end, const std::set<u16> &filter) {
70
CharRange r;
71
r.start = start;
72
r.end = end + 1;
73
r.result_index = 0;
74
r.filter = filter;
75
return r;
76
}
77
78
CharRange range(int start, int end) {
79
CharRange r;
80
r.start = start;
81
r.end = end + 1;
82
r.result_index = 0;
83
return r;
84
}
85
86
inline bool operator <(const CharRange &a, const CharRange &b) {
87
// These ranges should never overlap so this should be enough.
88
return a.start < b.start;
89
}
90
91
92
struct FontReference {
93
FontReference(string name, string file, std::vector<CharRange> ranges, int pixheight, float vertOffset)
94
: name_(name), file_(file), ranges_(ranges), size_(pixheight), vertOffset_(vertOffset) {
95
}
96
97
std::string name_;
98
std::string file_;
99
std::vector<CharRange> ranges_;
100
int size_;
101
float vertOffset_;
102
};
103
104
typedef std::vector<FontReference> FontReferenceList;
105
106
typedef vector<FT_Face> FT_Face_List;
107
108
inline vector<CharRange> merge(const vector<CharRange> &a, const vector<CharRange> &b) {
109
vector<CharRange> result = a;
110
for (size_t i = 0, in = b.size(); i < in; ++i) {
111
bool insert = true;
112
for (size_t j = 0, jn = a.size(); j < jn; ++j) {
113
// Should never overlap, so same start is always a duplicate.
114
if (b[i].start == a[j].start) {
115
insert = false;
116
break;
117
}
118
}
119
120
if (insert) {
121
result.push_back(b[i]);
122
}
123
}
124
125
return result;
126
}
127
128
struct Closest {
129
FT_Bitmap bmp;
130
Closest(FT_Bitmap bmp) : bmp(bmp) {}
131
float find_closest(int x, int y, char search) {
132
int best = 1 << 30;
133
for (int i = 1; i <= maxsearch; i++) {
134
if (i * i >= best)
135
break;
136
for (int f = -i; f < i; f++) {
137
int dist = i * i + f * f;
138
if (dist >= best) continue;
139
if (safe_access(x + i, y + f) == search || safe_access(x - f, y + i) == search || safe_access(x - i, y - f) == search || safe_access(x + f, y - i) == search)
140
best = dist;
141
}
142
}
143
return (float)sqrt((float)best);
144
}
145
char safe_access(int x, int y) {
146
if (x < 0 || y < 0 || x >= (int)bmp.width || y >= (int)bmp.rows)
147
return 0;
148
return bmp.buffer[x + y * bmp.width];
149
}
150
};
151
152
void RasterizeFonts(const FontReferenceList &fontRefs, vector<CharRange> &ranges, float *metrics_height, Bucket *bucket, int &global_id) {
153
FT_Library freetype = 0;
154
if (FT_Init_FreeType(&freetype) != 0) {
155
printf("ERROR: Failed to init freetype\n");
156
exit(1);
157
}
158
159
vector<FT_Face> fonts;
160
fonts.resize(fontRefs.size());
161
162
// The ranges may overlap, so build a list of fonts per range.
163
map<int, FT_Face_List> fontsByRange;
164
// TODO: Better way than average?
165
float totalHeight = 0.0f;
166
167
for (size_t i = 0, n = fontRefs.size(); i < n; ++i) {
168
FT_Face &font = fonts[i];
169
int err = FT_New_Face(freetype, fontRefs[i].file_.c_str(), 0, &font);
170
if (err != 0) {
171
printf("Failed to load font file %s (%d)\n", fontRefs[i].file_.c_str(), err);
172
printf("bailing");
173
exit(1);
174
}
175
printf("TTF info: %d glyphs, %08x flags, %d units, %d strikes\n", (int)font->num_glyphs, (int)font->face_flags, (int)font->units_per_EM, (int)font->num_fixed_sizes);
176
177
if (FT_Set_Pixel_Sizes(font, 0, fontRefs[i].size_ * supersample) != 0) {
178
printf("ERROR: Failed to set font size\n");
179
exit(1);
180
}
181
182
ranges = merge(ranges, fontRefs[i].ranges_);
183
for (size_t r = 0, rn = fontRefs[i].ranges_.size(); r < rn; ++r) {
184
const CharRange &range = fontRefs[i].ranges_[r];
185
fontsByRange[range.start].push_back(fonts[i]);
186
}
187
188
totalHeight += font->size->metrics.height;
189
}
190
191
// Wait what - how does this make sense?
192
*metrics_height = totalHeight / (float)fontRefs.size();
193
194
size_t missing_chars = 0;
195
196
// Convert all characters to bitmaps.
197
for (size_t r = 0, rn = ranges.size(); r < rn; r++) {
198
FT_Face_List &tryFonts = fontsByRange[ranges[r].start];
199
ranges[r].result_index = global_id;
200
for (int kar = ranges[r].start; kar < ranges[r].end; kar++) {
201
bool filtered = false;
202
if (ranges[r].filter.size()) {
203
if (ranges[r].filter.find((u16)kar) == ranges[r].filter.end())
204
filtered = true;
205
}
206
207
FT_Face font = nullptr;
208
bool foundMatch = false;
209
float vertOffset = 0;
210
for (size_t i = 0, n = tryFonts.size(); i < n; ++i) {
211
font = tryFonts[i];
212
vertOffset = fontRefs[i].vertOffset_;
213
if (FT_Get_Char_Index(font, kar) != 0) {
214
foundMatch = true;
215
break;
216
}
217
}
218
if (!foundMatch) {
219
// fprintf(stderr, "WARNING: No font contains character %x.\n", kar);
220
missing_chars++;
221
}
222
223
if (!foundMatch || filtered || 0 != FT_Load_Char(font, kar, FT_LOAD_RENDER | FT_LOAD_MONOCHROME)) {
224
Image img;
225
img.resize(1, 1);
226
Data dat;
227
228
dat.id = global_id++;
229
230
dat.sx = 0;
231
dat.sy = 0;
232
dat.ex = 0;
233
dat.ey = 0;
234
dat.ox = 0;
235
dat.oy = 0;
236
dat.wx = 0;
237
dat.voffset = 0;
238
dat.charNum = kar;
239
dat.redToWhiteAlpha = true;
240
bucket->AddItem(std::move(img), dat);
241
continue;
242
}
243
Image img;
244
245
// printf("%dx%d %p\n", font->glyph->bitmap.width, font->glyph->bitmap.rows, font->glyph->bitmap.buffer);
246
const int bord = (128 + distmult - 1) / distmult + 1;
247
if (font->glyph->bitmap.buffer) {
248
FT_Bitmap tempbitmap;
249
FT_Bitmap_New(&tempbitmap);
250
FT_Bitmap_Convert(freetype, &font->glyph->bitmap, &tempbitmap, 1);
251
Closest closest(tempbitmap);
252
253
// No resampling, just sets the size of the image.
254
img.resize((tempbitmap.width + supersample - 1) / supersample + bord * 2, (tempbitmap.rows + supersample - 1) / supersample + bord * 2);
255
int lmx = (int)img.width();
256
int lmy = (int)img.height();
257
258
// AA by finding distance to character. Probably a fairly decent approximation but why not do it right?
259
for (int y = 0; y < lmy; y++) {
260
int cty = (y - bord) * supersample + supersample / 2;
261
for (int x = 0; x < lmx; x++) {
262
int ctx = (x - bord) * supersample + supersample / 2;
263
float dist;
264
if (closest.safe_access(ctx, cty)) {
265
dist = closest.find_closest(ctx, cty, 0);
266
} else {
267
dist = -closest.find_closest(ctx, cty, 1);
268
}
269
dist = dist / supersample * distmult + 127.5f;
270
dist = floorf(dist + 0.5f);
271
if (dist < 0.0f) dist = 0.0f;
272
if (dist > 255.0f) dist = 255.0f;
273
274
// Only set the red channel. We process when adding the image.
275
img.set1(x, y, (u8)dist);
276
}
277
}
278
FT_Bitmap_Done(freetype, &tempbitmap);
279
} else {
280
img.resize(1, 1);
281
}
282
283
Data dat;
284
285
dat.id = global_id++;
286
287
dat.sx = 0;
288
dat.sy = 0;
289
dat.ex = (int)img.width();
290
dat.ey = (int)img.height();
291
dat.w = dat.ex;
292
dat.h = dat.ey;
293
dat.ox = (float)font->glyph->metrics.horiBearingX / 64 / supersample - bord;
294
dat.oy = -(float)font->glyph->metrics.horiBearingY / 64 / supersample - bord;
295
dat.voffset = vertOffset;
296
dat.wx = (float)font->glyph->metrics.horiAdvance / 64 / supersample;
297
dat.charNum = kar;
298
299
dat.redToWhiteAlpha = true;
300
bucket->AddItem(std::move(img), dat);
301
}
302
}
303
304
if (missing_chars) {
305
printf("Chars not found in any font: %d\n", (int)missing_chars);
306
}
307
308
for (size_t i = 0, n = fonts.size(); i < n; ++i) {
309
FT_Done_Face(fonts[i]);
310
}
311
FT_Done_FreeType(freetype);
312
}
313
314
// Use the result array, and recorded data, to generate C++ tables for everything.
315
struct FontDesc {
316
string name;
317
318
int first_char_id = -1;
319
320
float ascend = 0.0f;
321
float descend = 0.0f;
322
float height = 0.0f;
323
324
float metrics_height = 0.0f;
325
326
std::vector<CharRange> ranges;
327
328
void ComputeHeight(const vector<Data> &results, float distmult) {
329
ascend = 0;
330
descend = 0;
331
for (size_t r = 0; r < ranges.size(); r++) {
332
for (int i = ranges[r].start; i < ranges[r].end; i++) {
333
int idx = i - ranges[r].start + ranges[r].result_index;
334
ascend = max(ascend, -results[idx].oy);
335
descend = max(descend, results[idx].ey - results[idx].sy + results[idx].oy);
336
}
337
}
338
339
height = metrics_height / 64.0f / supersample;
340
}
341
342
AtlasFontHeader GetHeader() const {
343
int numChars = 0;
344
for (size_t r = 0; r < ranges.size(); r++) {
345
numChars += ranges[r].end - ranges[r].start;
346
}
347
AtlasFontHeader header{};
348
header.padding = height - ascend - descend;
349
header.height = ascend + descend;
350
header.ascend = ascend;
351
header.distslope = distmult / 256.0;
352
truncate_cpy(header.name, name);
353
header.numChars = numChars;
354
header.numRanges = (int)ranges.size();
355
return header;
356
}
357
358
vector<AtlasCharRange> GetRanges() const {
359
int start_index = 0;
360
vector<AtlasCharRange> out_ranges;
361
for (size_t r = 0; r < ranges.size(); r++) {
362
int first_char_id = ranges[r].start;
363
int last_char_id = ranges[r].end;
364
AtlasCharRange range;
365
range.start = first_char_id;
366
range.end = last_char_id;
367
range.result_index = start_index;
368
start_index += last_char_id - first_char_id;
369
out_ranges.push_back(range);
370
}
371
return out_ranges;
372
}
373
374
vector<AtlasChar> GetChars(float tw, float th, const vector<Data> &results) const {
375
vector<AtlasChar> chars;
376
for (size_t r = 0; r < ranges.size(); r++) {
377
for (int i = ranges[r].start; i < ranges[r].end; i++) {
378
int idx = i - ranges[r].start + ranges[r].result_index;
379
AtlasChar c;
380
c.sx = results[idx].sx / tw; // sx, sy, ex, ey
381
c.sy = results[idx].sy / th;
382
c.ex = results[idx].ex / tw;
383
c.ey = results[idx].ey / th;
384
c.ox = results[idx].ox; // ox, oy
385
c.oy = results[idx].oy + results[idx].voffset;
386
c.wx = results[idx].wx; //wx
387
c.pw = results[idx].ex - results[idx].sx; // pw, ph
388
c.ph = results[idx].ey - results[idx].sy;
389
chars.push_back(c);
390
}
391
}
392
return chars;
393
}
394
};
395
396
397
void LearnFile(const char *filename, const char *desc, std::set<u16> &chars, uint32_t lowerLimit, uint32_t upperLimit) {
398
FILE *f = fopen(filename, "rb");
399
if (f) {
400
fseek(f, 0, SEEK_END);
401
size_t sz = ftell(f);
402
fseek(f, 0, SEEK_SET);
403
char *data = new char[sz + 1];
404
fread(data, 1, sz, f);
405
fclose(f);
406
data[sz] = 0;
407
408
UTF8 utf(data);
409
int learnCount = 0;
410
while (!utf.end()) {
411
uint32_t c = utf.next();
412
if (c >= lowerLimit && c <= upperLimit) {
413
if (chars.find(c) == chars.end()) {
414
learnCount++;
415
chars.insert(c);
416
}
417
}
418
}
419
delete[] data;
420
printf("%d %s characters learned.\n", learnCount, desc);
421
}
422
}
423
424
void GetLocales(const char *locales, std::vector<CharRange> &ranges)
425
{
426
std::set<u16> kanji;
427
std::set<u16> hangul1, hangul2, hangul3;
428
for (int i = 0; i < sizeof(kanjiFilter) / sizeof(kanjiFilter[0]); i += 2)
429
{
430
// Kanji filtering.
431
if ((kanjiFilter[i + 1] & USE_KANJI) > 0) {
432
kanji.insert(kanjiFilter[i]);
433
}
434
}
435
436
LearnFile("assets/lang/zh_CN.ini", "Chinese", kanji, 0x3400, 0xFFFF);
437
LearnFile("assets/lang/zh_TW.ini", "Chinese", kanji, 0x3400, 0xFFFF);
438
LearnFile("assets/langregion.ini", "Chinese", kanji, 0x3400, 0xFFFF);
439
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul1, 0x1100, 0x11FF);
440
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul2, 0x3130, 0x318F);
441
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul3, 0xAC00, 0xD7A3);
442
LearnFile("assets/langregion.ini", "Korean", hangul1, 0x1100, 0x11FF);
443
LearnFile("assets/langregion.ini", "Korean", hangul2, 0x3130, 0x318F);
444
LearnFile("assets/langregion.ini", "Korean", hangul3, 0xAC00, 0xD7A3);
445
// The end point of a range is now inclusive!
446
447
for (size_t i = 0; i < strlen(locales); i++) {
448
switch (locales[i]) {
449
case 'U': // Basic Latin (US ASCII)
450
ranges.push_back(range(0x20, 0x7E)); // 00 - 1F are C0 Controls, 7F is DEL
451
break;
452
case 'W': // Latin-1 Supplement
453
ranges.push_back(range(0xA0, 0xFF)); // 80 - 9F are C1 Controls
454
break;
455
case 'E': // Latin Extended-A (various European languages, Slavic, Hungarian, Romanian, Turkish, etc.)
456
ranges.push_back(range(0x100, 0x17F));
457
break;
458
case 'e': // Latin Extended-B (additions for European languages, some Romanized African and Asian languages)
459
ranges.push_back(range(0x180, 0x24F));
460
break;
461
case 'G': // Greek and Coptic
462
ranges.push_back(range(0x0370, 0x03FF));
463
break;
464
case 'R': // Cyrillic (Russian, Bulgarian, etc.)
465
ranges.push_back(range(0x0400, 0x04FF));
466
break;
467
case 'H': // Hebrew
468
ranges.push_back(range(0x0590, 0x05FF));
469
break;
470
471
case 'S': // Select symbols
472
ranges.push_back(range(0x2007, 0x2007)); // Figure Space (digit-wide)
473
ranges.push_back(range(0x2020, 0x2021)); // Dagger and Double Dagger
474
ranges.push_back(range(0x20AC, 0x20AC)); // Euro sign
475
ranges.push_back(range(0x2116, 0x2116)); // "No." symbol
476
ranges.push_back(range(0x2120, 0x2122)); // "SM", "TEL" and "TM" symbols
477
ranges.push_back(range(0x2139, 0x2139)); // "i" symbol
478
ranges.push_back(range(0x2300, 0x2300)); // Diameter sign
479
ranges.push_back(range(0x2302, 0x2302)); // House sign
480
// ranges.push_back(range(0x2314, 0x2314)); // Place of Interest sign
481
// ranges.push_back(range(0x2328, 0x2328)); // Keyboard sign
482
// ranges.push_back(range(0x232B, 0x232B)); // Backspace symbol
483
// ranges.push_back(range(0x23CE, 0x23CF)); // Return and Eject symbols
484
// ranges.push_back(range(0x23E9, 0x23EF)); // UI and Media Control symbols 1
485
// ranges.push_back(range(0x23F4, 0x23FA)); // UI and Media Control symbols 2
486
ranges.push_back(range(0x2610, 0x2612)); // Ballot boxes
487
ranges.push_back(range(0x26A0, 0x26A1)); // Warning and High Voltage signs
488
ranges.push_back(range(0x32CF, 0x32CF)); // "LTD" symbol
489
ranges.push_back(range(0x33C7, 0x33C7)); // "Co." symbol
490
ranges.push_back(range(0x33CD, 0x33CD)); // "K.K." symbol
491
ranges.push_back(range(0xFFFD, 0xFFFD)); // "?" Replacement character
492
break;
493
494
case 'k': // Katakana
495
ranges.push_back(range(0x30A0, 0x30FF));
496
ranges.push_back(range(0x31F0, 0x31FF));
497
ranges.push_back(range(0xFF00, 0xFFEF)); // Halfwidth ascii
498
break;
499
case 'h': // Hiragana
500
ranges.push_back(range(0x3041, 0x3097));
501
ranges.push_back(range(0x3099, 0x309F));
502
break;
503
case 'J': // Shift JIS (for Japanese fonts)
504
ranges.push_back(range(0x2010, 0x2312)); // General Punctuation, Letterlike Symbols, Arrows,
505
// Mathematical Operators, Miscellaneous Technical
506
ranges.push_back(range(0x2500, 0x254B)); // Box drawing
507
ranges.push_back(range(0x25A0, 0x266F)); // Geometric Shapes, Miscellaneous Symbols
508
break;
509
case 'c': // All Kanji, filtered though!
510
ranges.push_back(range(0x3000, 0x303F)); // Ideographic symbols
511
ranges.push_back(range(0x4E00, 0x9FFF, kanji));
512
// ranges.push_back(range(0xFB00, 0xFAFF, kanji));
513
break;
514
case 'T': // Thai
515
ranges.push_back(range(0x0E00, 0x0E5B));
516
break;
517
case 'K': // Korean (hangul)
518
ranges.push_back(range(0xAC00, 0xD7A3, hangul3));
519
break;
520
case 'V': // Vietnamese (need 'e' too)
521
ranges.push_back(range(0x1EA0, 0x1EF9));
522
break;
523
}
524
}
525
526
ranges.push_back(range(0xFFFD, 0xFFFD));
527
std::sort(ranges.begin(), ranges.end());
528
}
529
530
static bool WriteCompressed(const void *src, size_t sz, size_t num, FILE *fp) {
531
size_t src_size = sz * num;
532
size_t compressed_size = ZSTD_compressBound(src_size);
533
uint8_t *compressed = new uint8_t[compressed_size];
534
compressed_size = ZSTD_compress(compressed, compressed_size, src, src_size, 22);
535
if (ZSTD_isError(compressed_size)) {
536
delete[] compressed;
537
return false;
538
}
539
540
uint32_t write_size = (uint32_t)compressed_size;
541
if (fwrite(&write_size, sizeof(uint32_t), 1, fp) != 1) {
542
delete[] compressed;
543
return false;
544
}
545
if (fwrite(compressed, 1, compressed_size, fp) != compressed_size) {
546
delete[] compressed;
547
return false;
548
}
549
550
delete[] compressed;
551
return true;
552
}
553
554
//argv[1], argv[2], argv[3]
555
int GenerateFromScript(const char *script_file, const char *atlas_name, bool highcolor) {
556
map<string, FontReferenceList> fontRefs;
557
vector<FontDesc> fonts;
558
vector<ImageDesc> images;
559
560
int global_id = 0;
561
562
std::string image_name = string(atlas_name) + "_atlas.zim";
563
std::string meta_name = string(atlas_name) + "_atlas.meta";
564
565
const std::string out_prefix = atlas_name;
566
567
char line[512]{};
568
FILE *script = fopen(script_file, "r");
569
if (!fgets(line, 512, script)) {
570
printf("Error fgets-ing\n");
571
}
572
int image_width = 0;
573
sscanf(line, "%i", &image_width);
574
printf("Texture width: %i\n", image_width);
575
while (!feof(script)) {
576
if (!fgets(line, 511, script)) break;
577
if (!strlen(line)) break;
578
if (line[0] == '#') continue;
579
char *rest = strchr(line, ' ');
580
if (rest) {
581
*rest = 0;
582
rest++;
583
}
584
char *word = line;
585
if (!strcmp(word, "font")) {
586
// Font!
587
char fontname[256];
588
char fontfile[256];
589
char locales[256];
590
int pixheight;
591
float vertOffset = 0;
592
sscanf(rest, "%255s %255s %255s %i %f", fontname, fontfile, locales, &pixheight, &vertOffset);
593
printf("Font: %s (%s) in size %i. Locales: %s\n", fontname, fontfile, pixheight, locales);
594
595
std::vector<CharRange> ranges;
596
GetLocales(locales, ranges);
597
printf("locales fetched.\n");
598
599
FontReference fnt(fontname, fontfile, ranges, pixheight, vertOffset);
600
fontRefs[fontname].push_back(fnt);
601
} else if (!strcmp(word, "image")) {
602
char imagename[256];
603
char imagefile[256];
604
char effectname[256];
605
sscanf(rest, "%255s %255s %255s", imagename, imagefile, effectname);
606
printf("Image %s\n", imagefile);
607
ImageDesc desc;
608
desc.fileName = imagefile;
609
desc.name = imagename;
610
desc.result_index = 0;
611
images.push_back(desc);
612
} else {
613
fprintf(stderr, "Warning: Failed to parse line starting with %s\n", line);
614
}
615
}
616
fclose(script);
617
618
Bucket bucket;
619
620
// Script fully read, now read images and rasterize the fonts.
621
for (auto &image : images) {
622
image.result_index = (int)bucket.data.size();
623
624
Image img;
625
bool success = img.LoadPNG(image.fileName.c_str());
626
if (!success) {
627
fprintf(stderr, "Failed to load image %s\n", image.fileName.c_str());
628
continue;
629
}
630
bucket.AddImage(std::move(img), global_id);
631
global_id++;
632
}
633
634
for (auto it = fontRefs.begin(), end = fontRefs.end(); it != end; ++it) {
635
FontDesc fnt;
636
fnt.first_char_id = (int)bucket.data.size();
637
638
vector<CharRange> finalRanges;
639
float metrics_height;
640
RasterizeFonts(it->second, finalRanges, &metrics_height, &bucket, global_id);
641
printf("font rasterized.\n");
642
643
fnt.ranges = finalRanges;
644
fnt.name = it->first;
645
fnt.metrics_height = metrics_height;
646
647
fonts.push_back(fnt);
648
}
649
650
// Script read, all subimages have been generated.
651
652
// Place the subimages onto the main texture. Also writes to png.
653
Image dest;
654
// Place things on the bitmap.
655
656
printf("Packing...\n");
657
bucket.Pack(image_width);
658
printf("Resolving...\n");
659
std::vector<Data> results = bucket.Resolve(&dest);
660
if (highcolor) {
661
printf("Writing .ZIM %ix%i RGBA8888...\n", dest.width(), dest.height());
662
dest.SaveZIM(image_name.c_str(), ZIM_RGBA8888 | ZIM_ZSTD_COMPRESSED);
663
} else {
664
printf("Writing .ZIM %ix%i RGBA4444...\n", dest.width(), dest.height());
665
dest.SaveZIM(image_name.c_str(), ZIM_RGBA4444 | ZIM_ZSTD_COMPRESSED);
666
}
667
668
// Also save PNG for debugging.
669
printf("Writing .PNG %s\n", (image_name + ".png").c_str());
670
dest.SavePNG((image_name + ".png").c_str());
671
672
printf("Done. Outputting meta file %s_atlas.meta.\n", out_prefix.c_str());
673
674
// Save all the metadata into a packed file.
675
{
676
FILE *meta = fopen(meta_name.c_str(), "wb");
677
AtlasHeader header{};
678
header.magic = ATLAS_MAGIC;
679
header.version = 1;
680
header.numFonts = (int)fonts.size();
681
header.numImages = (int)images.size();
682
fwrite(&header, 1, sizeof(header), meta);
683
// For each image
684
AtlasImage *atlas_images = new AtlasImage[images.size()];
685
for (int i = 0; i < (int)images.size(); i++) {
686
atlas_images[i] = ToAtlasImage(images[i].result_index, images[i].name, (float)dest.width(), (float)dest.height(), results);
687
}
688
WriteCompressed(atlas_images, sizeof(AtlasImage), images.size(), meta);
689
// For each font
690
for (int i = 0; i < (int)fonts.size(); i++) {
691
auto &font = fonts[i];
692
font.ComputeHeight(results, distmult);
693
AtlasFontHeader font_header = font.GetHeader();
694
fwrite(&font_header, 1, sizeof(font_header), meta);
695
auto ranges = font.GetRanges();
696
WriteCompressed(ranges.data(), sizeof(AtlasCharRange), ranges.size(), meta);
697
auto chars = font.GetChars((float)dest.width(), (float)dest.height(), results);
698
WriteCompressed(chars.data(), sizeof(AtlasChar), chars.size(), meta);
699
}
700
fclose(meta);
701
}
702
return 0;
703
}
704
705
int main(int argc, char **argv) {
706
// initProgram(&argc, const_cast<const char ***>(&argv));
707
// /usr/share/fonts/truetype/msttcorefonts/Arial_Black.ttf
708
// /usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-R.ttf
709
if (argc < 3) {
710
printf("Not enough arguments.\nSee buildatlas.sh for example.\n");
711
return 1;
712
}
713
assert(argc >= 3);
714
715
bool highcolor = false;
716
717
if (argc > 3) {
718
if (!strcmp(argv[3], "8888")) {
719
highcolor = true;
720
printf("RGBA8888 enabled!\n");
721
}
722
}
723
printf("Reading script %s\n", argv[1]);
724
725
return GenerateFromScript(argv[1], argv[2], highcolor);
726
}
727
728