diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h index 74954d63d5..1d4e51fd8b 100644 --- a/include/SDL3/SDL_mouse.h +++ b/include/SDL3/SDL_mouse.h @@ -423,8 +423,9 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 * data, * situations. For example, if the original surface is 32x32, then on a 2x * macOS display or 200% display scale on Windows, a 64x64 version of the * image will be used, if available. If a matching version of the image isn't - * available, the closest size image will be scaled to the appropriate size - * and be used instead. + * available, the closest larger size image will be downscaled to the + * appropriate size and be used instead, if available. Otherwise, the closest + * smaller image will be upscaled and be used instead. * * \param surface an SDL_Surface structure representing the cursor image. * \param hot_x the x position of the cursor hot spot. diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 358288c175..b75b338607 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1341,8 +1341,9 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window); * situations. For example, if the original surface is 32x32, then on a 2x * macOS display or 200% display scale on Windows, a 64x64 version of the * image will be used, if available. If a matching version of the image isn't - * available, the closest size image will be scaled to the appropriate size - * and be used instead. + * available, the closest larger size image will be downscaled to the + * appropriate size and be used instead, if available. Otherwise, the closest + * smaller image will be upscaled and be used instead. * * \param window the window to change. * \param icon an SDL_Surface structure containing the icon for the window. diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 457cd55ce6..49c0c7b117 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -514,18 +514,25 @@ SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale) return surface; } + // Find closest image. Images that are larger than the + // desired size are preferred over images that are smaller. SDL_Surface *closest = NULL; int desired_w = (int)SDL_round(surface->w * display_scale); int desired_h = (int)SDL_round(surface->h * display_scale); + int desired_size = desired_w * desired_h; int closest_distance = -1; + int closest_size = -1; for (int i = 0; images[i]; ++i) { SDL_Surface *candidate = images[i]; + int size = candidate->w * candidate->h; int delta_w = (candidate->w - desired_w); int delta_h = (candidate->h - desired_h); int distance = (delta_w * delta_w) + (delta_h * delta_h); - if (closest_distance < 0 || distance < closest_distance) { + if (closest_distance < 0 || distance < closest_distance || + (size > desired_size && closest_size < desired_size)) { closest = candidate; closest_distance = distance; + closest_size = size; } } SDL_free(images); @@ -536,8 +543,23 @@ SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale) return closest; } - // We need to scale an image to the correct size - return SDL_ScaleSurface(closest, desired_w, desired_h, SDL_SCALEMODE_LINEAR); + // We need to scale the image to the correct size. To maintain good image quality, downscaling + // is done in steps, never reducing the width and height by more than half each time. + SDL_Surface *scaled = closest; + do { + int next_scaled_w = SDL_max(desired_w, (scaled->w + 1) / 2); + int next_scaled_h = SDL_max(desired_h, (scaled->h + 1) / 2); + SDL_Surface *next_scaled = SDL_ScaleSurface(scaled, next_scaled_w, next_scaled_h, SDL_SCALEMODE_LINEAR); + if (scaled != closest) { + SDL_DestroySurface(scaled); + } + scaled = next_scaled; + if (!scaled) { + return NULL; + } + } while (scaled->w != desired_w || scaled->h != desired_h); + + return scaled; } void SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface)