[librptexture] ICO: Handle Win3.x 1bpp (monochrome) icons.

Tested by converting Win2.x BANG.ICO to Win3.x format.

ICOPrivate::loadImage_Win1():
- Note that the mask is *before* the icon.

ImageDecoder::fromLinearMono_WinIcon():
- Take separate pointers for the icon and mask data. Win3.x icons are
  typically stored upside-down, so this lets us handle that outside
  of the decoder function. This also means the image data size does
  not have to be 2x. Note that `img_siz == mask_siz` must be true.
This commit is contained in:
David Korth 2025-06-09 20:21:25 -04:00
parent da94256a37
commit 417825cd6f
3 changed files with 33 additions and 19 deletions

View File

@ -190,29 +190,33 @@ rp_image_ptr fromLinearGray2bpp(int width, int height,
* Convert a linear monochrome image to rp_image.
*
* Windows icons are handled a bit different compared to "regular" monochrome images:
* - Actual image height should be double the `height` value.
* - Two images are stored: mask, then image.
* - Two images are required: icon and mask
* - Transparency is supported using the mask.
* - 0 == black; 1 == white
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)*2]
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)]
* @param mask_buf [in] Mask buffer
* @param mask_siz [in] Size of mask buffer [must be == img_siz]
* @param stride [in,opt] Stride, in bytes (if 0, assumes width*bytespp)
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image_ptr fromLinearMono_WinIcon(int width, int height,
const uint8_t *RESTRICT img_buf, size_t img_siz, int stride)
const uint8_t *RESTRICT img_buf, size_t img_siz,
const uint8_t *RESTRICT mask_buf, size_t mask_siz, int stride)
{
// Verify parameters.
assert(img_buf != nullptr);
assert(width > 0);
assert(height > 0);
assert(img_siz >= (static_cast<size_t>(width) * static_cast<size_t>(height) / 8) * 2);
assert(img_siz >= (static_cast<size_t>(width) * static_cast<size_t>(height) / 8));
assert(img_siz == mask_siz);
if (!img_buf || width <= 0 || height <= 0 ||
img_siz < (static_cast<size_t>(width) * static_cast<size_t>(height) / 8) * 2)
img_siz < (static_cast<size_t>(width) * static_cast<size_t>(height) / 8) ||
img_siz != mask_siz)
{
return {};
}
@ -247,12 +251,10 @@ rp_image_ptr fromLinearMono_WinIcon(int width, int height,
// Convert one line at a time. (monochrome -> CI8)
uint8_t *px_dest = static_cast<uint8_t*>(img->bits());
const uint8_t *mask_buf = img_buf;
const uint8_t *icon_buf = img_buf + (static_cast<size_t>(height) * static_cast<size_t>(stride));
for (unsigned int y = static_cast<unsigned int>(height); y > 0; y--) {
for (int x = width; x > 0; x -= 8) {
uint8_t pxMask = *mask_buf++;
uint8_t pxIcon = *icon_buf++;
uint8_t pxIcon = *img_buf++;
// For images where width is not a multiple of 8,
// we'll discard the remaining bits in the last byte.
@ -281,6 +283,7 @@ rp_image_ptr fromLinearMono_WinIcon(int width, int height,
// Set the sBIT metadata.
// NOTE: Setting the grayscale value, though we're
// not saving grayscale PNGs at the moment.
// TODO: Don't set alpha if the icon mask doesn't have any set bits?
static const rp_image::sBIT_t sBIT = {1,1,1,1,1};
img->set_sBIT(&sBIT);

View File

@ -49,20 +49,23 @@ rp_image_ptr fromLinearGray2bpp(int width, int height,
* Convert a linear monochrome image to rp_image.
*
* Windows icons are handled a bit different compared to "regular" monochrome images:
* - Actual image height should be double the `height` value.
* - Two images are stored: mask, then image.
* - Two images are required: icon and mask
* - Transparency is supported using the mask.
* - 0 == black; 1 == white
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)*2]
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)]
* @param mask_buf [in] Mask buffer
* @param mask_siz [in] Size of mask buffer [must be == img_siz]
* @param stride [in,opt] Stride, in bytes (if 0, assumes width*bytespp)
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
ATTR_ACCESS_SIZE(read_only, 5, 6)
rp_image_ptr fromLinearMono_WinIcon(int width, int height,
const uint8_t *RESTRICT img_buf, size_t img_siz, int stride = 0);
const uint8_t *RESTRICT img_buf, size_t img_siz,
const uint8_t *RESTRICT mask_buf, size_t mask_siz, int stride = 0);
} }

View File

@ -504,7 +504,7 @@ int ICOPrivate::loadIconDirectory_Win3(void)
rp_image_const_ptr ICOPrivate::loadImage_Win1(void)
{
// Icon data is located immediately after the header.
// Each icon is actually two icons: a 1bpp icon, then a 1bpp mask.
// Each icon is actually two icons: a 1bpp mask, then a 1bpp icon.
// NOTE: If the file has *both* DIB and DDB, then the DIB is first,
// followed by the DDB, with its own icon header. Not handling this
@ -546,7 +546,10 @@ rp_image_const_ptr ICOPrivate::loadImage_Win1(void)
}
// Convert the icon.
img = ImageDecoder::fromLinearMono_WinIcon(width, height, icon_data.data(), icon_size * 2, stride);
const uint8_t *const p_mask_data = icon_data.data();
const uint8_t *const p_icon_data = p_mask_data + icon_size;
img = ImageDecoder::fromLinearMono_WinIcon(width, height,
p_icon_data, icon_size, p_mask_data, icon_size, stride);
return img;
}
@ -718,9 +721,12 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
return {};
case 1:
// Monochrome (TODO: Find a test icon)
assert(!"Win3.x 1bpp icon format is not supported yet!");
return {};
// 1bpp (monochrome)
// NOTE: ImageDecoder::fromLinearMono_WinIcon() handles the mask.
img = ImageDecoder::fromLinearMono_WinIcon(width, half_height,
icon_data, icon_size,
mask_data, mask_size, stride);
break;
case 4:
// 16-color
@ -760,7 +766,9 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
// Apply the icon mask.
rp_image::sBIT_t sBIT;
img->get_sBIT(&sBIT);
if (bitcount < 8) {
if (bitcount == 1) {
// Monochrome icons are handled by ImageDecoder::fromLinearMono_WinIcon().
} else if (bitcount < 8) {
// Keep the icon as CI8 and add a transparency color.
// TODO: Set sBIT.
assert(img->format() == rp_image::Format::CI8);