From 417825cd6f59868480916ef2f3acb25c1ae653d6 Mon Sep 17 00:00:00 2001 From: David Korth Date: Mon, 9 Jun 2025 20:21:25 -0400 Subject: [PATCH] [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. --- .../decoder/ImageDecoder_Linear_Gray.cpp | 21 +++++++++++-------- .../decoder/ImageDecoder_Linear_Gray.hpp | 11 ++++++---- src/librptexture/fileformat/ICO.cpp | 20 ++++++++++++------ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/librptexture/decoder/ImageDecoder_Linear_Gray.cpp b/src/librptexture/decoder/ImageDecoder_Linear_Gray.cpp index ec88588ff..dee1dd248 100644 --- a/src/librptexture/decoder/ImageDecoder_Linear_Gray.cpp +++ b/src/librptexture/decoder/ImageDecoder_Linear_Gray.cpp @@ -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(width) * static_cast(height) / 8) * 2); + assert(img_siz >= (static_cast(width) * static_cast(height) / 8)); + assert(img_siz == mask_siz); if (!img_buf || width <= 0 || height <= 0 || - img_siz < (static_cast(width) * static_cast(height) / 8) * 2) + img_siz < (static_cast(width) * static_cast(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(img->bits()); - const uint8_t *mask_buf = img_buf; - const uint8_t *icon_buf = img_buf + (static_cast(height) * static_cast(stride)); for (unsigned int y = static_cast(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); diff --git a/src/librptexture/decoder/ImageDecoder_Linear_Gray.hpp b/src/librptexture/decoder/ImageDecoder_Linear_Gray.hpp index fbecaa8cc..d2395c56d 100644 --- a/src/librptexture/decoder/ImageDecoder_Linear_Gray.hpp +++ b/src/librptexture/decoder/ImageDecoder_Linear_Gray.hpp @@ -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); } } diff --git a/src/librptexture/fileformat/ICO.cpp b/src/librptexture/fileformat/ICO.cpp index ef50ee1b5..ebbe75740 100644 --- a/src/librptexture/fileformat/ICO.cpp +++ b/src/librptexture/fileformat/ICO.cpp @@ -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);