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}