src/atlas.c
1#include "internal.h"
2
3#include <png.h>
4
5uint64_t hash_glyph(const void* item, const uint64_t seed0, const uint64_t seed1)
6{
7 return hashmap_murmur((FT_Long*)item, sizeof(FT_Long), seed0, seed1);
8}
9
10int compare_glyph(const void* va, const void* vb, void* udata)
11{
12 const AlbaGlyph* a = va;
13 const AlbaGlyph* b = vb;
14 return a->character - b->character;
15}
16
17void free_glyph(void* vglyph)
18{
19 const AlbaGlyph* glyph = vglyph;
20 free(glyph->bitmap.buffer);
21}
22
23AlbaAtlas atlas_new(const uint32_t capacity)
24{
25 AlbaAtlas atlas = {0};
26 atlas.glyphs = hashmap_new(sizeof(AlbaGlyph), capacity, 0, 0, hash_glyph, compare_glyph, free_glyph, NULL);
27 if (capacity != 0)
28 {
29 atlas_reserve(&atlas, capacity);
30 }
31 return atlas;
32}
33
34void atlas_reserve(AlbaAtlas* atlas, const uint32_t capacity)
35{
36 alba_dynarray_reserve(&atlas->glyph_sizes, capacity * 4);
37}
38
39void atlas_append(AlbaAtlas* atlas, const FT_Face face, const uint32_t character)
40{
41 const FT_Long glyph_index = FT_Load_Char(face, character, FT_LOAD_RENDER);
42
43 const FT_Bitmap* bitmap = &face->glyph->bitmap;
44 AlbaGlyph glyph = {
45 .character = character,
46 .glyph_index = glyph_index,
47 .metrics = face->glyph->metrics,
48 .bitmap = *bitmap,
49 };
50
51 // Copy buffer
52 glyph.bitmap.buffer = malloc(bitmap->rows * bitmap->pitch);
53 memcpy(glyph.bitmap.buffer, bitmap->buffer, bitmap->rows * bitmap->pitch);
54
55 if (hashmap_get(atlas->glyphs, &glyph.character) == NULL)
56 {
57 hashmap_set(atlas->glyphs, &glyph);
58 atlas->dirty = 1;
59 }
60}
61
62uint32_t sort_by_height_desc(void* vpivot, void* velem)
63{
64 const AlbaGlyph** pivot = vpivot;
65 const AlbaGlyph** elem = velem;
66 return (*elem)->bitmap.rows > (*pivot)->bitmap.rows;
67}
68
69void copy_bitmap(const FT_Bitmap src, void* dst, const uint32_t x, const uint32_t y, const uint32_t dst_row_stride)
70{
71 for (uint32_t i = 0; i < src.rows; i++)
72 {
73 // memcpy(dst + x + (y + i) * dst_row_stride, src.buffer + i * src.pitch, sizeof(uint8_t) * src.pitch);
74 for (uint32_t j = 0; j < src.width; j++)
75 {
76 const uint32_t src_index = j + i * src.pitch;
77 const uint32_t dst_index = (x + j + (y + i) * dst_row_stride) * 4;
78 ((uint8_t*)dst)[dst_index] = ((uint8_t*)src.buffer)[src_index];
79 ((uint8_t*)dst)[dst_index + 1] = ((uint8_t*)src.buffer)[src_index];
80 ((uint8_t*)dst)[dst_index + 2] = ((uint8_t*)src.buffer)[src_index];
81 ((uint8_t*)dst)[dst_index + 3] = ((uint8_t*)src.buffer)[src_index];
82 }
83 }
84}
85
86void write_png(const char* output_file_path, const int width, const int height, const int depth, void* data)
87{
88 FILE* fp = fopen(output_file_path, "wb");
89 if (fp == NULL)
90 {
91 fprintf(stderr, "error: opening file '%s'.", output_file_path);
92 exit(1);
93 }
94
95 png_structp write = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
96 if (write == NULL)
97 {
98 fprintf(stderr, "error: creating png write struct.");
99 exit(1);
100 }
101 png_infop info = png_create_info_struct(write);
102 if (info == NULL)
103 {
104 png_destroy_write_struct(&write, (png_infopp)NULL);
105 fprintf(stderr, "error: creating png info struct.");
106 exit(1);
107 }
108
109 png_init_io(write, fp);
110 png_set_IHDR(
111 write, info, width, height, 8,
112 depth == 8 ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGBA,
113 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
114 );
115
116 const png_bytepp row_pointers = (png_bytepp)png_malloc(write, sizeof(png_bytepp) * height);
117 for (int i = 0; i < height; i++)
118 {
119 row_pointers[i] = (png_bytep)png_malloc(write, png_get_rowbytes(write, info));
120 }
121 for (int hi = 0; hi < height; hi++)
122 {
123 for (int wi = 0; wi < width; wi++)
124 {
125 for (int di = 0; di < depth / 8; di++)
126 {
127 row_pointers[hi][wi * depth / 8 + di] = ((unsigned char*)data)[(wi + width * hi) * depth / 8 + di];
128 }
129 }
130 }
131
132 png_write_info(write, info);
133 png_write_image(write, row_pointers);
134 png_write_end(write, info);
135 png_destroy_write_struct(&write, &info);
136
137 fclose(fp);
138}
139
140// TODO: mutex?
141void atlas_generate_image(AlbaAtlas* atlas)
142{
143 if (atlas->dirty == 0) return;
144
145 // Sort caracters by height
146 size_t iter = 0;
147 AlbaGlyph* glyph;
148 AlbaDynArray glyphs = dynarray_new(sizeof(AlbaGlyph*), hashmap_count(atlas->glyphs));
149 while (hashmap_iter(atlas->glyphs, &iter, (void**)&glyph))
150 {
151 alba_dynarray_append(&glyphs, &glyph);
152 }
153 alba_dynarray_sort(&glyphs, sort_by_height_desc);
154
155 // Calculate minimum size that fits the characters
156 uint32_t image_size = 256;
157 for (; image_size <= 8192; image_size *= 2)
158 {
159 const uint32_t stride = image_size;
160 uint32_t x = 0;
161 uint32_t y = 0;
162 uint32_t row_height = 0;
163 for (uint32_t i = 0; i < glyphs.length; i++)
164 {
165 glyph = ((AlbaGlyph**)glyphs.data)[i];
166
167 // Go to new line
168 if (x + glyph->bitmap.width > image_size)
169 {
170 x = 0;
171 y += row_height;
172 }
173 // store max row_height
174 if (x == 0)
175 {
176 row_height = glyph->bitmap.rows;
177 if (y + row_height > image_size) break;
178 }
179
180 x += glyph->bitmap.width;
181 }
182
183 if (y + row_height < image_size)
184 {
185 break;
186 }
187 }
188
189 if (image_size > 8192)
190 {
191 printf(
192 "error: font atlas too big (> 8192*8192 pixels). "
193 "Consider reducing the number of fonts, text sizes "
194 "(especially large sizes) or characters in use."
195 );
196 exit(1);
197 }
198
199 atlas->size = (AlbaVector){image_size, image_size};
200 atlas->image = reallocarray(atlas->image, atlas->size.x * atlas->size.y * 4, sizeof(uint8_t));
201 memset(atlas->image, 0, atlas->size.x * atlas->size.y * 4 * sizeof(uint8_t));
202
203 const uint32_t stride = image_size;
204 uint32_t x = 0;
205 uint32_t y = 0;
206 uint32_t row_height = 0;
207 for (uint32_t i = 0; i < glyphs.length; i++)
208 {
209 glyph = ((AlbaGlyph**)glyphs.data)[i];
210
211 if (x + glyph->bitmap.width > image_size)
212 {
213 x = 0;
214 y += row_height;
215 }
216 if (x == 0)
217 {
218 row_height = glyph->bitmap.rows;
219 }
220
221 copy_bitmap(glyph->bitmap, atlas->image, x, y, stride);
222 glyph->start = (AlbaVector){
223 ((float)x) / image_size,
224 ((float)y) / image_size,
225 };
226 glyph->end = (AlbaVector){
227 ((float)(x + glyph->bitmap.width)) / image_size,
228 ((float)(y + glyph->bitmap.rows)) / image_size,
229 };
230
231 x += glyph->bitmap.width;
232 }
233
234 // write_png("test.png", image_size, image_size, 32, atlas->image);
235
236 alba_dynarray_release(&glyphs);
237
238 atlas->dirty = 0;
239}
240
241void atlas_release(const AlbaAtlas* atlas)
242{
243 hashmap_free(atlas->glyphs);
244 alba_dynarray_release(&atlas->glyph_sizes);
245 if (atlas->image != NULL)
246 {
247 free(atlas->image);
248 }
249}