From 755e201aa56117ba7be2a534f4897cd02448e015 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 12 Aug 2024 09:05:52 -0700
Subject: [PATCH] Improved color accuracy blitting to 8-bit indexed surfaces
Fixes https://github.com/libsdl-org/SDL/issues/10519
---
src/video/SDL_blit.h | 3 +
src/video/SDL_blit_N.c | 405 +-------------------------------------
src/video/SDL_blit_slow.c | 16 +-
src/video/SDL_pixels.c | 53 +++--
src/video/SDL_pixels_c.h | 1 +
5 files changed, 43 insertions(+), 435 deletions(-)
diff --git a/src/video/SDL_blit.h b/src/video/SDL_blit.h
index fea3a3632593e..af0b8c3a1eae4 100644
--- a/src/video/SDL_blit.h
+++ b/src/video/SDL_blit.h
@@ -23,6 +23,8 @@
#ifndef SDL_blit_h_
#define SDL_blit_h_
+#include "../SDL_hashtable.h"
+
/* Table to do pixel byte expansion */
extern const Uint8 *SDL_expand_byte[9];
extern const Uint16 SDL_expand_byte_10[];
@@ -70,6 +72,7 @@ typedef struct
const SDL_PixelFormatDetails *dst_fmt;
const SDL_Palette *dst_pal;
Uint8 *table;
+ SDL_HashTable *palette_map;
int flags;
Uint32 colorkey;
Uint8 r, g, b, a;
diff --git a/src/video/SDL_blit_N.c b/src/video/SDL_blit_N.c
index b94f43f832cf4..0a5d0704e13ca 100644
--- a/src/video/SDL_blit_N.c
+++ b/src/video/SDL_blit_N.c
@@ -933,234 +933,6 @@ static void Blit_RGB444_XRGB8888ARMSIMD(SDL_BlitInfo *info)
#define LO 1
#endif
-/* Special optimized blit for RGB 8-8-8 --> RGB 3-3-2 */
-#define RGB888_RGB332(dst, src) \
- { \
- dst = (Uint8)((((src)&0x00E00000) >> 16) | \
- (((src)&0x0000E000) >> 11) | \
- (((src)&0x000000C0) >> 6)); \
- }
-static void Blit_XRGB8888_index8(SDL_BlitInfo *info)
-{
-#ifndef USE_DUFFS_LOOP
- int c;
-#endif
- int width, height;
- Uint32 *src;
- const Uint8 *map;
- Uint8 *dst;
- int srcskip, dstskip;
-
- /* Set up some basic variables */
- width = info->dst_w;
- height = info->dst_h;
- src = (Uint32 *)info->src;
- srcskip = info->src_skip / 4;
- dst = info->dst;
- dstskip = info->dst_skip;
- map = info->table;
-
- if (!map) {
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- RGB888_RGB332(*dst++, *src);
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width / 4; c; --c) {
- /* Pack RGB into 8bit pixel */
- ++src;
- RGB888_RGB332(*dst++, *src);
- ++src;
- RGB888_RGB332(*dst++, *src);
- ++src;
- RGB888_RGB332(*dst++, *src);
- ++src;
- }
- switch (width & 3) {
- case 3:
- RGB888_RGB332(*dst++, *src);
- ++src;
- SDL_FALLTHROUGH;
- case 2:
- RGB888_RGB332(*dst++, *src);
- ++src;
- SDL_FALLTHROUGH;
- case 1:
- RGB888_RGB332(*dst++, *src);
- ++src;
- }
-#endif /* USE_DUFFS_LOOP */
- src += srcskip;
- dst += dstskip;
- }
- } else {
- int Pixel;
-
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width / 4; c; --c) {
- /* Pack RGB into 8bit pixel */
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- }
- switch (width & 3) {
- case 3:
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- SDL_FALLTHROUGH;
- case 2:
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- SDL_FALLTHROUGH;
- case 1:
- RGB888_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- }
-#endif /* USE_DUFFS_LOOP */
- src += srcskip;
- dst += dstskip;
- }
- }
-}
-
-/* Special optimized blit for RGB 10-10-10 --> RGB 3-3-2 */
-#define RGB101010_RGB332(dst, src) \
- { \
- dst = (Uint8)((((src)&0x38000000) >> 22) | \
- (((src)&0x000E0000) >> 15) | \
- (((src)&0x00000300) >> 8)); \
- }
-static void Blit_RGB101010_index8(SDL_BlitInfo *info)
-{
-#ifndef USE_DUFFS_LOOP
- int c;
-#endif
- int width, height;
- Uint32 *src;
- const Uint8 *map;
- Uint8 *dst;
- int srcskip, dstskip;
-
- /* Set up some basic variables */
- width = info->dst_w;
- height = info->dst_h;
- src = (Uint32 *)info->src;
- srcskip = info->src_skip / 4;
- dst = info->dst;
- dstskip = info->dst_skip;
- map = info->table;
-
- if (!map) {
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- RGB101010_RGB332(*dst++, *src);
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width / 4; c; --c) {
- /* Pack RGB into 8bit pixel */
- ++src;
- RGB101010_RGB332(*dst++, *src);
- ++src;
- RGB101010_RGB332(*dst++, *src);
- ++src;
- RGB101010_RGB332(*dst++, *src);
- ++src;
- }
- switch (width & 3) {
- case 3:
- RGB101010_RGB332(*dst++, *src);
- ++src;
- SDL_FALLTHROUGH;
- case 2:
- RGB101010_RGB332(*dst++, *src);
- ++src;
- SDL_FALLTHROUGH;
- case 1:
- RGB101010_RGB332(*dst++, *src);
- ++src;
- }
-#endif /* USE_DUFFS_LOOP */
- src += srcskip;
- dst += dstskip;
- }
- } else {
- int Pixel;
-
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width / 4; c; --c) {
- /* Pack RGB into 8bit pixel */
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- }
- switch (width & 3) {
- case 3:
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- SDL_FALLTHROUGH;
- case 2:
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- SDL_FALLTHROUGH;
- case 1:
- RGB101010_RGB332(Pixel, *src);
- *dst++ = map[Pixel];
- ++src;
- }
-#endif /* USE_DUFFS_LOOP */
- src += srcskip;
- dst += dstskip;
- }
- }
-}
-
/* Special optimized blit for RGB 8-8-8 --> RGB 5-5-5 */
#define RGB888_RGB555(dst, src) \
{ \
@@ -2072,97 +1844,6 @@ static void Blit_RGB555_ARGB1555(SDL_BlitInfo *info)
}
}
-static void BlitNto1(SDL_BlitInfo *info)
-{
-#ifndef USE_DUFFS_LOOP
- int c;
-#endif
- int width, height;
- Uint8 *src;
- const Uint8 *map;
- Uint8 *dst;
- int srcskip, dstskip;
- int srcbpp;
- Uint32 Pixel;
- int sR, sG, sB;
- const SDL_PixelFormatDetails *srcfmt;
-
- /* Set up some basic variables */
- width = info->dst_w;
- height = info->dst_h;
- src = info->src;
- srcskip = info->src_skip;
- dst = info->dst;
- dstskip = info->dst_skip;
- map = info->table;
- srcfmt = info->src_fmt;
- srcbpp = srcfmt->bytes_per_pixel;
-
- if (!map) {
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel,
- sR, sG, sB);
- if ( 1 ) {
- /* Pack RGB into 8bit pixel */
- *dst = (Uint8)(((sR>>5)<<(3+2)) | ((sG>>5)<<(2)) | ((sB>>6)<<(0)));
- }
- dst++;
- src += srcbpp;
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width; c; --c) {
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel, sR, sG, sB);
- if (1) {
- /* Pack RGB into 8bit pixel */
- *dst = ((sR >> 5) << (3 + 2)) |
- ((sG >> 5) << (2)) | ((sB >> 6) << (0));
- }
- dst++;
- src += srcbpp;
- }
-#endif
- src += srcskip;
- dst += dstskip;
- }
- } else {
- while (height--) {
-#ifdef USE_DUFFS_LOOP
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel,
- sR, sG, sB);
- if ( 1 ) {
- /* Pack RGB into 8bit pixel */
- *dst = map[((sR>>5)<<(3+2))|
- ((sG>>5)<<(2)) |
- ((sB>>6)<<(0)) ];
- }
- dst++;
- src += srcbpp;
- , width);
- /* *INDENT-ON* */ /* clang-format on */
-#else
- for (c = width; c; --c) {
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel, sR, sG, sB);
- if (1) {
- /* Pack RGB into 8bit pixel */
- *dst = map[((sR >> 5) << (3 + 2)) |
- ((sG >> 5) << (2)) | ((sB >> 6) << (0))];
- }
- dst++;
- src += srcbpp;
- }
-#endif /* USE_DUFFS_LOOP */
- src += srcskip;
- dst += dstskip;
- }
- }
-}
-
/* blits 32 bit RGB<->RGBA with both surfaces having the same R,G,B fields */
static void Blit4to4MaskAlpha(SDL_BlitInfo *info)
{
@@ -2474,71 +2155,6 @@ static void BlitNtoNCopyAlpha(SDL_BlitInfo *info)
}
}
-static void BlitNto1Key(SDL_BlitInfo *info)
-{
- int width = info->dst_w;
- int height = info->dst_h;
- Uint8 *src = info->src;
- int srcskip = info->src_skip;
- Uint8 *dst = info->dst;
- int dstskip = info->dst_skip;
- const SDL_PixelFormatDetails *srcfmt = info->src_fmt;
- const Uint8 *palmap = info->table;
- Uint32 ckey = info->colorkey;
- Uint32 rgbmask = ~srcfmt->Amask;
- int srcbpp;
- Uint32 Pixel;
- unsigned sR, sG, sB;
-
- /* Set up some basic variables */
- srcbpp = srcfmt->bytes_per_pixel;
- ckey &= rgbmask;
-
- if (!palmap) {
- while (height--) {
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- {
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel,
- sR, sG, sB);
- if ( (Pixel & rgbmask) != ckey ) {
- /* Pack RGB into 8bit pixel */
- *dst = (Uint8)(((sR>>5)<<(3+2))|
- ((sG>>5)<<(2)) |
- ((sB>>6)<<(0)));
- }
- dst++;
- src += srcbpp;
- },
- width);
- /* *INDENT-ON* */ /* clang-format on */
- src += srcskip;
- dst += dstskip;
- }
- } else {
- while (height--) {
- /* *INDENT-OFF* */ /* clang-format off */
- DUFFS_LOOP(
- {
- DISEMBLE_RGB(src, srcbpp, srcfmt, Pixel,
- sR, sG, sB);
- if ( (Pixel & rgbmask) != ckey ) {
- /* Pack RGB into 8bit pixel */
- *dst = (Uint8)palmap[((sR>>5)<<(3+2))|
- ((sG>>5)<<(2)) |
- ((sB>>6)<<(0)) ];
- }
- dst++;
- src += srcbpp;
- },
- width);
- /* *INDENT-ON* */ /* clang-format on */
- src += srcskip;
- dst += dstskip;
- }
- }
-}
-
static void Blit2to2Key(SDL_BlitInfo *info)
{
int width = info->dst_w;
@@ -3343,22 +2959,7 @@ SDL_BlitFunc SDL_CalculateBlitN(SDL_Surface *surface)
switch (surface->internal->map.info.flags & ~SDL_COPY_RLE_MASK) {
case 0:
blitfun = NULL;
- if (dstfmt->bits_per_pixel == 8) {
- if ((srcfmt->bytes_per_pixel == 4) &&
- (srcfmt->Rmask == 0x00FF0000) &&
- (srcfmt->Gmask == 0x0000FF00) &&
- (srcfmt->Bmask == 0x000000FF)) {
- blitfun = Blit_XRGB8888_index8;
- } else if ((srcfmt->bytes_per_pixel == 4) &&
- (srcfmt->Rmask == 0x3FF00000) &&
- (srcfmt->Gmask == 0x000FFC00) &&
- (srcfmt->Bmask == 0x000003FF)) {
- blitfun = Blit_RGB101010_index8;
- } else {
- blitfun = BlitNto1;
- }
- } else {
- /* Now the meat, choose the blitter we want */
+ if (dstfmt->bits_per_pixel > 8) {
Uint32 a_need = NO_ALPHA;
if (dstfmt->Amask) {
a_need = srcfmt->Amask ? COPY_ALPHA : SET_ALPHA;
@@ -3418,15 +3019,13 @@ SDL_BlitFunc SDL_CalculateBlitN(SDL_Surface *surface)
if (srcfmt->bytes_per_pixel == 2 && surface->internal->map.identity != 0) {
return Blit2to2Key;
- } else if (dstfmt->bytes_per_pixel == 1) {
- return BlitNto1Key;
} else {
#ifdef SDL_ALTIVEC_BLITTERS
if ((srcfmt->bytes_per_pixel == 4) && (dstfmt->bytes_per_pixel == 4) && SDL_HasAltiVec()) {
return Blit32to32KeyAltivec;
} else
#endif
- if (srcfmt->Amask && dstfmt->Amask) {
+ if (srcfmt->Amask && dstfmt->Amask) {
return BlitNtoNKeyCopyAlpha;
} else {
return BlitNtoNKey;
diff --git a/src/video/SDL_blit_slow.c b/src/video/SDL_blit_slow.c
index 3b8703e19c176..17a9c66559bdf 100644
--- a/src/video/SDL_blit_slow.c
+++ b/src/video/SDL_blit_slow.c
@@ -69,15 +69,21 @@ void SDL_Blit_Slow(SDL_BlitInfo *info)
const SDL_Palette *src_pal = info->src_pal;
const SDL_PixelFormatDetails *dst_fmt = info->dst_fmt;
const SDL_Palette *dst_pal = info->dst_pal;
+ SDL_HashTable *palette_map = info->palette_map;
int srcbpp = src_fmt->bytes_per_pixel;
int dstbpp = dst_fmt->bytes_per_pixel;
SlowBlitPixelAccess src_access;
SlowBlitPixelAccess dst_access;
Uint32 rgbmask = ~src_fmt->Amask;
Uint32 ckey = info->colorkey & rgbmask;
+ Uint32 last_pixel = 0;
+ Uint8 last_index = 0;
src_access = GetPixelAccessMethod(src_fmt->format);
dst_access = GetPixelAccessMethod(dst_fmt->format);
+ if (dst_access == SlowBlitPixelAccess_Index8) {
+ last_index = SDL_LookupRGBAColor(palette_map, last_pixel, dst_pal);
+ }
incy = ((Uint64)info->src_h << 16) / info->dst_h;
incx = ((Uint64)info->src_w << 16) / info->dst_w;
@@ -275,12 +281,12 @@ void SDL_Blit_Slow(SDL_BlitInfo *info)
switch (dst_access) {
case SlowBlitPixelAccess_Index8:
- RGB332_FROM_RGB(dstpixel, dstR, dstG, dstB);
- if (info->table) {
- *dst = info->table[dstpixel];
- } else {
- *dst = dstpixel;
+ dstpixel = (dstR << 24 | dstG << 16 | dstB << 8 | dstA);
+ if (dstpixel != last_pixel) {
+ last_pixel = dstpixel;
+ last_index = SDL_LookupRGBAColor(palette_map, dstpixel, dst_pal);
}
+ *dst = last_index;
break;
case SlowBlitPixelAccess_RGB:
ASSEMBLE_RGB(dst, dstbpp, dst_fmt, dstR, dstG, dstB);
diff --git a/src/video/SDL_pixels.c b/src/video/SDL_pixels.c
index d8003f1039b71..8d4ee72f1f2d1 100644
--- a/src/video/SDL_pixels.c
+++ b/src/video/SDL_pixels.c
@@ -1131,6 +1131,23 @@ Uint8 SDL_FindColor(const SDL_Palette *pal, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
return pixel;
}
+Uint8 SDL_LookupRGBAColor(SDL_HashTable *palette_map, Uint32 pixel, const SDL_Palette *pal)
+{
+ Uint8 color_index = 0;
+ const void *value;
+ if (SDL_FindInHashTable(palette_map, (const void *)(uintptr_t)pixel, &value)) {
+ color_index = (Uint8)(uintptr_t)value;
+ } else {
+ Uint8 r = (Uint8)((pixel >> 24) & 0xFF);
+ Uint8 g = (Uint8)((pixel >> 16) & 0xFF);
+ Uint8 b = (Uint8)((pixel >> 8) & 0xFF);
+ Uint8 a = (Uint8)((pixel >> 0) & 0xFF);
+ color_index = SDL_FindColor(pal, r, g, b, a);
+ SDL_InsertIntoHashTable(palette_map, (const void *)(uintptr_t)pixel, (const void *)(uintptr_t)color_index);
+ }
+ return color_index;
+}
+
/* Tell whether palette is opaque, and if it has an alpha_channel */
void SDL_DetectPalette(const SDL_Palette *pal, SDL_bool *is_opaque, SDL_bool *has_alpha_channel)
{
@@ -1401,24 +1418,6 @@ static Uint8 *Map1toN(const SDL_Palette *pal, Uint8 Rmod, Uint8 Gmod, Uint8 Bmod
return map;
}
-/* Map from BitField to Dithered-Palette to Palette */
-static Uint8 *MapNto1(const SDL_PixelFormatDetails *src, const SDL_Palette *pal, int *identical)
-{
- /* Generate a 256 color dither palette */
- SDL_Palette dithered;
- SDL_Color colors[256];
-
- if (!pal) {
- SDL_SetError("dst does not have a palette set");
- return NULL;
- }
-
- dithered.colors = colors;
- dithered.ncolors = SDL_arraysize(colors);
- SDL_DitherPalette(&dithered);
- return Map1to1(&dithered, pal, identical);
-}
-
int SDL_ValidateMap(SDL_Surface *src, SDL_Surface *dst)
{
SDL_BlitMap *map = &src->internal->map;
@@ -1449,8 +1448,14 @@ void SDL_InvalidateMap(SDL_BlitMap *map)
map->info.dst_pal = NULL;
map->src_palette_version = 0;
map->dst_palette_version = 0;
- SDL_free(map->info.table);
- map->info.table = NULL;
+ if (map->info.table) {
+ SDL_free(map->info.table);
+ map->info.table = NULL;
+ }
+ if (map->info.palette_map) {
+ SDL_DestroyHashTable(map->info.palette_map);
+ map->info.palette_map = NULL;
+ }
}
int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst)
@@ -1504,13 +1509,7 @@ int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst)
} else {
if (SDL_ISPIXELFORMAT_INDEXED(dstfmt->format)) {
/* BitField --> Palette */
- map->info.table = MapNto1(srcfmt, dstpal, &map->identity);
- if (!map->identity) {
- if (!map->info.table) {
- return -1;
- }
- }
- map->identity = 0; /* Don't optimize to copy */
+ map->info.palette_map = SDL_CreateHashTable(NULL, 32, SDL_HashID, SDL_KeyMatchID, NULL, SDL_FALSE);
} else {
/* BitField --> BitField */
if (srcfmt == dstfmt) {
diff --git a/src/video/SDL_pixels_c.h b/src/video/SDL_pixels_c.h
index e444dab04a3af..4da44bd9ca4d8 100644
--- a/src/video/SDL_pixels_c.h
+++ b/src/video/SDL_pixels_c.h
@@ -50,6 +50,7 @@ extern int SDL_MapSurface(SDL_Surface *src, SDL_Surface *dst);
/* Miscellaneous functions */
extern void SDL_DitherPalette(SDL_Palette *palette);
extern Uint8 SDL_FindColor(const SDL_Palette *pal, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
+extern Uint8 SDL_LookupRGBAColor(SDL_HashTable *palette_map, Uint32 pixel, const SDL_Palette *pal);
extern void SDL_DetectPalette(const SDL_Palette *pal, SDL_bool *is_opaque, SDL_bool *has_alpha_channel);
extern SDL_Surface *SDL_DuplicatePixels(int width, int height, SDL_PixelFormat format, SDL_Colorspace colorspace, void *pixels, int pitch);