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