2020-06-15 21:36:05 -04:00
/*
* Copyright ( c ) 2020 , Paul Roukema < roukemap @ gmail . com >
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-06-15 21:36:05 -04:00
*/
# include <AK/ByteBuffer.h>
2021-01-24 15:28:26 +01:00
# include <AK/Debug.h>
2020-09-20 12:19:01 +02:00
# include <AK/MemoryStream.h>
2020-06-15 21:36:05 -04:00
# include <AK/NonnullOwnPtrVector.h>
2020-06-18 21:18:35 -04:00
# include <AK/Types.h>
2020-06-15 21:36:05 -04:00
# include <LibGfx/ICOLoader.h>
# include <LibGfx/PNGLoader.h>
# include <string.h>
namespace Gfx {
// FIXME: This is in little-endian order. Maybe need a NetworkOrdered<T> equivalent eventually.
struct ICONDIR {
2020-09-20 12:19:01 +02:00
u16 must_be_0 = 0 ;
u16 must_be_1 = 0 ;
u16 image_count = 0 ;
2020-06-15 21:36:05 -04:00
} ;
2021-09-05 01:00:46 -07:00
static_assert ( AssertSize < ICONDIR , 6 > ( ) ) ;
2020-06-15 21:36:05 -04:00
struct ICONDIRENTRY {
u8 width ;
u8 height ;
u8 color_count ;
u8 reserved_0 ;
u16 planes ;
u16 bits_per_pixel ;
u32 size ;
u32 offset ;
} ;
2021-09-05 01:00:46 -07:00
static_assert ( AssertSize < ICONDIRENTRY , 16 > ( ) ) ;
2020-06-15 21:36:05 -04:00
2020-12-30 22:44:54 +01:00
struct [[gnu::packed]] BMPFILEHEADER {
2020-06-18 21:18:35 -04:00
u8 signature [ 2 ] ;
u32 size ;
u16 reserved1 ;
u16 reserved2 ;
u32 offset ;
} ;
static_assert ( sizeof ( BMPFILEHEADER ) = = 14 ) ;
struct BITMAPINFOHEADER {
u32 size ;
i32 width ;
i32 height ;
u16 planes ;
u16 bpp ;
u32 compression ;
u32 size_image ;
u32 vres ;
u32 hres ;
u32 palette_size ;
u32 important_colors ;
} ;
static_assert ( sizeof ( BITMAPINFOHEADER ) = = 40 ) ;
2020-12-30 22:44:54 +01:00
struct [[gnu::packed]] BMP_ARGB {
2020-06-18 21:18:35 -04:00
u8 b ;
u8 g ;
u8 r ;
u8 a ;
} ;
static_assert ( sizeof ( BMP_ARGB ) = = 4 ) ;
2021-05-28 07:01:52 +02:00
struct ICOImageDescriptor {
2020-06-15 21:36:05 -04:00
u16 width ;
u16 height ;
size_t offset ;
size_t size ;
RefPtr < Gfx : : Bitmap > bitmap ;
} ;
struct ICOLoadingContext {
enum State {
NotDecoded = 0 ,
Error ,
DirectoryDecoded ,
BitmapDecoded
} ;
State state { NotDecoded } ;
const u8 * data { nullptr } ;
size_t data_size { 0 } ;
2021-05-28 07:01:52 +02:00
Vector < ICOImageDescriptor > images ;
2020-06-16 23:24:01 -04:00
size_t largest_index ;
2020-06-15 21:36:05 -04:00
} ;
2020-09-20 12:19:01 +02:00
static Optional < size_t > decode_ico_header ( InputMemoryStream & stream )
2020-06-15 21:36:05 -04:00
{
ICONDIR header ;
2020-09-20 12:19:01 +02:00
stream > > Bytes { & header , sizeof ( header ) } ;
if ( stream . handle_any_error ( ) )
2020-06-15 21:36:05 -04:00
return { } ;
if ( header . must_be_0 ! = 0 | | header . must_be_1 ! = 1 )
return { } ;
return { header . image_count } ;
}
2021-05-28 07:01:52 +02:00
static Optional < ICOImageDescriptor > decode_ico_direntry ( InputMemoryStream & stream )
2020-06-15 21:36:05 -04:00
{
ICONDIRENTRY entry ;
2020-09-20 12:19:01 +02:00
stream > > Bytes { & entry , sizeof ( entry ) } ;
if ( stream . handle_any_error ( ) )
2020-06-15 21:36:05 -04:00
return { } ;
2021-05-28 07:01:52 +02:00
ICOImageDescriptor desc = { entry . width , entry . height , entry . offset , entry . size , nullptr } ;
2020-06-15 21:36:05 -04:00
if ( desc . width = = 0 )
desc . width = 256 ;
if ( desc . height = = 0 )
desc . height = 256 ;
return { desc } ;
}
2020-06-16 23:24:01 -04:00
static size_t find_largest_image ( const ICOLoadingContext & context )
{
size_t max_area = 0 ;
size_t index = 0 ;
size_t largest_index = 0 ;
2020-08-05 14:09:38 +02:00
for ( const auto & desc : context . images ) {
2020-06-16 23:24:01 -04:00
if ( desc . width * desc . height > max_area ) {
max_area = desc . width * desc . height ;
largest_index = index ;
}
+ + index ;
}
return largest_index ;
}
2020-06-15 21:36:05 -04:00
static bool load_ico_directory ( ICOLoadingContext & context )
{
2020-09-20 12:19:01 +02:00
InputMemoryStream stream { { context . data , context . data_size } } ;
2020-06-15 21:36:05 -04:00
auto image_count = decode_ico_header ( stream ) ;
if ( ! image_count . has_value ( ) | | image_count . value ( ) = = 0 ) {
return false ;
}
for ( size_t i = 0 ; i < image_count . value ( ) ; + + i ) {
auto maybe_desc = decode_ico_direntry ( stream ) ;
if ( ! maybe_desc . has_value ( ) ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_directory: error loading entry: {} " , i ) ;
2020-06-15 21:36:05 -04:00
return false ;
}
auto & desc = maybe_desc . value ( ) ;
if ( desc . offset + desc . size < desc . offset // detect integer overflow
| | ( desc . offset + desc . size ) > context . data_size ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_directory: offset: {} size: {} doesn't fit in ICO size: {} " , desc . offset , desc . size , context . data_size ) ;
2020-06-15 21:36:05 -04:00
return false ;
}
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_directory: index {} width: {} height: {} offset: {} size: {} " , i , desc . width , desc . height , desc . offset , desc . size ) ;
2020-06-15 21:36:05 -04:00
context . images . append ( desc ) ;
}
2020-06-16 23:24:01 -04:00
context . largest_index = find_largest_image ( context ) ;
2020-06-18 21:18:35 -04:00
context . state = ICOLoadingContext : : State : : DirectoryDecoded ;
return true ;
}
2021-05-28 07:01:52 +02:00
static bool load_ico_bmp ( ICOLoadingContext & context , ICOImageDescriptor & desc )
2020-06-18 21:18:35 -04:00
{
BITMAPINFOHEADER info ;
if ( desc . size < sizeof ( info ) )
return false ;
memcpy ( & info , context . data + desc . offset , sizeof ( info ) ) ;
if ( info . size ! = sizeof ( info ) ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: info size: {}, expected: {} " , info . size , sizeof ( info ) ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
if ( info . width < 0 ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: width {} < 0 " , info . width ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
2021-05-29 17:47:12 +03:00
if ( info . height = = NumericLimits < i32 > : : min ( ) ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: height == NumericLimits<i32>::min() " ) ;
2021-05-29 17:47:12 +03:00
return false ;
}
2020-06-18 21:18:35 -04:00
bool topdown = false ;
if ( info . height < 0 ) {
topdown = true ;
info . height = - info . height ;
}
if ( info . planes ! = 1 ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: planes: {} != 1 " , info . planes ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
if ( info . bpp ! = 32 ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: unsupported bpp: {} " , info . bpp ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: width: {} height: {} direction: {} bpp: {} size_image: {} " ,
info . width , info . height , topdown ? " TopDown " : " BottomUp " , info . bpp , info . size_image ) ;
2020-06-18 21:18:35 -04:00
if ( info . compression ! = 0 | | info . palette_size ! = 0 | | info . important_colors ! = 0 ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: following fields must be 0: compression: {} palette_size: {} important_colors: {} " , info . compression , info . palette_size , info . important_colors ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
if ( info . width ! = desc . width | | info . height ! = 2 * desc . height ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: size mismatch: ico {}x{}, bmp {}x{} " , desc . width , desc . height , info . width , info . height ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
// Mask is 1bpp, and each row must be 4-byte aligned
2020-08-05 14:09:38 +02:00
size_t mask_row_len = align_up_to ( align_up_to ( desc . width , 8 ) / 8 , 4 ) ;
2020-06-18 21:18:35 -04:00
size_t required_len = desc . height * ( desc . width * sizeof ( BMP_ARGB ) + mask_row_len ) ;
size_t available_len = desc . size - sizeof ( info ) ;
if ( required_len > available_len ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bmp: required_len: {} > available_len: {} " , required_len , available_len ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
2021-11-06 19:30:59 +01:00
auto bitmap_or_error = Bitmap : : try_create ( BitmapFormat : : BGRA8888 , { desc . width , desc . height } ) ;
if ( bitmap_or_error . is_error ( ) )
2020-12-20 16:04:29 +01:00
return false ;
2021-11-06 19:30:59 +01:00
desc . bitmap = bitmap_or_error . release_value_but_fixme_should_propagate_errors ( ) ;
2020-06-18 21:18:35 -04:00
Bitmap & bitmap = * desc . bitmap ;
const u8 * image_base = context . data + desc . offset + sizeof ( info ) ;
const BMP_ARGB * data_base = ( const BMP_ARGB * ) image_base ;
const u8 * mask_base = image_base + desc . width * desc . height * sizeof ( BMP_ARGB ) ;
for ( int y = 0 ; y < desc . height ; y + + ) {
const u8 * row_mask = mask_base + mask_row_len * y ;
const BMP_ARGB * row_data = data_base + desc . width * y ;
for ( int x = 0 ; x < desc . width ; x + + ) {
u8 mask = ! ! ( row_mask [ x / 8 ] & ( 0x80 > > ( x % 8 ) ) ) ;
BMP_ARGB data = row_data [ x ] ;
bitmap . set_pixel ( x , topdown ? y : desc . height - y - 1 ,
Color ( data . r , data . g , data . b , mask ? 0 : data . a ) ) ;
}
}
2020-06-15 21:36:05 -04:00
return true ;
}
2020-06-16 23:24:01 -04:00
static bool load_ico_bitmap ( ICOLoadingContext & context , Optional < size_t > index )
2020-06-15 21:36:05 -04:00
{
if ( context . state < ICOLoadingContext : : State : : DirectoryDecoded ) {
if ( ! load_ico_directory ( context ) ) {
context . state = ICOLoadingContext : : State : : Error ;
return false ;
}
context . state = ICOLoadingContext : : State : : DirectoryDecoded ;
}
2020-06-16 23:24:01 -04:00
size_t real_index = context . largest_index ;
if ( index . has_value ( ) )
real_index = index . value ( ) ;
if ( real_index > = context . images . size ( ) ) {
2020-06-15 21:36:05 -04:00
return false ;
}
2021-05-28 07:01:52 +02:00
ICOImageDescriptor & desc = context . images [ real_index ] ;
2020-06-15 21:36:05 -04:00
PNGImageDecoderPlugin png_decoder ( context . data + desc . offset , desc . size ) ;
if ( png_decoder . sniff ( ) ) {
desc . bitmap = png_decoder . bitmap ( ) ;
if ( ! desc . bitmap ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bitmap: failed to load PNG encoded image index: {} " , real_index ) ;
2020-06-15 21:36:05 -04:00
return false ;
}
return true ;
} else {
2020-06-18 21:18:35 -04:00
if ( ! load_ico_bmp ( context , desc ) ) {
2021-05-29 17:54:33 +03:00
dbgln_if ( ICO_DEBUG , " load_ico_bitmap: failed to load BMP encoded image index: {} " , real_index ) ;
2020-06-18 21:18:35 -04:00
return false ;
}
return true ;
2020-06-15 21:36:05 -04:00
}
}
ICOImageDecoderPlugin : : ICOImageDecoderPlugin ( const u8 * data , size_t size )
{
m_context = make < ICOLoadingContext > ( ) ;
m_context - > data = data ;
m_context - > data_size = size ;
}
ICOImageDecoderPlugin : : ~ ICOImageDecoderPlugin ( ) { }
IntSize ICOImageDecoderPlugin : : size ( )
{
if ( m_context - > state = = ICOLoadingContext : : State : : Error ) {
return { } ;
}
if ( m_context - > state < ICOLoadingContext : : State : : DirectoryDecoded ) {
if ( ! load_ico_directory ( * m_context ) ) {
m_context - > state = ICOLoadingContext : : State : : Error ;
return { } ;
}
m_context - > state = ICOLoadingContext : : State : : DirectoryDecoded ;
}
2020-06-16 23:24:01 -04:00
return { m_context - > images [ m_context - > largest_index ] . width , m_context - > images [ m_context - > largest_index ] . height } ;
2020-06-15 21:36:05 -04:00
}
RefPtr < Gfx : : Bitmap > ICOImageDecoderPlugin : : bitmap ( )
{
if ( m_context - > state = = ICOLoadingContext : : State : : Error )
return nullptr ;
if ( m_context - > state < ICOLoadingContext : : State : : BitmapDecoded ) {
// NOTE: This forces the chunk decoding to happen.
2020-06-16 23:24:01 -04:00
bool success = load_ico_bitmap ( * m_context , { } ) ;
2020-06-15 21:36:05 -04:00
if ( ! success ) {
m_context - > state = ICOLoadingContext : : State : : Error ;
return nullptr ;
}
m_context - > state = ICOLoadingContext : : State : : BitmapDecoded ;
}
2021-02-23 20:42:32 +01:00
VERIFY ( m_context - > images [ m_context - > largest_index ] . bitmap ) ;
2020-06-16 23:24:01 -04:00
return m_context - > images [ m_context - > largest_index ] . bitmap ;
2020-06-15 21:36:05 -04:00
}
void ICOImageDecoderPlugin : : set_volatile ( )
{
if ( m_context - > images [ 0 ] . bitmap )
m_context - > images [ 0 ] . bitmap - > set_volatile ( ) ;
}
2021-07-24 22:49:48 +02:00
bool ICOImageDecoderPlugin : : set_nonvolatile ( bool & was_purged )
2020-06-15 21:36:05 -04:00
{
if ( ! m_context - > images [ 0 ] . bitmap )
return false ;
2021-07-24 22:49:48 +02:00
return m_context - > images [ 0 ] . bitmap - > set_nonvolatile ( was_purged ) ;
2020-06-15 21:36:05 -04:00
}
bool ICOImageDecoderPlugin : : sniff ( )
{
2020-09-20 12:19:01 +02:00
InputMemoryStream stream { { m_context - > data , m_context - > data_size } } ;
2020-06-15 21:36:05 -04:00
return decode_ico_header ( stream ) . has_value ( ) ;
}
bool ICOImageDecoderPlugin : : is_animated ( )
{
return false ;
}
size_t ICOImageDecoderPlugin : : loop_count ( )
{
return 0 ;
}
size_t ICOImageDecoderPlugin : : frame_count ( )
{
return 1 ;
}
ImageFrameDescriptor ICOImageDecoderPlugin : : frame ( size_t i )
{
2021-07-27 01:29:50 +02:00
if ( i > 0 )
return { } ;
return { bitmap ( ) , 0 } ;
2020-06-15 21:36:05 -04:00
}
}