Add: 32bpp-to-8bpp palette index lookup.

Lookups are calculated on demand and caches in a 256KB in-memory table.
This commit is contained in:
Peter Nelson 2023-12-22 16:01:32 +00:00 committed by Peter Nelson
parent daec0e2ca4
commit 0bc22dd310
2 changed files with 114 additions and 0 deletions

View File

@ -10,6 +10,7 @@
#include "stdafx.h"
#include "blitter/base.hpp"
#include "blitter/factory.hpp"
#include "fileio_func.h"
#include "gfx_type.h"
#include "landscape_type.h"
#include "palette_func.h"
@ -26,6 +27,112 @@ byte _colour_gradient[COLOUR_END][8];
static std::recursive_mutex _palette_mutex; ///< To coordinate access to _cur_palette.
/**
* PALETTE_BITS reduces the bits-per-channel of 32bpp graphics data to allow faster palette lookups from
* a smaller lookup table.
*
* 6 bpc is chosen as this results in a palette lookup table of 256KiB with adequate fidelty.
* In constract, a 5 bpc lookup table would be 32KiB, and 7 bpc would be 2MiB.
*
* Values in the table are filled as they are first encountered -- larger lookup table means more colour
* distance calculations, and is therefore slower.
*/
const uint PALETTE_BITS = 6;
const uint PALETTE_SHIFT = 8 - PALETTE_BITS;
const uint PALETTE_BITS_MASK = ((1U << PALETTE_BITS) - 1) << PALETTE_SHIFT;
const uint PALETTE_BITS_OR = (1U << (PALETTE_SHIFT - 1));
/* Palette and reshade lookup table. */
using PaletteLookup = std::array<uint8_t, 1U << (PALETTE_BITS * 3)>;
static PaletteLookup _palette_lookup{};
/**
* Reduce bits per channel to PALETTE_BITS, and place value in the middle of the reduced range.
* This is to counteract the information lost between bright and dark pixels, e.g if PALETTE_BITS was 2:
* 0 - 63 -> 32
* 64 - 127 -> 96
* 128 - 191 -> 160
* 192 - 255 -> 224
* @param c 8 bit colour component.
* @returns Colour component reduced to PALETTE_BITS.
*/
inline uint CrunchColour(uint c)
{
return (c & PALETTE_BITS_MASK) | PALETTE_BITS_OR;
}
/**
* Calculate distance between two colours.
* @param col1 First colour.
* @param r2 Red component of second colour.
* @param g2 Green component of second colour.
* @param b2 Blue component of second colour.
* @returns Euclidean distance between first and second colour.
*/
static uint CalculateColourDistance(const Colour &col1, int r2, int g2, int b2)
{
/* Euclidean colour distance for sRGB based on https://en.wikipedia.org/wiki/Color_difference#sRGB */
int r = (int)col1.r - (int)r2;
int g = (int)col1.g - (int)g2;
int b = (int)col1.b - (int)b2;
int avgr = (col1.r + r2) / 2;
return ((2 + (avgr / 256.0)) * r * r) + (4 * g * g) + ((2 + ((255 - avgr) / 256.0)) * b * b);
}
/* Palette indexes for conversion. See docs/palettes/palette_key.png */
const uint8_t PALETTE_INDEX_CC_START = 198; ///< Palette index of start of company colour remap area.
const uint8_t PALETTE_INDEX_CC_END = PALETTE_INDEX_CC_START + 8; ///< Palette index of end of company colour remap area.
const uint8_t PALETTE_INDEX_START = 1; ///< Palette index of start of defined palette.
const uint8_t PALETTE_INDEX_END = 215; ///< Palette index of end of defined palette.
/**
* Find nearest colour palette index for a 32bpp pixel.
* @param r Red component.
* @param g Green component.
* @param b Blue component.
* @returns palette index of nearest colour.
*/
static uint8_t FindNearestColourIndex(uint8_t r, uint8_t g, uint8_t b)
{
r = CrunchColour(r);
g = CrunchColour(g);
b = CrunchColour(b);
uint best_index = 0;
uint best_distance = UINT32_MAX;
for (uint i = PALETTE_INDEX_START; i < PALETTE_INDEX_CC_START; i++) {
if (uint distance = CalculateColourDistance(_palette.palette[i], r, g, b); distance < best_distance) {
best_index = i;
best_distance = distance;
}
}
/* There's a hole in the palette reserved for company colour remaps. */
for (uint i = PALETTE_INDEX_CC_END; i < PALETTE_INDEX_END; i++) {
if (uint distance = CalculateColourDistance(_palette.palette[i], r, g, b); distance < best_distance) {
best_index = i;
best_distance = distance;
}
}
return best_index;
}
/**
* Get nearest colour palette index from an RGB colour.
* A search is performed if this colour is not already in the lookup table.
* @param r Red component.
* @param g Green component.
* @param b Blue component.
* @returns nearest colour palette index.
*/
uint8_t GetNearestColourIndex(uint8_t r, uint8_t g, uint8_t b)
{
uint32_t key = (r >> PALETTE_SHIFT) | (g >> PALETTE_SHIFT) << PALETTE_BITS | (b >> PALETTE_SHIFT) << (PALETTE_BITS * 2);
if (_palette_lookup[key] == 0) _palette_lookup[key] = FindNearestColourIndex(r, g, b);
return _palette_lookup[key];
}
void DoPaletteAnimations();
void GfxInitPalettes()

View File

@ -19,6 +19,13 @@ extern Palette _cur_palette; ///< Current palette
bool CopyPalette(Palette &local_palette, bool force_copy = false);
void GfxInitPalettes();
uint8_t GetNearestColourIndex(uint8_t r, uint8_t g, uint8_t b);
static inline uint8_t GetNearestColourIndex(const Colour colour)
{
return GetNearestColourIndex(colour.r, colour.g, colour.b);
}
/**
* Checks if a Colours value is valid.
*