freetype2/src/sfnt/pngshim.c

337 lines
8.6 KiB
C
Raw Normal View History

Add support for color embedded bitmaps (eg. color emoji). A new load flag, FT_LOAD_COLOR, makes FreeType load color embedded-bitmaps, following this draft specification https://color-emoji.googlecode.com/git/specification/v1.html which defines two new SFNT tables, `CBDT' and `CBLC' (named and modeled after `EBDT' and `EBLC', respectively). The color bitmaps are stored in the new FT_PIXEL_MODE_BGRA format to represent BGRA pre-multiplied sRGB images. If PNG support is available, PNG color images as defined in the same proposed specification are supported also. Note that color bitmaps are converted to grayscale if client didn't ask for color. * builds/unix/configure.raw: Search for libpng. Add `--without-png' option. * devel/ftoption.h, include/freetype/config/ftoption.h (FT_CONFIG_OPTION_USE_PNG): New macro. * include/freetype/freetype.h (FT_LOAD_COLOR): New load flag. * include/freetype/ftimage.h (FT_Pixel_Mode): Add `FT_PIXEL_MODE_BGRA'. * include/freetype/tttags.h (TTAG_CBDT, TTAG_CBLC): New tags. * src/base/ftbitmap.c (FT_Bitmap_Embolden): Updated. (ft_gray_for_premultiplied_srgb_bgra): New function. (FT_Bitmap_Convert): Handle FT_PIXEL_MODE_BGRA. * src/sfnt/pngshim.c, src/sfnt/pngshim.h: New files. * src/sfnt/sfnt.c: Include `pngshim.c'. * src/sfnt/ttsbit.c: Include FT_BITMAP_H and `pngshim.h' (tt_face_load_eblc): Load `CBLC'. (tt_sbit_decoder_init): Load `CBDT'. (tt_sbit_decoder_alloc_bitmap): Pass load flags to select between color and grayscale bitmaps. Set `num_grays'. This is used by `ftview' to choose the blending algorithm. (tt_sbit_decoder_load_byte_aligned, tt_sbit_decoder_load_bit_aligned, tt_sbit_decoder_load_compound, tt_sbit_decoder_load_image): Pass load flag. s/write/pwrite/. Don't call `tt_sbit_decoder_alloc_bitmap'. Updated. (tt_sbit_decoder_load_png) [FT_CONFIG_OPTION_USE_PNG]: New function. (tt_sbit_decoder_load_bitmap): Pass load flag. Handle new glyph formats 17, 18, and 19. Call `tt_sbit_decoder_alloc_bitmap'. Flatten color bitmaps if necessary. (tt_face_load_sbit_image): Updated. * src/sfnt/rules.mk (SFNT_DRV_SRC): Add `pngshim.c'. * docs/CHANGES: Updated.
2013-05-29 11:36:18 +02:00
/***************************************************************************/
/* */
/* pngshim.c */
/* */
/* PNG Bitmap glyph support. */
/* */
/* Copyright 2013 by Google, Inc. */
/* Written by Stuart Gill and Behdad Esfahbod. */
/* */
/* This file is part of the FreeType project, and may only be used, */
/* modified, and distributed under the terms of the FreeType project */
/* license, LICENSE.TXT. By continuing to use, modify, or distribute */
/* this file you indicate that you have read the license and */
/* understand and accept it fully. */
/* */
/***************************************************************************/
#include <ft2build.h>
#include FT_INTERNAL_DEBUG_H
#include FT_INTERNAL_STREAM_H
#include FT_TRUETYPE_TAGS_H
#include FT_CONFIG_STANDARD_LIBRARY_H
#ifdef FT_CONFIG_OPTION_USE_PNG
/* We always include <stjmp.h>, so make libpng shut up! */
#define PNG_SKIP_SETJMP_CHECK 1
#include <png.h>
#include "pngshim.h"
#include "sferrors.h"
/* This code is freely based on cairo-png.c. There's so many ways */
/* to call libpng, and the way cairo does it is defacto standard. */
static int
multiply_alpha( int alpha,
int color )
{
int temp = ( alpha * color ) + 0x80;
return ( temp + ( temp >> 8 ) ) >> 8;
}
/* Premultiplies data and converts RGBA bytes => native endian. */
static void
premultiply_data( png_structp png,
png_row_infop row_info,
png_bytep data )
{
unsigned int i;
FT_UNUSED( png );
for ( i = 0; i < row_info->rowbytes; i += 4 )
{
unsigned char* base = &data[i];
unsigned int alpha = base[3];
if ( alpha == 0 )
base[0] = base[1] = base[2] = base[3] = 0;
else
{
unsigned int red = base[0];
unsigned int green = base[1];
unsigned int blue = base[2];
if ( alpha != 0xFF )
{
red = multiply_alpha( alpha, red );
green = multiply_alpha( alpha, green );
blue = multiply_alpha( alpha, blue );
}
base[0] = blue;
base[1] = green;
base[2] = red;
base[3] = alpha;
}
}
}
/* Converts RGBx bytes to BGRA. */
static void
convert_bytes_to_data( png_structp png,
png_row_infop row_info,
png_bytep data )
{
unsigned int i;
FT_UNUSED( png );
for ( i = 0; i < row_info->rowbytes; i += 4 )
{
unsigned char* base = &data[i];
unsigned int red = base[0];
unsigned int green = base[1];
unsigned int blue = base[2];
base[0] = blue;
base[1] = green;
base[2] = red;
base[3] = 0xFF;
}
}
/* Use error callback to avoid png writing to stderr. */
static void
error_callback( png_structp png,
png_const_charp error_msg )
{
FT_Error* error = png_get_error_ptr( png );
FT_UNUSED( error_msg );
*error = FT_THROW( Out_Of_Memory );
#ifdef PNG_SETJMP_SUPPORTED
longjmp( png_jmpbuf( png ), 1 );
#endif
/* if we get here, then we have no choice but to abort ... */
}
/* Use warning callback to avoid png writing to stderr. */
static void
warning_callback( png_structp png,
png_const_charp error_msg )
{
FT_UNUSED( png );
FT_UNUSED( error_msg );
/* Just ignore warnings. */
}
static void
read_data_from_FT_Stream( png_structp png,
png_bytep data,
png_size_t length )
{
FT_Error error;
png_voidp p = png_get_io_ptr( png );
FT_Stream stream = (FT_Stream)p;
if ( FT_FRAME_ENTER( length ) )
{
FT_Error* e = png_get_error_ptr( png );
*e = FT_THROW( Invalid_Stream_Read );
png_error( png, NULL );
return;
}
memcpy( data, stream->cursor, length );
FT_FRAME_EXIT();
}
static FT_Error
Load_SBit_Png( FT_Bitmap* map,
FT_Int x_offset,
FT_Int y_offset,
FT_Int pix_bits,
TT_SBit_Metrics metrics,
FT_Memory memory,
FT_Byte* data,
FT_UInt png_len )
{
FT_Error error = FT_Err_Ok;
FT_StreamRec stream;
png_structp png;
png_infop info;
png_uint_32 imgWidth, imgHeight;
int bitdepth, color_type, interlace;
FT_Int i;
png_byte* *rows;
if ( x_offset < 0 || x_offset + metrics->width > map->width ||
y_offset < 0 || y_offset + metrics->height > map->rows ||
pix_bits != 32 || map->pixel_mode != FT_PIXEL_MODE_BGRA )
{
error = FT_THROW( Invalid_Argument );
goto Exit;
}
FT_Stream_OpenMemory( &stream, data, png_len );
png = png_create_read_struct( PNG_LIBPNG_VER_STRING,
&error,
error_callback,
warning_callback );
if ( !png )
{
error = FT_THROW( Out_Of_Memory );
goto Exit;
}
info = png_create_info_struct( png );
if ( !info )
{
error = FT_THROW( Out_Of_Memory );
png_destroy_read_struct( &png, NULL, NULL );
goto Exit;
}
if ( ft_setjmp( png_jmpbuf( png ) ) )
{
error = FT_THROW( Invalid_File_Format );
goto DestroyExit;
}
png_set_read_fn( png, &stream, read_data_from_FT_Stream );
png_read_info( png, info );
png_get_IHDR( png, info,
&imgWidth, &imgHeight,
&bitdepth, &color_type, &interlace,
NULL, NULL );
if ( error != FT_Err_Ok ||
(FT_Int)imgWidth != metrics->width ||
(FT_Int)imgHeight != metrics->height )
goto DestroyExit;
/* convert palette/gray image to rgb */
if ( color_type == PNG_COLOR_TYPE_PALETTE )
png_set_palette_to_rgb( png );
/* expand gray bit depth if needed */
if ( color_type == PNG_COLOR_TYPE_GRAY )
{
#if PNG_LIBPNG_VER >= 10209
png_set_expand_gray_1_2_4_to_8( png );
#else
png_set_gray_1_2_4_to_8( png );
#endif
}
/* transform transparency to alpha */
if ( png_get_valid(png, info, PNG_INFO_tRNS ) )
png_set_tRNS_to_alpha( png );
if ( bitdepth == 16 )
png_set_strip_16( png );
if ( bitdepth < 8 )
png_set_packing( png );
/* convert grayscale to RGB */
if ( color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
png_set_gray_to_rgb( png );
if ( interlace != PNG_INTERLACE_NONE )
png_set_interlace_handling( png );
png_set_filler( png, 0xFF, PNG_FILLER_AFTER );
/* recheck header after setting EXPAND options */
png_read_update_info(png, info );
png_get_IHDR( png, info,
&imgWidth, &imgHeight,
&bitdepth, &color_type, &interlace,
NULL, NULL );
if ( bitdepth != 8 ||
!( color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_RGB_ALPHA ) )
{
error = FT_THROW( Invalid_File_Format );
goto DestroyExit;
}
switch ( color_type )
{
default:
/* Shouldn't happen, but fall through. */
case PNG_COLOR_TYPE_RGB_ALPHA:
png_set_read_user_transform_fn( png, premultiply_data );
break;
case PNG_COLOR_TYPE_RGB:
/* Humm, this smells. Carry on though. */
png_set_read_user_transform_fn( png, convert_bytes_to_data );
break;
}
if ( FT_NEW_ARRAY( rows, imgHeight ) )
{
error = FT_THROW( Out_Of_Memory );
goto DestroyExit;
}
for ( i = 0; i < (FT_Int)imgHeight; i++ )
rows[i] = map->buffer + ( y_offset + i ) * map->pitch + x_offset * 4;
png_read_image( png, rows );
FT_FREE( rows );
png_read_end( png, info );
DestroyExit:
png_destroy_read_struct( &png, &info, NULL );
FT_Stream_Close( &stream );
Exit:
return error;
}
#endif /* FT_CONFIG_OPTION_USE_PNG */
/* END */