diff --git a/src/blitter/32bpp_anim.cpp b/src/blitter/32bpp_anim.cpp index f78edcbbaa..dad55dc244 100644 --- a/src/blitter/32bpp_anim.cpp +++ b/src/blitter/32bpp_anim.cpp @@ -37,6 +37,7 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel uint16_t *anim = this->anim_buf + this->ScreenToAnimOffset((uint32_t *)bp->dst) + bp->top * this->anim_buf_pitch + bp->left; const uint8_t *remap = bp->remap->GetPaletteRemap(); // store so we don't have to access it via bp every time + const Colour *remap_rgba = mode == BlitterMode::RGBAColourRemap ? bp->remap->GetRGBARemap() : nullptr; for (int y = 0; y < bp->height; y++) { Colour *dst_ln = dst + bp->pitch; @@ -135,6 +136,43 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel } break; + case BlitterMode::RGBAColourRemap: + if (src_px->a == 255) { + do { + uint m = *src_n; + /* In case the m-channel is zero, do not remap this pixel in any way */ + if (m == 0) { + *dst = src_px->data; + *anim = 0; + } else { + const Colour &c = remap_rgba[GB(m, 0, 8)]; + *anim = 0; + *dst = AdjustBrightness(c, GB(m, 8, 8)); + } + anim++; + dst++; + src_px++; + src_n++; + } while (--n != 0); + } else { + do { + uint m = *src_n; + if (m == 0) { + *dst = ComposeColourRGBANoCheck(src_px->r, src_px->g, src_px->b, src_px->a, *dst); + *anim = 0; + } else { + const Colour &c = remap_rgba[GB(m, 0, 8)]; + *anim = 0; + *dst = ComposeColourPANoCheck(AdjustBrightness(c, GB(m, 8, 8)), c.a * src_px->a / 255, *dst); + } + anim++; + dst++; + src_px++; + src_n++; + } while (--n != 0); + } + break; + case BlitterMode::CrashRemap: if (src_px->a == 255) { do { @@ -273,6 +311,7 @@ void Blitter_32bppAnim::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomL default: NOT_REACHED(); case BlitterMode::Normal: Draw(bp, zoom); return; case BlitterMode::ColourRemap: Draw(bp, zoom); return; + case BlitterMode::RGBAColourRemap: Draw(bp, zoom); return; case BlitterMode::Transparent: Draw(bp, zoom); return; case BlitterMode::TransparentRemap: Draw(bp, zoom); return; case BlitterMode::CrashRemap: Draw(bp, zoom); return; diff --git a/src/blitter/32bpp_optimized.cpp b/src/blitter/32bpp_optimized.cpp index fa2d1369c9..972cea6780 100644 --- a/src/blitter/32bpp_optimized.cpp +++ b/src/blitter/32bpp_optimized.cpp @@ -49,6 +49,7 @@ inline void Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomL /* store so we don't have to access it via bp every time (compiler assumes pointer aliasing) */ const uint8_t *remap = bp->remap->GetPaletteRemap(); + const Colour *remap_rgba = mode == BlitterMode::RGBAColourRemap ? bp->remap->GetRGBARemap() : nullptr; for (int y = 0; y < bp->height; y++) { /* next dst line begins here */ @@ -142,6 +143,37 @@ inline void Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomL } break; + case BlitterMode::RGBAColourRemap: + if (src_px->a == 255) { + do { + uint m = *src_n; + /* In case the m-channel is zero, do not remap this pixel in any way */ + if (m == 0) { + *dst = src_px->data; + } else { + const Colour c = remap_rgba[GB(m, 0, 8)]; + *dst = AdjustBrightness(c, GB(m, 8, 8)); + } + dst++; + src_px++; + src_n++; + } while (--n != 0); + } else { + do { + uint m = *src_n; + if (m == 0) { + *dst = ComposeColourRGBANoCheck(src_px->r, src_px->g, src_px->b, src_px->a, *dst); + } else { + const Colour c = remap_rgba[GB(m, 0, 8)]; + *dst = ComposeColourPANoCheck(AdjustBrightness(c, GB(m, 8, 8)), src_px->a, *dst); + } + dst++; + src_px++; + src_n++; + } while (--n != 0); + } + break; + case BlitterMode::CrashRemap: if (src_px->a == 255) { do { @@ -263,6 +295,7 @@ void Blitter_32bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, default: NOT_REACHED(); case BlitterMode::Normal: Draw(bp, zoom); return; case BlitterMode::ColourRemap: Draw(bp, zoom); return; + case BlitterMode::RGBAColourRemap: Draw(bp, zoom); return; case BlitterMode::Transparent: Draw(bp, zoom); return; case BlitterMode::TransparentRemap: Draw(bp, zoom); return; case BlitterMode::CrashRemap: Draw(bp, zoom); return; diff --git a/src/blitter/32bpp_simple.cpp b/src/blitter/32bpp_simple.cpp index 04ac7b6a66..8c0310f5e1 100644 --- a/src/blitter/32bpp_simple.cpp +++ b/src/blitter/32bpp_simple.cpp @@ -24,6 +24,7 @@ void Blitter_32bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoo const Blitter_32bppSimple::Pixel *src, *src_line; Colour *dst, *dst_line; const uint8_t *remap = bp->remap->GetPaletteRemap(); + const Colour *remap_rgba = mode == BlitterMode::RGBAColourRemap ? bp->remap->GetRGBARemap() : nullptr; /* Find where to start reading in the source sprite */ src_line = (const Blitter_32bppSimple::Pixel *)bp->sprite + (bp->skip_top * bp->sprite_width + bp->skip_left) * ScaleByZoom(1, zoom); @@ -47,6 +48,18 @@ void Blitter_32bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoo } break; + case BlitterMode::RGBAColourRemap: + /* In case the m-channel is zero, do not remap this pixel in any way */ + if (src->m == 0) { + if (src->a != 0) *dst = ComposeColourRGBA(src->r, src->g, src->b, src->a, *dst); + } else { + const Colour &c = remap_rgba[src->m]; + if (c.a != 0) { + *dst = ComposeColourPA(AdjustBrightness(c, src->v), src->a, *dst); + } + } + break; + case BlitterMode::CrashRemap: if (src->m == 0) { if (src->a != 0) { diff --git a/src/blitter/40bpp_anim.cpp b/src/blitter/40bpp_anim.cpp index 59a3294173..7a6ffc6c3a 100644 --- a/src/blitter/40bpp_anim.cpp +++ b/src/blitter/40bpp_anim.cpp @@ -127,6 +127,7 @@ inline void Blitter_40bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel /* store so we don't have to access it via bp every time (compiler assumes pointer aliasing) */ const uint8_t *remap = bp->remap->GetPaletteRemap(); + const Colour *remap_rgba = mode == BlitterMode::RGBAColourRemap ? bp->remap->GetRGBARemap() : nullptr; for (int y = 0; y < bp->height; y++) { /* next dst line begins here */ @@ -237,6 +238,49 @@ inline void Blitter_40bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel } break; + case BlitterMode::RGBAColourRemap: + if (src_px->a == 255) { + do { + uint8_t m = GB(*src_n, 0, 8); + /* In case the m-channel is zero, only apply the crash remap by darkening the RGB colour. */ + if (m == 0) { + *dst = *src_px; + *anim = 0; + } else { + const Colour &c = remap_rgba[GB(m, 0, 8)]; + if (c.a != 0) { + *dst = ComposeColourRGBANoCheck(c.r, c.g, c.b, c.a, *dst); + *anim = 0; + } + } + anim++; + dst++; + src_px++; + src_n++; + } while (--n != 0); + } else { + do { + uint8_t m = GB(*src_n, 0, 8); + Colour b = this->RealizeBlendedColour(*anim, *dst); + if (m == 0) { + Colour c = *src_px; + *dst = this->ComposeColourRGBANoCheck(c.r, c.g, c.b, src_px->a / 255, b); + *anim = 0; + } else { + const Colour &c = remap_rgba[m]; + if (c.a != 0) { + *dst = this->ComposeColourPANoCheck(c, c.a * src_px->a / 255, b); + *anim = 0; // Animation colours don't work with alpha-blending. + } + } + anim++; + dst++; + src_px++; + src_n++; + } while (--n != 0); + } + break; + case BlitterMode::BlackRemap: do { *anim++ = 0; @@ -353,6 +397,7 @@ void Blitter_40bppAnim::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomL default: NOT_REACHED(); case BlitterMode::Normal: Draw(bp, zoom); return; case BlitterMode::ColourRemap: Draw(bp, zoom); return; + case BlitterMode::RGBAColourRemap: Draw(bp, zoom); return; case BlitterMode::Transparent: Draw(bp, zoom); return; case BlitterMode::TransparentRemap: Draw(bp, zoom); return; case BlitterMode::CrashRemap: Draw(bp, zoom); return; diff --git a/src/blitter/base.hpp b/src/blitter/base.hpp index 1139dc50d6..b809c4961d 100644 --- a/src/blitter/base.hpp +++ b/src/blitter/base.hpp @@ -17,6 +17,7 @@ enum class BlitterMode : uint8_t { Normal, ///< Perform the simple blitting. ColourRemap, ///< Perform a colour remapping. + RGBAColourRemap, ///< Perform a colour remapping. Transparent, ///< Perform transparency darkening remapping. TransparentRemap, ///< Perform transparency colour remapping. CrashRemap, ///< Perform a crash remapping. diff --git a/src/gfx.cpp b/src/gfx.cpp index 66a3c7bee4..319fe0d789 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -983,13 +983,13 @@ Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom) * @param pal The palette to get the blitter mode for. * @return The blitter mode associated with the palette. */ -static BlitterMode GetBlitterMode(PaletteID pal) +static BlitterMode GetBlitterMode(PaletteID pal, const RecolourSprite &recolour) { switch (pal) { case PAL_NONE: return BlitterMode::Normal; case PALETTE_CRASH: return BlitterMode::CrashRemap; case PALETTE_ALL_BLACK: return BlitterMode::BlackRemap; - default: return BlitterMode::ColourRemap; + default: return recolour.IsRGB() && BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32 ? BlitterMode::RGBAColourRemap : BlitterMode::ColourRemap; } } @@ -1014,7 +1014,7 @@ void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSpri } else { _colour_remap_ptr = GetRecolourSprite(GB(pal, 0, PALETTE_WIDTH)); } - GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal), sub, real_sprite); + GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal, *_colour_remap_ptr), sub, real_sprite); } else { GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, BlitterMode::Normal, sub, real_sprite); } @@ -1042,7 +1042,7 @@ void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, } else { _colour_remap_ptr = GetRecolourSprite(GB(pal, 0, PALETTE_WIDTH)); } - GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal), sub, real_sprite, zoom); + GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal, *_colour_remap_ptr), sub, real_sprite, zoom); } else { GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, BlitterMode::Normal, sub, real_sprite, zoom); } diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 5e37f04710..bac740a582 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -556,8 +556,16 @@ void SetupColoursAndInitialWindow() const RecolourSprite *rs = GetRecolourSprite(GetColourPalette(i)); assert(rs != nullptr); const uint8_t *remap = rs->GetPaletteRemap(); - for (ColourShade j = SHADE_BEGIN; j < SHADE_END; j++) { - SetColourGradient(i, j, PixelColour{remap[0xC6 + j]}); + if (rs->IsRGB()) { + const Colour *rgba_remap = rs->GetRGBARemap(); + for (ColourShade j = SHADE_BEGIN; j < SHADE_END; j++) { + const Colour &c = rgba_remap[j]; + SetColourGradient(i, j, PixelColour{remap[0xC6 + j], c}); + } + } else { + for (ColourShade j = SHADE_BEGIN; j < SHADE_END; j++) { + SetColourGradient(i, j, PixelColour{remap[0xC6 + j]}); + } } } diff --git a/src/spritecache.cpp b/src/spritecache.cpp index 738afaf02a..147e3b1bf8 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -427,6 +427,19 @@ void RecolourSprite::Read(SpriteFile &file, size_t entries) } } +void RecolourSpriteRGBA::Read(SpriteFile &file, size_t entries) +{ + this->RecolourSprite::Read(file, entries); + + /* Colour is byte-arranged differently by platform, so read components individually. */ + for (uint i = 0; i < entries; ++i) { + this->rgba[i].r = file.ReadByte(); + this->rgba[i].g = file.ReadByte(); + this->rgba[i].b = file.ReadByte(); + this->rgba[i].a = file.ReadByte(); + } +} + /** * Load a recolour sprite into memory. * @param file GRF we're reading from. @@ -450,7 +463,8 @@ static void *ReadRecolourSprite(SpriteFile &file, size_t file_pos, uint num, Spr entries = num; } - RecolourSprite *rs = allocator.New(); + num -= entries; + RecolourSprite *rs = (num == entries * 4) ? allocator.New() : allocator.New(); rs->Read(file, entries); return rs; } diff --git a/src/spritecache_type.h b/src/spritecache_type.h index e41c9848c2..afa4bb7ab1 100644 --- a/src/spritecache_type.h +++ b/src/spritecache_type.h @@ -11,6 +11,7 @@ #define SPRITECACHE_TYPE_H #include "core/enum_type.hpp" +#include "gfx_type.h" #include "spriteloader/sprite_file_type.hpp" /** Data structure describing a sprite. */ @@ -27,12 +28,24 @@ class RecolourSprite { public: static constexpr size_t PALETTE_SIZE = 256; ///< Number of entries in a recolour sprite. - void Read(SpriteFile &file, size_t entries); + virtual ~RecolourSprite() {} + virtual void Read(SpriteFile &file, size_t entries); inline const uint8_t *GetPaletteRemap() const { return this->palette.data(); } + virtual bool IsRGB() const { return false; } + virtual const Colour *GetRGBARemap() const { return nullptr; } std::array palette{}; ///< Palette index remap, mapping from one palette index to another. }; +class RecolourSpriteRGBA : public RecolourSprite { +public: + void Read(SpriteFile &file, size_t entries) override; + bool IsRGB() const override { return true; } + const Colour *GetRGBARemap() const override { return this->rgba.data(); } + + std::array rgba{}; ///< RGBA remap, mapping from palette index to colour. +}; + enum class SpriteCacheCtrlFlag : uint8_t { AllowZoomMin1xPal, ///< Allow use of sprite min zoom setting at 1x in palette mode. AllowZoomMin1x32bpp, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.