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
136void atlas_generate_image(Atlas* atlas)
137{
138    if (atlas->dirty == 0) return;
139
140    // Sort caracters by height
141    size_t iter = 0;
142    Glyph* glyph;
143    DynArray glyphs = dynarray_new(sizeof(Glyph*), hashmap_count(atlas->glyphs));
144    while (hashmap_iter(atlas->glyphs, &iter, (void**)&glyph))
145    {
146        dynarray_append(&glyphs, &glyph);
147    }
148    dynarray_sort(&glyphs, sort_by_height_desc);
149
150    // Calculate minimum size that fits the characters
151    uint32_t image_size = 256;
152    for (; image_size <= 8192; image_size *= 2)
153    {
154        const uint32_t stride = image_size;
155        uint32_t x = 0;
156        uint32_t y = 0;
157        uint32_t row_height = 0;
158        for (uint32_t i = 0; i < glyphs.length; i++)
159        {
160            glyph = ((Glyph**)glyphs.data)[i];
161
162            // Go to new line
163            if (x + glyph->bitmap.width > image_size)
164            {
165                x = 0;
166                y += row_height;
167            }
168            // store max row_height
169            if (x == 0)
170            {
171                row_height = glyph->bitmap.rows;
172                if (y + row_height > image_size) break;
173            }
174
175            x += glyph->bitmap.width;
176        }
177
178        if (y + row_height < image_size)
179        {
180            break;
181        }
182    }
183
184    if (image_size > 8192)
185    {
186        printf(
187            "error: font atlas too big (> 8192*8192 pixels). "
188            "Consider reducing the number of fonts, text sizes "
189            "(especially large sizes) or characters in use."
190        );
191        exit(1);
192    }
193
194    atlas->width = image_size;
195    atlas->height = image_size;
196    atlas->image = reallocarray(atlas->image, atlas->width * atlas->height, sizeof(uint8_t));
197    memset(atlas->image, 0, atlas->width * atlas->height * sizeof(uint8_t));
198
199    const uint32_t stride = image_size;
200    uint32_t x = 0;
201    uint32_t y = 0;
202    uint32_t row_height = 0;
203    for (uint32_t i = 0; i < glyphs.length; i++)
204    {
205        glyph = ((Glyph**)glyphs.data)[i];
206
207        if (x + glyph->bitmap.width > image_size)
208        {
209            x = 0;
210            y += row_height;
211        }
212        if (x == 0)
213        {
214            row_height = glyph->bitmap.rows;
215        }
216
217        copy_bitmap(glyph->bitmap, atlas->image, x, y, stride);
218        glyph->x0 = x;
219        glyph->y0 = y;
220        glyph->x1 = x + glyph->bitmap.width;
221        glyph->y1 = y + glyph->bitmap.rows;
222
223        x += glyph->bitmap.width;
224    }
225
226    dynarray_release(&glyphs);
227}
228
229void atlas_release(const Atlas* atlas)
230{
231    hashmap_free(atlas->glyphs);
232    dynarray_release(&atlas->glyph_sizes);
233    if (atlas->image != NULL)
234    {
235        free(atlas->image);
236    }
237}