Feature: Support converting 32bpp-only sprites to indexed 8bpp.

This uses nearest colour lookup to convert 32bpp-only sprites to indexed 8bpp on the fly. This provides a reasonable usable sprite instead of being incompatible.
This commit is contained in:
Peter Nelson 2025-01-15 17:36:31 +00:00 committed by Peter Nelson
parent c5d3ac7a71
commit 7b091000b0
6 changed files with 129 additions and 0 deletions

View File

@ -44,6 +44,9 @@ const uint PALETTE_BITS_OR = (1U << (PALETTE_SHIFT - 1));
using PaletteLookup = std::array<uint8_t, 1U << (PALETTE_BITS * 3)>;
static PaletteLookup _palette_lookup{};
using ReshadeLookup = std::array<uint8_t, 1U << PALETTE_BITS>;
static ReshadeLookup _reshade_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:
@ -116,6 +119,27 @@ static uint8_t FindNearestColourIndex(uint8_t r, uint8_t g, uint8_t b)
return best_index;
}
/**
* Find nearest company colour palette index for a brightness level.
* @param pixel Pixel to find.
* @returns palette index of nearest colour.
*/
static uint8_t FindNearestColourReshadeIndex(uint8_t b)
{
b = CrunchColour(b);
uint best_index = 0;
uint best_distance = UINT32_MAX;
for (uint i = PALETTE_INDEX_CC_START; i < PALETTE_INDEX_CC_END; i++) {
if (uint distance = CalculateColourDistance(_palette.palette[i], b, b, 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.
@ -131,6 +155,19 @@ uint8_t GetNearestColourIndex(uint8_t r, uint8_t g, uint8_t b)
return _palette_lookup[key];
}
/**
* Get nearest colour palette index from a brightness level.
* A search is performed if this brightness level is not already in the lookup table.
* @param b Brightness component.
* @returns nearest colour palette index.
*/
uint8_t GetNearestColourReshadeIndex(uint8_t b)
{
uint32_t key = (b >> PALETTE_SHIFT);
if (_reshade_lookup[key] == 0) _reshade_lookup[key] = FindNearestColourReshadeIndex(b);
return _reshade_lookup[key];
}
/**
* Adjust brightness of colour.
* @param colour Colour to adjust.

View File

@ -21,6 +21,7 @@ bool CopyPalette(Palette &local_palette, bool force_copy = false);
void GfxInitPalettes();
uint8_t GetNearestColourIndex(uint8_t r, uint8_t g, uint8_t b);
uint8_t GetNearestColourReshadeIndex(uint8_t b);
inline uint8_t GetNearestColourIndex(const Colour colour)
{

View File

@ -10,6 +10,7 @@
#include "stdafx.h"
#include "random_access_file_type.h"
#include "spriteloader/grf.hpp"
#include "spriteloader/makeindexed.h"
#include "gfx_func.h"
#include "error.h"
#include "error_func.h"
@ -488,6 +489,11 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
}
if (sprite_avail == 0) {
sprite_avail = sprite_loader.LoadSprite(sprite, file, file_pos, sprite_type, false, sc->control_flags, avail_8bpp, avail_32bpp);
if (sprite_type == SpriteType::Normal && avail_32bpp != 0 && !encoder->Is32BppSupported() && sprite_avail == 0) {
/* No 8bpp available, try converting from 32bpp. */
SpriteLoaderMakeIndexed make_indexed(sprite_loader);
sprite_avail = make_indexed.LoadSprite(sprite, file, file_pos, sprite_type, true, sc->control_flags, sprite_avail, avail_32bpp);
}
}
if (sprite_avail == 0) {

View File

@ -1,6 +1,8 @@
add_files(
grf.cpp
grf.hpp
makeindexed.cpp
makeindexed.h
sprite_file.cpp
sprite_file_type.hpp
spriteloader.hpp

View File

@ -0,0 +1,60 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file makeindexed.cpp Implementation for converting sprites from another source from 32bpp RGBA to indexed 8bpp. */
#include "../stdafx.h"
#include "../core/bitmath_func.hpp"
#include "../core/math_func.hpp"
#include "../gfx_func.h"
#include "../palette_func.h"
#include "makeindexed.h"
#include "../safeguards.h"
/**
* Convert in place a 32bpp sprite to 8bpp.
* @param sprite Sprite to convert.
*/
static void Convert32bppTo8bpp(SpriteLoader::Sprite &sprite)
{
const auto *pixel_end = sprite.data + sprite.width * sprite.height;
for (auto *pixel = sprite.data; pixel != pixel_end; ++pixel) {
if (pixel->m != 0) {
/* Pixel has 8bpp mask, test if should be reshaded. */
uint8_t brightness = std::max({pixel->r, pixel->g, pixel->b});
if (brightness == 0 || brightness == 128) continue;
/* Update RGB component with reshaded palette colour, and enabled reshade. */
Colour c = AdjustBrightness(_cur_palette.palette[pixel->m], brightness);
if (IsInsideMM(pixel->m, 0xC6, 0xCE)) {
/* Dumb but simple brightness conversion. */
pixel->m = GetNearestColourReshadeIndex((c.r + c.g + c.b) / 3);
} else {
pixel->m = GetNearestColourIndex(c.r, c.g, c.b);
}
} else if (pixel->a < 128) {
/* Transparent pixel. */
pixel->m = 0;
} else {
/* Find nearest match from palette. */
pixel->m = GetNearestColourIndex(pixel->r, pixel->g, pixel->b);
}
}
}
uint8_t SpriteLoaderMakeIndexed::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool, uint8_t control_flags, uint8_t &avail_8bpp, uint8_t &avail_32bpp)
{
uint8_t avail = this->baseloader.LoadSprite(sprite, file, file_pos, sprite_type, true, control_flags, avail_8bpp, avail_32bpp);
for (ZoomLevel zoom = ZOOM_LVL_NORMAL; zoom != ZOOM_LVL_END; zoom++) {
if (HasBit(avail, zoom)) Convert32bppTo8bpp(sprite[zoom]);
}
return avail;
}

View File

@ -0,0 +1,23 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file makeindexed.h Base for converting sprites from another source from 32bpp RGBA to indexed 8bpp. */
#ifndef SPRITELOADER_MAKEINDEXED_H
#define SPRITELOADER_MAKEINDEXED_H
#include "spriteloader.hpp"
/** Sprite loader for converting graphics coming from another source. */
class SpriteLoaderMakeIndexed : public SpriteLoader {
SpriteLoader &baseloader;
public:
SpriteLoaderMakeIndexed(SpriteLoader &baseloader) : baseloader(baseloader) {}
uint8_t LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, uint8_t &avail_8bpp, uint8_t &avail_32bpp) override;
};
#endif /* SPRITELOADER_MAKEINDEXED_H */