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