summaryrefslogblamecommitdiffstatshomepage
path: root/src/frontend/mame/ui/icorender.cpp
blob: 7b7a58fa9231a72b1724f32035b87cf2948566d4 (plain) (tree)




















                                                                            
                         
                       





                     
                
 

                                                                                               
 
                               







                     































                                                                                          
 
                                                                                            




                                                                                        

                                                                          
         















                                                                                                                   


                                                       

                                                                                             


            

                                                



                                                                                                                         






                                           
                                                                                            
 
                                      

                                                                                                          
         



                                                                                                   
                 
                                                                                                             
                 
                    

                            




                                                                                                                   
                 
                            

                
                                                
                    

                                                                                                                   
                                           
                                          

                             


 
                                                                                                              

















                                                                                           
                                                                                                 

                                   
                                 
                      
                             

                                                                                         
                                                                    
                                           






















                                                                                                                         
                                        

                                              
                                 

                          

                                   
                                                                          
                                              




















                                                                                                 
                                                                                  






















                                                                                                            
                                                                        










                                                         
                                                                                 


                                                                        










                                                                                              
         
                                   
                 


                                                                                          
                 

                                                                          
                 


                                                                           

                 

































                                                                                                  



                       
// license:BSD-3-Clause
// copyright-holders:Maurizio Petrarota, Victor Laskin
/***************************************************************************

    ui/icorender.h

    Windows icon file parser.

    Previously based on code by Victor Laskin (victor.laskin@gmail.com)
    http://vitiy.info/Code/ico.cpp

    TODO:
    * Add variant that loads all images from the file
    * Allow size hint for choosing best candidate
    * Allow selecting amongst candidates based on colour depth

***************************************************************************/

#include "emu.h"
#include "icorender.h"

#include "util/ioprocs.h"
#include "util/msdib.h"
#include "util/png.h"

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <tuple>

// need to set LOG_OUTPUT_FUNC or LOG_OUTPUT_STREAM because there's no logerror outside devices
#define LOG_OUTPUT_FUNC osd_printf_verbose

//#define VERBOSE (LOG_GENERAL)

#include "logmacro.h"


namespace ui {

namespace {

// ICO file header
struct icon_dir_t
{
	uint16_t    reserved;   // must be 0
	uint16_t    type;       // 1 for icon or 2 for cursor
	uint16_t    count;      // number of images in the file
};

// ICO file directory entry
struct icon_dir_entry_t
{
	constexpr unsigned get_width() const { return width ? width : 256U; }
	constexpr unsigned get_height() const { return height ? height : 256U; }

	void byteswap()
	{
		planes = little_endianize_int16(planes);
		bpp = little_endianize_int16(bpp);
		size = little_endianize_int32(size);
		offset = little_endianize_int32(offset);
	}

	uint8_t     width;      // 0 means 256
	uint8_t     height;     // 0 means 256
	uint8_t     colors;     // for indexed colour, or 0 for direct colour
	uint8_t     reserved;   // documentation says this should be 0 but .NET writes 255
	uint16_t    planes;     // or hotspot X for cursor
	uint16_t    bpp;        // 0 to infer from image data, or hotspot Y for cursor
	uint32_t    size;       // image data size in bytes
	uint32_t    offset;     // offset to image data from start of file
};


bool load_ico_png(util::random_read &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap)
{
	// skip out if the data isn't a reasonable size - PNG magic alone is eight bytes
	if (9U >= dir.size)
		return false;
	fp.seek(dir.offset, SEEK_SET);
	std::error_condition const err(util::png_read_bitmap(fp, bitmap));
	if (!err)
	{
		// found valid PNG image
		assert(bitmap.valid());
		if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height())))
		{
			LOG("Loaded %d*%d pixel PNG image from ICO file\n", bitmap.width(), bitmap.height());
		}
		else
		{
			LOG(
					"Loaded %d*%d pixel PNG image from ICO file (directory indicated %u*%u)\n",
					bitmap.width(),
					bitmap.height(),
					dir.get_width(),
					dir.get_height());
		}
		return true;
	}
	else if (util::png_error::BAD_SIGNATURE == err)
	{
		// doesn't look like PNG data - just fall back to DIB without the file header
		return false;
	}
	else
	{
		// invalid PNG data or I/O error
		LOG(
				"Error %s:%d %s reading PNG image data from ICO file at offset %u (directory size %u)\n",
				err.category().name(),
				err.value(),
				err.message(),
				dir.offset,
				dir.size);
		return false;
	}
}


bool load_ico_dib(util::random_read &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap)
{
	fp.seek(dir.offset, SEEK_SET);
	util::msdib_error const err(util::msdib_read_bitmap_data(fp, bitmap, dir.size, dir.get_height()));
	switch (err)
	{
	case util::msdib_error::NONE:
		// found valid DIB image
		assert(bitmap.valid());
		if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height())))
		{
			LOG("Loaded %d*%d pixel DIB image from ICO file\n", bitmap.width(), bitmap.height());
		}
		else
		{
			LOG(
					"Loaded %d*%d pixel DIB image from ICO file (directory indicated %u*%u)\n",
					bitmap.width(),
					bitmap.height(),
					dir.get_width(),
					dir.get_height());
		}
		return true;

	default:
		// invalid DIB data or I/O error
		LOG(
				"Error %u reading DIB image data from ICO file at offset %u (directory size %u)\n",
				unsigned(err),
				dir.offset,
				dir.size);
		return false;
	}
}


bool load_ico_image(util::random_read &fp, unsigned index, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap)
{
	// try loading PNG image data (contains PNG file magic if used), and then fall back
	if (load_ico_png(fp, dir, bitmap))
	{
		LOG("Successfully loaded PNG image from ICO file entry %u\n", index);
		return true;
	}
	else if (load_ico_dib(fp, dir, bitmap))
	{
		LOG("Successfully loaded DIB image from ICO file entry %u\n", index);
		return true;
	}

	// no luck
	return false;
}


bool load_ico_image(util::random_read &fp, unsigned count, unsigned index, bitmap_argb32 &bitmap)
{
	// read the directory entry
	std::error_condition err;
	size_t actual;
	icon_dir_entry_t dir;
	err = fp.seek(sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * index), SEEK_SET);
	if (!err)
		std::tie(err, actual) = read(fp, &dir, sizeof(dir));
	if (err || (sizeof(dir) != actual))
	{
		LOG("Failed to read ICO file directory entry %u\n", index);
		return false;
	}
	dir.byteswap();
	if ((sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count)) > dir.offset)
	{
		LOG(
				"ICO file image %u data starting at %u overlaps %u bytes of file header and directory\n",
				index,
				dir.offset,
				sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count));
		return false;
	}
	else
	{
		return load_ico_image(fp, index, dir, bitmap);
	}
}

} // anonymous namespace


int images_in_ico(util::random_read &fp)
{
	// read and check the icon file header
	std::error_condition err;
	size_t actual;
	icon_dir_t header;
	err = fp.seek(0, SEEK_SET);
	if (!err)
		std::tie(err, actual) = read(fp, &header, sizeof(header));
	if (err || (sizeof(header) != actual))
	{
		LOG("Failed to read ICO file header\n");
		return -1;
	}
	header.reserved = little_endianize_int16(header.reserved);
	header.type = little_endianize_int16(header.type);
	header.count = little_endianize_int16(header.count);
	if (0U != header.reserved)
	{
		LOG("Invalid ICO file header reserved field %u (expected 0)\n", header.reserved);
		return -1;
	}
	if ((1U != header.type) && (2U != header.type))
	{
		LOG("Invalid ICO file header type field %u (expected 1 or 2)\n", header.type);
		return -1;
	}
	return int(unsigned(little_endianize_int16(header.count)));
}


void render_load_ico(util::random_read &fp, unsigned index, bitmap_argb32 &bitmap)
{
	// check that these things haven't been padded somehow
	static_assert(sizeof(icon_dir_t) == 6U, "compiler has applied padding to icon_dir_t");
	static_assert(sizeof(icon_dir_entry_t) == 16U, "compiler has applied padding to icon_dir_entry_t");

	// read and check the icon file header, then try to load the specified image
	int const count(images_in_ico(fp));
	if (0 > count)
	{
		// images_in_ico already logged an error
	}
	else if (index >= count)
	{
		osd_printf_verbose("Requested image %u from ICO file containing %d images\n", index, count);
	}
	else if (load_ico_image(fp, count, index, bitmap))
	{
		return;
	}
	bitmap.reset();
}


void render_load_ico_first(util::random_read &fp, bitmap_argb32 &bitmap)
{
	int const count(images_in_ico(fp));
	for (int i = 0; count > i; ++i)
	{
		if (load_ico_image(fp, count, i, bitmap))
			return;
	}
	bitmap.reset();
}


void render_load_ico_highest_detail(util::random_read &fp, bitmap_argb32 &bitmap)
{
	// read and check the icon file header - logs a message on error
	int const count(images_in_ico(fp));
	if (0 > count)
	{
		bitmap.reset();
		return;
	}

	// now load all the directory entries
	size_t const dir_bytes(sizeof(icon_dir_entry_t) * count);
	std::unique_ptr<icon_dir_entry_t []> dir(new (std::nothrow) icon_dir_entry_t [count]);
	std::unique_ptr<unsigned []> index(new (std::nothrow) unsigned [count]);
	if (count)
	{
		if (!dir || !index)
		{
			LOG("Failed to allocate memory for ICO file directory entries\n");
			bitmap.reset();
			return;
		}
		auto const [err, actual] = read(fp, dir.get(), dir_bytes);
		if (err || (dir_bytes != actual))
		{
			LOG("Failed to read ICO file directory entries\n");
			bitmap.reset();
			return;
		}
	}

	// byteswap and sort by (pixels, depth)
	for (int i = 0; count > i; ++i)
	{
		dir[i].byteswap();
		index[i] = i;
	}
	std::stable_sort(
			index.get(),
			index.get() + count,
			[&dir] (unsigned x, unsigned y)
			{
				unsigned const x_pixels(dir[x].get_width() * dir[x].get_height());
				unsigned const y_pixels(dir[y].get_width() * dir[y].get_height());
				if (x_pixels > y_pixels)
					return true;
				else if (x_pixels < y_pixels)
					return false;
				else
					return dir[x].bpp > dir[y].bpp;
			});

	// walk down until something works
	for (int i = 0; count > i; ++i)
	{
		LOG(
				"Try loading ICO file entry %u: %u*%u, %u bits per pixel\n",
				index[i],
				dir[index[i]].get_width(),
				dir[index[i]].get_height(),
				dir[index[i]].bpp);
		if (load_ico_image(fp, index[i], dir[index[i]], bitmap))
			return;
	}
	bitmap.reset();
}

} // namespace ui