| /***************************************************************************/ |
| /* */ |
| /* 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 */ |