1 // license:BSD-3-Clause
2 // copyright-holders:Vas Crabb
3 /***************************************************************************
4 
5     msdib.h
6 
7     Microsoft Device-Independent Bitmap file loading.
8 
9 ***************************************************************************/
10 
11 #include "msdib.h"
12 
13 #include "eminline.h"
14 
15 #include <cassert>
16 #include <cstdlib>
17 #include <cstring>
18 
19 
20 #define LOG_GENERAL (1U << 0)
21 #define LOG_DIB     (1U << 1)
22 
23 //#define VERBOSE (LOG_GENERAL | LOG_DIB)
24 
25 #define LOG_OUTPUT_FUNC osd_printf_verbose
26 
27 #ifndef VERBOSE
28 #define VERBOSE 0
29 #endif
30 
31 #define LOGMASKED(mask, ...) do { if (VERBOSE & (mask)) (LOG_OUTPUT_FUNC)(__VA_ARGS__); } while (false)
32 
33 #define LOG(...) LOGMASKED(LOG_GENERAL, __VA_ARGS__)
34 
35 
36 namespace util {
37 
38 namespace {
39 
40 // DIB compression schemes
41 enum : std::uint32_t
42 {
43 	DIB_COMP_NONE       = 0,
44 	DIB_COMP_RLE8       = 1,
45 	DIB_COMP_RLE4       = 2,
46 	DIB_COMP_BITFIELDS  = 3
47 };
48 
49 
50 // file header doesn't use natural alignment
51 using bitmap_file_header = std::uint8_t [14];
52 
53 
54 // old-style DIB header
55 struct bitmap_core_header
56 {
57 	std::uint32_t       size;       // size of the header (12, 16 or 64)
58 	std::int16_t        width;      // width of bitmap in pixels
59 	std::int16_t        height;     // height of the image in pixels
60 	std::uint16_t       planes;     // number of colour planes (must be 1)
61 	std::uint16_t       bpp;        // bits per pixel
62 };
63 
64 // new-style DIB header
65 struct bitmap_info_header
66 {
67 	std::uint32_t       size;       // size of the header
68 	std::int32_t        width;      // width of bitmap in pixels
69 	std::int32_t        height;     // height of bitmap in pixels
70 	std::uint16_t       planes;     // number of colour planes (must be 1)
71 	std::uint16_t       bpp;        // bits per pixel
72 	std::uint32_t       comp;       // compression method
73 	std::uint32_t       rawsize;    // size of bitmap data after decompression or 0 if uncompressed
74 	std::int32_t        hres;       // horizontal resolution in pixels/metre
75 	std::int32_t        vres;       // horizontal resolution in pixels/metre
76 	std::uint32_t       colors;     // number of colours or 0 for 1 << bpp
77 	std::uint32_t       important;  // number of important colours or 0 if all important
78 	std::uint32_t       red;        // red field mask - must be contiguous
79 	std::uint32_t       green;      // green field mask - must be contiguous
80 	std::uint32_t       blue;       // blue field mask - must be contiguous
81 	std::uint32_t       alpha;      // alpha field mask - must be contiguous
82 };
83 
84 
85 union bitmap_headers
86 {
87 	bitmap_core_header  core;
88 	bitmap_info_header  info;
89 };
90 
91 
dib_parse_mask(uint32_t mask,unsigned & shift,unsigned & bits)92 bool dib_parse_mask(uint32_t mask, unsigned &shift, unsigned &bits)
93 {
94 	shift = count_leading_zeros(mask);
95 	mask <<= shift;
96 	bits = count_leading_ones(mask);
97 	mask <<= shift;
98 	shift = 32 - shift - bits;
99 	return !mask;
100 }
101 
102 
dib_truncate_channel(unsigned & shift,unsigned & bits)103 void dib_truncate_channel(unsigned &shift, unsigned &bits)
104 {
105 	if (8U < bits)
106 	{
107 		unsigned const excess(bits - 8);
108 		shift += excess;
109 		bits -= excess;
110 	}
111 }
112 
113 
dib_splat_sample(uint8_t val,unsigned bits)114 uint8_t dib_splat_sample(uint8_t val, unsigned bits)
115 {
116 	assert(8U >= bits);
117 	for (val <<= (8U - bits); bits && (8U > bits); bits <<= 1)
118 		val |= val >> bits;
119 	return val;
120 }
121 
122 
dib_read_file_header(core_file & fp,std::uint32_t & filelen)123 msdib_error dib_read_file_header(core_file &fp, std::uint32_t &filelen)
124 {
125 	// the bitmap file header doesn't use natural alignment
126 	bitmap_file_header file_header;
127 	if (fp.read(file_header, sizeof(file_header)) != sizeof(file_header))
128 	{
129 		LOG("Error reading DIB file header\n");
130 		return msdib_error::FILE_TRUNCATED;
131 	}
132 
133 	// only support Windows bitmaps for now
134 	if ((0x42 != file_header[0]) || (0x4d != file_header[1]))
135 		return msdib_error::BAD_SIGNATURE;
136 
137 	// do a very basic check on the file length
138 	std::uint32_t const file_length(
139 			(std::uint32_t(file_header[2]) << 0) |
140 			(std::uint32_t(file_header[3]) << 8) |
141 			(std::uint32_t(file_header[4]) << 16) |
142 			(std::uint32_t(file_header[5]) << 24));
143 	if ((sizeof(file_header) + sizeof(bitmap_core_header)) > file_length)
144 		return msdib_error::FILE_CORRUPT;
145 
146 	// check that the offset to the pixel data looks half sane
147 	std::uint32_t const pixel_offset(
148 			(std::uint32_t(file_header[10]) << 0) |
149 			(std::uint32_t(file_header[11]) << 8) |
150 			(std::uint32_t(file_header[12]) << 16) |
151 			(std::uint32_t(file_header[13]) << 24));
152 	if (((sizeof(file_header) + sizeof(bitmap_core_header)) > pixel_offset) || (file_length < pixel_offset))
153 		return msdib_error::FILE_CORRUPT;
154 
155 	// looks OK enough
156 	filelen = file_length;
157 	return msdib_error::NONE;
158 }
159 
160 
dib_read_bitmap_header(core_file & 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_t length)161 msdib_error dib_read_bitmap_header(
162 		core_file &fp,
163 		bitmap_headers &header,
164 		unsigned &palette_bytes,
165 		bool &indexed,
166 		std::size_t &palette_entries,
167 		std::size_t &palette_size,
168 		std::size_t &row_bytes,
169 		std::uint32_t length)
170 {
171 	// check that these things haven't been padded somehow
172 	static_assert(sizeof(bitmap_core_header) == 12U, "compiler has applied padding to bitmap_core_header");
173 	static_assert(sizeof(bitmap_info_header) == 56U, "compiler has applied padding to bitmap_info_header");
174 
175 	// ensure the header fits in the space for the image data
176 	assert(&header.core.size == &header.info.size);
177 	if (sizeof(header.core) > length)
178 		return msdib_error::FILE_TRUNCATED;
179 	std::memset(&header, 0, sizeof(header));
180 	if (fp.read(&header.core.size, sizeof(header.core.size)) != sizeof(header.core.size))
181 	{
182 		LOG("Error reading DIB header size (length %u)\n", length);
183 		return msdib_error::FILE_TRUNCATED;
184 	}
185 	header.core.size = little_endianize_int32(header.core.size);
186 	if (length < header.core.size)
187 	{
188 		LOG("DIB image data (%u bytes) is too small for DIB header (%u bytes)\n", length, header.core.size);
189 		return msdib_error::FILE_CORRUPT;
190 	}
191 
192 	// identify and read the header - convert OS/2 headers to Windows 3 format
193 	palette_bytes = 4U;
194 	switch (header.core.size)
195 	{
196 	case 16U:
197 	case 64U:
198 		// extended OS/2 bitmap header with support for compression
199 		LOG(
200 				"DIB image data (%u bytes) uses unsupported OS/2 DIB header (size %u)\n",
201 				length,
202 				header.core.size);
203 		return msdib_error::UNSUPPORTED_FORMAT;
204 
205 	case 12U:
206 		// introduced in OS/2 and Windows 2.0
207 		{
208 			palette_bytes = 3U;
209 			std::uint32_t const header_read(std::min<std::uint32_t>(header.core.size, sizeof(header.core)) - sizeof(header.core.size));
210 			if (fp.read(&header.core.width, header_read) != header_read)
211 			{
212 				LOG("Error reading DIB core header from image data (%u bytes)\n", length);
213 				return msdib_error::FILE_TRUNCATED;
214 			}
215 			fp.seek(header.core.size - sizeof(header.core.size) - header_read, SEEK_CUR);
216 			header.core.width = little_endianize_int16(header.core.width);
217 			header.core.height = little_endianize_int16(header.core.height);
218 			header.core.planes = little_endianize_int16(header.core.planes);
219 			header.core.bpp = little_endianize_int16(header.core.bpp);
220 			LOGMASKED(
221 					LOG_DIB,
222 					"Read DIB core header from image data : %d*%d, %u planes, %u bpp\n",
223 					header.core.width,
224 					header.core.height,
225 					header.core.planes,
226 					header.core.bpp);
227 
228 			// this works because the core header only aliases the width/height of the info header
229 			header.info.bpp = header.core.bpp;
230 			header.info.planes = header.core.planes;
231 			header.info.height = header.core.height;
232 			header.info.width = header.core.width;
233 			header.info.size = 40U;
234 		}
235 		break;
236 
237 	default:
238 		// the next version will be longer
239 		if (124U >= header.core.size)
240 		{
241 			LOG(
242 					"DIB image data (%u bytes) uses unsupported DIB header format (size %u)\n",
243 					length,
244 					header.core.size);
245 			return msdib_error::UNSUPPORTED_FORMAT;
246 		}
247 		// fall through
248 	case 40U:
249 	case 52U:
250 	case 56U:
251 	case 108U:
252 	case 124U:
253 		// the Windows 3 bitmap header with optional extensions
254 		{
255 			palette_bytes = 4U;
256 			std::uint32_t const header_read(std::min<std::uint32_t>(header.info.size, sizeof(header.info)) - sizeof(header.info.size));
257 			if (fp.read(&header.info.width,  header_read) != header_read)
258 			{
259 				LOG("Error reading DIB info header from image data (%u bytes)\n", length);
260 				return msdib_error::FILE_TRUNCATED;
261 			}
262 			fp.seek(header.info.size - sizeof(header.info.size) - header_read, SEEK_CUR);
263 			header.info.width = little_endianize_int32(header.info.width);
264 			header.info.height = little_endianize_int32(header.info.height);
265 			header.info.planes = little_endianize_int16(header.info.planes);
266 			header.info.bpp = little_endianize_int16(header.info.bpp);
267 			header.info.comp = little_endianize_int32(header.info.comp);
268 			header.info.rawsize = little_endianize_int32(header.info.rawsize);
269 			header.info.hres = little_endianize_int32(header.info.hres);
270 			header.info.vres = little_endianize_int32(header.info.vres);
271 			header.info.colors = little_endianize_int32(header.info.colors);
272 			header.info.important = little_endianize_int32(header.info.important);
273 			header.info.red = little_endianize_int32(header.info.red);
274 			header.info.green = little_endianize_int32(header.info.green);
275 			header.info.blue = little_endianize_int32(header.info.blue);
276 			header.info.alpha = little_endianize_int32(header.info.alpha);
277 			LOGMASKED(
278 					LOG_DIB,
279 					"Read DIB info header from image data: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n",
280 					header.info.width,
281 					header.info.height,
282 					header.info.hres,
283 					header.info.vres,
284 					header.info.planes,
285 					header.info.bpp,
286 					header.info.important,
287 					header.info.colors ? "" : "2^",
288 					header.info.colors ? header.info.colors : header.info.bpp);
289 		}
290 		break;
291 	}
292 
293 	// check for unsupported planes/bit depth
294 	if ((1U != header.info.planes) || !header.info.bpp || (32U < header.info.bpp) || ((8U < header.info.bpp) ? (header.info.bpp % 8) : (8 % header.info.bpp)))
295 	{
296 		LOG("DIB image data uses unsupported planes/bits per pixel %u*%u\n", header.info.planes, header.info.bpp);
297 		return msdib_error::UNSUPPORTED_FORMAT;
298 	}
299 
300 	// check dimensions
301 	if ((0 >= header.info.width) || (0 == header.info.height))
302 	{
303 		LOG("DIB image data has invalid dimensions %u*%u\n", header.info.width, header.info.height);
304 		return msdib_error::FILE_CORRUPT;
305 	}
306 
307 	// ensure compression scheme is supported
308 	bool no_palette(false);
309 	indexed = true;
310 	switch (header.info.comp)
311 	{
312 	case DIB_COMP_NONE:
313 		// uncompressed - direct colour with implied bitfields if more than eight bits/pixel
314 		indexed = 8U >= header.info.bpp;
315 		if (indexed)
316 		{
317 			if ((1U << header.info.bpp) < header.info.colors)
318 			{
319 				osd_printf_verbose(
320 						"DIB image data has oversized palette with %u entries for %u bits per pixel\n",
321 						header.info.colors,
322 						header.info.bpp);
323 			}
324 		}
325 		if (!indexed)
326 		{
327 			no_palette = true;
328 			switch(header.info.bpp)
329 			{
330 			case 16U:
331 				header.info.red = 0x00007c00;
332 				header.info.green = 0x000003e0;
333 				header.info.blue = 0x0000001f;
334 				header.info.alpha = 0x00000000;
335 				break;
336 			case 24U:
337 			case 32U:
338 				header.info.red = 0x00ff0000;
339 				header.info.green = 0x0000ff00;
340 				header.info.blue = 0x000000ff;
341 				header.info.alpha = 0x00000000;
342 				break;
343 			}
344 		}
345 		break;
346 
347 	case DIB_COMP_BITFIELDS:
348 		// uncompressed direct colour with explicitly-specified bitfields
349 		indexed = false;
350 		if (offsetof(bitmap_info_header, alpha) > header.info.size)
351 		{
352 			osd_printf_verbose(
353 					"DIB image data specifies bit masks but is too small (size %u)\n",
354 					header.info.size);
355 			return msdib_error::FILE_CORRUPT;
356 		}
357 		break;
358 
359 	default:
360 		LOG("DIB image data uses unsupported compression scheme %u\n", header.info.comp);
361 		return msdib_error::UNSUPPORTED_FORMAT;
362 	}
363 
364 	// we can now calculate the size of the palette and row data
365 	palette_entries =
366 			indexed
367 				? ((1U == header.info.bpp) ? 2U : header.info.colors ? header.info.colors : (1U << header.info.bpp))
368 				: (no_palette ? 0U : header.info.colors);
369 	palette_size = palette_bytes * palette_entries;
370 	row_bytes = ((31 + (header.info.width * header.info.bpp)) >> 5) << 2;
371 
372 	// header looks OK
373 	return msdib_error::NONE;
374 }
375 
376 } // anonymous namespace
377 
378 
379 
msdib_verify_header(core_file & fp)380 msdib_error msdib_verify_header(core_file &fp)
381 {
382 	msdib_error err;
383 
384 	// file header
385 	std::uint32_t file_length;
386 	err = dib_read_file_header(fp, file_length);
387 	if (msdib_error::NONE != err)
388 		return err;
389 
390 	// bitmap header
391 	bitmap_headers header;
392 	unsigned palette_bytes;
393 	bool indexed;
394 	std::size_t palette_entries, palette_size, row_bytes;
395 	err = dib_read_bitmap_header(
396 			fp,
397 			header,
398 			palette_bytes,
399 			indexed,
400 			palette_entries,
401 			palette_size,
402 			row_bytes,
403 			file_length - sizeof(bitmap_file_header));
404 	if (msdib_error::NONE != err)
405 		return err;
406 
407 	// check length
408 	std::size_t const required_size(
409 			sizeof(bitmap_file_header) +
410 			header.info.size +
411 			palette_size +
412 			(row_bytes * std::abs(header.info.height)));
413 	if (required_size > file_length)
414 		return msdib_error::FILE_TRUNCATED;
415 
416 	// good chance this file is supported
417 	return msdib_error::NONE;
418 }
419 
420 
msdib_read_bitmap(core_file & fp,bitmap_argb32 & bitmap)421 msdib_error msdib_read_bitmap(core_file &fp, bitmap_argb32 &bitmap)
422 {
423 	std::uint32_t file_length;
424 	msdib_error const headerr(dib_read_file_header(fp, file_length));
425 	if (msdib_error::NONE != headerr)
426 		return headerr;
427 
428 	return msdib_read_bitmap_data(fp, bitmap, file_length - sizeof(bitmap_file_header), 0U);
429 }
430 
431 
msdib_read_bitmap_data(core_file & fp,bitmap_argb32 & bitmap,std::uint32_t length,std::uint32_t dirheight)432 msdib_error msdib_read_bitmap_data(core_file &fp, bitmap_argb32 &bitmap, std::uint32_t length, std::uint32_t dirheight)
433 {
434 	// read the bitmap header
435 	bitmap_headers header;
436 	unsigned palette_bytes;
437 	bool indexed;
438 	std::size_t palette_entries, palette_size, row_bytes;
439 	msdib_error const head_error(dib_read_bitmap_header(fp, header, palette_bytes, indexed, palette_entries, palette_size, row_bytes, length));
440 	if (msdib_error::NONE != head_error)
441 		return head_error;
442 
443 	// check dimensions
444 	bool const top_down(0 > header.info.height);
445 	if (top_down)
446 		header.info.height = -header.info.height;
447 	bool have_and_mask((2 * dirheight) == header.info.height);
448 	if (!have_and_mask && (0 < dirheight) && (dirheight != header.info.height))
449 	{
450 		osd_printf_verbose(
451 				"DIB image data height %d doesn't match directory height %u with or without AND mask\n",
452 				header.info.height,
453 				dirheight);
454 		return msdib_error::UNSUPPORTED_FORMAT;
455 	}
456 	if (have_and_mask)
457 		header.info.height >>= 1;
458 
459 	// we can now calculate the size of the image data
460 	std::size_t const mask_row_bytes(((31 + header.info.width) >> 5) << 2);
461 	std::size_t const required_size(
462 			header.info.size +
463 			palette_size +
464 			((row_bytes + (have_and_mask ? mask_row_bytes : 0U)) * header.info.height));
465 	if (required_size > length)
466 	{
467 		LOG("DIB image data (%u bytes) smaller than calculated DIB data size (%u bytes)\n", length, required_size);
468 		return msdib_error::FILE_TRUNCATED;
469 	}
470 
471 	// load the palette for indexed colour formats or the shifts for direct colour formats
472 	unsigned red_shift(0), green_shift(0), blue_shift(0), alpha_shift(0);
473 	unsigned red_bits(0), green_bits(0), blue_bits(0), alpha_bits(0);
474 	std::unique_ptr<rgb_t []> palette;
475 	if (indexed)
476 	{
477 		// read palette and convert
478 		std::unique_ptr<std::uint8_t []> palette_data;
479 		try { palette_data.reset(new std::uint8_t [palette_size]); }
480 		catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
481 		if (fp.read(palette_data.get(), palette_size) != palette_size)
482 		{
483 			LOG("Error reading palette from DIB image data (%u bytes)\n", length);
484 			return msdib_error::FILE_TRUNCATED;
485 		}
486 		std::size_t const palette_usable(std::min<std::size_t>(palette_entries, std::size_t(1) << header.info.bpp));
487 		try { palette.reset(new rgb_t [palette_usable]); }
488 		catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
489 		std::uint8_t const *ptr(palette_data.get());
490 		for (std::size_t i = 0; palette_usable > i; ++i, ptr += palette_bytes)
491 			palette[i] = rgb_t(ptr[2], ptr[1], ptr[0]);
492 	}
493 	else
494 	{
495 		// skip over the palette if necessary
496 		if (palette_entries)
497 			fp.seek(palette_bytes * palette_entries, SEEK_CUR);
498 
499 		// convert masks to shifts
500 		bool const masks_contiguous(
501 				dib_parse_mask(header.info.red, red_shift, red_bits) &&
502 				dib_parse_mask(header.info.green, green_shift, green_bits) &&
503 				dib_parse_mask(header.info.blue, blue_shift, blue_bits) &&
504 				dib_parse_mask(header.info.alpha, alpha_shift, alpha_bits));
505 		if (!masks_contiguous)
506 		{
507 			osd_printf_verbose(
508 					"DIB image data specifies non-contiguous channel masks 0x%x | 0x%x | 0x%x | 0x%x\n",
509 					header.info.red,
510 					header.info.green,
511 					header.info.blue,
512 					header.info.alpha);
513 		}
514 		if ((32U != header.info.bpp) && ((header.info.red | header.info.green | header.info.blue | header.info.alpha) >> header.info.bpp))
515 		{
516 			LOG(
517 					"DIB image data specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n",
518 					header.info.red,
519 					header.info.green,
520 					header.info.blue,
521 					header.info.alpha,
522 					header.info.bpp);
523 			return msdib_error::FILE_CORRUPT;
524 		}
525 		LOGMASKED(
526 				LOG_DIB,
527 				"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",
528 				(header.info.bpp + 3) >> 2,
529 				red_shift,
530 				(std::uint32_t(1) << red_bits) - 1,
531 				green_shift,
532 				(std::uint32_t(1) << green_bits) - 1,
533 				blue_shift,
534 				(std::uint32_t(1) << blue_bits) - 1,
535 				alpha_shift,
536 				(std::uint32_t(1) << alpha_bits) - 1);
537 
538 		// the MAME bitmap only supports 8 bits/sample maximum
539 		dib_truncate_channel(red_shift, red_bits);
540 		dib_truncate_channel(green_shift, green_bits);
541 		dib_truncate_channel(blue_shift, blue_bits);
542 		dib_truncate_channel(alpha_shift, alpha_bits);
543 	}
544 
545 	// allocate the bitmap and process row data
546 	std::unique_ptr<std::uint8_t []> row_data;
547 	try {row_data.reset(new std::uint8_t [row_bytes]); }
548 	catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
549 	bitmap.allocate(header.info.width, header.info.height);
550 	int const y_inc(top_down ? 1 : -1);
551 	for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
552 	{
553 		if (fp.read(row_data.get(), row_bytes) != row_bytes)
554 		{
555 			LOG("Error reading DIB row %d data from image data\n", i);
556 			return msdib_error::FILE_TRUNCATED;
557 		}
558 		std::uint8_t *src(row_data.get());
559 		std::uint32_t *dest(&bitmap.pix(y));
560 		unsigned shift(0U);
561 		for (std::int32_t x = 0; header.info.width > x; ++x, ++dest)
562 		{
563 			// extract or compose a pixel
564 			std::uint32_t pix(0U);
565 			if (8U >= header.info.bpp)
566 			{
567 				assert(8U > shift);
568 				pix = *src >> (8U - header.info.bpp);
569 				*src <<= header.info.bpp;
570 				shift += header.info.bpp;
571 				if (8U <= shift)
572 				{
573 					shift = 0U;
574 					++src;
575 				}
576 			}
577 			else for (shift = 0; header.info.bpp > shift; shift += 8U, ++src)
578 			{
579 				pix |= std::uint32_t(*src) << shift;
580 			}
581 
582 			// convert to RGB
583 			if (indexed)
584 			{
585 				if (palette_entries > pix)
586 				{
587 					*dest = palette[pix];
588 				}
589 				else
590 				{
591 					*dest = rgb_t::transparent();
592 					osd_printf_verbose(
593 							"DIB image data has out-of-range color %u at (%d, %d) with %u palette entries\n",
594 							pix,
595 							x,
596 							y,
597 							palette_entries);
598 				}
599 			}
600 			else
601 			{
602 				std::uint8_t r(dib_splat_sample((pix >> red_shift) & ((std::uint32_t(1) << red_bits) - 1), red_bits));
603 				std::uint8_t g(dib_splat_sample((pix >> green_shift) & ((std::uint32_t(1) << green_bits) - 1), green_bits));
604 				std::uint8_t b(dib_splat_sample((pix >> blue_shift) & ((std::uint32_t(1) << blue_bits) - 1), blue_bits));
605 				std::uint8_t a(dib_splat_sample((pix >> alpha_shift) & ((std::uint32_t(1) << alpha_bits) - 1), alpha_bits));
606 				*dest = rgb_t(alpha_bits ? a : 255, r, g, b);
607 			}
608 		}
609 	}
610 
611 	// process the AND mask if present
612 	if (have_and_mask)
613 	{
614 		for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
615 		{
616 			if (fp.read(row_data.get(), mask_row_bytes) != mask_row_bytes)
617 			{
618 				LOG("Error reading DIB mask row %d data from image data\n", i);
619 				return msdib_error::FILE_TRUNCATED;
620 			}
621 			std::uint8_t *src(row_data.get());
622 			std::uint32_t *dest(&bitmap.pix(y));
623 			unsigned shift(0U);
624 			for (std::int32_t x = 0; header.info.width > x; ++x, ++dest)
625 			{
626 				assert(8U > shift);
627 				rgb_t pix(*dest);
628 				*dest = pix.set_a(BIT(*src, 7U - shift) ? 0U : pix.a());
629 				if (8U <= ++shift)
630 				{
631 					shift = 0U;
632 					++src;
633 				}
634 			}
635 		}
636 	}
637 
638 	// we're done!
639 	return msdib_error::NONE;
640 }
641 
642 } // namespace util
643