From d2f44c83fccc7415afda1e69ae3b2608fa87a7a6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 20 Oct 2025 02:12:54 -0700
Subject: [PATCH] Added support for alternate icon/cursor images
A CUR or ICO file can have multiple image representations, so we provide each size to accommodate high DPI scenarios.
---
src/IMG_bmp.c | 288 ++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 231 insertions(+), 57 deletions(-)
diff --git a/src/IMG_bmp.c b/src/IMG_bmp.c
index 9d47735b..8581c147 100644
--- a/src/IMG_bmp.c
+++ b/src/IMG_bmp.c
@@ -46,6 +46,25 @@
#define ICON_TYPE_ICO 1
#define ICON_TYPE_CUR 2
+typedef struct
+{
+ Sint64 offset;
+ int width;
+ int height;
+ int ncolors;
+ int hot_x;
+ int hot_y;
+ SDL_Surface *surface;
+} IconEntry;
+
+typedef struct
+{
+ int num_entries;
+ int max_entries;
+ IconEntry *entries;
+} IconEntries;
+
+
/* See if an image is contained in a data source */
bool IMG_isBMP(SDL_IOStream *src)
{
@@ -122,7 +141,54 @@ static SDL_Surface *LoadBMP_IO(SDL_IOStream *src, bool closeio)
return SDL_LoadBMP_IO(src, closeio);
}
-static SDL_Surface *ReadBitmapSurface(SDL_IOStream *src)
+static bool GetBMPIconInfo(SDL_IOStream *src, int *width, int *height, int *ncolors)
+{
+ /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
+ Uint32 biSize;
+ Sint32 biWidth;
+ Sint32 biHeight;
+ /* Uint16 biPlanes; */
+ Uint16 biBitCount;
+ Uint32 biCompression;
+ /*
+ Uint32 biSizeImage;
+ Sint32 biXPelsPerMeter;
+ Sint32 biYPelsPerMeter;
+ Uint32 biClrImportant;
+ */
+ Uint32 biClrUsed;
+
+ if (!SDL_ReadU32LE(src, &biSize) ||
+ !SDL_ReadS32LE(src, &biWidth) ||
+ !SDL_ReadS32LE(src, &biHeight) ||
+ !SDL_ReadU16LE(src, NULL /* biPlanes */) ||
+ !SDL_ReadU16LE(src, &biBitCount) ||
+ !SDL_ReadU32LE(src, &biCompression) ||
+ !SDL_ReadU32LE(src, NULL /* biSizeImage */) ||
+ !SDL_ReadU32LE(src, NULL /* biXPelsPerMeter */) ||
+ !SDL_ReadU32LE(src, NULL /* biYPelsPerMeter */) ||
+ !SDL_ReadU32LE(src, &biClrUsed) ||
+ !SDL_ReadU32LE(src, NULL /* biClrImportant */)) {
+ return false;
+ }
+
+ biHeight >>= 1;
+
+ if (biBitCount <= 8) {
+ if (biClrUsed == 0) {
+ biClrUsed = 1 << biBitCount;
+ }
+ } else {
+ biClrUsed = 256;
+ }
+
+ *width = biWidth;
+ *height = biHeight;
+ *ncolors = biClrUsed;
+ return true;
+}
+
+static SDL_Surface *GetBMPSurface(SDL_IOStream *src)
{
bool was_error = true;
int bmpPitch;
@@ -133,7 +199,7 @@ static SDL_Surface *ReadBitmapSurface(SDL_IOStream *src)
Uint32 palette[256];
/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
- /* Uint32 biSize; */
+ Uint32 biSize;
Sint32 biWidth;
Sint32 biHeight;
/* Uint16 biPlanes; */
@@ -147,7 +213,8 @@ static SDL_Surface *ReadBitmapSurface(SDL_IOStream *src)
*/
Uint32 biClrUsed;
- if (!SDL_ReadS32LE(src, &biWidth) ||
+ if (!SDL_ReadU32LE(src, &biSize) ||
+ !SDL_ReadS32LE(src, &biWidth) ||
!SDL_ReadS32LE(src, &biHeight) ||
!SDL_ReadU16LE(src, NULL /* biPlanes */) ||
!SDL_ReadU16LE(src, &biBitCount) ||
@@ -348,16 +415,128 @@ static SDL_Surface *ReadBitmapSurface(SDL_IOStream *src)
return surface;
}
+static bool GetPNGIconInfo(SDL_IOStream *src, int *width, int *height, int *ncolors)
+{
+ Uint8 magic[16];
+ Uint32 unWidth = 0;
+ Uint32 unHeight = 0;
+
+ if (SDL_ReadIO(src, magic, sizeof(magic)) != sizeof(magic) ||
+ !SDL_ReadU32BE(src, &unWidth) || unWidth > SDL_MAX_SINT32 ||
+ !SDL_ReadU32BE(src, &unHeight) || unHeight > SDL_MAX_SINT32) {
+ return false;
+ }
+
+ *width = (int)unWidth;
+ *height = (int)unHeight;
+ *ncolors = 256;
+ return true;
+}
+
+static bool GetIconInfo(SDL_IOStream *src, Sint64 offset, int *width, int *height, int *ncolors)
+{
+ bool result = false;
+ Uint32 biSize;
+ Sint64 start = SDL_TellIO(src);
+
+ if (SDL_SeekIO(src, offset, SDL_IO_SEEK_SET) < 0) {
+ return false;
+ }
+
+ if (!SDL_ReadU32LE(src, &biSize) ||
+ SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR) < 0) {
+ goto done;
+ }
+ if (biSize == 40) {
+ result = GetBMPIconInfo(src, width, height, ncolors);
+ } else if (biSize == RIFF_FOURCC(0x89, 'P', 'N', 'G')) {
+ result = GetPNGIconInfo(src, width, height, ncolors);
+ } else {
+ SDL_SetError("Unsupported ICO bitmap format");
+ }
+
+done:
+ (void)SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
+ return result;
+}
+
+static bool AddIconEntry(IconEntries *entries, Sint64 offset, int width, int height, int ncolors, int hot_x, int hot_y)
+{
+ int i;
+
+ for (i = 0; i < entries->num_entries; ++i) {
+ IconEntry *entry = &entries->entries[i];
+ if (width == entry->width && height == entry->height) {
+ if (ncolors > entry->ncolors) {
+ // Replace the existing entry
+ entry->offset = offset;
+ entry->ncolors = ncolors;
+ entry->hot_x = hot_x;
+ entry->hot_y = hot_y;
+ } else {
+ // The existing entry is better
+ }
+ return true;
+ }
+ }
+
+ if (entries->num_entries == entries->max_entries) {
+ int max_entries = entries->max_entries + 8;
+ IconEntry *new_entries = (IconEntry *)SDL_realloc(entries->entries, max_entries * sizeof(*entries->entries));
+ if (!new_entries) {
+ return false;
+ }
+ entries->entries = new_entries;
+ entries->max_entries = max_entries;
+ }
+
+ IconEntry *entry = &entries->entries[entries->num_entries++];
+ entry->offset = offset;
+ entry->width = width;
+ entry->height = height;
+ entry->ncolors = ncolors;
+ entry->hot_x = hot_x;
+ entry->hot_y = hot_y;
+ return true;
+}
+
+static SDL_Surface *GetIconSurface(SDL_IOStream *src, Sint64 offset, int type, int hot_x, int hot_y)
+{
+ SDL_Surface *surface = NULL;
+ Uint32 biSize;
+
+ if (SDL_SeekIO(src, offset, SDL_IO_SEEK_SET) < 0) {
+ return false;
+ }
+
+ if (!SDL_ReadU32LE(src, &biSize) ||
+ SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR) < 0) {
+ goto done;
+ }
+ if (biSize == 40) {
+ surface = GetBMPSurface(src);
+ } else if (biSize == RIFF_FOURCC(0x89, 'P', 'N', 'G')) {
+ surface = SDL_LoadPNG_IO(src, false);
+ } else {
+ SDL_SetError("Unsupported ICO bitmap format");
+ }
+
+ if (type == ICON_TYPE_CUR) {
+ SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
+ SDL_SetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, hot_x);
+ SDL_SetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, hot_y);
+ }
+
+done:
+ return surface;
+}
+
static SDL_Surface *LoadICOCUR_IO(SDL_IOStream *src, int type, bool closeio)
{
bool was_error = true;
- Sint64 fp_offset = 0;
+ Sint64 start = 0;
int i;
- int maxCol = 0;
- Uint32 icoOfs = 0;
- int nHotX = 0;
- int nHotY = 0;
- Uint32 biSize;
+ IconEntries entries = { 0, 0, NULL };
SDL_Surface *surface = NULL;
/* The Win32 ICO file header (14 bytes) */
@@ -371,7 +550,7 @@ static SDL_Surface *LoadICOCUR_IO(SDL_IOStream *src, int type, bool closeio)
}
/* Read in the ICO file header */
- fp_offset = SDL_TellIO(src);
+ start = SDL_TellIO(src);
if (!SDL_ReadU16LE(src, &bfReserved) ||
!SDL_ReadU16LE(src, &bfType) ||
@@ -382,7 +561,9 @@ static SDL_Surface *LoadICOCUR_IO(SDL_IOStream *src, int type, bool closeio)
}
/* Read the Win32 Icon Directory */
- //SDL_Log("Icon directory: %d cursors", bfCount);
+#ifdef DEBUG_ICONDIR
+ SDL_Log("Icon directory: %d cursors", bfCount);
+#endif
for (i = 0; i < bfCount; ++i) {
/* Icon Directory Entries */
Uint8 bWidth; /* Uint8, but 0 = 256 ! */
@@ -393,7 +574,7 @@ static SDL_Surface *LoadICOCUR_IO(SDL_IOStream *src, int type, bool closeio)
Uint16 wBitCount;
Uint32 dwBytesInRes;
Uint32 dwImageOffset;
- int nWidth, nHeight, nColorCount;
+ int nWidth, nHeight, nColorCount, nHotX, nHotY;
if (!SDL_ReadU8(src, &bWidth) ||
!SDL_ReadU8(src, &bHeight) ||
@@ -406,75 +587,68 @@ static SDL_Surface *LoadICOCUR_IO(SDL_IOStream *src, int type, bool closeio)
goto done;
}
- if (bWidth) {
- nWidth = bWidth;
- } else {
- nWidth = 256;
- }
- if (bHeight) {
- nHeight = bHeight;
- } else {
- nHeight = 256;
- }
- if (bColorCount) {
- nColorCount = bColorCount;
- } else {
- nColorCount = 256;
- }
-
if (type == ICON_TYPE_CUR) {
nHotX = wPlanes;
nHotY = wBitCount;
+ } else {
+ nHotX = 0;
+ nHotY = 0;
}
- //SDL_Log("%dx%d@%d - %d", nWidth, nHeight, nColorCount, (int)dwImageOffset);
- (void)nWidth;
- (void)nHeight;
- if (nColorCount > maxCol) {
- //SDL_Log("%d > %d, marked", nColorCount, maxCol);
- maxCol = nColorCount;
- icoOfs = dwImageOffset;
+ if (!GetIconInfo(src, start + dwImageOffset, &nWidth, &nHeight, &nColorCount)) {
+ continue;
}
- }
- /* Advance to the DIB Data */
- if (SDL_SeekIO(src, fp_offset + icoOfs, SDL_IO_SEEK_SET) < 0) {
- goto done;
+#ifdef DEBUG_ICONDIR
+ SDL_Log("%dx%d@%d - %d, hot: %d,%d", nWidth, nHeight, nColorCount, (int)dwImageOffset, nHotX, nHotY);
+#endif
+ if (!AddIconEntry(&entries, start + dwImageOffset, nWidth, nHeight, nColorCount, nHotX, nHotY)) {
+ continue;
+ }
}
- /* Read the Win32 BITMAPINFOHEADER */
- if (!SDL_ReadU32LE(src, &biSize)) {
+ if (entries.num_entries == 0) {
+ SDL_SetError("Couldn't find any valid icons");
goto done;
}
- if (biSize == 40) {
- surface = ReadBitmapSurface(src);
- } else if (biSize == RIFF_FOURCC(0x89, 'P', 'N', 'G')) {
- if (SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR) < 0) {
+
+ /* Load the icon surfaces */
+ for (i = 0; i < entries.num_entries; ++i) {
+ IconEntry *entry = &entries.entries[i];
+ entry->surface = GetIconSurface(src, entry->offset, type, entry->hot_x, entry->hot_y);
+ if (!entry->surface) {
goto done;
}
- surface = SDL_LoadPNG_IO(src, false);
- } else {
- SDL_SetError("Unsupported ICO bitmap format");
- }
- if (!surface) {
- goto done;
- }
- if (type == ICON_TYPE_CUR) {
- SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
- SDL_SetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, nHotX);
- SDL_SetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, nHotY);
+ if (i == 0) {
+ surface = entry->surface;
+ ++surface->refcount;
+ }
+ }
+ for (i = 0; i < entries.num_entries; ++i) {
+ IconEntry *entry = &entries.entries[i];
+ if (entry->surface != surface) {
+ SDL_AddSurfaceAlternateImage(surface, entry->surface);
+ }
}
+ /* All done! */
was_error = false;
done:
if (closeio && src) {
SDL_CloseIO(src);
}
+
+ for (i = 0; i < entries.num_entries; ++i) {
+ IconEntry *entry = &entries.entries[i];
+ SDL_DestroySurface(entry->surface);
+ }
+ SDL_free(entries.entries);
+
if (was_error) {
if (src && !closeio) {
- SDL_SeekIO(src, fp_offset, SDL_IO_SEEK_SET);
+ SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
}
if (surface) {
SDL_DestroySurface(surface);