2020-01-18 08:38:21 +00:00
/*
2024-06-18 19:06:15 +00:00
* Copyright ( c ) 2018 - 2024 , Andreas Kling < andreas @ ladybird . org >
2022-03-13 22:09:41 +00:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-01-18 08:38:21 +00:00
*
2021-04-22 08:24:48 +00:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 08:38:21 +00:00
*/
2021-11-11 12:30:37 +00:00
# include <AK/Vector.h>
2024-10-16 03:51:39 +00:00
# include <LibGfx/ImageFormats/ExifOrientedBitmap.h>
2023-03-21 18:58:06 +00:00
# include <LibGfx/ImageFormats/PNGLoader.h>
2024-05-23 02:45:22 +00:00
# include <LibGfx/ImageFormats/TIFFLoader.h>
# include <LibGfx/ImageFormats/TIFFMetadata.h>
2024-11-09 04:48:17 +00:00
# include <LibGfx/ImmutableBitmap.h>
2024-11-04 11:03:27 +00:00
# include <LibGfx/Painter.h>
2024-06-18 19:06:15 +00:00
# include <png.h>
2019-03-21 02:57:42 +00:00
2020-02-06 10:56:38 +00:00
namespace Gfx {
2019-03-21 02:57:42 +00:00
struct PNGLoadingContext {
2024-06-18 19:06:15 +00:00
ReadonlyBytes data ;
IntSize size ;
u32 frame_count { 0 } ;
u32 loop_count { 0 } ;
Vector < ImageFrameDescriptor > frame_descriptors ;
Optional < ByteBuffer > icc_profile ;
2024-05-23 02:45:22 +00:00
OwnPtr < ExifMetadata > exif_metadata ;
2024-06-18 19:06:15 +00:00
ErrorOr < size_t > read_frames ( png_structp , png_infop ) ;
2024-10-16 03:51:39 +00:00
ErrorOr < void > apply_exif_orientation ( ) ;
2019-03-21 15:19:11 +00:00
} ;
2024-06-18 19:06:15 +00:00
ErrorOr < NonnullOwnPtr < ImageDecoderPlugin > > PNGImageDecoderPlugin : : create ( ReadonlyBytes bytes )
2019-03-21 15:40:40 +00:00
{
2024-06-18 19:06:15 +00:00
auto decoder = adopt_own ( * new PNGImageDecoderPlugin ( bytes ) ) ;
2024-11-04 09:58:08 +00:00
TRY ( decoder - > initialize ( ) ) ;
2024-06-18 19:06:15 +00:00
return decoder ;
2019-03-21 15:40:40 +00:00
}
2024-06-18 19:06:15 +00:00
PNGImageDecoderPlugin : : PNGImageDecoderPlugin ( ReadonlyBytes data )
: m_context ( adopt_own ( * new PNGLoadingContext ) )
2020-06-02 15:34:56 +00:00
{
2024-06-18 19:06:15 +00:00
m_context - > data = data ;
2020-06-02 15:34:56 +00:00
}
2024-06-18 19:06:15 +00:00
size_t PNGImageDecoderPlugin : : first_animated_frame_index ( )
2020-06-02 15:34:56 +00:00
{
2024-06-18 19:06:15 +00:00
return 0 ;
2020-06-02 15:34:56 +00:00
}
2024-06-18 19:06:15 +00:00
IntSize PNGImageDecoderPlugin : : size ( )
2020-06-02 15:34:56 +00:00
{
2024-06-18 19:06:15 +00:00
return m_context - > size ;
2020-06-02 15:34:56 +00:00
}
2024-06-18 19:06:15 +00:00
bool PNGImageDecoderPlugin : : is_animated ( )
2022-02-14 16:55:35 +00:00
{
2024-06-18 19:06:15 +00:00
return m_context - > frame_count > 1 ;
2022-02-14 16:55:35 +00:00
}
2024-06-18 19:06:15 +00:00
size_t PNGImageDecoderPlugin : : loop_count ( )
2019-03-21 15:19:11 +00:00
{
2024-06-18 19:06:15 +00:00
return m_context - > loop_count ;
2019-03-21 15:19:11 +00:00
}
2024-06-18 19:06:15 +00:00
size_t PNGImageDecoderPlugin : : frame_count ( )
2019-03-21 02:57:42 +00:00
{
2024-06-18 19:06:15 +00:00
return m_context - > frame_count ;
2019-10-16 18:01:11 +00:00
}
2019-03-21 13:08:14 +00:00
2024-06-18 19:06:15 +00:00
ErrorOr < ImageFrameDescriptor > PNGImageDecoderPlugin : : frame ( size_t index , Optional < IntSize > )
2019-10-16 18:01:11 +00:00
{
2024-06-18 19:06:15 +00:00
if ( index > = m_context - > frame_descriptors . size ( ) )
return Error : : from_errno ( EINVAL ) ;
2023-04-09 05:40:55 +00:00
2024-06-18 19:06:15 +00:00
return m_context - > frame_descriptors [ index ] ;
2019-10-16 18:01:11 +00:00
}
2024-06-18 19:06:15 +00:00
ErrorOr < Optional < ReadonlyBytes > > PNGImageDecoderPlugin : : icc_data ( )
2023-04-09 05:40:55 +00:00
{
2024-06-18 19:06:15 +00:00
if ( m_context - > icc_profile . has_value ( ) )
return Optional < ReadonlyBytes > ( * m_context - > icc_profile ) ;
return OptionalNone { } ;
2023-04-09 05:40:55 +00:00
}
2024-11-04 09:58:08 +00:00
ErrorOr < void > PNGImageDecoderPlugin : : initialize ( )
2023-04-09 05:40:55 +00:00
{
2024-06-18 19:06:15 +00:00
png_structp png_ptr = png_create_read_struct ( PNG_LIBPNG_VER_STRING , nullptr , nullptr , nullptr ) ;
if ( ! png_ptr )
2024-11-04 09:58:08 +00:00
return Error : : from_string_view ( " Failed to allocate read struct " sv ) ;
2023-04-09 05:40:55 +00:00
2024-06-18 19:06:15 +00:00
png_infop info_ptr = png_create_info_struct ( png_ptr ) ;
if ( ! info_ptr ) {
png_destroy_read_struct ( & png_ptr , nullptr , nullptr ) ;
2024-11-04 09:58:08 +00:00
return Error : : from_string_view ( " Failed to allocate info struct " sv ) ;
2023-04-09 05:40:55 +00:00
}
2024-11-04 09:58:08 +00:00
if ( auto error_value = setjmp ( png_jmpbuf ( png_ptr ) ) ; error_value ) {
2024-06-18 19:06:15 +00:00
png_destroy_read_struct ( & png_ptr , & info_ptr , nullptr ) ;
2024-11-04 09:58:08 +00:00
return Error : : from_errno ( error_value ) ;
2019-03-21 02:57:42 +00:00
}
2024-06-18 19:06:15 +00:00
png_set_read_fn ( png_ptr , & m_context - > data , [ ] ( png_structp png_ptr , png_bytep data , png_size_t length ) {
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 07:32:17 +00:00
}
2024-06-18 19:06:15 +00:00
memcpy ( data , read_data - > data ( ) , length ) ;
* read_data = read_data - > slice ( length ) ;
} ) ;
2020-06-11 07:32:17 +00:00
2024-06-18 19:06:15 +00:00
png_read_info ( png_ptr , info_ptr ) ;
2021-05-21 09:30:21 +00:00
2024-06-18 19:06:15 +00:00
u32 width = 0 ;
u32 height = 0 ;
int bit_depth = 0 ;
int color_type = 0 ;
png_get_IHDR ( png_ptr , info_ptr , & width , & height , & bit_depth , & color_type , nullptr , nullptr , nullptr ) ;
m_context - > size = { static_cast < int > ( width ) , static_cast < int > ( height ) } ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
if ( color_type = = PNG_COLOR_TYPE_PALETTE )
png_set_palette_to_rgb ( png_ptr ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
if ( color_type = = PNG_COLOR_TYPE_GRAY & & bit_depth < 8 )
png_set_expand_gray_1_2_4_to_8 ( png_ptr ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
if ( png_get_valid ( png_ptr , info_ptr , PNG_INFO_tRNS ) )
png_set_tRNS_to_alpha ( png_ptr ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
if ( bit_depth = = 16 )
png_set_strip_16 ( png_ptr ) ;
2020-12-23 18:04:12 +00:00
2024-06-18 19:06:15 +00:00
if ( color_type = = PNG_COLOR_TYPE_GRAY | | color_type = = PNG_COLOR_TYPE_GRAY_ALPHA )
png_set_gray_to_rgb ( png_ptr ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
png_set_filler ( png_ptr , 0xFF , PNG_FILLER_AFTER ) ;
png_set_bgr ( png_ptr ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
char * profile_name = nullptr ;
int compression_type = 0 ;
u8 * profile_data = nullptr ;
u32 profile_len = 0 ;
2024-11-04 09:58:08 +00:00
if ( png_get_iCCP ( png_ptr , info_ptr , & profile_name , & compression_type , & profile_data , & profile_len ) )
2024-06-18 19:06:15 +00:00
m_context - > icc_profile = TRY ( ByteBuffer : : copy ( profile_data , profile_len ) ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
png_read_update_info ( png_ptr , info_ptr ) ;
m_context - > frame_count = TRY ( m_context - > read_frames ( png_ptr , info_ptr ) ) ;
2020-06-13 17:17:43 +00:00
2024-06-18 19:06:15 +00:00
u8 * exif_data = nullptr ;
u32 exif_length = 0 ;
int const num_exif_chunks = png_get_eXIf_1 ( png_ptr , info_ptr , & exif_length , & exif_data ) ;
2024-11-04 09:58:08 +00:00
if ( num_exif_chunks > 0 )
2024-06-18 19:06:15 +00:00
m_context - > exif_metadata = TRY ( TIFFImageDecoderPlugin : : read_exif_metadata ( { exif_data , exif_length } ) ) ;
2020-06-13 17:17:43 +00:00
2024-11-04 09:58:08 +00:00
if ( m_context - > exif_metadata )
TRY ( m_context - > apply_exif_orientation ( ) ) ;
2024-10-16 03:51:39 +00:00
2024-06-18 19:06:15 +00:00
png_destroy_read_struct ( & png_ptr , & info_ptr , nullptr ) ;
2024-11-04 09:58:08 +00:00
return { } ;
2019-10-15 19:48:08 +00:00
}
2024-10-16 03:51:39 +00: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 19:06:15 +00:00
ErrorOr < size_t > PNGLoadingContext : : read_frames ( png_structp png_ptr , png_infop info_ptr )
2023-04-09 05:40:55 +00:00
{
2024-06-18 19:06:15 +00:00
if ( png_get_acTL ( png_ptr , info_ptr , & frame_count , & loop_count ) ) {
// acTL chunk present: This is an APNG.
2023-04-09 05:40:55 +00:00
2024-06-18 19:06:15 +00:00
png_set_acTL ( png_ptr , info_ptr , frame_count , loop_count ) ;
2023-04-09 05:40:55 +00:00
2024-11-04 11:03:27 +00: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 ) ;
Vector < u8 * > row_pointers ;
2024-06-18 19:06:15 +00: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 13:49:11 +00:00
u8 dispose_op = PNG_DISPOSE_OP_NONE ;
u8 blend_op = PNG_BLEND_OP_SOURCE ;
2024-11-04 11:03:27 +00: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 ) ;
} ;
2024-08-19 13:49:11 +00:00
if ( png_get_valid ( png_ptr , info_ptr , PNG_INFO_fcTL ) ) {
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 19:06:15 +00:00
}
2024-11-04 11:03:27 +00:00
auto frame_rect = FloatRect { x , y , width , height } ;
2023-06-11 23:14:59 +00:00
2024-11-04 11:03:27 +00:00
auto decoded_frame_bitmap = TRY ( Bitmap : : create ( BitmapFormat : : BGRA8888 , AlphaType : : Unpremultiplied , IntSize { static_cast < int > ( width ) , static_cast < int > ( height ) } ) ) ;
2024-06-18 19:06:15 +00:00
row_pointers . resize ( height ) ;
for ( u32 i = 0 ; i < height ; + + i ) {
row_pointers [ i ] = decoded_frame_bitmap - > scanline_u8 ( i ) ;
}
png_read_image ( png_ptr , row_pointers . data ( ) ) ;
2023-04-09 05:40:55 +00:00
2024-11-04 11:03:27 +00: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.
painter - > clear_rect ( frame_rect , Gfx : : Color : : Transparent ) ;
2024-11-09 04:48:17 +00:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * decoded_frame_bitmap ) , decoded_frame_bitmap - > rect ( ) , Gfx : : ScalingMode : : NearestNeighbor , 1.0f ) ;
2024-11-04 11:03:27 +00: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.
2024-11-09 04:48:17 +00:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * decoded_frame_bitmap ) , decoded_frame_bitmap - > rect ( ) , ScalingMode : : NearestNeighbor , 1.0f ) ;
2024-11-04 11:03:27 +00:00
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
2023-04-09 05:40:55 +00:00
2024-11-04 11:03:27 +00:00
frame_descriptors . append ( { TRY ( output_buffer - > clone ( ) ) , duration_ms ( ) } ) ;
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.
painter - > clear_rect ( frame_rect , Gfx : : Color : : Transparent ) ;
2024-11-09 04:48:17 +00:00
painter - > draw_bitmap ( frame_rect , Gfx : : ImmutableBitmap : : create ( * prev_output_buffer ) , IntRect { x , y , width , height } , Gfx : : ScalingMode : : NearestNeighbor , 1.0f ) ;
2024-11-04 11:03:27 +00:00
break ;
default :
VERIFY_NOT_REACHED ( ) ;
2024-06-18 19:06:15 +00:00
}
}
} else {
// This is a single-frame PNG.
2023-06-11 20:20:14 +00:00
2024-06-18 19:06:15 +00:00
frame_count = 1 ;
loop_count = 0 ;
2019-03-21 02:57:42 +00:00
2024-11-04 11:03:27 +00:00
auto decoded_frame_bitmap = TRY ( Bitmap : : create ( BitmapFormat : : BGRA8888 , AlphaType : : Unpremultiplied , size ) ) ;
Vector < u8 * > row_pointers ;
2024-06-18 19:06:15 +00:00
row_pointers . resize ( size . height ( ) ) ;
for ( int i = 0 ; i < size . height ( ) ; + + i )
row_pointers [ i ] = decoded_frame_bitmap - > scanline_u8 ( i ) ;
2023-10-07 09:02:32 +00:00
2024-06-18 19:06:15 +00:00
png_read_image ( png_ptr , row_pointers . data ( ) ) ;
frame_descriptors . append ( { move ( decoded_frame_bitmap ) , 0 } ) ;
2023-10-07 09:02:32 +00:00
}
2024-06-18 19:06:15 +00:00
return this - > frame_count ;
2019-10-15 19:48:08 +00:00
}
2022-03-14 19:26:37 +00:00
PNGImageDecoderPlugin : : ~ PNGImageDecoderPlugin ( ) = default ;
2019-10-15 19:48:08 +00:00
2023-02-26 18:02:50 +00:00
bool PNGImageDecoderPlugin : : sniff ( ReadonlyBytes data )
2023-01-20 08:13:14 +00:00
{
2024-11-04 09:48:24 +00:00
auto constexpr png_signature_size_in_bytes = 8 ;
if ( data . size ( ) < png_signature_size_in_bytes )
2023-04-09 05:40:55 +00:00
return false ;
2024-11-04 09:48:24 +00:00
return png_sig_cmp ( data . data ( ) , 0 , png_signature_size_in_bytes ) = = 0 ;
2020-05-03 16:12:54 +00:00
}
2024-05-23 02:45:22 +00:00
Optional < Metadata const & > PNGImageDecoderPlugin : : metadata ( )
{
if ( m_context - > exif_metadata )
return * m_context - > exif_metadata ;
return OptionalNone { } ;
}
2020-02-06 10:56:38 +00:00
}