diff --git a/ChangeLog b/ChangeLog index 0d0e8f3bc..09f468fa0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2021-06-08 Anuj Verma + + [sdf] Use 8 bits for final SDF output instead of 16bits. + + Since 8-bits is enough to represent SDF data we no longer require + 16-bits for this purpose. Also, we now normalize the output data + to use the entire 8-bit range efficiently. For example: if we use + 3.5 format with a spread of 1 we basically only use the starting + 5-bits. By normalizing we can use the entire 8-bit range. + + * include/freetype/freetype.h (FT_Render_Mode): Updated description + for `FT_RENDER_MODE_SDF` regarding this change. + + * include/freetype/ftimage.h (FT_Pixel_Mode): Removed + `FT_PIXEL_MODE_GRAY16` since no longer required. + + * include/freetype/fttypes.h (FT_F6Dot10): Removed since no longer + required. + + * src/sdf/ftsdfrend.c (ft_sdf_render, ft_bsdf_render): Allocate 8-bit + bitmap instead of 16-bit buffer. + + * src/sdf/ftsdfcommon.h (map_fixed_to_sdf): Added function to convert + 16.16 distance value to our desired format. + + * src/sdf/ftsdf.c (sdf_generate_with_overlaps, + sdf_generate_bounding_box): Use the new `map_fixed_to_sdf` function + and also use 8-bit output buffer. + + * src/sdf/ftbsdf.c (finalize_sdf): Output to a 8-bit buffer instead + of 16-bit buffer. + 2021-06-02 Ben Wagner Werner Lemberg diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h index 03438b531..ebb129f71 100644 --- a/include/freetype/freetype.h +++ b/include/freetype/freetype.h @@ -3302,19 +3302,46 @@ FT_BEGIN_HEADER * pixels and use the @FT_PIXEL_MODE_LCD_V mode. * * FT_RENDER_MODE_SDF :: - * This mode corresponds to 16-bit signed distance fields (SDF) + * This mode corresponds to 8-bit signed distance fields (SDF) * bitmaps. Each pixel in a SDF bitmap contains information about the * nearest edge of the glyph outline. The distances are calculated * from the center of the pixel and are positive if they are filled by - * the outline (i.e., inside the outline) and negative otherwise. The - * output bitmap buffer is represented as 6.10 fixed-point values; use - * @FT_F6Dot10 and convert accordingly. + * the outline (i.e., inside the outline) and negative otherwise. Check + * the note below on how to convert the output values to usable data. * * @note: * The selected render mode only affects vector glyphs of a font. * Embedded bitmaps often have a different pixel mode like * @FT_PIXEL_MODE_MONO. You can use @FT_Bitmap_Convert to transform them * into 8-bit pixmaps. + * + * For @FT_RENDER_MODE_SDF output bitmap buffer contains normalized + * distance values that are packed into unsigned 8-bit buffer. To get + * pixel values in floating point representation use the following + * conversion: + * + * ``` + * + * + * ... + * FT_Byte buffer = glyph->bitmap->buffer; + * + * for pixel in buffer + * { + * <`sd` is the signed distance and spread is the current `spread`, + * the default spread is 2 and can be changed> + * + * float sd = (float)pixel - 128.0f; + * + * + * + * sd = ( sd / 128.0f ) * spread; + * + * + * } + * + * ``` */ typedef enum FT_Render_Mode_ { diff --git a/include/freetype/ftimage.h b/include/freetype/ftimage.h index e3cc68f6c..66a8b89aa 100644 --- a/include/freetype/ftimage.h +++ b/include/freetype/ftimage.h @@ -157,13 +157,6 @@ FT_BEGIN_HEADER * in font files according to the OpenType specification. We haven't * found a single font using this format, however. * - * FT_PIXEL_MODE_GRAY16 :: - * A 16-bit per pixel bitmap used to represent signed distances in a - * signed distance field bitmap as needed by @FT_RENDER_MODE_SDF. - * Values are represented in a 6.10 fixed-point format; this means - * that you have to divide by 1024 to get the actual data generated by - * the SDF rasterizers. - * * FT_PIXEL_MODE_LCD :: * An 8-bit bitmap, representing RGB or BGR decimated glyph images used * for display on LCD displays; the bitmap is three times wider than @@ -194,7 +187,6 @@ FT_BEGIN_HEADER FT_PIXEL_MODE_LCD, FT_PIXEL_MODE_LCD_V, FT_PIXEL_MODE_BGRA, - FT_PIXEL_MODE_GRAY16, FT_PIXEL_MODE_MAX /* do not remove */ diff --git a/include/freetype/fttypes.h b/include/freetype/fttypes.h index 3e4a474bd..d5ca1c4f4 100644 --- a/include/freetype/fttypes.h +++ b/include/freetype/fttypes.h @@ -78,7 +78,6 @@ FT_BEGIN_HEADER * FT_FWord * FT_UFWord * FT_F2Dot14 - * FT_F6Dot10 * FT_UnitVector * FT_F26Dot6 * FT_Data @@ -265,17 +264,6 @@ FT_BEGIN_HEADER typedef signed short FT_F2Dot14; - /************************************************************************** - * - * @type: - * FT_F6Dot10 - * - * @description: - * A signed 6.10 fixed-point type used for signed distance values. - */ - typedef signed short FT_F6Dot10; - - /************************************************************************** * * @type: diff --git a/src/sdf/ftbsdf.c b/src/sdf/ftbsdf.c index 97e043835..659e0de89 100644 --- a/src/sdf/ftbsdf.c +++ b/src/sdf/ftbsdf.c @@ -1092,12 +1092,12 @@ finalize_sdf( BSDF_Worker* worker, const FT_Bitmap* target ) { - FT_Error error = FT_Err_Ok; + FT_Error error = FT_Err_Ok; - FT_Int w, r; - FT_Int i, j; - FT_6D10* t_buffer; - FT_16D16 spread; + FT_Int w, r; + FT_Int i, j; + FT_SDFFormat* t_buffer; + FT_16D16 spread; if ( !worker || !target ) @@ -1108,7 +1108,7 @@ w = (int)target->width; r = (int)target->rows; - t_buffer = (FT_6D10*)target->buffer; + t_buffer = (FT_SDFFormat*)target->buffer; if ( w != worker->width || r != worker->rows ) @@ -1128,10 +1128,10 @@ { for ( i = 0; i < w; i++ ) { - FT_Int index; - FT_16D16 dist; - FT_6D10 final_dist; - FT_Char sign; + FT_Int index; + FT_16D16 dist; + FT_SDFFormat final_dist; + FT_Char sign; index = j * w + i; @@ -1144,10 +1144,6 @@ dist = square_root( dist ); #endif - /* convert from 16.16 to 6.10 */ - dist /= 64; - final_dist = (FT_6D10)(dist & 0x0000FFFF); - /* We assume that if the pixel is inside a contour */ /* its coverage value must be > 127. */ sign = worker->distance_map[index].alpha < 127 ? -1 : 1; @@ -1156,7 +1152,10 @@ if ( worker->params.flip_sign ) sign = -sign; - t_buffer[index] = final_dist * sign; + /* concatenate from 16.16 to appropriate format */ + final_dist = map_fixed_to_sdf( dist * sign, spread ); + + t_buffer[index] = final_dist; } } diff --git a/src/sdf/ftsdf.c b/src/sdf/ftsdf.c index f9b16bc6f..335b800e5 100644 --- a/src/sdf/ftsdf.c +++ b/src/sdf/ftsdf.c @@ -2897,6 +2897,10 @@ /* `sdf_generate' is not used at the moment */ #if 0 + #error "DO NOT USE THIS!" + #error "The function still output 16-bit data which might cause memory" + #error "corruption. If required I will add this later." + /************************************************************************** * * @Function: @@ -3193,7 +3197,7 @@ FT_Int sp_sq; /* max value to check */ SDF_Contour* contours; /* list of all contours */ - FT_Short* buffer; /* the bitmap buffer */ + FT_SDFFormat* buffer; /* the bitmap buffer */ /* This buffer has the same size in indices as the */ /* bitmap buffer. When we check a pixel position for */ @@ -3202,6 +3206,8 @@ /* and also determine the signs properly. */ SDF_Signed_Distance* dists = NULL; + const FT_16D16 fixed_spread = FT_INT_16D16( spread ); + if ( !shape || !bitmap ) { @@ -3229,12 +3235,12 @@ contours = shape->contours; width = (FT_Int)bitmap->width; rows = (FT_Int)bitmap->rows; - buffer = (FT_Short*)bitmap->buffer; + buffer = (FT_SDFFormat*)bitmap->buffer; if ( USE_SQUARED_DISTANCES ) - sp_sq = (FT_Int)FT_INT_16D16( spread * spread ); + sp_sq = fixed_spread * fixed_spread; else - sp_sq = (FT_Int)FT_INT_16D16( spread ); + sp_sq = fixed_spread; if ( width == 0 || rows == 0 ) { @@ -3347,21 +3353,23 @@ /* if the pixel is not set */ /* its shortest distance is more than `spread` */ if ( dists[index].sign == 0 ) - dists[index].distance = FT_INT_16D16( spread ); + dists[index].distance = fixed_spread; else current_sign = dists[index].sign; /* clamp the values */ - if ( dists[index].distance > (FT_Int)FT_INT_16D16( spread ) ) - dists[index].distance = FT_INT_16D16( spread ); + if ( dists[index].distance > fixed_spread ) + dists[index].distance = fixed_spread; - /* convert from 16.16 to 6.10 */ - dists[index].distance /= 64; + /* flip sign if required */ + dists[index].distance *= internal_params.flip_sign ? + -current_sign : + current_sign; - if ( internal_params.flip_sign ) - buffer[index] = (FT_Short)dists[index].distance * -current_sign; - else - buffer[index] = (FT_Short)dists[index].distance * current_sign; + /* concatenate to appropriate format */ + buffer[index] = map_fixed_to_sdf( + dists[index].distance, + fixed_spread ); } } @@ -3498,9 +3506,9 @@ SDF_Contour* head; /* head of the contour list */ SDF_Shape temp_shape; /* temporary shape */ - FT_Memory memory; /* to allocate memory */ - FT_6D10* t; /* target bitmap buffer */ - FT_Bool flip_sign; /* filp sign? */ + FT_Memory memory; /* to allocate memory */ + FT_SDFFormat* t; /* target bitmap buffer */ + FT_Bool flip_sign; /* filp sign? */ /* orientation of all the separate contours */ SDF_Contour_Orientation* orientations; @@ -3621,7 +3629,7 @@ shape->contours = head; /* cast the output bitmap buffer */ - t = (FT_6D10*)bitmap->buffer; + t = (FT_SDFFormat*)bitmap->buffer; /* Iterate over all pixels and combine all separate */ /* contours. These are the rules for combining: */ @@ -3636,18 +3644,18 @@ { for ( i = 0; i < width; i++ ) { - FT_Int id = j * width + i; /* index of current pixel */ - FT_Int c; /* contour iterator */ + FT_Int id = j * width + i; /* index of current pixel */ + FT_Int c; /* contour iterator */ - FT_6D10 val_c = SHRT_MIN; /* max clockwise value */ - FT_6D10 val_ac = SHRT_MAX; /* min counter-clockwise val */ + FT_SDFFormat val_c = 0; /* max clockwise value */ + FT_SDFFormat val_ac = UCHAR_MAX; /* min counter-clockwise val */ /* iterate through all the contours */ for ( c = 0; c < num_contours; c++ ) { /* current contour value */ - FT_6D10 temp = ((FT_6D10*)bitmaps[c].buffer)[id]; + FT_SDFFormat temp = ( (FT_SDFFormat*)bitmaps[c].buffer )[id]; if ( orientations[c] == SDF_ORIENTATION_CW ) @@ -3658,7 +3666,10 @@ /* Finally find the smaller of the two and assign to output. */ /* Also apply `flip_sign` if set. */ - t[id] = FT_MIN( val_c, val_ac ) * ( flip_sign ? -1 : 1 ); + t[id] = FT_MIN( val_c, val_ac ); + + if ( flip_sign ) + t[id] = invert_sign( t[id] ); } } @@ -3681,6 +3692,9 @@ } } + /* restore the `flip_sign` property */ + internal_params.flip_sign = flip_sign; + return error; } diff --git a/src/sdf/ftsdfcommon.h b/src/sdf/ftsdfcommon.h index 925a4dc3e..7ce49bb98 100644 --- a/src/sdf/ftsdfcommon.h +++ b/src/sdf/ftsdfcommon.h @@ -115,7 +115,7 @@ FT_BEGIN_HEADER typedef FT_Fixed FT_16D16; /* 16.16 fixed-point representation */ typedef FT_Fixed FT_26D6; /* 26.6 fixed-point representation */ - typedef FT_Short FT_6D10; /* 6.10 fixed-point representation */ + typedef FT_Byte FT_SDFFormat; /* format to represent SDF data */ typedef FT_BBox FT_CBox; /* control box of a curve */ @@ -162,6 +162,75 @@ FT_BEGIN_HEADER return (FT_16D16)q; } + /************************************************************************** + * + * format and sign manipulating functions + * + */ + + /* + * Convert 16.16 fixed point value to the desired output format. + * In this case we reduce 16.16 fixed point value to normalized + * 8-bit values. + * The `max_value` in the parameter is the maximum value in the + * distance field map and is equal to the spread. We normalize + * the distances using this value instead of computing the maximum + * value for the entire bitmap. + * You can use this function to map the 16.16 signed values to any + * format required. Do note that the output buffer is 8-bit, so only + * use 8-bit format for `FT_SDFFormat`, or increase buffer size in + * `ftsdfrend.c`. + */ + static FT_SDFFormat + map_fixed_to_sdf( FT_16D16 dist, FT_16D16 max_value ) + { + FT_SDFFormat out; + FT_16D16 udist; + + + /* normalize the distance values */ + dist = FT_DivFix( dist, max_value ); + + udist = dist < 0 ? -dist : dist; + + /* Reduce the distance values to 8 bits, +1/-1 in */ + /* 16.16 takes the 16th bit. So we right shift the */ + /* number by 9 to make it fit in the 7 bit range. */ + /* 1 bit is reserved for the sign. */ + udist >>= 9; + + /* Since char can only store max positive value */ + /* of 127 we need to make sure it does not wrap */ + /* around and give a negative value. */ + if ( dist > 0 && udist > 127 ) + udist = 127; + if ( dist < 0 && udist > 128 ) + udist = 128; + + /* Output the data; negative values are from [0, 127] and positive */ + /* from [128, 255]. One important thing is that negative values */ + /* are inverted here, that means [0, 128] maps to [-128, 0] linearly. */ + /* More on that in `freetype.h` near `FT_RENDER_MODE_SDF` */ + out = dist < 0 ? 128 - (FT_SDFFormat)udist : + (FT_SDFFormat)udist + 128; + return out; + } + + /* + * Invert the signed distance packed into the corresponding format. + * So if the values are negative they will become positive in the + * chosen format. + * + * [Note]: This function should only be used after converting the + * 16.16 signed distance values to `FT_SDFFormat`, if that + * conversion has not been done, then simply invert the sign + * and use the above function to pack the values. + */ + static FT_SDFFormat + invert_sign( FT_SDFFormat dist ) { + return 255 - dist; + } + FT_END_HEADER diff --git a/src/sdf/ftsdfrend.c b/src/sdf/ftsdfrend.c index fa2fd6d27..aedc5bbec 100644 --- a/src/sdf/ftsdfrend.c +++ b/src/sdf/ftsdfrend.c @@ -313,9 +313,9 @@ bitmap->width += x_pad * 2; /* ignore the pitch, pixel mode and set custom */ - bitmap->pixel_mode = FT_PIXEL_MODE_GRAY16; - bitmap->pitch = (int)( bitmap->width * 2 ); - bitmap->num_grays = 65535; + bitmap->pixel_mode = FT_PIXEL_MODE_GRAY; + bitmap->pitch = (int)( bitmap->width ); + bitmap->num_grays = 255; /* allocate new buffer */ if ( FT_ALLOC_MULT( bitmap->buffer, bitmap->rows, bitmap->pitch ) ) @@ -524,9 +524,9 @@ target.width = bitmap->width + x_pad * 2; /* set up the target bitmap */ - target.pixel_mode = FT_PIXEL_MODE_GRAY16; - target.pitch = (int)( target.width * 2 ); - target.num_grays = 65535; + target.pixel_mode = FT_PIXEL_MODE_GRAY; + target.pitch = (int)( target.width ); + target.num_grays = 255; if ( FT_ALLOC_MULT( target.buffer, target.rows, target.pitch ) ) goto Exit;