2020-01-18 09:38:21 +01:00
/*
2024-06-18 21:06:15 +02:00
* Copyright ( c ) 2018 - 2024 , Andreas Kling < andreas @ ladybird . org >
2022-03-13 16:09:41 -06:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2021-11-11 07:30:37 -05:00
# include <AK/Vector.h>
2024-10-16 05:51:39 +02:00
# include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
2023-03-21 14:58:06 -04:00
# include <LibGfx/ImageFormats/PNGLoader.h>
2024-05-22 22:45:22 -04:00
# include <LibGfx/ImageFormats/TIFFLoader.h>
# include <LibGfx/ImageFormats/TIFFMetadata.h>
2024-11-09 05:48:17 +01:00
# include <LibGfx/ImmutableBitmap.h>
2024-11-04 12:03:27 +01:00
# include <LibGfx/Painter.h>
2024-06-18 21:06:15 +02:00
# include <png.h>
2019-03-21 03:57:42 +01:00
2020-02-06 11:56:38 +01:00
namespace Gfx {
2019-03-21 03:57:42 +01:00
struct PNGLoadingContext {
2024-12-18 08:54:05 +01:00
~ PNGLoadingContext ( )
{
png_destroy_read_struct ( & png_ptr , & info_ptr , nullptr ) ;
}
png_structp png_ptr { nullptr } ;
png_infop info_ptr { nullptr } ;
2024-06-18 21:06:15 +02:00
ReadonlyBytes data ;
IntSize size ;
u32 frame_count { 0 } ;
u32 loop_count { 0 } ;
Vector < ImageFrameDescriptor > frame_descriptors ;
2025-02-08 00:13:12 -05:00
Optional < Media : : CodingIndependentCodePoints > cicp ;
2024-06-18 21:06:15 +02:00
Optional < ByteBuffer > icc_profile ;
2024-05-22 22:45:22 -04:00
OwnPtr < ExifMetadata > exif_metadata ;
2024-06-18 21:06:15 +02:00
ErrorOr < size_t > read_frames ( png_structp , png_infop ) ;
2024-10-16 05:51:39 +02:00
ErrorOr < void > apply_exif_orientation ( ) ;
2024-12-18 08:54:05 +01:00
ErrorOr < void > read_all_frames ( )
{
// NOTE: We need to setjmp() here because libpng uses longjmp() for error handling.
if ( auto error_value = setjmp ( png_jmpbuf ( png_ptr ) ) ; error_value ) {
return Error : : from_errno ( error_value ) ;
}
png_read_update_info ( png_ptr , info_ptr ) ;
frame_count = TRY ( read_frames ( png_ptr , info_ptr ) ) ;
if ( exif_metadata )
TRY ( apply_exif_orientation ( ) ) ;
return { } ;
}
2019-03-21 16:19:11 +01:00
} ;
2024-06-18 21:06:15 +02:00
ErrorOr < NonnullOwnPtr < ImageDecoderPlugin > > PNGImageDecoderPlugin : : create ( ReadonlyBytes bytes )
2019-03-21 16:40:40 +01:00
{
2024-06-18 21:06:15 +02:00
auto decoder = adopt_own ( * new PNGImageDecoderPlugin ( bytes ) ) ;
2024-11-04 10:58:08 +01:00
TRY ( decoder - > initialize ( ) ) ;
2024-12-18 08:54:05 +01:00
auto result = decoder - > m_context - > read_all_frames ( ) ;
if ( result . is_error ( ) ) {
// NOTE: If we didn't fail in initialize(), that means we have size information.
// We can create a single-frame bitmap with that size and return it.
// This is weird, but kinda matches the behavior of other browsers.
auto bitmap = TRY ( Bitmap : : create ( BitmapFormat : : BGRA8888 , AlphaType : : Premultiplied , decoder - > m_context - > size ) ) ;
decoder - > m_context - > frame_descriptors . append ( { move ( bitmap ) , 0 } ) ;
decoder - > m_context - > frame_count = 1 ;
return decoder ;
}
2024-06-18 21:06:15 +02:00
return decoder ;
2019-03-21 16:40:40 +01:00
}
2024-06-18 21:06:15 +02:00
PNGImageDecoderPlugin : : PNGImageDecoderPlugin ( ReadonlyBytes data )
: m_context ( adopt_own ( * new PNGLoadingContext ) )
2020-06-02 17:34:56 +02:00
{
2024-06-18 21:06:15 +02:00
m_context - > data = data ;
2020-06-02 17:34:56 +02:00
}
2024-06-18 21:06:15 +02:00
size_t PNGImageDecoderPlugin : : first_animated_frame_index ( )
2020-06-02 17:34:56 +02:00
{
2024-06-18 21:06:15 +02:00
return 0 ;
2020-06-02 17:34:56 +02:00
}
2024-06-18 21:06:15 +02:00
IntSize PNGImageDecoderPlugin : : size ( )
2020-06-02 17:34:56 +02:00
{
2024-06-18 21:06:15 +02:00
return m_context - > size ;
2020-06-02 17:34:56 +02:00
}
2024-06-18 21:06:15 +02:00
bool PNGImageDecoderPlugin : : is_animated ( )
2022-02-14 17:55:35 +01:00
{
2024-06-18 21:06:15 +02:00
return m_context - > frame_count > 1 ;
2022-02-14 17:55:35 +01:00
}
2024-06-18 21:06:15 +02:00
size_t PNGImageDecoderPlugin : : loop_count ( )
2019-03-21 16:19:11 +01:00
{
2024-06-18 21:06:15 +02:00
return m_context - > loop_count ;
2019-03-21 16:19:11 +01:00
}
2024-06-18 21:06:15 +02:00
size_t PNGImageDecoderPlugin : : frame_count ( )
2019-03-21 03:57:42 +01:00
{
2024-06-18 21:06:15 +02:00
return m_context - > frame_count ;
2019-10-16 20:01:11 +02:00
}
2019-03-21 14:08:14 +01:00
2024-06-18 21:06:15 +02:00
ErrorOr < ImageFrameDescriptor > PNGImageDecoderPlugin : : frame ( size_t index , Optional < IntSize > )
2019-10-16 20:01:11 +02:00
{
2024-06-18 21:06:15 +02:00
if ( index > = m_context - > frame_descriptors . size ( ) )
return Error : : from_errno ( EINVAL ) ;
2023-04-08 23:40:55 -06:00
2024-06-18 21:06:15 +02:00
return m_context - > frame_descriptors [ index ] ;
2019-10-16 20:01:11 +02:00
}
2025-02-08 00:13:12 -05:00
ErrorOr < Optional < Media : : CodingIndependentCodePoints > > PNGImageDecoderPlugin : : cicp ( )
{
return m_context - > cicp ;
}
2024-06-18 21:06:15 +02:00
ErrorOr < Optional < ReadonlyBytes > > PNGImageDecoderPlugin : : icc_data ( )
2023-04-08 23:40:55 -06:00
{
2024-06-18 21:06:15 +02:00
if ( m_context - > icc_profile . has_value ( ) )
return Optional < ReadonlyBytes > ( * m_context - > icc_profile ) ;
return OptionalNone { } ;
2023-04-08 23:40:55 -06:00
}
2024-12-08 11:51:18 -06:00
static void log_png_error ( png_structp png_ptr , char const * error_message )
{
dbgln ( " libpng error: {} " , error_message ) ;
png_longjmp ( png_ptr , 1 ) ;
}
static void log_png_warning ( png_structp , char const * warning_message )
{
dbgln ( " libpng warning: {} " , warning_message ) ;
}
2024-11-04 10:58:08 +01:00
ErrorOr < void > PNGImageDecoderPlugin : : initialize ( )
2023-04-08 23:40:55 -06:00
{
2024-12-18 08:54:05 +01:00
m_context - > png_ptr = png_create_read_struct ( PNG_LIBPNG_VER_STRING , nullptr , nullptr , nullptr ) ;
if ( ! m_context - > png_ptr )
2024-11-04 10:58:08 +01:00
return Error : : from_string_view ( " Failed to allocate read struct " sv ) ;
2023-04-08 23:40:55 -06:00
2024-12-18 08:54:05 +01:00
m_context - > info_ptr = png_create_info_struct ( m_context - > png_ptr ) ;
if ( ! m_context - > info_ptr ) {
2024-11-04 10:58:08 +01:00
return Error : : from_string_view ( " Failed to allocate info struct " sv ) ;
2023-04-08 23:40:55 -06:00
}
2024-12-18 08:54:05 +01:00
if ( auto error_value = setjmp ( png_jmpbuf ( m_context - > png_ptr ) ) ; error_value ) {
2024-11-04 10:58:08 +01:00
return Error : : from_errno ( error_value ) ;
2019-03-21 03:57:42 +01:00
}
2024-12-18 08:54:05 +01:00
png_set_read_fn ( m_context - > png_ptr , & m_context - > data , [ ] ( png_structp png_ptr , png_bytep data , png_size_t length ) {
2024-06-18 21:06:15 +02:00
auto * read_data = reinterpret_cast < ReadonlyBytes * > ( png_get_io_ptr ( png_ptr ) ) ;
if ( read_data - > size ( ) < length ) {
png_error ( png_ptr , " Read error " ) ;
return ;
2020-06-11 09:32:17 +02:00
}
2024-06-18 21:06:15 +02:00
memcpy ( data , read_data - > data ( ) , length ) ;
* read_data = read_data - > slice ( length ) ;
} ) ;
2020-06-11 09:32:17 +02:00
2024-12-18 08:54:05 +01:00
png_set_error_fn ( m_context - > png_ptr , nullptr , log_png_error , log_png_warning ) ;
2024-12-08 11:51:18 -06:00
2024-12-18 08:54:05 +01:00
png_read_info ( m_context - > png_ptr , m_context - > info_ptr ) ;
2021-05-21 10:30:21 +01:00
2024-06-18 21:06:15 +02:00
u32 width = 0 ;
u32 height = 0 ;
int bit_depth = 0 ;
int color_type = 0 ;
2024-12-04 19:08:56 +01:00
int interlace_type = 0 ;
2024-12-18 08:54:05 +01:00
png_get_IHDR ( m_context - > png_ptr , m_context - > info_ptr , & width , & height , & bit_depth , & color_type , & interlace_type , nullptr , nullptr ) ;
2024-06-18 21:06:15 +02:00
m_context - > size = { static_cast < int > ( width ) , static_cast < int > ( height ) } ;
2020-06-13 13:17:43 -04:00
2024-06-18 21:06:15 +02:00
if ( color_type = = PNG_COLOR_TYPE_PALETTE )
2024-12-18 08:54:05 +01:00
png_set_palette_to_rgb ( m_context - > png_ptr ) ;
2020-06-13 13:17:43 -04:00
2024-06-18 21:06:15 +02:00
if ( color_type = = PNG_COLOR_TYPE_GRAY & & bit_depth < 8 )
2024-12-18 08:54:05 +01:00
png_set_expand_gray_1_2_4_to_8 ( m_context - > png_ptr ) ;
2020-06-13 13:17:43 -04:00
2024-12-18 08:54:05 +01:00
if ( png_get_valid ( m_context - > png_ptr , m_context - > info_ptr , PNG_INFO_tRNS ) )
png_set_tRNS_to_alpha ( m_context - > png_ptr ) ;
2020-06-13 13:17:43 -04:00
2024-06-18 21:06:15 +02:00
if ( bit_depth = = 16 )
2024-12-18 08:54:05 +01:00
png_set_strip_16 ( m_context - > png_ptr ) ;
2020-12-23 19:04:12 +01:00
2024-06-18 21:06:15 +02:00
if ( color_type = = PNG_COLOR_TYPE_GRAY | | color_type = = PNG_COLOR_TYPE_GRAY_ALPHA )
2024-12-18 08:54:05 +01:00
png_set_gray_to_rgb ( m_context - > png_ptr ) ;
2020-06-13 13:17:43 -04:00
2024-12-04 19:08:56 +01:00
if ( interlace_type ! = PNG_INTERLACE_NONE )
2024-12-18 08:54:05 +01:00
png_set_interlace_handling ( m_context - > png_ptr ) ;
2024-12-04 19:08:56 +01:00
2024-12-18 08:54:05 +01:00
png_set_filler ( m_context - > png_ptr , 0xFF , PNG_FILLER_AFTER ) ;
png_set_bgr ( m_context - > png_ptr ) ;
2020-06-13 13:17:43 -04:00
2025-02-08 00:13:12 -05:00
png_byte color_primaries { 0 } ;
png_byte transfer_function { 0 } ;
png_byte matrix_coefficients { 0 } ;
png_byte video_full_range_flag { 0 } ;
if ( png_get_cICP ( m_context - > png_ptr , m_context - > info_ptr , & color_primaries , & transfer_function , & matrix_coefficients , & video_full_range_flag ) ) {
Media : : ColorPrimaries cp { color_primaries } ;
Media : : TransferCharacteristics tc { transfer_function } ;
Media : : MatrixCoefficients mc { matrix_coefficients } ;
Media : : VideoFullRangeFlag rf { video_full_range_flag } ;
m_context - > cicp = Media : : CodingIndependentCodePoints { cp , tc , mc , rf } ;
} else {
char * profile_name = nullptr ;
int compression_type = 0 ;
u8 * profile_data = nullptr ;
u32 profile_len = 0 ;
if ( png_get_iCCP ( m_context - > png_ptr , m_context - > info_ptr , & profile_name , & compression_type , & profile_data , & profile_len ) )
m_context - > icc_profile = TRY ( ByteBuffer : : copy ( profile_data , profile_len ) ) ;
}
2020-06-13 13:17:43 -04:00
2024-06-18 21:06:15 +02:00
u8 * exif_data = nullptr ;
u32 exif_length = 0 ;
2024-12-18 08:54:05 +01:00
int const num_exif_chunks = png_get_eXIf_1 ( m_context - > png_ptr , m_context - > info_ptr , & exif_length , & exif_data ) ;
2024-11-04 10:58:08 +01:00
if ( num_exif_chunks > 0 )
2024-06-18 21:06:15 +02:00
m_context - > exif_metadata = TRY ( TIFFImageDecoderPlugin : : read_exif_metadata ( { exif_data , exif_length } ) ) ;
2020-06-13 13:17:43 -04:00
2024-11-04 10:58:08 +01:00
return { } ;
2019-10-15 21:48:08 +02:00
}
2024-10-16 05:51:39 +02:00
ErrorOr < void > PNGLoadingContext : : apply_exif_orientation ( )
{
auto orientation = exif_metadata - > orientation ( ) . value_or ( TIFF : : Orientation : : Default ) ;
if ( orientation = = TIFF : : Orientation : : Default )
return { } ;
for ( auto & img_frame_descriptor : frame_descriptors ) {
auto & img = img_frame_descriptor . image ;
auto oriented_bmp = TRY ( ExifOrientedBitmap : : create ( orientation , img - > size ( ) , img - > format ( ) ) ) ;
for ( int y = 0 ; y < img - > size ( ) . height ( ) ; + + y ) {
for ( int x = 0 ; x < img - > size ( ) . width ( ) ; + + x ) {
auto pixel = img - > get_pixel ( x , y ) ;
oriented_bmp . set_pixel ( x , y , pixel . value ( ) ) ;
}
}
img_frame_descriptor . image = oriented_bmp . bitmap ( ) ;
}
size = ExifOrientedBitmap : : oriented_size ( size , orientation ) ;
return { } ;
}
2024-06-18 21:06:15 +02:00
ErrorOr < size_t > PNGLoadingContext : : read_frames ( png_structp png_ptr , png_infop info_ptr )
2023-04-08 23:40:55 -06:00
{
2025-02-26 16:38:13 +01:00
Vector < u8 * > row_pointers ;
auto decode_frame = [ & ] ( IntSize frame_size ) - > ErrorOr < NonnullRefPtr < Bitmap > > {
auto frame_bitmap = TRY ( Bitmap : : create ( BitmapFormat : : BGRA8888 , AlphaType : : Unpremultiplied , frame_size ) ) ;
row_pointers . resize_and_keep_capacity ( frame_size . height ( ) ) ;
for ( auto i = 0 ; i < frame_size . height ( ) ; + + i )
row_pointers [ i ] = frame_bitmap - > scanline_u8 ( i ) ;
png_read_image ( png_ptr , row_pointers . data ( ) ) ;
return frame_bitmap ;
} ;
2024-06-18 21:06:15 +02:00
if ( png_get_acTL ( png_ptr , info_ptr , & frame_count , & loop_count ) ) {
// acTL chunk present: This is an APNG.
png_set_acTL ( png_ptr , info_ptr , frame_count , loop_count ) ;
2023-04-08 23:40:55 -06:00
2024-11-04 12:03:27 +01:00
// Conceptually, at the beginning of each play the output buffer must be completely initialized to a fully transparent black rectangle, with width and height dimensions from the `IHDR` chunk.
auto output_buffer = TRY ( Bitmap : : create ( BitmapFormat : : BGRA8888 , AlphaType : : Unpremultiplied , size ) ) ;
auto painter = Painter : : create ( output_buffer ) ;
2025-04-30 14:57:35 +02:00
size_t animation_frame_count = 0 ;
2024-11-04 12:03:27 +01:00
2024-06-18 21:06:15 +02:00
for ( size_t frame_index = 0 ; frame_index < frame_count ; + + frame_index ) {
png_read_frame_head ( png_ptr , info_ptr ) ;
u32 width = 0 ;
u32 height = 0 ;
u32 x = 0 ;
u32 y = 0 ;
u16 delay_num = 0 ;
u16 delay_den = 0 ;
2024-08-19 15:49:11 +02:00
u8 dispose_op = PNG_DISPOSE_OP_NONE ;
u8 blend_op = PNG_BLEND_OP_SOURCE ;
2024-11-04 12:03:27 +01:00
auto duration_ms = [ & ] ( ) - > int {
if ( delay_num = = 0 )
return 1 ;
u32 const denominator = delay_den ! = 0 ? static_cast < u32 > ( delay_den ) : 100u ;
auto unsigned_duration_ms = ( delay_num * 1000 ) / denominator ;
if ( unsigned_duration_ms > INT_MAX )
return INT_MAX ;
return static_cast < int > ( unsigned_duration_ms ) ;
} ;
2025-04-30 14:57:35 +02:00
bool has_fcTL = png_get_valid ( png_ptr , info_ptr , PNG_INFO_fcTL ) ;
if ( has_fcTL ) {
2024-08-19 15:49:11 +02:00
png_get_next_frame_fcTL ( png_ptr , info_ptr , & width , & height , & x , & y , & delay_num , & delay_den , & dispose_op , & blend_op ) ;
} else {
width = png_get_image_width ( png_ptr , info_ptr ) ;
height = png_get_image_height ( png_ptr , info_ptr ) ;
2024-06-18 21:06:15 +02:00
}
2024-11-04 12:03:27 +01:00
auto frame_rect = FloatRect { x , y , width , height } ;
2023-06-11 19:14:59 -04:00
2025-02-26 16:38:13 +01:00
auto decoded_frame_bitmap = TRY ( decode_frame ( { width , height } ) ) ;
2023-04-08 23:40:55 -06:00
2025-05-07 01:04:50 +02:00
if ( ! has_fcTL )
continue ;
2024-11-04 12:03:27 +01:00
RefPtr < Bitmap > prev_output_buffer ;
if ( dispose_op = = PNG_DISPOSE_OP_PREVIOUS ) // Only actually clone if it's necessary
prev_output_buffer = TRY ( output_buffer - > clone ( ) ) ;
switch ( blend_op ) {
case PNG_BLEND_OP_SOURCE :
// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
2025-01-28 18:19:30 +01:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * decoded_frame_bitmap ) , decoded_frame_bitmap - > rect ( ) , Gfx : : ScalingMode : : NearestNeighbor , { } , 1.0f , Gfx : : CompositingAndBlendingOperator : : Copy ) ;
2024-11-04 12:03:27 +01:00
break ;
case PNG_BLEND_OP_OVER :
// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification.
2025-01-28 18:19:30 +01:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * decoded_frame_bitmap ) , decoded_frame_bitmap - > rect ( ) , ScalingMode : : NearestNeighbor , { } , 1.0f , Gfx : : CompositingAndBlendingOperator : : SourceOver ) ;
2024-11-04 12:03:27 +01:00
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
2023-04-08 23:40:55 -06:00
2025-05-07 01:04:50 +02:00
animation_frame_count + + ;
frame_descriptors . append ( { TRY ( output_buffer - > clone ( ) ) , duration_ms ( ) } ) ;
2024-11-04 12:03:27 +01:00
switch ( dispose_op ) {
case PNG_DISPOSE_OP_NONE :
// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
break ;
case PNG_DISPOSE_OP_BACKGROUND :
// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
painter - > clear_rect ( frame_rect , Gfx : : Color : : Transparent ) ;
break ;
case PNG_DISPOSE_OP_PREVIOUS :
// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
2025-01-28 18:19:30 +01:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * prev_output_buffer ) , IntRect { x , y , width , height } , Gfx : : ScalingMode : : NearestNeighbor , { } , 1.0f , Gfx : : CompositingAndBlendingOperator : : Copy ) ;
2024-11-04 12:03:27 +01:00
break ;
default :
VERIFY_NOT_REACHED ( ) ;
2024-06-18 21:06:15 +02:00
}
}
2025-04-30 14:57:35 +02:00
frame_count = animation_frame_count ;
// If we didn't find any valid animation frames with fcTL chunks, fall back to using the base IDAT data as a single frame.
if ( frame_count = = 0 ) {
auto frame_bitmap = TRY ( decode_frame ( size ) ) ;
frame_descriptors . append ( { move ( frame_bitmap ) , 0 } ) ;
frame_count = 1 ;
}
2024-06-18 21:06:15 +02:00
} else {
// This is a single-frame PNG.
frame_count = 1 ;
loop_count = 0 ;
2019-03-21 03:57:42 +01:00
2025-02-26 16:38:13 +01:00
auto decoded_frame_bitmap = TRY ( decode_frame ( size ) ) ;
2024-06-18 21:06:15 +02:00
frame_descriptors . append ( { move ( decoded_frame_bitmap ) , 0 } ) ;
2023-10-07 10:02:32 +01:00
}
2025-04-30 14:57:35 +02:00
return frame_count ;
2019-10-15 21:48:08 +02:00
}
2022-03-14 13:26:37 -06:00
PNGImageDecoderPlugin : : ~ PNGImageDecoderPlugin ( ) = default ;
2019-10-15 21:48:08 +02:00
2023-02-26 18:02:50 +00:00
bool PNGImageDecoderPlugin : : sniff ( ReadonlyBytes data )
2023-01-20 10:13:14 +02:00
{
2024-11-04 10:48:24 +01:00
auto constexpr png_signature_size_in_bytes = 8 ;
if ( data . size ( ) < png_signature_size_in_bytes )
2023-04-08 23:40:55 -06:00
return false ;
2024-11-04 10:48:24 +01:00
return png_sig_cmp ( data . data ( ) , 0 , png_signature_size_in_bytes ) = = 0 ;
2020-05-03 17:12:54 +01:00
}
2024-05-22 22:45:22 -04:00
Optional < Metadata const & > PNGImageDecoderPlugin : : metadata ( )
{
if ( m_context - > exif_metadata )
return * m_context - > exif_metadata ;
return OptionalNone { } ;
}
2020-02-06 11:56:38 +01:00
}