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}