From 7b091000b0a2b0578606cd17b487149a2f9792dd Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 15 Jan 2025 17:36:31 +0000 Subject: [PATCH] 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. --- src/palette.cpp | 37 ++++++++++++++++++++ src/palette_func.h | 1 + src/spritecache.cpp | 6 ++++ src/spriteloader/CMakeLists.txt | 2 ++ src/spriteloader/makeindexed.cpp | 60 ++++++++++++++++++++++++++++++++ src/spriteloader/makeindexed.h | 23 ++++++++++++ 6 files changed, 129 insertions(+) create mode 100644 src/spriteloader/makeindexed.cpp create mode 100644 src/spriteloader/makeindexed.h diff --git a/src/palette.cpp b/src/palette.cpp index c31305abbc..87741706f1 100644 --- a/src/palette.cpp +++ b/src/palette.cpp @@ -44,6 +44,9 @@ const uint PALETTE_BITS_OR = (1U << (PALETTE_SHIFT - 1)); using PaletteLookup = std::array; static PaletteLookup _palette_lookup{}; +using ReshadeLookup = std::array; +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. diff --git a/src/palette_func.h b/src/palette_func.h index 4bb0c23e1c..5494203b0f 100644 --- a/src/palette_func.h +++ b/src/palette_func.h @@ -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) { diff --git a/src/spritecache.cpp b/src/spritecache.cpp index 035e06d003..f40ecc67df 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -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) { diff --git a/src/spriteloader/CMakeLists.txt b/src/spriteloader/CMakeLists.txt index 804bb1a2ee..454aa1710f 100644 --- a/src/spriteloader/CMakeLists.txt +++ b/src/spriteloader/CMakeLists.txt @@ -1,6 +1,8 @@ add_files( grf.cpp grf.hpp + makeindexed.cpp + makeindexed.h sprite_file.cpp sprite_file_type.hpp spriteloader.hpp diff --git a/src/spriteloader/makeindexed.cpp b/src/spriteloader/makeindexed.cpp new file mode 100644 index 0000000000..05df4cc063 --- /dev/null +++ b/src/spriteloader/makeindexed.cpp @@ -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 . + */ + +/** @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; +} diff --git a/src/spriteloader/makeindexed.h b/src/spriteloader/makeindexed.h new file mode 100644 index 0000000000..7b0f690663 --- /dev/null +++ b/src/spriteloader/makeindexed.h @@ -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 . + */ + +/** @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 */