]> git.tdb.fi Git - ttf2png.git/blobdiff - ttf2png.c
Add support for generating distance field textures
[ttf2png.git] / ttf2png.c
index 6224fc3419436c10c94bd2e1ec0198774fdc4270..a634b2b9def92998add21bd3e7ecfebf077db243 100644 (file)
--- a/ttf2png.c
+++ b/ttf2png.c
@@ -28,7 +28,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 typedef struct sImage
 {
        unsigned w, h;
-       char *data;
+       unsigned char *data;
 } Image;
 
 typedef struct sGlyph
@@ -78,10 +78,13 @@ void sort_and_compact_ranges(Range *, unsigned *);
 int range_cmp(const void *, const void *);
 unsigned round_to_pot(unsigned);
 void *alloc_image_data(size_t, size_t);
-int init_font(Font *, FT_Face, const Range *, unsigned, bool);
-int init_glyphs(Font *, FT_Face, const Range *, bool);
-int render_grid(Font *, unsigned, unsigned, unsigned, bool);
-int render_packed(Font *, unsigned, unsigned);
+int init_font(Font *, FT_Face, const Range *, unsigned, bool, unsigned);
+int init_glyphs(Font *, FT_Face, const Range *, bool, unsigned);
+int copy_bitmap(const FT_Bitmap *, Image *);
+void propagate_distance(unsigned short *, int);
+int create_distance_field(const FT_Bitmap *, Image *, unsigned, unsigned);
+int render_grid(Font *, unsigned, unsigned, unsigned, bool, bool);
+int render_packed(Font *, unsigned, unsigned, bool);
 int save_defs(const char *, const Font *);
 int save_png(const char *, const Image *, char);
 
@@ -103,6 +106,8 @@ int main(int argc, char **argv)
        bool pack = 0;
        unsigned margin = 0;
        unsigned padding = 1;
+       bool npot = 0;
+       unsigned distfield = 0;
 
        FT_Library freetype;
        FT_Face face;
@@ -121,7 +126,7 @@ int main(int argc, char **argv)
                return 1;
        }
 
-       while((i = getopt(argc, argv, "r:s:l:c:o:atvh?ed:pim:n:")) != -1)
+       while((i = getopt(argc, argv, "r:s:l:c:o:atvh?ed:pim:n:gf:")) != -1)
        {
                switch(i)
                {
@@ -172,6 +177,12 @@ int main(int argc, char **argv)
                case 'n':
                        padding = convert_numeric_option('n', 0);
                        break;
+               case 'g':
+                       npot = 1;
+                       break;
+               case 'f':
+                       distfield = convert_numeric_option('f', 1);
+                       break;
                }
        }
        if(!strcmp(out_fn, "-"))
@@ -208,16 +219,25 @@ int main(int argc, char **argv)
                printf("Glyphs:    %ld\n", face->num_glyphs);
        }
 
-       err = FT_Set_Pixel_Sizes(face, 0, size);
+       err = FT_Set_Pixel_Sizes(face, 0, (distfield ? size*distfield : size));
        if(err)
        {
                fprintf(stderr, "Couldn't set size\n");
                return 1;
        }
 
+       if(!n_ranges)
+       {
+               ranges = malloc(sizeof(Range));
+               ranges[0].first = 0;
+               ranges[0].last = 255;
+               n_ranges = 1;
+       }
+       else
+               sort_and_compact_ranges(ranges, &n_ranges);
+
        font.size = size;
-       sort_and_compact_ranges(ranges, &n_ranges);
-       err = init_font(&font, face, ranges, n_ranges, autohinter);
+       err = init_font(&font, face, ranges, n_ranges, autohinter, distfield);
        if(err)
                return 1;
 
@@ -228,18 +248,18 @@ int main(int argc, char **argv)
        }
 
        if(pack)
-               err = render_packed(&font, margin, padding);
+               err = render_packed(&font, margin, padding, npot);
        else
-               err = render_grid(&font, cellw, cellh, cpl, seq);
+               err = render_grid(&font, cellw, cellh, cpl, seq, npot);
        if(err)
                return 1;
 
-       if(invert)
+       if(invert || distfield)
        {
                for(i=0; (unsigned)i<font.image.w*font.image.h; ++i)
                        font.image.data[i] = 255-font.image.data[i];
        }
-       err = save_png(out_fn, &font.image, alpha);
+       err = save_png(out_fn, &font.image, (alpha && !distfield));
        if(err)
                return 1;
 
@@ -251,6 +271,7 @@ int main(int argc, char **argv)
        free(font.glyphs);
        free(font.kerning);
        free(font.image.data);
+       free(ranges);
 
        FT_Done_Face(face);
        FT_Done_FreeType(freetype);
@@ -280,6 +301,8 @@ void usage(void)
                "  -p  Pack the glyphs tightly instead of in a grid\n"
                "  -m  Margin around image edges (packed mode only) [0]\n"
                "  -n  Padding between glyphs (packed mode only) [1]\n"
+               "  -g  Allow non-power-of-two result\n"
+               "  -f  Create a distance field texture\n"
                "  -d  File name for writing glyph definitions\n"
                "  -h  Print this message\n");
 }
@@ -481,13 +504,14 @@ void *alloc_image_data(size_t a, size_t b)
        return ptr;
 }
 
-int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges, bool autohinter)
+int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges, bool autohinter, unsigned distfield)
 {
        unsigned i, j;
        unsigned size = 0;
+       int scale = (distfield>0 ? distfield : 1);
 
-       font->ascent = (face->size->metrics.ascender+63)>>6;
-       font->descent = (face->size->metrics.descender+63)>>6;
+       font->ascent = (face->size->metrics.ascender/scale+63)/64;
+       font->descent = (face->size->metrics.descender/scale-63)/64;
 
        if(verbose>=1)
        {
@@ -498,7 +522,7 @@ int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges,
        font->n_glyphs = 0;
        font->glyphs = NULL;
        for(i=0; i<n_ranges; ++i)
-               if(init_glyphs(font, face, &ranges[i], autohinter))
+               if(init_glyphs(font, face, &ranges[i], autohinter, distfield))
                        return -1;
 
        if(verbose>=1)
@@ -527,7 +551,7 @@ int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges,
                                kern = &font->kerning[font->n_kerning++];
                                kern->left_code = font->glyphs[i].code;
                                kern->right_code = font->glyphs[j].code;
-                               kern->distance = kerning.x/64;
+                               kern->distance = (kerning.x/scale+32)/64;
                        }
                }
 
@@ -537,16 +561,16 @@ int init_font(Font *font, FT_Face face, const Range *ranges, unsigned n_ranges,
        return 0;
 }
 
-int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter)
+int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter, unsigned distfield)
 {
        unsigned i, j;
        unsigned size = font->n_glyphs;
+       int scale = (distfield>0 ? distfield : 1);
 
        for(i=range->first; i<=range->last; ++i)
        {
                unsigned n;
                FT_Bitmap *bmp = &face->glyph->bitmap;
-               unsigned x, y;
                int flags = 0;
                Glyph *glyph;
 
@@ -557,7 +581,7 @@ int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter)
                if(autohinter)
                        flags |= FT_LOAD_FORCE_AUTOHINT;
                FT_Load_Glyph(face, n, flags);
-               FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
+               FT_Render_Glyph(face->glyph, (distfield ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL));
 
                if(verbose>=2)
                {
@@ -577,12 +601,12 @@ int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter)
 
                                printf(" (%s)", utf8);
                        }
-                       printf(": glyph %u, size %dx%d\n", n, bmp->width, bmp->rows);
+                       printf(": glyph %u, size %dx%d\n", n, bmp->width/scale, bmp->rows/scale);
                }
 
-               if(bmp->pixel_mode!=FT_PIXEL_MODE_GRAY)
+               if(bmp->pixel_mode!=FT_PIXEL_MODE_GRAY && bmp->pixel_mode!=FT_PIXEL_MODE_MONO)
                {
-                       fprintf(stderr, "Warning: Glyph %u skipped, not grayscale\n", n);
+                       fprintf(stderr, "Warning: Glyph %u skipped, incompatible pixel mode\n", n);
                        continue;
                }
 
@@ -595,37 +619,173 @@ int init_glyphs(Font *font, FT_Face face, const Range *range, bool autohinter)
                glyph = &font->glyphs[font->n_glyphs++];
                glyph->index = n;
                glyph->code = i;
-               glyph->image.w = bmp->width;
-               glyph->image.h = bmp->rows;
-               glyph->image.data = (char *)malloc(bmp->width*bmp->rows);
-               if(!glyph->image.data)
-               {
-                       fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", bmp->width*bmp->rows);
-                       return -1;
-               }
-               glyph->offset_x = face->glyph->bitmap_left;
-               glyph->offset_y = face->glyph->bitmap_top-bmp->rows;
-               glyph->advance = (int)(face->glyph->advance.x+32)/64;
+               glyph->offset_x = (int)(face->glyph->bitmap_left+scale/2)/scale;
+               glyph->offset_y = (int)(face->glyph->bitmap_top-bmp->rows+scale/2)/scale;
+               glyph->advance = (int)(face->glyph->advance.x/scale+32)/64;
 
                /* Copy the glyph image since FreeType uses a global buffer, which would
                be overwritten by the next glyph.  Negative pitch means the scanlines
                start from the bottom. */
-               if(bmp->pitch<0)
+               if(distfield)
+               {
+                       unsigned margin = 3;
+
+                       glyph->offset_x -= margin;
+                       glyph->offset_y -= margin;
+                       create_distance_field(bmp, &glyph->image, distfield, margin);
+               }
+               else
+               {
+                       if(copy_bitmap(bmp, &glyph->image))
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+int copy_bitmap(const FT_Bitmap *bmp, Image *image)
+{
+       unsigned x, y;
+       unsigned char *src;
+       unsigned char *dst;
+
+       image->w = bmp->width;
+       image->h = bmp->rows;
+       if(!image->w || !image->h)
+       {
+               image->data = NULL;
+               return 0;
+       }
+
+       image->data = (unsigned char *)malloc(image->w*image->h);
+       if(!image->data)
+       {
+               fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", image->w*image->h);
+               return -1;
+       }
+
+       if(bmp->pitch<0)
+               src = bmp->buffer+(bmp->rows-1)*-bmp->pitch;
+       else
+               src = bmp->buffer;
+       dst = image->data;
+
+       for(y=0; y<bmp->rows; ++y)
+       {
+               if(bmp->pixel_mode==FT_PIXEL_MODE_MONO)
                {
-                       for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
-                               glyph->image.data[x+(glyph->image.h-1-y)*glyph->image.w] = bmp->buffer[x-y*bmp->pitch];
+                       for(x=0; x<bmp->width; ++x)
+                               dst[x] = ((src[x/8]&(0x80>>(x%8))) ? 0xFF : 0x00);
                }
                else
                {
-                       for(y=0; y<bmp->rows; ++y) for(x=0; x<bmp->width; ++x)
-                               glyph->image.data[x+y*glyph->image.w] = bmp->buffer[x+y*bmp->pitch];
+                       for(x=0; x<bmp->width; ++x)
+                               dst[x] = src[x];
                }
+
+               src += bmp->pitch;
+               dst += image->w;
        }
 
        return 0;
 }
 
-int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool seq)
+void propagate_distance(unsigned short *pixel, int offset)
+{
+       unsigned short *neighbor = pixel+offset;
+       if((*neighbor^*pixel)&0x8000)
+               *neighbor = (*neighbor&0x8000)+1;
+       else if((*neighbor&0x7FFF)>(*pixel&0x7FFF))
+               *neighbor = *pixel+2;
+}
+
+int create_distance_field(const FT_Bitmap *bmp, Image *image, unsigned scale, unsigned margin)
+{
+       unsigned x, y;
+       Image base_image;
+       unsigned short *distance_map;
+       unsigned map_w;
+       unsigned map_h;
+       unsigned offset;
+
+       if(!bmp->width || !bmp->rows)
+       {
+               image->w = 0;
+               image->h = 0;
+               image->data = NULL;
+               return 0;
+       }
+
+       if(copy_bitmap(bmp, &base_image))
+               return -1;
+
+       map_w = base_image.w+2*margin*scale+scale-1;
+       map_w -= map_w%scale;
+       map_h = base_image.h+2*margin*scale+scale-1;
+       map_h -= map_h%scale;
+       distance_map = (unsigned short *)malloc(map_w*map_h*sizeof(unsigned short));
+       if(!distance_map)
+       {
+               fprintf(stderr, "Cannot allocate %d bytes of memory for distance map\n", map_w*map_h);
+               free(base_image.data);
+               return -1;
+       }
+
+       image->w = map_w/scale;
+       image->h = map_h/scale;
+       image->data = (unsigned char *)malloc(image->w*image->h);
+       if(!image->data)
+       {
+               fprintf(stderr, "Cannot allocate %d bytes of memory for glyph\n", image->w*image->h);
+               free(base_image.data);
+               free(distance_map);
+               return -1;
+       }
+
+       for(x=0; x<map_w*map_h; ++x)
+               distance_map[x] = 0x7FFF;
+
+       offset = margin*scale*(map_w+1);
+       for(y=0; y<base_image.h; ++y) for(x=0; x<base_image.w; ++x)
+               distance_map[offset+x+y*map_w] |= (base_image.data[x+y*base_image.w]&0x80)<<8;
+
+       for(y=0; y<map_h; ++y)
+       {
+               for(x=0; x+1<map_w; ++x)
+                       propagate_distance(distance_map+x+y*map_w, 1);
+               for(x=map_w-1; x>0; --x)
+                       propagate_distance(distance_map+x+y*map_w, -1);
+       }
+
+       for(x=0; x<map_w; ++x)
+       {
+               for(y=0; y+1<map_h; ++y)
+                       propagate_distance(distance_map+x+y*map_w, map_w);
+               for(y=map_h-1; y>0; --y)
+                       propagate_distance(distance_map+x+y*map_w, -map_w);
+       }
+
+       offset = scale/2*(map_w+1);
+       for(y=0; y<image->h; ++y) for(x=0; x<image->w; ++x)
+       {
+               unsigned short pixel = distance_map[offset+(x+y*map_w)*scale];
+               unsigned short dist = (pixel&0x7FFF)*0x7F/(margin*scale*2+1);
+               if(dist>0x7F)
+                       dist = 0x7F;
+               if(pixel&0x8000)
+                       image->data[x+y*image->w] = 0x80+dist;
+               else
+                       image->data[x+y*image->w] = 0x7F-dist;
+       }
+
+       free(distance_map);
+       free(base_image.data);
+
+       return 0;
+}
+
+int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool seq, bool npot)
 {
        unsigned i;
        int top = 0, bot = 0;
@@ -689,13 +849,17 @@ int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool s
                first -= first%cpl;
        last = font->glyphs[font->n_glyphs-1].code;
 
-       font->image.w = round_to_pot(cpl*cellw);
+       font->image.w = cpl*cellw;
+       if(!npot)
+               font->image.w = round_to_pot(font->image.w);
        if(seq)
-               font->image.h = round_to_pot((font->n_glyphs+cpl-1)/cpl*cellh);
+               font->image.h = (font->n_glyphs+cpl-1)/cpl*cellh;
        else
-               font->image.h = round_to_pot((last-first+cpl)/cpl*cellh);
+               font->image.h = (last-first+cpl)/cpl*cellh;
+       if(!npot)
+               font->image.h = round_to_pot(font->image.h);
 
-       font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
+       font->image.data = (unsigned char *)alloc_image_data(font->image.w, font->image.h);
        if(!font->image.data)
                return -1;
        memset(font->image.data, 255, font->image.w*font->image.h);
@@ -734,7 +898,7 @@ int render_grid(Font *font, unsigned cellw, unsigned cellh, unsigned cpl, bool s
        return 0;
 }
 
-int render_packed(Font *font, unsigned margin, unsigned padding)
+int render_packed(Font *font, unsigned margin, unsigned padding, bool npot)
 {
        unsigned i;
        size_t area = 0;
@@ -765,13 +929,14 @@ int render_packed(Font *font, unsigned margin, unsigned padding)
                if(font->image.h<=font->image.w)
                        break;
        }
-       font->image.h = round_to_pot(font->image.h);
+       if(!npot)
+               font->image.h = round_to_pot(font->image.h);
 
        /* Allocate arrays for storing the image and keeping track of used pixels and
        glyphs.  Since glyphs are rectangular and the image is filled starting from
        the top, it's enough to track the number of used pixels at the top of each
        column. */
-       font->image.data = (char *)alloc_image_data(font->image.w, font->image.h);
+       font->image.data = (unsigned char *)alloc_image_data(font->image.w, font->image.h);
        if(!font->image.data)
                return -1;
        memset(font->image.data, 255, font->image.w*font->image.h);
@@ -861,7 +1026,9 @@ int render_packed(Font *font, unsigned margin, unsigned padding)
 
        /* Trim the image to the actually used size, in case the original estimate
        was too pessimistic. */
-       font->image.h = round_to_pot(used_h);
+       font->image.h = used_h;
+       if(!npot)
+               font->image.h = round_to_pot(font->image.h);
 
        free(used_glyphs);
        free(used_pixels);