/***************************************************************************/ | |
/* */ | |
/* pngshim.c */ | |
/* */ | |
/* PNG Bitmap glyph support. */ | |
/* */ | |
/* Copyright 2013-2015 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 unsigned int | |
multiply_alpha( unsigned int alpha, | |
unsigned int color ) | |
{ | |
unsigned 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] = (unsigned char)blue; | |
base[1] = (unsigned char)green; | |
base[2] = (unsigned char)red; | |
base[3] = (unsigned char)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] = (unsigned char)blue; | |
base[1] = (unsigned char)green; | |
base[2] = (unsigned char)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 = (FT_Error*)png_get_error_ptr( png ); | |
FT_UNUSED( error_msg ); | |
*error = FT_THROW( Out_Of_Memory ); | |
#ifdef PNG_SETJMP_SUPPORTED | |
ft_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 = (FT_Error*)png_get_error_ptr( png ); | |
*e = FT_THROW( Invalid_Stream_Read ); | |
png_error( png, NULL ); | |
return; | |
} | |
memcpy( data, stream->cursor, length ); | |
FT_FRAME_EXIT(); | |
} | |
FT_LOCAL_DEF( FT_Error ) | |
Load_SBit_Png( FT_GlyphSlot slot, | |
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_Bool populate_map_and_metrics ) | |
{ | |
FT_Bitmap *map = &slot->bitmap; | |
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 = NULL; /* pacify compiler */ | |
if ( x_offset < 0 || | |
y_offset < 0 ) | |
{ | |
error = FT_THROW( Invalid_Argument ); | |
goto Exit; | |
} | |
if ( !populate_map_and_metrics && | |
( (FT_UInt)x_offset + metrics->width > map->width || | |
(FT_UInt)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 || | |
( !populate_map_and_metrics && | |
( (FT_Int)imgWidth != metrics->width || | |
(FT_Int)imgHeight != metrics->height ) ) ) | |
goto DestroyExit; | |
if ( populate_map_and_metrics ) | |
{ | |
FT_ULong size; | |
metrics->width = (FT_UShort)imgWidth; | |
metrics->height = (FT_UShort)imgHeight; | |
map->width = metrics->width; | |
map->rows = metrics->height; | |
map->pixel_mode = FT_PIXEL_MODE_BGRA; | |
map->pitch = (int)( map->width * 4 ); | |
map->num_grays = 256; | |
/* reject too large bitmaps similarly to the rasterizer */ | |
if ( map->rows > 0x7FFF || map->width > 0x7FFF ) | |
{ | |
error = FT_THROW( Array_Too_Large ); | |
goto DestroyExit; | |
} | |
/* this doesn't overflow: 0x7FFF * 0x7FFF * 4 < 2^32 */ | |
size = map->rows * (FT_ULong)map->pitch; | |
error = ft_glyphslot_alloc_bitmap( slot, size ); | |
if ( error ) | |
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 */ |