// license:BSD-3-Clause// copyright-holders:Vas Crabb/*************************************************************************** msdib.h Microsoft Device-Independent Bitmap file loading.***************************************************************************/#include"msdib.h"#include"coretmpl.h"#include"ioprocs.h"#include"multibyte.h"#include"eminline.h"#include"osdcore.h"#include<cassert>#include<cstdlib>#include<cstring>#include<tuple>#define LOG_GENERAL (1U << 0)#define LOG_DIB (1U << 1)//#define VERBOSE (LOG_GENERAL | LOG_DIB)#define LOG_OUTPUT_FUNC osd_printf_verbose#ifndef VERBOSE#define VERBOSE 0#endif#define LOGMASKED(mask, ...) do { if (VERBOSE & (mask)) (LOG_OUTPUT_FUNC)(__VA_ARGS__); } while (false)#define LOG(...) LOGMASKED(LOG_GENERAL, __VA_ARGS__)namespaceutil{namespace{// DIB compression schemesenum:std::uint32_t{DIB_COMP_NONE=0,DIB_COMP_RLE8=1,DIB_COMP_RLE4=2,DIB_COMP_BITFIELDS=3};// file header doesn't use natural alignmentusingbitmap_file_header=std::uint8_t[14];// old-style DIB headerstructbitmap_core_header{std::uint32_tsize;// size of the header (12, 16 or 64)std::int16_twidth;// width of bitmap in pixelsstd::int16_theight;// height of the image in pixelsstd::uint16_tplanes;// number of colour planes (must be 1)std::uint16_tbpp;// bits per pixel};// new-style DIB headerstructbitmap_info_header{std::uint32_tsize;// size of the headerstd::int32_twidth;// width of bitmap in pixelsstd::int32_theight;// height of bitmap in pixelsstd::uint16_tplanes;// number of colour planes (must be 1)std::uint16_tbpp;// bits per pixelstd::uint32_tcomp;// compression methodstd::uint32_trawsize;// size of bitmap data after decompression or 0 if uncompressedstd::int32_thres;// horizontal resolution in pixels/metrestd::int32_tvres;// horizontal resolution in pixels/metrestd::uint32_tcolors;// number of colours or 0 for 1 << bppstd::uint32_timportant;// number of important colours or 0 if all importantstd::uint32_tred;// red field mask - must be contiguousstd::uint32_tgreen;// green field mask - must be contiguousstd::uint32_tblue;// blue field mask - must be contiguousstd::uint32_talpha;// alpha field mask - must be contiguous};unionbitmap_headers{bitmap_core_headercore;bitmap_info_headerinfo;};booldib_parse_mask(std::uint32_tmask,unsigned&shift,unsigned&bits)noexcept{shift=count_leading_zeros_32(mask);mask<<=shift;bits=count_leading_ones_32(mask);mask<<=shift;shift=32-shift-bits;return!mask;}voiddib_truncate_channel(unsigned&shift,unsigned&bits)noexcept{if(8U<bits){unsignedconstexcess(bits-8);shift+=excess;bits-=excess;}}std::uint8_tdib_splat_sample(std::uint8_tval,unsignedbits)noexcept{assert(8U>=bits);for(val<<=(8U-bits);bits&&(8U>bits);bits<<=1)val|=val>>bits;returnval;}msdib_errordib_read_file_header(read_stream&fp,std::uint32_t&filelen)noexcept{// the bitmap file header doesn't use natural alignmentbitmap_file_headerfile_header;autoconst[err,actual]=read(fp,file_header,sizeof(file_header));if(err||(sizeof(file_header)!=actual)){LOG("Error reading DIB file header\n");returnmsdib_error::FILE_TRUNCATED;}// only support Windows bitmaps for nowif((0x42!=file_header[0])||(0x4d!=file_header[1]))returnmsdib_error::BAD_SIGNATURE;// do a very basic check on the file lengthstd::uint32_tconstfile_length(get_u32le(&file_header[2]));if((sizeof(file_header)+sizeof(bitmap_core_header))>file_length)returnmsdib_error::FILE_CORRUPT;// check that the offset to the pixel data looks half sanestd::uint32_tconstpixel_offset(get_u32le(&file_header[10]));if(((sizeof(file_header)+sizeof(bitmap_core_header))>pixel_offset)||(file_length<pixel_offset))returnmsdib_error::FILE_CORRUPT;// looks OK enoughfilelen=file_length;returnmsdib_error::NONE;}msdib_errordib_read_bitmap_header(random_read&fp,bitmap_headers&header,unsigned&palette_bytes,bool&indexed,std::size_t&palette_entries,std::size_t&palette_size,std::size_t&row_bytes,std::uint32_tlength)noexcept{std::error_conditionerr;std::size_tactual;// check that these things haven't been padded somehowstatic_assert(sizeof(bitmap_core_header)==12U,"compiler has applied padding to bitmap_core_header");static_assert(sizeof(bitmap_info_header)==56U,"compiler has applied padding to bitmap_info_header");// ensure the header fits in the space for the image dataassert(&header.core.size==&header.info.size);if(sizeof(header.core)>length)returnmsdib_error::FILE_TRUNCATED;std::memset(&header,0,sizeof(header));std::tie(err,actual)=read(fp,&header.core.size,sizeof(header.core.size));if(err||(sizeof(header.core.size)!=actual)){LOG("Error reading DIB header size (length %u)\n",length);returnmsdib_error::FILE_TRUNCATED;}header.core.size=little_endianize_int32(header.core.size);if(length<header.core.size){LOG("DIB image data (%u bytes) is too small for DIB header (%u bytes)\n",length,header.core.size);returnmsdib_error::FILE_CORRUPT;}// identify and read the header - convert OS/2 headers to Windows 3 formatpalette_bytes=4U;switch(header.core.size){case16U:case64U:// extended OS/2 bitmap header with support for compressionLOG("DIB image data (%u bytes) uses unsupported OS/2 DIB header (size %u)\n",length,header.core.size);returnmsdib_error::UNSUPPORTED_FORMAT;case12U:// introduced in OS/2 and Windows 2.0{palette_bytes=3U;std::uint32_tconstheader_read(std::min<std::uint32_t>(header.core.size,sizeof(header.core))-sizeof(header.core.size));std::tie(err,actual)=read(fp,&header.core.width,header_read);if(err||(header_read!=actual)){LOG("Error reading DIB core header from image data (%u bytes)\n",length);returnmsdib_error::FILE_TRUNCATED;}if(fp.seek(header.core.size-sizeof(header.core.size)-header_read,SEEK_CUR)){LOG("Error seeking past additional DIB header data (%u bytes)\n",header.core.size-sizeof(header.core.size)-header_read);returnmsdib_error::FILE_ERROR;}header.core.width=little_endianize_int16(header.core.width);header.core.height=little_endianize_int16(header.core.height);header.core.planes=little_endianize_int16(header.core.planes);header.core.bpp=little_endianize_int16(header.core.bpp);LOGMASKED(LOG_DIB,"Read DIB core header from image data : %d*%d, %u planes, %u bpp\n",header.core.width,header.core.height,header.core.planes,header.core.bpp);// this works because the core header only aliases the width/height of the info headerheader.info.bpp=header.core.bpp;header.info.planes=header.core.planes;header.info.height=header.core.height;header.info.width=header.core.width;header.info.size=40U;}break;default:// the next version will be longerif(124U>=header.core.size){LOG("DIB image data (%u bytes) uses unsupported DIB header format (size %u)\n",length,header.core.size);returnmsdib_error::UNSUPPORTED_FORMAT;}[[fallthrough]];case40U:case52U:case56U:case108U:case124U:// the Windows 3 bitmap header with optional extensions{palette_bytes=4U;std::uint32_tconstheader_read(std::min<std::uint32_t>(header.info.size,sizeof(header.info))-sizeof(header.info.size));std::tie(err,actual)=read(fp,&header.info.width,header_read);if(err||(header_read!=actual)){LOG("Error reading DIB info header from image data (%u bytes)\n",length);returnmsdib_error::FILE_TRUNCATED;}if(fp.seek(header.info.size-sizeof(header.info.size)-header_read,SEEK_CUR)){LOG("Error seeking past additional DIB header data (%u bytes)\n",header.info.size-sizeof(header.info.size)-header_read);returnmsdib_error::FILE_ERROR;}header.info.width=little_endianize_int32(header.info.width);header.info.height=little_endianize_int32(header.info.height);header.info.planes=little_endianize_int16(header.info.planes);header.info.bpp=little_endianize_int16(header.info.bpp);header.info.comp=little_endianize_int32(header.info.comp);header.info.rawsize=little_endianize_int32(header.info.rawsize);header.info.hres=little_endianize_int32(header.info.hres);header.info.vres=little_endianize_int32(header.info.vres);header.info.colors=little_endianize_int32(header.info.colors);header.info.important=little_endianize_int32(header.info.important);header.info.red=little_endianize_int32(header.info.red);header.info.green=little_endianize_int32(header.info.green);header.info.blue=little_endianize_int32(header.info.blue);header.info.alpha=little_endianize_int32(header.info.alpha);LOGMASKED(LOG_DIB,"Read DIB info header from image data: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n",header.info.width,header.info.height,header.info.hres,header.info.vres,header.info.planes,header.info.bpp,header.info.important,header.info.colors?"":"2^",header.info.colors?header.info.colors:header.info.bpp);}break;}// check for unsupported planes/bit depthif((1U!=header.info.planes)||!header.info.bpp||(32U<header.info.bpp)||((8U<header.info.bpp)?(header.info.bpp%8):(8%header.info.bpp))){LOG("DIB image data uses unsupported planes/bits per pixel %u*%u\n",header.info.planes,header.info.bpp);returnmsdib_error::UNSUPPORTED_FORMAT;}// check dimensionsif((0>=header.info.width)||(0==header.info.height)){LOG("DIB image data has invalid dimensions %u*%u\n",header.info.width,header.info.height);returnmsdib_error::FILE_CORRUPT;}// ensure compression scheme is supportedboolno_palette(false);indexed=true;switch(header.info.comp){caseDIB_COMP_NONE:// uncompressed - direct colour with implied bitfields if more than eight bits/pixelindexed=8U>=header.info.bpp;if(indexed){if((1U<<header.info.bpp)<header.info.colors){osd_printf_verbose("DIB image data has oversized palette with %u entries for %u bits per pixel\n",header.info.colors,header.info.bpp);}}if(!indexed){no_palette=true;switch(header.info.bpp){case16U:header.info.red=0x00007c00;header.info.green=0x000003e0;header.info.blue=0x0000001f;header.info.alpha=0x00000000;break;case24U:case32U:header.info.red=0x00ff0000;header.info.green=0x0000ff00;header.info.blue=0x000000ff;header.info.alpha=0x00000000;break;}}break;caseDIB_COMP_BITFIELDS:// uncompressed direct colour with explicitly-specified bitfieldsindexed=false;if(offsetof(bitmap_info_header,alpha)>header.info.size){osd_printf_verbose("DIB image data specifies bit masks but is too small (size %u)\n",header.info.size);returnmsdib_error::FILE_CORRUPT;}break;default:LOG("DIB image data uses unsupported compression scheme %u\n",header.info.comp);returnmsdib_error::UNSUPPORTED_FORMAT;}// we can now calculate the size of the palette and row datapalette_entries=indexed?((1U==header.info.bpp)?2U:header.info.colors?header.info.colors:(1U<<header.info.bpp)):(no_palette?0U:header.info.colors);palette_size=palette_bytes*palette_entries;row_bytes=((31+(header.info.width*header.info.bpp))>>5)<<2;// header looks OKreturnmsdib_error::NONE;}}// anonymous namespacemsdib_errormsdib_verify_header(random_read&fp)noexcept{msdib_errorerr;// file headerstd::uint32_tfile_length;err=dib_read_file_header(fp,file_length);if(msdib_error::NONE!=err)returnerr;// bitmap headerbitmap_headersheader;unsignedpalette_bytes;boolindexed;std::size_tpalette_entries,palette_size,row_bytes;err=dib_read_bitmap_header(fp,header,palette_bytes,indexed,palette_entries,palette_size,row_bytes,file_length-sizeof(bitmap_file_header));if(msdib_error::NONE!=err)returnerr;// check lengthstd::size_tconstrequired_size(sizeof(bitmap_file_header)+header.info.size+palette_size+(row_bytes*std::abs(header.info.height)));if(required_size>file_length)returnmsdib_error::FILE_TRUNCATED;// good chance this file is supportedreturnmsdib_error::NONE;}msdib_errormsdib_read_bitmap(random_read&fp,bitmap_argb32&bitmap)noexcept{std::uint32_tfile_length;msdib_errorconstheaderr(dib_read_file_header(fp,file_length));if(msdib_error::NONE!=headerr)returnheaderr;returnmsdib_read_bitmap_data(fp,bitmap,file_length-sizeof(bitmap_file_header),0U);}msdib_errormsdib_read_bitmap_data(random_read&fp,bitmap_argb32&bitmap,std::uint32_tlength,std::uint32_tdirheight)noexcept{// read the bitmap headerbitmap_headersheader;unsignedpalette_bytes;boolindexed;std::size_tpalette_entries,palette_size,row_bytes;msdib_errorconsthead_error(dib_read_bitmap_header(fp,header,palette_bytes,indexed,palette_entries,palette_size,row_bytes,length));if(msdib_error::NONE!=head_error)returnhead_error;// check dimensionsboolconsttop_down(0>header.info.height);if(top_down)header.info.height=-header.info.height;boolhave_and_mask((2*dirheight)==header.info.height);if(!have_and_mask&&(0<dirheight)&&(dirheight!=header.info.height)){osd_printf_verbose("DIB image data height %d doesn't match directory height %u with or without AND mask\n",header.info.height,dirheight);returnmsdib_error::UNSUPPORTED_FORMAT;}if(have_and_mask)header.info.height>>=1;// we can now calculate the size of the image datastd::size_tconstmask_row_bytes(((31+header.info.width)>>5)<<2);std::size_tconstrequired_size(header.info.size+palette_size+((row_bytes+(have_and_mask?mask_row_bytes:0U))*header.info.height));if(required_size>length){LOG("DIB image data (%u bytes) smaller than calculated DIB data size (%u bytes)\n",length,required_size);returnmsdib_error::FILE_TRUNCATED;}// load the palette for indexed colour formats or the shifts for direct colour formatsunsignedred_shift(0),green_shift(0),blue_shift(0),alpha_shift(0);unsignedred_bits(0),green_bits(0),blue_bits(0),alpha_bits(0);std::unique_ptr<rgb_t[]>palette;if(indexed){// read palette and convertstd::unique_ptr<std::uint8_t[]>palette_data(new(std::nothrow)std::uint8_t[palette_size]);if(!palette_data)returnmsdib_error::OUT_OF_MEMORY;autoconst[err,actual]=read(fp,palette_data.get(),palette_size);if(err||(palette_size!=actual)){LOG("Error reading palette from DIB image data (%u bytes)\n",length);returnmsdib_error::FILE_TRUNCATED;}std::size_tconstpalette_usable(std::min<std::size_t>(palette_entries,std::size_t(1)<<header.info.bpp));palette.reset(new(std::nothrow)rgb_t[palette_usable]);if(!palette)returnmsdib_error::OUT_OF_MEMORY;std::uint8_tconst*ptr(palette_data.get());for(std::size_ti=0;palette_usable>i;++i,ptr+=palette_bytes)palette[i]=rgb_t(ptr[2],ptr[1],ptr[0]);}else{// skip over the palette if necessaryif(palette_entries){if(fp.seek(palette_bytes*palette_entries,SEEK_CUR)){LOG("Error seeking past DIB palette data (%u bytes)\n",palette_bytes*palette_entries);returnmsdib_error::FILE_ERROR;}}// convert masks to shiftsboolconstmasks_contiguous(dib_parse_mask(header.info.red,red_shift,red_bits)&&dib_parse_mask(header.info.green,green_shift,green_bits)&&dib_parse_mask(header.info.blue,blue_shift,blue_bits)&&dib_parse_mask(header.info.alpha,alpha_shift,alpha_bits));if(!masks_contiguous){osd_printf_verbose("DIB image data specifies non-contiguous channel masks 0x%x | 0x%x | 0x%x | 0x%x\n",header.info.red,header.info.green,header.info.blue,header.info.alpha);}if((32U!=header.info.bpp)&&((header.info.red|header.info.green|header.info.blue|header.info.alpha)>>header.info.bpp)){LOG("DIB image data specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n",header.info.red,header.info.green,header.info.blue,header.info.alpha,header.info.bpp);returnmsdib_error::FILE_CORRUPT;}LOGMASKED(LOG_DIB,"DIB image data using channels: R((x >> %2$u) & 0x%3$0*1$x) G((x >> %4$u) & 0x%5$0*1$x) B((x >> %6$u) & 0x%7$0*1$x) A((x >> %8$u) & 0x%9$0*1$x)\n",(header.info.bpp+3)>>2,red_shift,(std::uint32_t(1)<<red_bits)-1,green_shift,(std::uint32_t(1)<<green_bits)-1,blue_shift,(std::uint32_t(1)<<blue_bits)-1,alpha_shift,(std::uint32_t(1)<<alpha_bits)-1);// the MAME bitmap only supports 8 bits/sample maximumdib_truncate_channel(red_shift,red_bits);dib_truncate_channel(green_shift,green_bits);dib_truncate_channel(blue_shift,blue_bits);dib_truncate_channel(alpha_shift,alpha_bits);}// allocate a row buffer as well as the destination bitmapstd::unique_ptr<std::uint8_t[]>row_data(new(std::nothrow)std::uint8_t[row_bytes]);if(!row_data)returnmsdib_error::OUT_OF_MEMORY;bitmap.allocate(header.info.width,header.info.height);if(!bitmap.valid())returnmsdib_error::OUT_OF_MEMORY;// process row dataintconsty_inc(top_down?1:-1);for(std::int32_ti=0,y=top_down?0:(header.info.height-1);header.info.height>i;++i,y+=y_inc){autoconst[err,actual]=read(fp,row_data.get(),row_bytes);if(err||(row_bytes!=actual)){LOG("Error reading DIB row %d data from image data\n",i);returnmsdib_error::FILE_TRUNCATED;}std::uint8_t*src(row_data.get());std::uint32_t*dest(&bitmap.pix(y));unsignedshift(0U);for(std::int32_tx=0;header.info.width>x;++x,++dest){// extract or compose a pixelstd::uint32_tpix(0U);if(8U>=header.info.bpp){assert(8U>shift);pix=*src>>(8U-header.info.bpp);*src<<=header.info.bpp;shift+=header.info.bpp;if(8U<=shift){shift=0U;++src;}}elsefor(shift=0;header.info.bpp>shift;shift+=8U,++src){pix|=std::uint32_t(*src)<<shift;}// convert to RGBif(indexed){if(palette_entries>pix){*dest=palette[pix];}else{*dest=rgb_t::transparent();osd_printf_verbose("DIB image data has out-of-range color %u at (%d, %d) with %u palette entries\n",pix,x,y,palette_entries);}}else{std::uint8_tr(dib_splat_sample(BIT(pix,red_shift,red_bits),red_bits));std::uint8_tg(dib_splat_sample(BIT(pix,green_shift,green_bits),green_bits));std::uint8_tb(dib_splat_sample(BIT(pix,blue_shift,blue_bits),blue_bits));std::uint8_ta(dib_splat_sample(BIT(pix,alpha_shift,alpha_bits),alpha_bits));*dest=rgb_t(alpha_bits?a:255,r,g,b);}}}// process the AND mask if presentif(have_and_mask){for(std::int32_ti=0,y=top_down?0:(header.info.height-1);header.info.height>i;++i,y+=y_inc){autoconst[err,actual]=read(fp,row_data.get(),mask_row_bytes);if(err||(mask_row_bytes!=actual)){LOG("Error reading DIB mask row %d data from image data\n",i);returnmsdib_error::FILE_TRUNCATED;}std::uint8_t*src(row_data.get());std::uint32_t*dest(&bitmap.pix(y));unsignedshift(0U);for(std::int32_tx=0;header.info.width>x;++x,++dest){assert(8U>shift);rgb_tpix(*dest);*dest=pix.set_a(BIT(*src,7U-shift)?0U:pix.a());if(8U<=++shift){shift=0U;++src;}}}}// we're done!returnmsdib_error::NONE;}}// namespace util