CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/ext/native/tools/atlastool.cpp
Views: 1401
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 <assert.h>
22
#include <libpng17/png.h>
23
#include <ft2build.h>
24
#include <freetype/ftbitmap.h>
25
#include <set>
26
#include <map>
27
#include <vector>
28
#include <algorithm>
29
#include <string>
30
#include <cmath>
31
#include <zstd.h>
32
33
#include "Common/Render/TextureAtlas.h"
34
35
#include "Common/Data/Format/PNGLoad.h"
36
#include "Common/Data/Format/ZIMSave.h"
37
38
#include "kanjifilter.h"
39
// extracted only JIS Kanji on the CJK Unified Ideographs of UCS2. Cannot reading BlockAllocator. (texture size over)
40
//#define USE_KANJI KANJI_STANDARD | KANJI_RARELY_USED | KANJI_LEVEL4
41
// daily-use character only. However, it is too enough this.
42
//#define USE_KANJI KANJI_STANDARD (texture size over)
43
// Shift-JIS filtering. (texture size over)
44
//#define USE_KANJI KANJI_SJIS_L1 | KANJI_SJIS_L2
45
// more conpact daily-use character. but, not enough this.
46
// if when you find the unintelligible sequence of characters,
47
// add kanjiFilter Array with KANJI_LEARNING_ORDER_ADDTIONAL.
48
#define USE_KANJI KANJI_LEARNING_ORDER_ALL
49
50
#include "Common/Data/Encoding/Utf8.h"
51
52
using namespace std;
53
static int global_id;
54
static bool highcolor = false; // Low color mode is used by PPGE, can't delete.
55
56
typedef unsigned short u16;
57
58
struct CharRange : public AtlasCharRange {
59
std::set<u16> filter;
60
};
61
62
enum class Effect {
63
FX_COPY = 0,
64
FX_RED_TO_ALPHA_SOLID_WHITE = 1, // for alpha fonts
65
FX_RED_TO_INTENSITY_ALPHA_255 = 2,
66
FX_PREMULTIPLY_ALPHA = 3,
67
FX_PINK_TO_ALPHA = 4, // for alpha fonts
68
FX_INVALID = 5,
69
};
70
71
const char *effect_str[5] = {
72
"copy", "r2a", "r2i", "pre", "p2a",
73
};
74
75
Effect GetEffect(const char *text) {
76
for (int i = 0; i < 5; i++) {
77
if (!strcmp(text, effect_str[i])) {
78
return (Effect)i;
79
}
80
}
81
return Effect::FX_INVALID;
82
}
83
84
struct FontReference {
85
FontReference(string name, string file, vector<CharRange> ranges, int pixheight, float vertOffset)
86
: name_(name), file_(file), ranges_(ranges), size_(pixheight), vertOffset_(vertOffset) {
87
}
88
89
string name_;
90
string file_;
91
vector<CharRange> ranges_;
92
int size_;
93
float vertOffset_;
94
};
95
96
typedef vector<FontReference> FontReferenceList;
97
98
template<class T>
99
struct Image {
100
vector<vector<T> > dat;
101
void resize(int x, int y) {
102
dat.resize(y);
103
for (int i = 0; i < y; i++)
104
dat[i].resize(x);
105
}
106
int width() const {
107
return (int)dat[0].size();
108
}
109
int height() const {
110
return (int)dat.size();
111
}
112
void copyfrom(const Image &img, int ox, int oy, Effect effect) {
113
assert(img.dat[0].size() + ox <= dat[0].size());
114
assert(img.dat.size() + oy <= dat.size());
115
for (int y = 0; y < (int)img.dat.size(); y++) {
116
for (int x = 0; x < (int)img.dat[y].size(); x++) {
117
switch (effect) {
118
case Effect::FX_COPY:
119
dat[y + oy][ox + x] = img.dat[y][x];
120
break;
121
case Effect::FX_RED_TO_ALPHA_SOLID_WHITE:
122
dat[y + oy][ox + x] = 0x00FFFFFF | (img.dat[y][x] << 24);
123
break;
124
case Effect::FX_RED_TO_INTENSITY_ALPHA_255:
125
dat[y + oy][ox + x] = 0xFF000000 | img.dat[y][x] | (img.dat[y][x] << 8) | (img.dat[y][x] << 16);
126
break;
127
case Effect::FX_PREMULTIPLY_ALPHA:
128
{
129
unsigned int color = img.dat[y][x];
130
unsigned int a = color >> 24;
131
unsigned int r = (color & 0xFF) * a >> 8, g = (color & 0xFF00) * a >> 8, b = (color & 0xFF0000) * a >> 8;
132
color = (color & 0xFF000000) | (r & 0xFF) | (g & 0xFF00) | (b & 0xFF0000);
133
// Simulate 4444
134
color = color & 0xF0F0F0F0;
135
color |= color >> 4;
136
dat[y + oy][ox + x] = color;
137
break;
138
}
139
case Effect::FX_PINK_TO_ALPHA:
140
dat[y + oy][ox + x] = ((img.dat[y][x] & 0xFFFFFF) == 0xFF00FF) ? 0x00FFFFFF : (img.dat[y][x] | 0xFF000000);
141
break;
142
default:
143
dat[y + oy][ox + x] = 0xFFFF00FF;
144
break;
145
}
146
}
147
}
148
}
149
void set(int sx, int sy, int ex, int ey, unsigned char fil) {
150
for (int y = sy; y < ey; y++)
151
fill(dat[y].begin() + sx, dat[y].begin() + ex, fil);
152
}
153
bool LoadPNG(const char *png_name) {
154
unsigned char *img_data;
155
int w, h;
156
if (1 != pngLoad(png_name, &w, &h, &img_data)) {
157
printf("Failed to load %s\n", png_name);
158
exit(1);
159
return false;
160
}
161
dat.resize(h);
162
for (int y = 0; y < h; y++) {
163
dat[y].resize(w);
164
memcpy(&dat[y][0], img_data + 4 * y * w, 4 * w);
165
}
166
free(img_data);
167
return true;
168
}
169
void SavePNG(const char *png_name) {
170
// Save PNG
171
FILE *fil = fopen(png_name, "wb");
172
png_structp png_ptr;
173
png_infop info_ptr;
174
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
175
assert(png_ptr);
176
info_ptr = png_create_info_struct(png_ptr);
177
assert(info_ptr);
178
png_init_io(png_ptr, fil);
179
//png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
180
png_set_IHDR(png_ptr, info_ptr, (uint32_t)dat[0].size(), (uint32_t)dat.size(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
181
png_write_info(png_ptr, info_ptr);
182
for (int y = 0; y < (int)dat.size(); y++) {
183
png_write_row(png_ptr, (png_byte*)&dat[y][0]);
184
}
185
png_write_end(png_ptr, NULL);
186
png_destroy_write_struct(&png_ptr, &info_ptr);
187
}
188
void SaveZIM(const char *zim_name, int zim_format) {
189
uint8_t *image_data = new uint8_t[width() * height() * 4];
190
for (int y = 0; y < height(); y++) {
191
memcpy(image_data + y * width() * 4, &dat[y][0], width() * 4);
192
}
193
FILE *f = fopen(zim_name, "wb");
194
// SaveZIM takes ownership over image_data, there's no leak.
195
::SaveZIM(f, width(), height(), width() * 4, zim_format | ZIM_DITHER, image_data);
196
fclose(f);
197
}
198
};
199
200
template<class S, class T>
201
bool operator<(const Image<S> &lhs, const Image<T> &rhs) {
202
return lhs.dat.size() * lhs.dat[0].size() > rhs.dat.size() * rhs.dat[0].size();
203
}
204
205
struct Data {
206
// item ID
207
int id;
208
// dimensions of its spot in the world
209
int sx, sy, ex, ey;
210
// offset from the origin
211
float ox, oy;
212
float voffset; // to apply at the end
213
// distance to move the origin forward
214
float wx;
215
216
int effect;
217
int charNum;
218
};
219
220
bool operator<(const Data &lhs, const Data &rhs) {
221
return lhs.id < rhs.id; // should be unique
222
}
223
224
string out_prefix;
225
226
int NextPowerOf2(int x) {
227
int powof2 = 1;
228
// Double powof2 until >= val
229
while (powof2 < x) powof2 <<= 1;
230
return powof2;
231
}
232
233
struct Bucket {
234
vector<pair<Image<unsigned int>, Data> > items;
235
void AddItem(const Image<unsigned int> &img, const Data &dat) {
236
items.push_back(make_pair(img, dat));
237
}
238
vector<Data> Resolve(int image_width, Image<unsigned int> &dest) {
239
// Place all the little images - whatever they are.
240
// Uses greedy fill algorithm. Slow but works surprisingly well, CPUs are fast.
241
Image<unsigned char> masq;
242
masq.resize(image_width, 1);
243
dest.resize(image_width, 1);
244
sort(items.begin(), items.end());
245
for (int i = 0; i < (int)items.size(); i++) {
246
if ((i + 1) % 2000 == 0) {
247
printf("Resolving (%i / %i)\n", i, (int)items.size());
248
}
249
int idx = (int)items[i].first.dat[0].size();
250
int idy = (int)items[i].first.dat.size();
251
if (idx > 1 && idy > 1) {
252
assert(idx <= image_width);
253
for (int ty = 0; ty < 2047; ty++) {
254
if (ty + idy + 1 > (int)dest.dat.size()) {
255
masq.resize(image_width, ty + idy + 16);
256
dest.resize(image_width, ty + idy + 16);
257
}
258
// Brute force packing.
259
int sz = (int)items[i].first.dat[0].size();
260
auto &masq_ty = masq.dat[ty];
261
auto &masq_idy = masq.dat[ty + idy - 1];
262
for (int tx = 0; tx < image_width - sz; tx++) {
263
bool valid = !(masq_ty[tx] || masq_idy[tx] || masq_ty[tx + idx - 1] || masq_idy[tx + idx - 1]);
264
if (valid) {
265
for (int ity = 0; ity < idy && valid; ity++) {
266
for (int itx = 0; itx < idx && valid; itx++) {
267
if (masq.dat[ty + ity][tx + itx]) {
268
goto skip;
269
}
270
}
271
}
272
dest.copyfrom(items[i].first, tx, ty, (Effect)items[i].second.effect);
273
masq.set(tx, ty, tx + idx + 1, ty + idy + 1, 255);
274
275
items[i].second.sx = tx;
276
items[i].second.sy = ty;
277
278
items[i].second.ex = tx + idx;
279
items[i].second.ey = ty + idy;
280
281
// printf("Placed %d at %dx%d-%dx%d\n", items[i].second.id, tx, ty, tx + idx, ty + idy);
282
goto found;
283
}
284
skip:
285
;
286
}
287
}
288
found:
289
;
290
}
291
}
292
293
if ((int)dest.dat.size() > image_width * 2) {
294
printf("PACKING FAIL : height=%i", (int)dest.dat.size());
295
exit(1);
296
}
297
dest.resize(image_width, (int)dest.dat.size());
298
299
// Output the glyph data.
300
vector<Data> dats;
301
for (int i = 0; i < (int)items.size(); i++)
302
dats.push_back(items[i].second);
303
return dats;
304
}
305
};
306
307
const int supersample = 16;
308
const 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"
309
const int maxsearch = (128 * supersample + distmult - 1) / distmult;
310
311
struct Closest {
312
FT_Bitmap bmp;
313
Closest(FT_Bitmap bmp) : bmp(bmp) { }
314
float find_closest(int x, int y, char search) {
315
int best = 1 << 30;
316
for (int i = 1; i <= maxsearch; i++) {
317
if (i * i >= best)
318
break;
319
for (int f = -i; f < i; f++) {
320
int dist = i * i + f * f;
321
if (dist >= best) continue;
322
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)
323
best = dist;
324
}
325
}
326
return sqrt((float)best);
327
}
328
char safe_access(int x, int y) {
329
if (x < 0 || y < 0 || x >= (int)bmp.width || y >= (int)bmp.rows)
330
return 0;
331
return bmp.buffer[x + y * bmp.width];
332
}
333
};
334
335
typedef vector<FT_Face> FT_Face_List;
336
337
inline vector<CharRange> merge(const vector<CharRange> &a, const vector<CharRange> &b) {
338
vector<CharRange> result = a;
339
for (size_t i = 0, in = b.size(); i < in; ++i) {
340
bool insert = true;
341
for (size_t j = 0, jn = a.size(); j < jn; ++j) {
342
// Should never overlap, so same start is always a duplicate.
343
if (b[i].start == a[j].start) {
344
insert = false;
345
break;
346
}
347
}
348
349
if (insert) {
350
result.push_back(b[i]);
351
}
352
}
353
354
return result;
355
}
356
357
void RasterizeFonts(const FontReferenceList &fontRefs, vector<CharRange> &ranges, float *metrics_height, Bucket *bucket) {
358
FT_Library freetype = 0;
359
if (FT_Init_FreeType(&freetype) != 0) {
360
printf("ERROR: Failed to init freetype\n");
361
exit(1);
362
}
363
364
vector<FT_Face> fonts;
365
fonts.resize(fontRefs.size());
366
367
// The ranges may overlap, so build a list of fonts per range.
368
map<int, FT_Face_List> fontsByRange;
369
// TODO: Better way than average?
370
float totalHeight = 0.0f;
371
372
for (size_t i = 0, n = fontRefs.size(); i < n; ++i) {
373
FT_Face &font = fonts[i];
374
int err = FT_New_Face(freetype, fontRefs[i].file_.c_str(), 0, &font);
375
if (err != 0) {
376
printf("Failed to load font file %s (%d)\n", fontRefs[i].file_.c_str(), err);
377
printf("bailing");
378
exit(1);
379
}
380
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);
381
382
if (FT_Set_Pixel_Sizes(font, 0, fontRefs[i].size_ * supersample) != 0) {
383
printf("ERROR: Failed to set font size\n");
384
exit(1);
385
}
386
387
ranges = merge(ranges, fontRefs[i].ranges_);
388
for (size_t r = 0, rn = fontRefs[i].ranges_.size(); r < rn; ++r) {
389
const CharRange &range = fontRefs[i].ranges_[r];
390
fontsByRange[range.start].push_back(fonts[i]);
391
}
392
393
totalHeight += font->size->metrics.height;
394
}
395
396
// Wait what - how does this make sense?
397
*metrics_height = totalHeight / (float)fontRefs.size();
398
399
size_t missing_chars = 0;
400
401
// Convert all characters to bitmaps.
402
for (size_t r = 0, rn = ranges.size(); r < rn; r++) {
403
FT_Face_List &tryFonts = fontsByRange[ranges[r].start];
404
ranges[r].result_index = global_id;
405
for (int kar = ranges[r].start; kar < ranges[r].end; kar++) {
406
bool filtered = false;
407
if (ranges[r].filter.size()) {
408
if (ranges[r].filter.find((u16)kar) == ranges[r].filter.end())
409
filtered = true;
410
}
411
412
FT_Face font = nullptr;
413
bool foundMatch = false;
414
float vertOffset = 0;
415
for (size_t i = 0, n = tryFonts.size(); i < n; ++i) {
416
font = tryFonts[i];
417
vertOffset = fontRefs[i].vertOffset_;
418
if (FT_Get_Char_Index(font, kar) != 0) {
419
foundMatch = true;
420
break;
421
}
422
}
423
if (!foundMatch) {
424
// fprintf(stderr, "WARNING: No font contains character %x.\n", kar);
425
missing_chars++;
426
}
427
428
Image<unsigned int> img;
429
if (!foundMatch || filtered || 0 != FT_Load_Char(font, kar, FT_LOAD_RENDER | FT_LOAD_MONOCHROME)) {
430
img.resize(1, 1);
431
Data dat;
432
433
dat.id = global_id++;
434
435
dat.sx = 0;
436
dat.sy = 0;
437
dat.ex = 0;
438
dat.ey = 0;
439
dat.ox = 0;
440
dat.oy = 0;
441
dat.wx = 0;
442
dat.voffset = 0;
443
dat.charNum = kar;
444
dat.effect = (int)Effect::FX_RED_TO_ALPHA_SOLID_WHITE;
445
bucket->AddItem(img, dat);
446
continue;
447
}
448
449
// printf("%dx%d %p\n", font->glyph->bitmap.width, font->glyph->bitmap.rows, font->glyph->bitmap.buffer);
450
const int bord = (128 + distmult - 1) / distmult + 1;
451
if (font->glyph->bitmap.buffer) {
452
FT_Bitmap tempbitmap;
453
FT_Bitmap_New(&tempbitmap);
454
FT_Bitmap_Convert(freetype, &font->glyph->bitmap, &tempbitmap, 1);
455
Closest closest(tempbitmap);
456
457
// No resampling, just sets the size of the image.
458
img.resize((tempbitmap.width + supersample - 1) / supersample + bord * 2, (tempbitmap.rows + supersample - 1) / supersample + bord * 2);
459
int lmx = (int)img.dat[0].size();
460
int lmy = (int)img.dat.size();
461
462
// AA by finding distance to character. Probably a fairly decent approximation but why not do it right?
463
for (int y = 0; y < lmy; y++) {
464
int cty = (y - bord) * supersample + supersample / 2;
465
for (int x = 0; x < lmx; x++) {
466
int ctx = (x - bord) * supersample + supersample / 2;
467
float dist;
468
if (closest.safe_access(ctx, cty)) {
469
dist = closest.find_closest(ctx, cty, 0);
470
} else {
471
dist = -closest.find_closest(ctx, cty, 1);
472
}
473
dist = dist / supersample * distmult + 127.5f;
474
dist = floor(dist + 0.5f);
475
if (dist < 0) dist = 0;
476
if (dist > 255) dist = 255;
477
478
// Only set the red channel. We process when adding the image.
479
img.dat[y][x] = (unsigned char)dist;
480
}
481
}
482
FT_Bitmap_Done(freetype, &tempbitmap);
483
} else {
484
img.resize(1, 1);
485
}
486
487
Data dat;
488
489
dat.id = global_id++;
490
491
dat.sx = 0;
492
dat.sy = 0;
493
dat.ex = (int)img.dat[0].size();
494
dat.ey = (int)img.dat.size();
495
dat.ox = (float)font->glyph->metrics.horiBearingX / 64 / supersample - bord;
496
dat.oy = -(float)font->glyph->metrics.horiBearingY / 64 / supersample - bord;
497
dat.voffset = vertOffset;
498
dat.wx = (float)font->glyph->metrics.horiAdvance / 64 / supersample;
499
dat.charNum = kar;
500
501
dat.effect = (int)Effect::FX_RED_TO_ALPHA_SOLID_WHITE;
502
bucket->AddItem(img, dat);
503
}
504
}
505
506
if (missing_chars) {
507
printf("Chars not found in any font: %d\n", (int)missing_chars);
508
}
509
510
for (size_t i = 0, n = fonts.size(); i < n; ++i) {
511
FT_Done_Face(fonts[i]);
512
}
513
FT_Done_FreeType(freetype);
514
}
515
516
517
bool LoadImage(const char *imagefile, Effect effect, Bucket *bucket) {
518
Image<unsigned int> img;
519
520
bool success = false;
521
if (!strcmp(imagefile, "white.png")) {
522
img.dat.resize(16);
523
for (int i = 0; i < 16; i++) {
524
img.dat[i].resize(16);
525
for (int j = 0; j < 16; j++) {
526
img.dat[i][j] = 0xFFFFFFFF;
527
}
528
}
529
success = true;
530
} else {
531
success = img.LoadPNG(imagefile);
532
// printf("loaded image: %ix%i\n", (int)img.dat[0].size(), (int)img.dat.size());
533
}
534
if (!success) {
535
return false;
536
}
537
538
Data dat;
539
memset(&dat, 0, sizeof(dat));
540
dat.id = global_id++;
541
dat.sx = 0;
542
dat.sy = 0;
543
dat.ex = (int)img.dat[0].size();
544
dat.ey = (int)img.dat.size();
545
dat.effect = (int)effect;
546
bucket->AddItem(img, dat);
547
return true;
548
}
549
550
// Use the result array, and recorded data, to generate C++ tables for everything.
551
struct FontDesc {
552
string name;
553
554
int first_char_id = -1;
555
556
float ascend = 0.0f;
557
float descend = 0.0f;
558
float height = 0.0f;
559
560
float metrics_height = 0.0f;
561
562
std::vector<CharRange> ranges;
563
564
void ComputeHeight(const vector<Data> &results, float distmult) {
565
ascend = 0;
566
descend = 0;
567
for (size_t r = 0; r < ranges.size(); r++) {
568
for (int i = ranges[r].start; i < ranges[r].end; i++) {
569
int idx = i - ranges[r].start + ranges[r].result_index;
570
ascend = max(ascend, -results[idx].oy);
571
descend = max(descend, results[idx].ey - results[idx].sy + results[idx].oy);
572
}
573
}
574
575
height = metrics_height / 64.0f / supersample;
576
}
577
578
void OutputSelf(FILE *fil, float tw, float th, const vector<Data> &results) const {
579
// Dump results as chardata.
580
fprintf(fil, "const AtlasChar font_%s_chardata[] = {\n", name.c_str());
581
int start_index = 0;
582
for (size_t r = 0; r < ranges.size(); r++) {
583
fprintf(fil, "// RANGE: 0x%x - 0x%x, start %d, result %d\n", ranges[r].start, ranges[r].end, start_index, ranges[r].result_index);
584
for (int i = ranges[r].start; i < ranges[r].end; i++) {
585
int idx = i - ranges[r].start + ranges[r].result_index;
586
fprintf(fil, " {%ff, %ff, %ff, %ff, %1.4ff, %1.4ff, %1.4ff, %i, %i}, // %04x\n",
587
/*results[i].id, */
588
results[idx].sx / tw,
589
results[idx].sy / th,
590
results[idx].ex / tw,
591
results[idx].ey / th,
592
results[idx].ox,
593
results[idx].oy + results[idx].voffset,
594
results[idx].wx,
595
results[idx].ex - results[idx].sx, results[idx].ey - results[idx].sy,
596
results[idx].charNum);
597
}
598
start_index += ranges[r].end - ranges[r].start;
599
}
600
fprintf(fil, "};\n");
601
602
fprintf(fil, "const AtlasCharRange font_%s_ranges[] = {\n", name.c_str());
603
// Write range information.
604
start_index = 0;
605
for (size_t r = 0; r < ranges.size(); r++) {
606
int first_char_id = ranges[r].start;
607
int last_char_id = ranges[r].end;
608
fprintf(fil, " { %i, %i, %i },\n", first_char_id, last_char_id, start_index);
609
start_index += last_char_id - first_char_id;
610
}
611
fprintf(fil, "};\n");
612
613
fprintf(fil, "const AtlasFont font_%s = {\n", name.c_str());
614
fprintf(fil, " %ff, // padding\n", height - ascend - descend);
615
fprintf(fil, " %ff, // height\n", ascend + descend);
616
fprintf(fil, " %ff, // ascend\n", ascend);
617
fprintf(fil, " %ff, // distslope\n", distmult / 256.0);
618
fprintf(fil, " font_%s_chardata,\n", name.c_str());
619
fprintf(fil, " font_%s_ranges,\n", name.c_str());
620
fprintf(fil, " %i,\n", (int)ranges.size());
621
fprintf(fil, " \"%s\", // name\n", name.c_str());
622
fprintf(fil, "};\n");
623
}
624
625
void OutputIndex(FILE *fil) const {
626
fprintf(fil, " &font_%s,\n", name.c_str());
627
}
628
629
void OutputHeader(FILE *fil, int index) const {
630
fprintf(fil, "#define %s %i\n", name.c_str(), index);
631
}
632
633
AtlasFontHeader GetHeader() const {
634
int numChars = 0;
635
for (size_t r = 0; r < ranges.size(); r++) {
636
numChars += ranges[r].end - ranges[r].start;
637
}
638
AtlasFontHeader header;
639
header.padding = height - ascend - descend;
640
header.height = ascend + descend;
641
header.ascend = ascend;
642
header.distslope = distmult / 256.0;
643
strncpy(header.name, name.c_str(), sizeof(header.name));
644
header.name[sizeof(header.name) - 1] = '\0';
645
header.numChars = numChars;
646
header.numRanges = (int)ranges.size();
647
return header;
648
}
649
650
vector<AtlasCharRange> GetRanges() const {
651
int start_index = 0;
652
vector<AtlasCharRange> out_ranges;
653
for (size_t r = 0; r < ranges.size(); r++) {
654
int first_char_id = ranges[r].start;
655
int last_char_id = ranges[r].end;
656
AtlasCharRange range;
657
range.start = first_char_id;
658
range.end = last_char_id;
659
range.result_index = start_index;
660
start_index += last_char_id - first_char_id;
661
out_ranges.push_back(range);
662
}
663
return out_ranges;
664
}
665
666
vector<AtlasChar> GetChars(float tw, float th, const vector<Data> &results) const {
667
vector<AtlasChar> chars;
668
for (size_t r = 0; r < ranges.size(); r++) {
669
for (int i = ranges[r].start; i < ranges[r].end; i++) {
670
int idx = i - ranges[r].start + ranges[r].result_index;
671
AtlasChar c;
672
c.sx = results[idx].sx / tw; // sx, sy, ex, ey
673
c.sy = results[idx].sy / th;
674
c.ex = results[idx].ex / tw;
675
c.ey = results[idx].ey / th;
676
c.ox = results[idx].ox; // ox, oy
677
c.oy = results[idx].oy + results[idx].voffset;
678
c.wx = results[idx].wx; //wx
679
c.pw = results[idx].ex - results[idx].sx; // pw, ph
680
c.ph = results[idx].ey - results[idx].sy;
681
chars.push_back(c);
682
}
683
}
684
return chars;
685
}
686
};
687
688
struct ImageDesc {
689
string name;
690
Effect effect;
691
int result_index;
692
693
AtlasImage ToAtlasImage(float tw, float th, const vector<Data> &results) {
694
AtlasImage img;
695
int i = result_index;
696
float toffx = 0.5f / tw;
697
float toffy = 0.5f / th;
698
img.u1 = results[i].sx / tw + toffx;
699
img.v1 = results[i].sy / th + toffy;
700
img.u2 = results[i].ex / tw - toffx;
701
img.v2 = results[i].ey / th - toffy;
702
img.w = results[i].ex - results[i].sx;
703
img.h = results[i].ey - results[i].sy;
704
strncpy(img.name, name.c_str(), sizeof(img.name));
705
img.name[sizeof(img.name) - 1] = 0;
706
return img;
707
}
708
709
void OutputSelf(FILE *fil, float tw, float th, const vector<Data> &results) {
710
int i = result_index;
711
float toffx = 0.5f / tw;
712
float toffy = 0.5f / th;
713
fprintf(fil, " {%ff, %ff, %ff, %ff, %d, %d, \"%s\"},\n",
714
results[i].sx / tw + toffx,
715
results[i].sy / th + toffy,
716
results[i].ex / tw - toffx,
717
results[i].ey / th - toffy,
718
results[i].ex - results[i].sx,
719
results[i].ey - results[i].sy,
720
name.c_str());
721
}
722
723
void OutputHeader(FILE *fil, int index) {
724
fprintf(fil, "#define %s %i\n", name.c_str(), index);
725
}
726
};
727
728
729
CharRange range(int start, int end, const std::set<u16> &filter) {
730
CharRange r;
731
r.start = start;
732
r.end = end + 1;
733
r.result_index = 0;
734
r.filter = filter;
735
return r;
736
}
737
738
CharRange range(int start, int end) {
739
CharRange r;
740
r.start = start;
741
r.end = end + 1;
742
r.result_index = 0;
743
return r;
744
}
745
746
inline bool operator <(const CharRange &a, const CharRange &b) {
747
// These ranges should never overlap so this should be enough.
748
return a.start < b.start;
749
}
750
751
752
void LearnFile(const char *filename, const char *desc, std::set<u16> &chars, uint32_t lowerLimit, uint32_t upperLimit) {
753
FILE *f = fopen(filename, "rb");
754
if (f) {
755
fseek(f, 0, SEEK_END);
756
size_t sz = ftell(f);
757
fseek(f, 0, SEEK_SET);
758
char *data = new char[sz + 1];
759
fread(data, 1, sz, f);
760
fclose(f);
761
data[sz] = 0;
762
763
UTF8 utf(data);
764
int learnCount = 0;
765
while (!utf.end()) {
766
uint32_t c = utf.next();
767
if (c >= lowerLimit && c <= upperLimit) {
768
if (chars.find(c) == chars.end()) {
769
learnCount++;
770
chars.insert(c);
771
}
772
}
773
}
774
delete[] data;
775
printf("%d %s characters learned.\n", learnCount, desc);
776
}
777
}
778
779
void GetLocales(const char *locales, std::vector<CharRange> &ranges)
780
{
781
std::set<u16> kanji;
782
std::set<u16> hangul1, hangul2, hangul3;
783
for (int i = 0; i < sizeof(kanjiFilter) / sizeof(kanjiFilter[0]); i += 2)
784
{
785
// Kanji filtering.
786
if ((kanjiFilter[i + 1] & USE_KANJI) > 0) {
787
kanji.insert(kanjiFilter[i]);
788
}
789
}
790
791
LearnFile("assets/lang/zh_CN.ini", "Chinese", kanji, 0x3400, 0xFFFF);
792
LearnFile("assets/lang/zh_TW.ini", "Chinese", kanji, 0x3400, 0xFFFF);
793
LearnFile("assets/langregion.ini", "Chinese", kanji, 0x3400, 0xFFFF);
794
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul1, 0x1100, 0x11FF);
795
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul2, 0x3130, 0x318F);
796
LearnFile("assets/lang/ko_KR.ini", "Korean", hangul3, 0xAC00, 0xD7A3);
797
LearnFile("assets/langregion.ini", "Korean", hangul1, 0x1100, 0x11FF);
798
LearnFile("assets/langregion.ini", "Korean", hangul2, 0x3130, 0x318F);
799
LearnFile("assets/langregion.ini", "Korean", hangul3, 0xAC00, 0xD7A3);
800
// The end point of a range is now inclusive!
801
802
for (size_t i = 0; i < strlen(locales); i++) {
803
switch (locales[i]) {
804
case 'U': // US ASCII
805
ranges.push_back(range(32, 127));
806
break;
807
case 'W': // Latin-1 extras 1
808
ranges.push_back(range(0x80, 0x80)); // euro sign (??? It's not here?)
809
ranges.push_back(range(0xA2, 0xFF)); // 80 - A0 appears to contain nothing interesting
810
ranges.push_back(range(0x2121, 0x2122)); // TEL symbol and trademark symbol
811
break;
812
case 'E': // Latin-1 Extended A (needed for Hungarian etc)
813
ranges.push_back(range(0x100, 0x17F));
814
break;
815
case 'e': // Latin-1 Extended B (for some African and latinized asian languages?)
816
ranges.push_back(range(0x180, 0x250));
817
break;
818
case 'k': // Katakana
819
ranges.push_back(range(0x30A0, 0x30FF));
820
ranges.push_back(range(0x31F0, 0x31FF));
821
ranges.push_back(range(0xFF00, 0xFFEF)); // half-width ascii
822
break;
823
case 'h': // Hiragana
824
ranges.push_back(range(0x3041, 0x3097));
825
ranges.push_back(range(0x3099, 0x309F));
826
break;
827
case 's': // ShiftJIS symbols
828
ranges.push_back(range(0x2010, 0x2312)); // General Punctuation, Letterlike Symbols, Arrows,
829
// Mathematical Operators, Miscellaneous Technical
830
ranges.push_back(range(0x2500, 0x254B)); // Box drawing
831
ranges.push_back(range(0x25A0, 0x266F)); // Geometric Shapes, Miscellaneous Symbols
832
ranges.push_back(range(0x3231, 0x3231)); // Co,.Ltd. symbol
833
ranges.push_back(range(0x2116, 0x2116)); // "No." symbol
834
ranges.push_back(range(0x33CD, 0x33CD)); // "K.K." symbol
835
break;
836
case 'H': // Hebrew
837
ranges.push_back(range(0x0590, 0x05FF));
838
break;
839
case 'G': // Greek
840
ranges.push_back(range(0x0370, 0x03FF));
841
break;
842
case 'R': // Russian
843
ranges.push_back(range(0x0400, 0x04FF));
844
break;
845
case 'c': // All Kanji, filtered though!
846
ranges.push_back(range(0x3000, 0x303f)); // Ideographic symbols
847
ranges.push_back(range(0x4E00, 0x9FFF, kanji));
848
// ranges.push_back(range(0xFB00, 0xFAFF, kanji));
849
break;
850
case 'T': // Thai
851
ranges.push_back(range(0x0E00, 0x0E5B));
852
break;
853
case 'K': // Korean (hangul)
854
ranges.push_back(range(0xAC00, 0xD7A3, hangul3));
855
break;
856
case 'V': // Vietnamese (need 'e' too)
857
ranges.push_back(range(0x1EA0, 0x1EF9));
858
break;
859
}
860
}
861
862
ranges.push_back(range(0xFFFD, 0xFFFD));
863
std::sort(ranges.begin(), ranges.end());
864
}
865
866
static bool WriteCompressed(const void *src, size_t sz, size_t num, FILE *fp) {
867
size_t src_size = sz * num;
868
size_t compressed_size = ZSTD_compressBound(src_size);
869
uint8_t *compressed = new uint8_t[compressed_size];
870
compressed_size = ZSTD_compress(compressed, compressed_size, src, src_size, 22);
871
if (ZSTD_isError(compressed_size)) {
872
delete[] compressed;
873
return false;
874
}
875
876
uint32_t write_size = (uint32_t)compressed_size;
877
if (fwrite(&write_size, sizeof(uint32_t), 1, fp) != 1) {
878
delete[] compressed;
879
return false;
880
}
881
if (fwrite(compressed, 1, compressed_size, fp) != compressed_size) {
882
delete[] compressed;
883
return false;
884
}
885
886
delete[] compressed;
887
return true;
888
}
889
890
int main(int argc, char **argv) {
891
// initProgram(&argc, const_cast<const char ***>(&argv));
892
// /usr/share/fonts/truetype/msttcorefonts/Arial_Black.ttf
893
// /usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-R.ttf
894
if (argc < 3) {
895
printf("Not enough arguments");
896
return 1;
897
}
898
assert(argc >= 3);
899
if (argc > 3) {
900
if (!strcmp(argv[3], "8888")) {
901
highcolor = true;
902
printf("RGBA8888 enabled!\n");
903
}
904
}
905
printf("Reading script %s\n", argv[1]);
906
const char *atlas_name = argv[2];
907
string image_name = string(atlas_name) + "_atlas.zim";
908
string meta_name = string(atlas_name) + "_atlas.meta";
909
out_prefix = argv[2];
910
911
map<string, FontReferenceList> fontRefs;
912
vector<FontDesc> fonts;
913
vector<ImageDesc> images;
914
915
Bucket bucket;
916
917
char line[512]{};
918
FILE *script = fopen(argv[1], "r");
919
if (!fgets(line, 512, script)) {
920
printf("Error fgets-ing\n");
921
}
922
int image_width = 0;
923
sscanf(line, "%i", &image_width);
924
printf("Texture width: %i\n", image_width);
925
while (!feof(script)) {
926
if (!fgets(line, 511, script)) break;
927
if (!strlen(line)) break;
928
if (line[0] == '#') continue;
929
char *rest = strchr(line, ' ');
930
if (rest) {
931
*rest = 0;
932
rest++;
933
}
934
char *word = line;
935
if (!strcmp(word, "font")) {
936
// Font!
937
char fontname[256];
938
char fontfile[256];
939
char locales[256];
940
int pixheight;
941
float vertOffset = 0;
942
sscanf(rest, "%255s %255s %255s %i %f", fontname, fontfile, locales, &pixheight, &vertOffset);
943
printf("Font: %s (%s) in size %i. Locales: %s\n", fontname, fontfile, pixheight, locales);
944
945
std::vector<CharRange> ranges;
946
GetLocales(locales, ranges);
947
printf("locales fetched.\n");
948
949
FontReference fnt(fontname, fontfile, ranges, pixheight, vertOffset);
950
fontRefs[fontname].push_back(fnt);
951
} else if (!strcmp(word, "image")) {
952
char imagename[256];
953
char imagefile[256];
954
char effectname[256];
955
sscanf(rest, "%255s %255s %255s", imagename, imagefile, effectname);
956
Effect effect = GetEffect(effectname);
957
printf("Image %s with effect %s (%i)\n", imagefile, effectname, (int)effect);
958
ImageDesc desc;
959
desc.name = imagename;
960
desc.effect = effect;
961
desc.result_index = (int)bucket.items.size();
962
images.push_back(desc);
963
if (!LoadImage(imagefile, effect, &bucket)) {
964
fprintf(stderr, "Failed to load image %s\n", imagefile);
965
}
966
} else {
967
fprintf(stderr, "Warning: Failed to parse line starting with %s\n", line);
968
}
969
}
970
fclose(script);
971
972
// Script fully read, now rasterize the fonts.
973
for (auto it = fontRefs.begin(), end = fontRefs.end(); it != end; ++it) {
974
FontDesc fnt;
975
fnt.first_char_id = (int)bucket.items.size();
976
977
vector<CharRange> finalRanges;
978
float metrics_height;
979
RasterizeFonts(it->second, finalRanges, &metrics_height, &bucket);
980
printf("font rasterized.\n");
981
982
fnt.ranges = finalRanges;
983
fnt.name = it->first;
984
fnt.metrics_height = metrics_height;
985
986
fonts.push_back(fnt);
987
}
988
989
// Script read, all subimages have been generated.
990
991
// Place the subimages onto the main texture. Also writes to png.
992
Image<unsigned int> dest;
993
// Place things on the bitmap.
994
printf("Resolving...\n");
995
996
vector<Data> results = bucket.Resolve(image_width, dest);
997
if (highcolor) {
998
printf("Writing .ZIM %ix%i RGBA8888...\n", dest.width(), dest.height());
999
dest.SaveZIM(image_name.c_str(), ZIM_RGBA8888 | ZIM_ZSTD_COMPRESSED);
1000
} else {
1001
printf("Writing .ZIM %ix%i RGBA4444...\n", dest.width(), dest.height());
1002
dest.SaveZIM(image_name.c_str(), ZIM_RGBA4444 | ZIM_ZSTD_COMPRESSED);
1003
}
1004
1005
// Also save PNG for debugging.
1006
printf("Writing .PNG %s\n", (image_name + ".png").c_str());
1007
dest.SavePNG((image_name + ".png").c_str());
1008
1009
printf("Done. Outputting source and meta files %s_atlas.cpp/h/meta.\n", out_prefix.c_str());
1010
// Sort items by ID.
1011
sort(results.begin(), results.end());
1012
1013
// Save all the metadata.
1014
{
1015
FILE *meta = fopen(meta_name.c_str(), "wb");
1016
AtlasHeader header;
1017
header.magic = ATLAS_MAGIC;
1018
header.version = 1;
1019
header.numFonts = (int)fonts.size();
1020
header.numImages = (int)images.size();
1021
fwrite(&header, 1, sizeof(header), meta);
1022
// For each image
1023
AtlasImage *atalas_images = new AtlasImage[images.size()];
1024
for (int i = 0; i < (int)images.size(); i++) {
1025
atalas_images[i] = images[i].ToAtlasImage((float)dest.width(), (float)dest.height(), results);
1026
}
1027
WriteCompressed(atalas_images, sizeof(AtlasImage), images.size(), meta);
1028
// For each font
1029
for (int i = 0; i < (int)fonts.size(); i++) {
1030
auto &font = fonts[i];
1031
font.ComputeHeight(results, distmult);
1032
AtlasFontHeader font_header = font.GetHeader();
1033
fwrite(&font_header, 1, sizeof(font_header), meta);
1034
auto ranges = font.GetRanges();
1035
WriteCompressed(ranges.data(), sizeof(AtlasCharRange), ranges.size(), meta);
1036
auto chars = font.GetChars((float)dest.width(), (float)dest.height(), results);
1037
WriteCompressed(chars.data(), sizeof(AtlasChar), chars.size(), meta);
1038
}
1039
fclose(meta);
1040
}
1041
1042
FILE *cpp_file = fopen((out_prefix + "_atlas.cpp").c_str(), "wb");
1043
fprintf(cpp_file, "// C++ generated by atlastool from %s ([email protected])\n\n", argv[1]);
1044
fprintf(cpp_file, "#include \"%s\"\n\n", (out_prefix + "_atlas.h").c_str());
1045
for (int i = 0; i < (int)fonts.size(); i++) {
1046
FontDesc &xfont = fonts[i];
1047
xfont.ComputeHeight(results, distmult);
1048
xfont.OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results);
1049
}
1050
1051
if (fonts.size()) {
1052
fprintf(cpp_file, "const AtlasFont *%s_fonts[%i] = {\n", atlas_name, (int)fonts.size());
1053
for (int i = 0; i < (int)fonts.size(); i++) {
1054
fonts[i].OutputIndex(cpp_file);
1055
}
1056
fprintf(cpp_file, "};\n");
1057
}
1058
1059
if (images.size()) {
1060
fprintf(cpp_file, "const AtlasImage %s_images[%i] = {\n", atlas_name, (int)images.size());
1061
for (int i = 0; i < (int)images.size(); i++) {
1062
images[i].OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results);
1063
}
1064
fprintf(cpp_file, "};\n");
1065
}
1066
1067
fprintf(cpp_file, "const Atlas %s_atlas = {\n", atlas_name);
1068
fprintf(cpp_file, " \"%s\",\n", image_name.c_str());
1069
if (fonts.size()) {
1070
fprintf(cpp_file, " %s_fonts, %i,\n", atlas_name, (int)fonts.size());
1071
} else {
1072
fprintf(cpp_file, " 0, 0,\n");
1073
}
1074
if (images.size()) {
1075
fprintf(cpp_file, " %s_images, %i,\n", atlas_name, (int)images.size());
1076
} else {
1077
fprintf(cpp_file, " 0, 0,\n");
1078
}
1079
fprintf(cpp_file, "};\n");
1080
// Should output a list pointing to all the fonts as well.
1081
fclose(cpp_file);
1082
1083
FILE *h_file = fopen((out_prefix + "_atlas.h").c_str(), "wb");
1084
fprintf(h_file, "// Header generated by atlastool from %s ([email protected])\n\n", argv[1]);
1085
fprintf(h_file, "#pragma once\n");
1086
fprintf(h_file, "#include \"gfx/texture_atlas.h\"\n\n");
1087
if (fonts.size()) {
1088
fprintf(h_file, "// FONTS_%s\n", atlas_name);
1089
for (int i = 0; i < (int)fonts.size(); i++) {
1090
fonts[i].OutputHeader(h_file, i);
1091
}
1092
fprintf(h_file, "\n\n");
1093
}
1094
if (images.size()) {
1095
fprintf(h_file, "// IMAGES_%s\n", atlas_name);
1096
for (int i = 0; i < (int)images.size(); i++) {
1097
images[i].OutputHeader(h_file, i);
1098
}
1099
fprintf(h_file, "\n\n");
1100
}
1101
fprintf(h_file, "extern const Atlas %s_atlas;\n", atlas_name);
1102
fprintf(h_file, "extern const AtlasImage %s_images[%i];\n", atlas_name, (int)images.size());
1103
fclose(h_file);
1104
}
1105
1106