diff --git a/src/blitter/32bpp_anim.cpp b/src/blitter/32bpp_anim.cpp index 797b0db738..dad55dc244 100644 --- a/src/blitter/32bpp_anim.cpp +++ b/src/blitter/32bpp_anim.cpp @@ -36,7 +36,8 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel Colour *dst = (Colour *)bp->dst + bp->top * bp->pitch + bp->left; 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; // store so we don't have to access it via bp every time + 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; @@ -323,7 +362,7 @@ void Blitter_32bppAnim::DrawColourMappingRect(void *dst, int width, int height, void Blitter_32bppAnim::SetPixel(void *video, int x, int y, PixelColour colour) { - *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour.p); + *((Colour *)video + x + y * _screen.pitch) = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); /* Set the colour in the anim-buffer too, if we are rendering to the screen */ if (_screen_disable_anim) return; @@ -333,7 +372,7 @@ void Blitter_32bppAnim::SetPixel(void *video, int x, int y, PixelColour colour) void Blitter_32bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { - const Colour c = LookupColourInPalette(colour.p); + const Colour c = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); if (_screen_disable_anim) { this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [&](int x, int y) { @@ -357,7 +396,7 @@ void Blitter_32bppAnim::DrawRect(void *video, int width, int height, PixelColour return; } - Colour colour32 = LookupColourInPalette(colour.p); + Colour colour32 = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); uint16_t *anim_line = this->ScreenToAnimOffset((uint32_t *)video) + this->anim_buf; do { diff --git a/src/blitter/32bpp_anim_sse4.cpp b/src/blitter/32bpp_anim_sse4.cpp index 4ed29b33c1..720ba7daad 100644 --- a/src/blitter/32bpp_anim_sse4.cpp +++ b/src/blitter/32bpp_anim_sse4.cpp @@ -33,7 +33,7 @@ template remap; + const uint8_t * const remap = bp->remap->GetPaletteRemap(); Colour *dst_line = (Colour *) bp->dst + bp->top * bp->pitch + bp->left; uint16_t *anim_line = this->anim_buf + this->ScreenToAnimOffset((uint32_t *)bp->dst) + bp->top * this->anim_buf_pitch + bp->left; int effective_width = bp->width; diff --git a/src/blitter/32bpp_base.cpp b/src/blitter/32bpp_base.cpp index fdf780b982..811203da63 100644 --- a/src/blitter/32bpp_base.cpp +++ b/src/blitter/32bpp_base.cpp @@ -20,12 +20,13 @@ void *Blitter_32bppBase::MoveTo(void *video, int x, int y) void Blitter_32bppBase::SetPixel(void *video, int x, int y, PixelColour colour) { - *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour.p); + const Colour c = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); + *((Colour *)video + x + y * _screen.pitch) = c; } void Blitter_32bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { - const Colour c = LookupColourInPalette(colour.p); + const Colour c = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [=](int x, int y) { *((Colour *)video + x + y * _screen.pitch) = c; }); @@ -33,7 +34,7 @@ void Blitter_32bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int void Blitter_32bppBase::DrawRect(void *video, int width, int height, PixelColour colour) { - Colour colour32 = LookupColourInPalette(colour.p); + Colour colour32 = colour.HasRGB() ? colour.ToColour() : LookupColourInPalette(colour.p); do { Colour *dst = (Colour *)video; diff --git a/src/blitter/32bpp_optimized.cpp b/src/blitter/32bpp_optimized.cpp index 7667597636..972cea6780 100644 --- a/src/blitter/32bpp_optimized.cpp +++ b/src/blitter/32bpp_optimized.cpp @@ -48,7 +48,8 @@ inline void Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomL Colour *dst = (Colour *)bp->dst + bp->top * bp->pitch + bp->left; /* store so we don't have to access it via bp every time (compiler assumes pointer aliasing) */ - const uint8_t *remap = bp->remap; + 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 d3fe2f98ca..8c0310f5e1 100644 --- a/src/blitter/32bpp_simple.cpp +++ b/src/blitter/32bpp_simple.cpp @@ -23,6 +23,8 @@ 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); @@ -42,7 +44,19 @@ void Blitter_32bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoo if (src->m == 0) { if (src->a != 0) *dst = ComposeColourRGBA(src->r, src->g, src->b, src->a, *dst); } else { - if (bp->remap[src->m] != 0) *dst = ComposeColourPA(AdjustBrightness(this->LookupColourInPalette(bp->remap[src->m]), src->v), src->a, *dst); + if (remap[src->m] != 0) *dst = ComposeColourPA(AdjustBrightness(this->LookupColourInPalette(remap[src->m]), src->v), src->a, *dst); + } + 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; @@ -53,7 +67,7 @@ void Blitter_32bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoo *dst = ComposeColourRGBA(g, g, g, src->a, *dst); } } else { - if (bp->remap[src->m] != 0) *dst = ComposeColourPA(AdjustBrightness(this->LookupColourInPalette(bp->remap[src->m]), src->v), src->a, *dst); + if (remap[src->m] != 0) *dst = ComposeColourPA(AdjustBrightness(this->LookupColourInPalette(remap[src->m]), src->v), src->a, *dst); } break; @@ -73,7 +87,7 @@ void Blitter_32bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoo case BlitterMode::TransparentRemap: /* Apply custom transparency remap. */ if (src->a != 0) { - *dst = this->LookupColourInPalette(bp->remap[GetNearestColourIndex(*dst)]); + *dst = this->LookupColourInPalette(remap[GetNearestColourIndex(*dst)]); } break; diff --git a/src/blitter/32bpp_sse_func.hpp b/src/blitter/32bpp_sse_func.hpp index bed92686cc..8afdcfdbcd 100644 --- a/src/blitter/32bpp_sse_func.hpp +++ b/src/blitter/32bpp_sse_func.hpp @@ -221,7 +221,7 @@ inline void Blitter_32bppSSSE3::Draw(const Blitter::BlitterParams *bp, ZoomLevel inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom) #endif { - const uint8_t * const remap = bp->remap; + const uint8_t * const remap = bp->remap->GetPaletteRemap(); Colour *dst_line = (Colour *) bp->dst + bp->top * bp->pitch + bp->left; int effective_width = bp->width; diff --git a/src/blitter/40bpp_anim.cpp b/src/blitter/40bpp_anim.cpp index 6e6d1233fa..7a6ffc6c3a 100644 --- a/src/blitter/40bpp_anim.cpp +++ b/src/blitter/40bpp_anim.cpp @@ -32,10 +32,14 @@ void Blitter_40bppAnim::SetPixel(void *video, int x, int y, PixelColour colour) if (_screen_disable_anim) { Blitter_32bppOptimized::SetPixel(video, x, y, colour); } else { - size_t y_offset = static_cast(y) * _screen.pitch; - *((Colour *)video + x + y_offset) = _black_colour; + bool has_rgb = colour.HasRGB(); + const Colour colour32 = has_rgb ? colour.ToColour() : _black_colour; + const uint8_t colour8 = has_rgb ? 0 : colour.p; - VideoDriver::GetInstance()->GetAnimBuffer()[((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + x + y_offset] = colour.p; + size_t y_offset = static_cast(y) * _screen.pitch; + *((Colour *)video + x + y_offset) = colour32; + + VideoDriver::GetInstance()->GetAnimBuffer()[((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + x + y_offset] = colour8; } } @@ -47,6 +51,10 @@ void Blitter_40bppAnim::DrawRect(void *video, int width, int height, PixelColour return; } + bool has_rgb = colour.HasRGB(); + const Colour colour32 = has_rgb ? colour.ToColour() : _black_colour; + const uint8_t colour8 = has_rgb ? 0 : colour.p; + assert(VideoDriver::GetInstance()->GetAnimBuffer() != nullptr); uint8_t *anim_line = ((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + VideoDriver::GetInstance()->GetAnimBuffer(); @@ -55,8 +63,8 @@ void Blitter_40bppAnim::DrawRect(void *video, int width, int height, PixelColour uint8_t *anim = anim_line; for (int i = width; i > 0; i--) { - *dst = _black_colour; - *anim = colour.p; + *dst = colour32; + *anim = colour8; dst++; anim++; } @@ -73,12 +81,16 @@ void Blitter_40bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int return; } + bool has_rgb = colour.HasRGB(); + const Colour colour32 = has_rgb ? colour.ToColour() : _black_colour; + const uint8_t colour8 = has_rgb ? 0 : colour.p; + assert(VideoDriver::GetInstance()->GetAnimBuffer() != nullptr); uint8_t *anim = ((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + VideoDriver::GetInstance()->GetAnimBuffer(); this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [=](int x, int y) { - *((Colour *)video + x + y * _screen.pitch) = _black_colour; - *(anim + x + y * _screen.pitch) = colour.p; + *((Colour *)video + x + y * _screen.pitch) = colour32; + *(anim + x + y * _screen.pitch) = colour8; }); } @@ -114,7 +126,8 @@ inline void Blitter_40bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel uint8_t *anim = VideoDriver::GetInstance()->GetAnimBuffer() + ((uint32_t *)bp->dst - (uint32_t *)_screen.dst_ptr) + bp->top * bp->pitch + bp->left; /* store so we don't have to access it via bp every time (compiler assumes pointer aliasing) */ - const uint8_t *remap = bp->remap; + 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 */ @@ -225,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; @@ -341,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; @@ -374,7 +431,8 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, anim = anim - width + _screen.pitch; } while (--height); } else if (pal == PALETTE_NEWSPAPER) { - const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; + const RecolourSprite *rs = GetRecolourSprite(pal); + const uint8_t *remap = rs->GetPaletteRemap(); do { for (int i = 0; i != width; i++) { if (*anim == 0) { @@ -389,7 +447,8 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, anim = anim - width + _screen.pitch; } while (--height); } else { - const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; + const RecolourSprite *rs = GetRecolourSprite(pal); + const uint8_t *remap = rs->GetPaletteRemap(); do { for (int i = 0; i != width; i++) { if (*anim != 0) *anim = remap[*anim]; diff --git a/src/blitter/8bpp_base.cpp b/src/blitter/8bpp_base.cpp index ae7858a17f..7e48c86ebf 100644 --- a/src/blitter/8bpp_base.cpp +++ b/src/blitter/8bpp_base.cpp @@ -16,7 +16,7 @@ void Blitter_8bppBase::DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) { - const uint8_t *ctab = GetNonSprite(pal, SpriteType::Recolour) + 1; + const uint8_t *ctab = GetRecolourSprite(pal)->GetPaletteRemap(); do { for (int i = 0; i != width; i++) *((uint8_t *)dst + i) = ctab[((uint8_t *)dst)[i]]; diff --git a/src/blitter/8bpp_optimized.cpp b/src/blitter/8bpp_optimized.cpp index 5cc16d975a..86cc487f13 100644 --- a/src/blitter/8bpp_optimized.cpp +++ b/src/blitter/8bpp_optimized.cpp @@ -85,7 +85,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z switch (mode) { case BlitterMode::ColourRemap: case BlitterMode::CrashRemap: { - const uint8_t *remap = bp->remap; + const uint8_t *remap = bp->remap->GetPaletteRemap(); do { uint m = remap[*src]; if (m != 0) *dst = m; @@ -101,7 +101,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z case BlitterMode::Transparent: case BlitterMode::TransparentRemap: { - const uint8_t *remap = bp->remap; + const uint8_t *remap = bp->remap->GetPaletteRemap(); src += pixels; do { *dst = remap[*dst]; diff --git a/src/blitter/8bpp_simple.cpp b/src/blitter/8bpp_simple.cpp index 54cb9ee404..b47699b139 100644 --- a/src/blitter/8bpp_simple.cpp +++ b/src/blitter/8bpp_simple.cpp @@ -20,6 +20,7 @@ void Blitter_8bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoom { const uint8_t *src, *src_line; uint8_t *dst, *dst_line; + const uint8_t *remap = bp->remap->GetPaletteRemap(); /* Find where to start reading in the source sprite */ src_line = (const uint8_t *)bp->sprite + (bp->skip_top * bp->sprite_width + bp->skip_left) * ScaleByZoom(1, zoom); @@ -38,12 +39,12 @@ void Blitter_8bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoom switch (mode) { case BlitterMode::ColourRemap: case BlitterMode::CrashRemap: - colour = bp->remap[*src]; + colour = remap[*src]; break; case BlitterMode::Transparent: case BlitterMode::TransparentRemap: - if (*src != 0) colour = bp->remap[*dst]; + if (*src != 0) colour = remap[*dst]; break; case BlitterMode::BlackRemap: diff --git a/src/blitter/base.hpp b/src/blitter/base.hpp index 361cee3642..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. @@ -31,7 +32,7 @@ public: /** Parameters related to blitting. */ struct BlitterParams { const void *sprite; ///< Pointer to the sprite how ever the encoder stored it - const uint8_t *remap; ///< XXX -- Temporary storage for remap array + const RecolourSprite *remap; ///< Pointer to the RecolourSprite to use with remapping. int skip_left; ///< How much pixels of the source to skip on the left (based on zoom of dst) int skip_top; ///< How much pixels of the source to skip on the top (based on zoom of dst) diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index e1c38ee285..21bba24a5e 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -35,6 +35,7 @@ #include "smallmap_gui.h" #include "game/game.hpp" #include "goal_base.h" +#include "spritecache.h" #include "story_base.h" #include "company_cmd.h" #include "timer/timer.h" @@ -55,6 +56,7 @@ void UpdateObjectColours(const Company *c); CompanyID _local_company; ///< Company controlled by the human player at this client. Can also be #COMPANY_SPECTATOR. CompanyID _current_company; ///< Company currently doing an action. TypedIndexContainer, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs. +TypedIndexContainer, CompanyID> _company_palettes; ///< NOSAVE: can be determined from company structs. std::string _company_manager_face; ///< for company manager face storage in openttd.cfg uint _cur_company_tick_index; ///< used to generate a name for one company that doesn't have a name yet per tick @@ -163,7 +165,7 @@ TextColour GetDrawStringCompanyColour(CompanyID company) */ PaletteID GetCompanyPalette(CompanyID company) { - return GetColourPalette(_company_colours[company]); + return _company_palettes[company]; } /** @@ -513,7 +515,7 @@ static Colours GenerateCompanyColour() /* Move the colours that look similar to each company's colour to the side */ for (const Company *c : Company::Iterate()) { - Colours pcolour = c->colour; + Colours pcolour = Colours(c->colour & 0xF); for (uint i = 0; i < COLOUR_END; i++) { if (colours[i] == pcolour) { @@ -566,6 +568,40 @@ restart:; } } +void ClearLivery(Livery &livery) +{ + DeallocateDynamicSprite(livery.cached_pal_1cc); + DeallocateDynamicSprite(livery.cached_pal_2cc); + DeallocateDynamicSprite(livery.cached_pal_2cr); + + livery.cached_pal_1cc = PALETTE_RECOLOUR_START + GB(livery.colour1, 0, 4); + livery.cached_pal_2cc = SPR_2CCMAP_BASE + GB(livery.colour1, 0, 4) + GB(livery.colour2, 0, 4) * 16; + livery.cached_pal_2cr = SPR_2CCMAP_BASE + GB(livery.colour2, 0, 4) + GB(livery.colour1, 0, 4) * 16; +} + +/** + * Update cached palettes for a livery. + * @param livery Livery to update. + * @param always_update Always update instead of clearing livery (for LS_DEFAULT which is always needed). + */ +void UpdateLivery(Livery &livery, bool always_update) +{ + if ((always_update || livery.in_use != 0) && (ColoursPacker(livery.colour1).IsCustom() || ColoursPacker(livery.colour2).IsCustom())) { + PaletteID pal_1cc = PALETTE_RECOLOUR_START + GB(livery.colour1, 0, 4); + livery.cached_pal_1cc = CreateCompanyColourRemap(livery.colour1, livery.colour1, false, pal_1cc, livery.cached_pal_1cc); + + if (_loaded_newgrf_features.has_2CC) { + PaletteID pal_2cc = SPR_2CCMAP_BASE + GB(livery.colour1, 0, 4) + GB(livery.colour2, 0, 4) * 16; + livery.cached_pal_2cc = CreateCompanyColourRemap(livery.colour1, livery.colour2, true, pal_2cc, livery.cached_pal_2cc); + + PaletteID pal_2cr = SPR_2CCMAP_BASE + GB(livery.colour2, 0, 4) + GB(livery.colour1, 0, 4) * 16; + livery.cached_pal_2cr = CreateCompanyColourRemap(livery.colour2, livery.colour1, true, pal_2cr, livery.cached_pal_2cr); + } + } else { + ClearLivery(livery); + } +} + /** * Reset the livery schemes to the company's primary colour. * This is used on loading games without livery information and on new company start up. @@ -577,6 +613,7 @@ void ResetCompanyLivery(Company *c) c->livery[scheme].in_use = 0; c->livery[scheme].colour1 = c->colour; c->livery[scheme].colour2 = c->colour; + UpdateLivery(c->livery[scheme], scheme == LS_DEFAULT); } for (Group *g : Group::Iterate()) { @@ -584,8 +621,12 @@ void ResetCompanyLivery(Company *c) g->livery.in_use = 0; g->livery.colour1 = c->colour; g->livery.colour2 = c->colour; + UpdateLivery(g->livery, false); } } + + _company_colours[c->index] = c->livery[LS_DEFAULT].colour1; + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; } /** @@ -613,7 +654,6 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid( c->colour = colour; ResetCompanyLivery(c); - _company_colours[c->index] = c->colour; /* Scale the initial loan based on the inflation rounded down to the loan interval. The maximum loan has already been inflation adjusted. */ c->money = c->current_loan = std::min((INITIAL_LOAN * _economy.inflation_prices >> 16) / LOAN_INTERVAL * LOAN_INTERVAL, _economy.max_loan); @@ -1076,11 +1116,16 @@ CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint32_t bits, uint s */ void UpdateCompanyLiveries(Company *c) { + UpdateLivery(c->livery[LS_DEFAULT], true); for (int i = 1; i < LS_END; i++) { if (!HasBit(c->livery[i].in_use, 0)) c->livery[i].colour1 = c->livery[LS_DEFAULT].colour1; if (!HasBit(c->livery[i].in_use, 1)) c->livery[i].colour2 = c->livery[LS_DEFAULT].colour2; + UpdateLivery(c->livery[i], false); } UpdateCompanyGroupLiveries(c); + + _company_colours[c->index] = c->livery[LS_DEFAULT].colour1; + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; } /** @@ -1093,7 +1138,7 @@ void UpdateCompanyLiveries(Company *c) */ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool primary, Colours colour) { - if (scheme >= LS_END || (colour >= COLOUR_END && colour != INVALID_COLOUR)) return CMD_ERROR; + if (scheme >= LS_END) return CMD_ERROR; /* Default scheme can't be reset to invalid. */ if (scheme == LS_DEFAULT && colour == INVALID_COLOUR) return CMD_ERROR; @@ -1117,9 +1162,11 @@ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool * original and cached company colours too. */ if (scheme == LS_DEFAULT) { UpdateCompanyLiveries(c); - _company_colours[_current_company] = colour; + /* Update cached colour/palette for company */ c->colour = colour; CompanyAdminUpdate(c); + } else { + UpdateLivery(c->livery[scheme], false); } } else { if (scheme != LS_DEFAULT) AssignBit(c->livery[scheme].in_use, 1, colour != INVALID_COLOUR); @@ -1128,6 +1175,8 @@ CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool if (scheme == LS_DEFAULT) { UpdateCompanyLiveries(c); + } else { + UpdateLivery(c->livery[scheme], false); } } diff --git a/src/company_cmd.h b/src/company_cmd.h index eb30f9b361..508ff0de3d 100644 --- a/src/company_cmd.h +++ b/src/company_cmd.h @@ -15,7 +15,7 @@ #include "livery.h" enum ClientID : uint32_t; -enum Colours : uint8_t; +enum Colours : uint32_t; CommandCost CmdCompanyCtrl(DoCommandFlags flags, CompanyCtrlAction cca, CompanyID company_id, CompanyRemoveReason reason, ClientID client_id); CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAction action, const std::string &public_key); diff --git a/src/company_func.h b/src/company_func.h index 3a68556f37..8fe31f24db 100644 --- a/src/company_func.h +++ b/src/company_func.h @@ -14,6 +14,7 @@ #include "company_type.h" #include "gfx_type.h" #include "vehicle_type.h" +#include "livery.h" bool CheckTakeoverVehicleLimit(CompanyID cbig, CompanyID small); void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner); @@ -24,6 +25,8 @@ void CompanyAdminUpdate(const Company *company); void CompanyAdminBankrupt(CompanyID company_id); void UpdateLandscapingLimits(); void UpdateCompanyLiveries(Company *c); +void ClearLivery(Livery &livery); +void UpdateLivery(Livery &livery, bool always_update); Money GetAvailableMoney(CompanyID company); Money GetAvailableMoneyForCommand(); @@ -37,7 +40,9 @@ extern CompanyID _local_company; extern CompanyID _current_company; extern TypedIndexContainer, CompanyID> _company_colours; +extern TypedIndexContainer, CompanyID> _company_palettes; extern std::string _company_manager_face; + PaletteID GetCompanyPalette(CompanyID company); /** diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 9debd59c5b..b38a4bc9c9 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -7,6 +7,7 @@ /** @file company_gui.cpp %Company related GUIs. */ +#include "palette_func.h" #include "stdafx.h" #include "currency.h" #include "error.h" @@ -47,6 +48,7 @@ #include "timer/timer.h" #include "timer/timer_window.h" #include "core/string_consumer.hpp" +#include "blitter/factory.hpp" #include "widgets/company_widget.h" @@ -584,6 +586,13 @@ static const LiveryClass _livery_class[LS_END] = { LC_ROAD, LC_ROAD, }; +StringID GetColourString(Colours colour) +{ + if (ColoursPacker(colour).IsCustom()) return STR_COLOUR_CUSTOM; + if (colour == INVALID_COLOUR) return STR_COLOUR_DEFAULT; + return STR_COLOUR_DARK_BLUE + colour; +} + /** * Colour selection list item, with icon and string components. * @tparam TSprite Recolourable sprite to draw as icon. @@ -591,12 +600,372 @@ static const LiveryClass _livery_class[LS_END] = { template class DropDownListColourItem : public DropDownIcon> { public: - DropDownListColourItem(int colour, bool masked) : - DropDownIcon>(TSprite, GetColourPalette(static_cast(colour % COLOUR_END)), GetString(colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) + DropDownListColourItem(PaletteID pal, StringID str, int value, bool masked) : + DropDownIcon>(TSprite, pal, GetString(str), value, masked) { } }; +class SelectCustomColourWindow : public Window { + /* Preserved values from parent window when this window was opened. */ + uint32_t sel; + LiveryClass lc; + bool ctrl_pressed; + bool primary; + bool group; + + Scrollbar *hue, *sat, *val, *con; + Colours current; + + HsvColour current_hsv; + uint8_t contrast; + + static inline const int BUTTON_SIZE = 10; + static inline const int CON_MAX = UINT8_MAX; + + void SetPresetColours() + { + for (uint i = 0; i < std::size(_settings_client.gui.preset_colours); ++i) { + this->GetWidget(WID_SCC_PRESETS + i)->colour = _settings_client.gui.preset_colours[i]; + } + } + + Colour AdjustBrightness(const Colour &rgb, ColourShade shade, int contrast) const + { + int level = (shade - SHADE_NORMAL) * contrast / (SHADE_END / 2); + return { + ClampTo(std::max(1, rgb.r + level)), + ClampTo(std::max(1, rgb.g + level)), + ClampTo(std::max(1, rgb.b + level)) + }; + } + + bool SetScrollbarPositions() + { + bool changed = false; + changed |= this->hue->SetPosition(HsvColour::HUE_MAX - this->current_hsv.h); + changed |= this->sat->SetPosition(this->current_hsv.s); + changed |= this->val->SetPosition(HsvColour::VAL_MAX - this->current_hsv.v); + changed |= this->con->SetPosition(CON_MAX - this->contrast); + return changed; + } + +public: + SelectCustomColourWindow(WindowDesc &desc, int window_number, CompanyID company, Colours colour, uint32_t sel, LiveryClass lc, bool ctrl, bool primary, bool group) : + Window(desc), sel(sel), lc(lc), ctrl_pressed(ctrl), primary(primary), group(group) + { + this->InitNested(window_number); + this->owner = (Owner)company; + + this->current = colour; + + ColoursPacker colourp(colour); + if ((colourp.GetHue() | colourp.GetSaturation() | colourp.GetValue()) == 0) { + auto [rgba, contrast] = GetCompanyColourRGB(colour); + this->current_hsv = ConvertRgbToHsv(rgba); + this->contrast = contrast; + } else { + this->current_hsv = colourp.Hsv(); + this->contrast = colourp.GetContrast(); + } + + this->hue = this->GetScrollbar(WID_SCC_SCROLLBAR_HUE); + this->hue->SetCapacity(HsvColour::HUE_MAX / BUTTON_SIZE); + this->hue->SetCount(HsvColour::HUE_MAX + HsvColour::HUE_MAX / BUTTON_SIZE); + this->sat = this->GetScrollbar(WID_SCC_SCROLLBAR_SAT); + this->sat->SetCapacity(HsvColour::SAT_MAX / BUTTON_SIZE); + this->sat->SetCount(HsvColour::SAT_MAX + HsvColour::SAT_MAX / BUTTON_SIZE); + this->val = this->GetScrollbar(WID_SCC_SCROLLBAR_VAL); + this->val->SetCapacity(HsvColour::VAL_MAX / BUTTON_SIZE); + this->val->SetCount(HsvColour::VAL_MAX + HsvColour::VAL_MAX / BUTTON_SIZE); + this->con = this->GetScrollbar(WID_SCC_SCROLLBAR_CON); + this->con->SetCapacity(CON_MAX / BUTTON_SIZE); + this->con->SetCount(CON_MAX + CON_MAX / BUTTON_SIZE); + + this->SetScrollbarPositions(); + this->SetPresetColours(); + } + + std::string GetWidgetString(WidgetID widget, StringID stringid) const override + { + switch (widget) { + case WID_SCC_CAPTION: + return GetString(stringid, this->owner); + + default: + return this->Window::GetWidgetString(widget, stringid); + } + } + + void DrawWidget(const Rect &r, WidgetID widget) const override + { + HsvColour hsv = this->current_hsv; + switch (widget) { + case WID_SCC_HUE: { + int range = r.Height() - 1; + int mid = CentreBounds(r.left, r.right, 0); + hsv.s = HsvColour::SAT_MAX; + for (int i = 0; i <= range; i++) { + hsv.h = HsvColour::HUE_MAX * i / range; + hsv.v = HsvColour::VAL_MAX; + GfxFillRect(r.left, r.bottom - i, mid - 1, r.bottom - i, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + hsv.v = HsvColour::VAL_MAX * 3 / 4; + GfxFillRect(mid, r.bottom - i, r.right, r.bottom - i, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + } + + /* Mark current value. */ + GfxDrawLine(r.left, r.bottom - this->current_hsv.h * range / HsvColour::HUE_MAX, r.right, r.bottom - this->current_hsv.h * range / HsvColour::HUE_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_SAT: { + int width = r.Width() - 1; + int height = r.Height() - 1; + + for (int x = 0; x <= width; x++) { + hsv.s = HsvColour::SAT_MAX * x / width; + for (int y = 0; y <= height; y++) { + hsv.v = HsvColour::VAL_MAX * y / height; + GfxFillRect(r.left + x, r.bottom - y, r.left + x, r.bottom - y, ConvertHsvToRgb(hsv), FILLRECT_OPAQUE); + } + } + + /* Mark current value. */ + GfxDrawLine(r.left + this->current_hsv.s * width / HsvColour::SAT_MAX, r.top, r.left + this->current_hsv.s * width / HsvColour::SAT_MAX, r.bottom, PC_WHITE, 1, 1); + GfxDrawLine(r.left, r.bottom - this->current_hsv.v * height / HsvColour::VAL_MAX, r.right, r.bottom - this->current_hsv.v * height / HsvColour::VAL_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_CON: { + int range = r.Height() - 1; + int mid = CentreBounds(r.left, r.right, 0); + Colour rgb = ConvertHsvToRgb(HsvColour{0, 0, HsvColour::VAL_MAX / 2}); + for (int i = 0; i <= range; i++) { + int contrast = i * CON_MAX / width; + GfxFillRect(r.left, r.bottom - i, mid - 1, r.bottom - i, AdjustBrightness(rgb, SHADE_LIGHTEST, contrast), FILLRECT_OPAQUE); + GfxFillRect(mid, r.bottom - i, r.right, r.bottom - i, AdjustBrightness(rgb, SHADE_DARKEST, contrast), FILLRECT_OPAQUE); + } + + /* Mark current value. */ + GfxDrawLine(r.left, r.bottom - this->contrast * range / CON_MAX, r.right, r.bottom - this->contrast * range / CON_MAX, PC_WHITE, 1, 1); + break; + } + + case WID_SCC_OUTPUT: { + int range = r.Height(); + /* Pack and unpack to colours, to produce the result. */ + Colours colour = this->PackColour(); + ColoursPacker colourp(colour); + HsvColour hsv = colourp.Hsv(); + uint8_t con = colourp.GetContrast(); + for (ColourShade shade = SHADE_BEGIN; shade != SHADE_END; ++shade) { + Colour c = ConvertHsvToRgb(AdjustHsvColourBrightness(hsv, shade, con)); + GfxFillRect(r.left, r.bottom - (shade + 1) * range / SHADE_END + 1, r.right, r.bottom - shade * range / SHADE_END, c, FILLRECT_OPAQUE); + } + break; + } + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + bool changed = false; + + if (widget >= WID_SCC_DEFAULT && widget <= WID_SCC_DEFAULT_LAST && !_ctrl_pressed) { + /* Select colour from default preset. */ + this->current = this->GetWidget(widget)->colour; + + auto [rgba, contrast] = GetCompanyColourRGB(this->current); + this->current_hsv = ConvertRgbToHsv(rgba); + this->contrast = contrast; + + changed = this->SetScrollbarPositions(); + } + + if (widget >= WID_SCC_PRESETS && widget <= WID_SCC_PRESETS_LAST) { + if (_ctrl_pressed) { + /* Save colour to preset. */ + _settings_client.gui.preset_colours[widget - WID_SCC_PRESETS] = this->PackColour(); + this->SetPresetColours(); + } else { + /* Select colour from preset. */ + ColoursPacker cp(_settings_client.gui.preset_colours[widget - WID_SCC_PRESETS]); + this->current_hsv = cp.Hsv(); + this->contrast = cp.GetContrast(); + + changed = this->SetScrollbarPositions(); + } + } + + if (widget == WID_SCC_HUE) { + /* Click and drag on hue widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->current_hsv.h = std::clamp(HsvColour::HUE_MAX - (pt.y - wi->pos_y) * HsvColour::HUE_MAX / (int)wi->current_y, 0, HsvColour::HUE_MAX); + this->contrast = CON_MAX - this->con->GetPosition(); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (widget == WID_SCC_SAT) { + /* Click and drag on saturation/value widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->current_hsv.s = std::clamp((pt.x - wi->pos_x) * HsvColour::SAT_MAX / (int)wi->current_x, 0, HsvColour::SAT_MAX); + this->current_hsv.v = std::clamp(HsvColour::VAL_MAX - (pt.y - wi->pos_y) * HsvColour::VAL_MAX / (int)wi->current_y, 0, HsvColour::VAL_MAX); + this->contrast = CON_MAX - this->con->GetPosition(); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (widget == WID_SCC_CON) { + /* Click and drag on contrast widget */ + const NWidgetCore *wi = this->GetWidget(widget); + this->contrast = std::clamp(CON_MAX - (pt.y - wi->pos_y) * CON_MAX / (int)wi->current_y, 0, CON_MAX); + + changed = this->SetScrollbarPositions(); + if (click_count > 0) this->mouse_capture_widget = widget; + } + + if (!changed) return; + + this->SetDirty(); + this->SetTimeout(); + } + + void OnScrollbarScroll(WidgetID) override + { + /* Update colour from new scrollbar positions. */ + this->current_hsv.h = HsvColour::HUE_MAX - this->hue->GetPosition(); + this->current_hsv.s = this->sat->GetPosition(); + this->current_hsv.v = HsvColour::VAL_MAX - this->val->GetPosition(); + this->contrast = CON_MAX - this->con->GetPosition(); + + this->SetTimeout(); + } + + Colours PackColour() const + { + Colours colour; + ColoursPacker cp(colour); + cp.SetIndex(this->current); + cp.SetCustom(true); + cp.SetHue(this->current_hsv.h); + cp.SetSaturation(this->current_hsv.s); + cp.SetValue(this->current_hsv.v); + cp.SetContrast(this->contrast); + return colour; + } + + void OnTimeout() override + { + Colours colour = this->PackColour(); + if (colour != this->current) { + this->current = colour; + + if (this->group) { + Command::Post((GroupID)this->sel, primary, this->current); + } else { + for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { + /* Changed colour for the selected scheme, or all visible schemes if CTRL is pressed. */ + if (HasBit(this->sel, scheme) || (this->ctrl_pressed && _livery_class[scheme] == this->lc && HasBit(_loaded_newgrf_features.used_liveries, scheme))) { + Command::Post(scheme, primary, this->current); + } + } + } + } + } +}; + +static constexpr NWidgetPart _nested_select_custom_colour_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCC_CAPTION), SetStringTip(STR_LIVERY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), + NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), SetPadding(WidgetDimensions::unscaled.frametext), + NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_BLUE, WID_SCC_DEFAULT + 0), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_PALE_GREEN, WID_SCC_DEFAULT + 1), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PINK, WID_SCC_DEFAULT + 2), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_SCC_DEFAULT + 3), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_SCC_DEFAULT + 4), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_SCC_DEFAULT + 5), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_SCC_DEFAULT + 6), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_SCC_DEFAULT + 7), SetFill(1, 1), + EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_BLUE, WID_SCC_DEFAULT + 8), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_SCC_DEFAULT + 9), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_SCC_DEFAULT + 10), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PURPLE, WID_SCC_DEFAULT + 11), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCC_DEFAULT + 12), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SCC_DEFAULT + 13), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCC_DEFAULT + 14), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_SCC_DEFAULT + 15), SetFill(1, 1), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_BLUE, WID_SCC_PRESETS + 0), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_PALE_GREEN, WID_SCC_PRESETS + 1), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PINK, WID_SCC_PRESETS + 2), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_SCC_PRESETS + 3), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_RED, WID_SCC_PRESETS + 4), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_SCC_PRESETS + 5), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREEN, WID_SCC_PRESETS + 6), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_DARK_GREEN, WID_SCC_PRESETS + 7), SetFill(1, 1), + EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag::EqualSize), + NWidget(WWT_PUSHTXTBTN, COLOUR_BLUE, WID_SCC_PRESETS + 8), SetFill(1, 1), SetMinimalSize(16, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_SCC_PRESETS + 9), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_SCC_PRESETS + 10), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_PURPLE, WID_SCC_PRESETS + 11), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCC_PRESETS + 12), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SCC_PRESETS + 13), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCC_PRESETS + 14), SetFill(1, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_SCC_PRESETS + 15), SetFill(1, 1), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_HUE), SetMinimalSize(16, 0), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_HUE), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_SAT), SetMinimalSize(160, 160), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_VAL), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_SAT), + NWidget(NWID_SPACER), SetMinimalSize(11, 11), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_CON), SetMinimalSize(16, 0), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCC_SCROLLBAR_CON), + EndContainer(), + NWidget(WWT_INSET, COLOUR_GREY), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCC_OUTPUT), SetFill(1, 1), SetMinimalSize(16, 0), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_custom_colour_desc( + WDP_AUTO, {}, 0, 0, + WC_CUSTOM_COLOUR, WC_NONE, + {}, + _nested_select_custom_colour_widgets +); + /** Company livery colour scheme window. */ struct SelectCompanyLiveryWindow : public Window { private: @@ -608,57 +977,86 @@ private: GUIGroupList groups{}; Scrollbar *vscroll = nullptr; - void ShowColourDropDownMenu(uint32_t widget) + /** + * Get the first selected livery. + */ + const Livery *GetSelectedLivery() const { - uint32_t used_colours = 0; - const Livery *livery, *default_livery = nullptr; - bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; - uint8_t default_col = 0; - - /* Disallow other company colours for the primary colour */ - if (this->livery_class < LC_GROUP_RAIL && HasBit(this->sel, LS_DEFAULT) && primary) { - for (const Company *c : Company::Iterate()) { - if (c->index != _local_company) SetBit(used_colours, c->colour); - } - } - - const Company *c = Company::Get(this->window_number); - if (this->livery_class < LC_GROUP_RAIL) { /* Get the first selected livery to use as the default dropdown item */ LiveryScheme scheme; for (scheme = LS_BEGIN; scheme < LS_END; scheme++) { if (HasBit(this->sel, scheme)) break; } + if (scheme == LS_END) scheme = LS_DEFAULT; - livery = &c->livery[scheme]; - if (scheme != LS_DEFAULT) default_livery = &c->livery[LS_DEFAULT]; + + const Company *c = Company::Get((CompanyID)this->window_number); + return &c->livery[scheme]; + } else { + const Group *g = Group::Get(this->sel); + return &g->livery; + } + } + + /** + * Get the default selected livery. + */ + const Livery *GetDefaultLivery() const + { + const Company *c = Company::Get((CompanyID)this->window_number); + + if (this->livery_class < LC_GROUP_RAIL) { + if (!HasBit(this->sel, LS_DEFAULT)) return &c->livery[LS_DEFAULT]; } else { const Group *g = Group::Get(this->sel); - livery = &g->livery; if (g->parent == GroupID::Invalid()) { - default_livery = &c->livery[LS_DEFAULT]; + return &c->livery[LS_DEFAULT]; } else { const Group *pg = Group::Get(g->parent); - default_livery = &pg->livery; + return &pg->livery; } } + return nullptr; + } + + + void ShowColourDropDownMenu(uint32_t widget) + { + uint32_t used_colours = 0; + bool primary = widget == WID_SCL_PRI_COL_DROPDOWN; + + /* Disallow other company colours for the primary colour */ + if (this->livery_class < LC_GROUP_RAIL && HasBit(this->sel, LS_DEFAULT) && primary) { + for (const Company *c : Company::Iterate()) { + if (c->index != _local_company) SetBit(used_colours, c->colour & 0xF); + } + } + + /* Get the first selected livery to use as the default dropdown item */ + const Livery *livery = GetSelectedLivery(); + const Livery *default_livery = GetDefaultLivery(); DropDownList list; if (default_livery != nullptr) { /* Add COLOUR_END to put the colour out of range, but also allow us to show what the default is */ - default_col = (primary ? default_livery->colour1 : default_livery->colour2) + COLOUR_END; - list.push_back(std::make_unique>(default_col, false)); + list.push_back(std::make_unique>(primary ? default_livery->cached_pal_1cc : default_livery->cached_pal_2cr, STR_COLOUR_DEFAULT, INVALID_COLOUR, false)); } for (Colours colour = COLOUR_BEGIN; colour != COLOUR_END; colour++) { - list.push_back(std::make_unique>(colour, HasBit(used_colours, colour))); + list.push_back(std::make_unique>(GetColourPalette(colour), GetColourString(colour), colour, HasBit(used_colours, colour))); } + list.push_back(std::make_unique>(primary ? livery->cached_pal_1cc : livery->cached_pal_2cr, STR_COLOUR_CUSTOM, 0xFF, BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32)); - uint8_t sel; + int sel; if (default_livery == nullptr || HasBit(livery->in_use, primary ? 0 : 1)) { - sel = primary ? livery->colour1 : livery->colour2; + Colours col = primary ? livery->colour1 : livery->colour2; + if (ColoursPacker(col).IsCustom()) { + sel = 0xFF; + } else { + sel = ColoursPacker(col).GetIndex(); + } } else { - sel = default_col; + sel = INVALID_COLOUR; } ShowDropDownList(this, std::move(list), sel, widget); } @@ -820,7 +1218,7 @@ public: if (scheme == LS_END) scheme = LS_DEFAULT; const Livery *livery = &c->livery[scheme]; if (scheme == LS_DEFAULT || HasBit(livery->in_use, primary ? 0 : 1)) { - colour = STR_COLOUR_DARK_BLUE + (primary ? livery->colour1 : livery->colour2); + colour = GetColourString(primary ? livery->colour1 : livery->colour2); } } } else { @@ -828,7 +1226,7 @@ public: const Group *g = Group::Get(this->sel); const Livery *livery = &g->livery; if (HasBit(livery->in_use, primary ? 0 : 1)) { - colour = STR_COLOUR_DARK_BLUE + (primary ? livery->colour1 : livery->colour2); + colour = GetColourString(primary ? livery->colour1 : livery->colour2); } } } @@ -868,26 +1266,26 @@ public: int y = ir.top; + const Company *c = Company::Get((CompanyID)this->window_number); + /* Helper function to draw livery info. */ auto draw_livery = [&](std::string_view str, const Livery &livery, bool is_selected, bool is_default_scheme, int indent) { /* Livery Label. */ DrawString(sch.left + (rtl ? 0 : indent), sch.right - (rtl ? indent : 0), y + text_offs, str, is_selected ? TC_WHITE : TC_BLACK); /* Text below the first dropdown. */ - DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour1), pri_squ.left, y + square_offs); - DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? STR_COLOUR_DARK_BLUE + livery.colour1 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); + DrawSprite(SPR_SQUARE, HasBit(livery.in_use, 0) ? livery.cached_pal_1cc : c->livery[LS_DEFAULT].cached_pal_1cc, pri_squ.left, y + square_offs); + DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? GetColourString(livery.colour1) : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); /* Text below the second dropdown. */ if (sec.right > sec.left) { // Second dropdown has non-zero size. - DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour2), sec_squ.left, y + square_offs); - DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? STR_COLOUR_DARK_BLUE + livery.colour2 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); + DrawSprite(SPR_SQUARE, HasBit(livery.in_use, 1) ? livery.cached_pal_2cr : c->livery[LS_DEFAULT].cached_pal_2cr, sec_squ.left, y + square_offs); + DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? GetColourString(livery.colour2) : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); } y += this->line_height; }; - const Company *c = Company::Get(this->window_number); - if (livery_class < LC_GROUP_RAIL) { int pos = this->vscroll->GetPosition(); for (LiveryScheme scheme = LS_DEFAULT; scheme < LS_END; scheme++) { @@ -998,6 +1396,16 @@ public: bool local = this->window_number == _local_company; if (!local) return; + if (index == 0xFF) { + /* Special case for 'Custom...' option */ + int number = ((widget == WID_SCL_PRI_COL_DROPDOWN) ? 1 : 0) | (this->window_number << 1); + if (BringWindowToFrontById(WC_CUSTOM_COLOUR, number)) return; + + const Livery *livery = GetSelectedLivery(); + new SelectCustomColourWindow(_select_custom_colour_desc, number, (CompanyID)this->window_number, (widget == WID_SCL_PRI_COL_DROPDOWN) ? livery->colour1 : livery->colour2, this->sel, this->livery_class, _ctrl_pressed, widget == WID_SCL_PRI_COL_DROPDOWN, this->livery_class >= LC_GROUP_RAIL); + return; + } + Colours colour = static_cast(index); if (colour >= COLOUR_END) colour = INVALID_COLOUR; @@ -1114,10 +1522,10 @@ void ShowCompanyLiveryWindow(CompanyID company, GroupID group) /** * Draws the face of a company manager's face. * @param cmf the company manager's face - * @param colour the (background) colour of the gradient + * @param palette the (background) colour of the gradient * @param r position to draw the face */ -void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r) +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, PaletteID palette, const Rect &r) { /* Determine offset from centre of drawing rect. */ Dimension d = GetSpriteSize(SPR_GRADIENT); @@ -1148,7 +1556,7 @@ void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const } /* Draw the gradient (background) */ - DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y); + DrawSprite(SPR_GRADIENT, palette, x, y); /* Thirdly, draw sprites. */ for (auto var : SetBitIterator(active_vars)) { @@ -2105,7 +2513,7 @@ struct CompanyWindow : Window const Company *c = Company::Get(this->window_number); switch (widget) { case WID_C_FACE: - DrawCompanyManagerFace(c->face, c->colour, r); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; case WID_C_FACE_TITLE: @@ -2359,8 +2767,8 @@ struct BuyCompanyWindow : Window { { switch (widget) { case WID_BC_FACE: { - const Company *c = Company::Get(this->window_number); - DrawCompanyManagerFace(c->face, c->colour, r); + const Company *c = Company::Get((CompanyID)this->window_number); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; } diff --git a/src/company_manager_face.h b/src/company_manager_face.h index 663da09a11..21d2848e70 100644 --- a/src/company_manager_face.h +++ b/src/company_manager_face.h @@ -180,6 +180,6 @@ uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf); std::optional ParseCompanyManagerFaceCode(std::string_view str); -void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r); +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, PaletteID palette, const Rect &r); #endif /* COMPANY_MANAGER_FACE_H */ diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 52dfc9cffa..f17cf86116 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -1949,7 +1949,7 @@ static bool ConCompanies(std::span argv) /* Grab the company name */ std::string company_name = GetString(STR_COMPANY_NAME, c->index); - std::string colour = GetString(STR_COLOUR_DARK_BLUE + _company_colours[c->index]); + std::string colour = GetString(STR_COLOUR_DARK_BLUE + _company_colours[c->index] & 0xF); IConsolePrint(CC_INFO, "#:{}({}) Company Name: '{}' Year Founded: {} Money: {} Loan: {} Value: {} (T:{}, R:{}, P:{}, S:{}) {}", c->index + 1, colour, company_name, c->inaugurated_year, (int64_t)c->money, (int64_t)c->current_loan, (int64_t)CalculateCompanyValue(c), diff --git a/src/error_gui.cpp b/src/error_gui.cpp index 182ac7229f..a6204579cc 100644 --- a/src/error_gui.cpp +++ b/src/error_gui.cpp @@ -18,6 +18,7 @@ #include "company_base.h" #include "company_func.h" #include "company_manager_face.h" +#include "sprite.h" #include "strings_func.h" #include "zoom_func.h" #include "window_func.h" @@ -188,7 +189,7 @@ public: switch (widget) { case WID_EM_FACE: { const Company *c = Company::Get(this->company); - DrawCompanyManagerFace(c->face, c->colour, r); + DrawCompanyManagerFace(c->face, GetCompanyPalette(c->index), r); break; } diff --git a/src/gfx.cpp b/src/gfx.cpp index cf1340686d..50f195f60f 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -72,8 +72,8 @@ int _gui_scale_cfg; ///< GUI scale in config. * @ingroup dirty */ static Rect _invalid_rect; -static const uint8_t *_colour_remap_ptr; -static uint8_t _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #SpriteType::Font sprites only use colours 0 to 2. +static const RecolourSprite *_colour_remap_ptr; +static RecolourSpriteRGBA _string_colourremap; ///< RecolourSprite for string drawing. static const uint DIRTY_BLOCK_HEIGHT = 8; static const uint DIRTY_BLOCK_WIDTH = 64; @@ -470,19 +470,32 @@ void DrawRectOutline(const Rect &r, PixelColour colour, int width, int dash) * Set the colour remap to be for the given colour. * @param colour the new colour of the remap. */ -static void SetColourRemap(TextColour colour) +static BlitterMode SetColourRemap(TextColour colour) { - if (colour == TC_INVALID) return; + if (colour == TC_INVALID) return BlitterMode::ColourRemap; /* Black strings have no shading ever; the shading is black, so it * would be invisible at best, but it actually makes it illegible. */ bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK; + + if ((colour & TC_IS_RGB_COLOUR) && BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) { + /* Unpack RGB TextColour */ + TextColourPacker tcp(colour); + _string_colourremap.rgba[1] = tcp.ToColour(); + _string_colourremap.rgba[2] = _cur_palette.palette[no_shade ? 0 : PC_BLACK.p]; + _colour_remap_ptr = &_string_colourremap; + + return BlitterMode::RGBAColourRemap; + } + bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0; colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR | TC_FORCED); - _string_colourremap[1] = raw_colour ? (uint8_t)colour : _string_colourmap[colour].p; - _string_colourremap[2] = no_shade ? 0 : 1; - _colour_remap_ptr = _string_colourremap; + _string_colourremap.palette[1] = raw_colour ? (uint8_t)colour : _string_colourmap[colour].p; + _string_colourremap.palette[2] = no_shade ? 0 : PC_BLACK.p; + _colour_remap_ptr = &_string_colourremap; + + return BlitterMode::ColourRemap; } /** @@ -602,7 +615,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, /* Update the last colour for the truncation ellipsis. */ last_colour = colour; if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue; - SetColourRemap(do_shadow ? TC_BLACK : colour); + BlitterMode bm = SetColourRemap(do_shadow ? TC_BLACK : colour); // the last run also sets the colour for the truncation dots for (int i = 0; i < run.GetGlyphCount(); i++) { GlyphID glyph = glyphs[i]; @@ -623,7 +636,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue; - GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap); + GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), bm); } } return last_colour; @@ -640,7 +653,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, } if (underline) { - GfxFillRect(left, y + h, right, y + h + WidgetDimensions::scaled.bevel.top - 1, PixelColour{_string_colourremap[1]}); + GfxFillRect(left, y + h, right, y + h + WidgetDimensions::scaled.bevel.top - 1, PixelColour{_string_colourremap.GetPaletteRemap()[1]}); } return (align & SA_HOR_MASK) == SA_RIGHT ? left : right; @@ -948,11 +961,11 @@ Dimension GetStringListBoundingBox(std::span list, FontSize font */ void DrawCharCentered(char32_t c, const Rect &r, TextColour colour) { - SetColourRemap(colour); + BlitterMode bm = SetColourRemap(colour); GfxMainBlitter(GetGlyph(FS_NORMAL, c), CentreBounds(r.left, r.right, GetCharacterWidth(FS_NORMAL, c)), CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), - BlitterMode::ColourRemap); + bm); } /** @@ -983,13 +996,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; } } @@ -1006,15 +1019,17 @@ void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSpri SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH); if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) { pal = GB(pal, 0, PALETTE_WIDTH); - _colour_remap_ptr = GetNonSprite(pal, SpriteType::Recolour) + 1; + _colour_remap_ptr = GetRecolourSprite(pal); GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, pal == PALETTE_TO_TRANSPARENT ? BlitterMode::Transparent : BlitterMode::TransparentRemap, sub, real_sprite); } else if (pal != PAL_NONE) { + BlitterMode bm; if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) { - SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH)); + bm = SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH)); } else { - _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour) + 1; + _colour_remap_ptr = GetRecolourSprite(GB(pal, 0, PALETTE_WIDTH)); + bm = GetBlitterMode(pal, *_colour_remap_ptr); } - GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal), sub, real_sprite); + GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, bm, sub, real_sprite); } else { GfxMainBlitterViewport(GetSprite(real_sprite, SpriteType::Normal), x, y, BlitterMode::Normal, sub, real_sprite); } @@ -1034,15 +1049,17 @@ void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH); if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) { pal = GB(pal, 0, PALETTE_WIDTH); - _colour_remap_ptr = GetNonSprite(pal, SpriteType::Recolour) + 1; + _colour_remap_ptr = GetRecolourSprite(pal); GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, pal == PALETTE_TO_TRANSPARENT ? BlitterMode::Transparent : BlitterMode::TransparentRemap, sub, real_sprite, zoom); } else if (pal != PAL_NONE) { + BlitterMode bm; if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) { - SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH)); + bm = SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH)); } else { - _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour) + 1; + _colour_remap_ptr = GetRecolourSprite(GB(pal, 0, PALETTE_WIDTH)); + bm = GetBlitterMode(pal, *_colour_remap_ptr); } - GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, GetBlitterMode(pal), sub, real_sprite, zoom); + GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, bm, sub, real_sprite, zoom); } else { GfxMainBlitter(GetSprite(real_sprite, SpriteType::Normal), x, y, BlitterMode::Normal, sub, real_sprite, zoom); } diff --git a/src/gfx_type.h b/src/gfx_type.h index 4434690bdd..bbd7e005c2 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -280,7 +280,7 @@ struct SubSprite { int left, top, right, bottom; }; -enum Colours : uint8_t { +enum Colours : uint32_t { COLOUR_BEGIN, COLOUR_DARK_BLUE = COLOUR_BEGIN, COLOUR_PALE_GREEN, @@ -299,13 +299,14 @@ enum Colours : uint8_t { COLOUR_GREY, COLOUR_WHITE, COLOUR_END, - INVALID_COLOUR = 0xFF, + INVALID_COLOUR = UINT32_MAX, }; DECLARE_INCREMENT_DECREMENT_OPERATORS(Colours) DECLARE_ENUM_AS_ADDABLE(Colours) +DECLARE_ENUM_AS_BIT_SET(Colours) /** Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palette.png */ -enum TextColour : uint16_t { +enum TextColour : uint32_t { TC_BEGIN = 0x00, TC_FROMSTRING = 0x00, TC_BLUE = 0x00, @@ -331,9 +332,10 @@ enum TextColour : uint16_t { TC_IS_PALETTE_COLOUR = 0x100, ///< Colour value is already a real palette colour index, not an index of a StringColour. TC_NO_SHADE = 0x200, ///< Do not add shading to this text colour. TC_FORCED = 0x400, ///< Ignore colour changes from strings. + TC_IS_RGB_COLOUR = 0x800, ///< Colour includes RGB component. TC_COLOUR_MASK = 0xFF, ///< Mask to test if TextColour (without flags) is within limits. - TC_FLAGS_MASK = 0x700, ///< Mask to test if TextColour (with flags) is within limits. + TC_FLAGS_MASK = 0xF00, ///< Mask to test if TextColour (with flags) is within limits. }; DECLARE_ENUM_AS_BIT_SET(TextColour) @@ -404,12 +406,30 @@ DECLARE_ENUM_AS_BIT_SET(StringAlignment) /** Colour for pixel/line drawing. */ struct PixelColour { - uint8_t p; ///< Palette index. + uint8_t p = 0; ///< Palette index. + uint8_t r = 0; ///< Red component. + uint8_t g = 0; ///< Green component. + uint8_t b = 0; ///< Blue component. - constexpr PixelColour() : p(0) {} + constexpr PixelColour() {} explicit constexpr PixelColour(uint8_t p) : p(p) {} + constexpr PixelColour(uint8_t p, Colour colour) : p(p), r(colour.r), g(colour.g), b(colour.b) {} + PixelColour(Colour colour); - constexpr inline TextColour ToTextColour() const { return static_cast(this->p) | TC_IS_PALETTE_COLOUR; } + constexpr inline bool HasRGB() const { return (this->r | this->g | this->b) != 0; } + constexpr inline Colour ToColour() const { return {this->r, this->g, this->b}; } + TextColour ToTextColour() const; +}; + +struct HsvColour { + static constexpr int HUE_MAX = 360 * 128; ///< Maximum value for hue. + static constexpr int SAT_MAX = UINT8_MAX; ///< Maximum value for saturation. + static constexpr int VAL_MAX = UINT8_MAX; ///< Maximum value for value. + static constexpr int HUE_RGN = HUE_MAX / 6; + + uint16_t h; ///< Hue ranging from 0 to HUE_MAX. + uint8_t s; ///< Saturation ranging from 0 to SAT_MAX. + uint8_t v; ///< Value (brightness) ranging from 0 to VAL_MAX. }; #endif /* GFX_TYPE_H */ diff --git a/src/group.h b/src/group.h index 28de518012..c9f650bca8 100644 --- a/src/group.h +++ b/src/group.h @@ -87,6 +87,7 @@ struct Group : GroupPool::PoolItem<&_group_pool> { Group() {} Group(CompanyID owner, VehicleType vehicle_type) : owner(owner), vehicle_type(vehicle_type) {} + ~Group(); }; diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 84527d9b9f..bc15b66924 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -19,6 +19,7 @@ #include "core/pool_func.hpp" #include "order_backup.h" #include "group_cmd.h" +#include "spritecache.h" #include "table/strings.h" @@ -308,6 +309,7 @@ static void PropagateChildLivery(const Group *g, bool reset_cache) Group *cg = Group::Get(childgroup); if (!HasBit(cg->livery.in_use, 0)) cg->livery.colour1 = g->livery.colour1; if (!HasBit(cg->livery.in_use, 1)) cg->livery.colour2 = g->livery.colour2; + UpdateLivery(cg->livery, false); PropagateChildLivery(cg, reset_cache); } } @@ -323,11 +325,20 @@ void UpdateCompanyGroupLiveries(const Company *c) if (g->owner == c->index && g->parent == GroupID::Invalid()) { if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = c->livery[LS_DEFAULT].colour1; if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = c->livery[LS_DEFAULT].colour2; + UpdateLivery(g->livery, false); PropagateChildLivery(g, false); } } } +Group::~Group() +{ + if (CleaningPool()) return; + + DeallocateDynamicSprite(this->livery.cached_pal_1cc); + DeallocateDynamicSprite(this->livery.cached_pal_2cc); + DeallocateDynamicSprite(this->livery.cached_pal_2cr); +} /** * Create a new vehicle group. @@ -483,8 +494,9 @@ CommandCost CmdAlterGroup(DoCommandFlags flags, AlterGroupMode mode, GroupID gro if (!HasBit(g->livery.in_use, 0) || !HasBit(g->livery.in_use, 1)) { /* Update livery with new parent's colours if either colour is default. */ const Livery *livery = GetParentLivery(g); - if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = livery->colour1; - if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = livery->colour2; + if (!HasBit(g->livery.in_use, 0)) if (!HasBit(g->livery.in_use, 0)) g->livery.colour1 = livery->colour1; + if (!HasBit(g->livery.in_use, 1)) if (!HasBit(g->livery.in_use, 1)) g->livery.colour2 = livery->colour2; + UpdateLivery(g->livery, false); PropagateChildLivery(g, true); MarkWholeScreenDirty(); @@ -680,8 +692,6 @@ CommandCost CmdSetGroupLivery(DoCommandFlags flags, GroupID group_id, bool prima if (g == nullptr || g->owner != _current_company) return CMD_ERROR; - if (colour >= COLOUR_END && colour != INVALID_COLOUR) return CMD_ERROR; - if (flags.Test(DoCommandFlag::Execute)) { if (primary) { AssignBit(g->livery.in_use, 0, colour != INVALID_COLOUR); @@ -692,6 +702,7 @@ CommandCost CmdSetGroupLivery(DoCommandFlags flags, GroupID group_id, bool prima if (colour == INVALID_COLOUR) colour = GetParentLivery(g)->colour2; g->livery.colour2 = colour; } + UpdateLivery(g->livery, false); PropagateChildLivery(g, true); MarkWholeScreenDirty(); diff --git a/src/group_cmd.h b/src/group_cmd.h index abef37b542..e990e0ca25 100644 --- a/src/group_cmd.h +++ b/src/group_cmd.h @@ -16,7 +16,7 @@ #include "vehiclelist.h" #include "vehiclelist_cmd.h" -enum Colours : uint8_t; +enum Colours : uint32_t; enum class GroupFlag : uint8_t; /** Action for \c CmdAlterGroup. */ diff --git a/src/lang/english.txt b/src/lang/english.txt index 41531939f3..bc15217e7c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -170,6 +170,7 @@ STR_ITEMS :{COMMA}{NBSP}it STR_CRATES :{COMMA}{NBSP}crate{P "" s} STR_COLOUR_DEFAULT :Default +STR_COLOUR_CUSTOM :Custom... ###length 17 STR_COLOUR_DARK_BLUE :Dark Blue STR_COLOUR_PALE_GREEN :Pale Green @@ -2325,6 +2326,11 @@ STR_LIVERY_LARGE_PLANE :Large Aeroplane STR_LIVERY_PASSENGER_TRAM :Passenger Tram STR_LIVERY_FREIGHT_TRAM :Freight Tram +STR_LIVERY_HUE :{BLACK}Hue +STR_LIVERY_SATURATION :{BLACK}Saturation +STR_LIVERY_LIGHTNESS :{BLACK}Lightness +STR_LIVERY_CONTRAST :{BLACK}Contrast + # Face selection window STR_FACE_CAPTION :{WHITE}Face Selection STR_FACE_CANCEL_TOOLTIP :{BLACK}Cancel new face selection diff --git a/src/livery.h b/src/livery.h index 2de27555d9..501e4a540d 100644 --- a/src/livery.h +++ b/src/livery.h @@ -79,6 +79,10 @@ struct Livery { uint8_t in_use = 0; ///< Bit 0 set if this livery should override the default livery first colour, Bit 1 for the second colour. Colours colour1 = COLOUR_BEGIN; ///< First colour, for all vehicles. Colours colour2 = COLOUR_BEGIN; ///< Second colour, for vehicles with 2CC support. + + PaletteID cached_pal_1cc = 0; ///< NOSAVE: cached 1CC palette. + PaletteID cached_pal_2cc = 0; ///< NOSAVE: cached 2CC palette. + PaletteID cached_pal_2cr = 0; ///< NOSAVE: cached reversed 2CC palette. }; void ResetCompanyLivery(Company *c); diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 841cd5b5b5..82c08577d3 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -553,10 +553,19 @@ void ShowSelectGameWindow(); void SetupColoursAndInitialWindow() { for (Colours i = COLOUR_BEGIN; i != COLOUR_END; i++) { - const uint8_t *b = GetNonSprite(GetColourPalette(i), SpriteType::Recolour) + 1; - assert(b != nullptr); - for (ColourShade j = SHADE_BEGIN; j < SHADE_END; j++) { - SetColourGradient(i, j, PixelColour{b[0xC6 + j]}); + const RecolourSprite *rs = GetRecolourSprite(GetColourPalette(i)); + assert(rs != nullptr); + const uint8_t *remap = rs->GetPaletteRemap(); + 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/network/network_admin.cpp b/src/network/network_admin.cpp index 82b7bd5383..c6508d1716 100644 --- a/src/network/network_admin.cpp +++ b/src/network/network_admin.cpp @@ -325,7 +325,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company p->Send_uint8 (c->index); p->Send_string(GetString(STR_COMPANY_NAME, c->index)); p->Send_string(GetString(STR_PRESIDENT_NAME, c->index)); - p->Send_uint8 (c->colour); + p->Send_uint8 (c->colour & 0xF); p->Send_bool (true); p->Send_uint32(c->inaugurated_year.base()); p->Send_bool (c->is_ai); @@ -348,7 +348,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Compa p->Send_uint8 (c->index); p->Send_string(GetString(STR_COMPANY_NAME, c->index)); p->Send_string(GetString(STR_PRESIDENT_NAME, c->index)); - p->Send_uint8 (c->colour); + p->Send_uint8 (c->colour & 0xF); p->Send_bool (true); p->Send_uint8 (CeilDiv(c->months_of_bankruptcy, 3)); // send as quarters_of_bankruptcy diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 6cb2919198..e3538e927b 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1678,6 +1678,9 @@ static void AfterLoadGRFs() InitRailTypes(); InitRoadTypes(); + /* Force cached palettes to be refreshed */ + ResetVehicleColourMap(); + for (Engine *e : Engine::IterateType(VEH_ROAD)) { if (_gted[e->index].rv_max_speed != 0) { /* Set RV maximum speed from the mph/0.8 unit value */ @@ -1855,6 +1858,9 @@ void LoadNewGRF(SpriteID load_index, uint num_baseset) /* Pseudo sprite processing is finished; free temporary stuff */ _cur_gps.ClearDataForNextFile(); + /* Make note of last sprite ID loaded for dynamic sprite management */ + ClearDynamicSprites(); + /* Call any functions that should be run after GRFs have been loaded. */ AfterLoadGRFs(); diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index bc2520718a..e5e96eeaa7 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -17,6 +17,7 @@ #include "water.h" #include "landscape.h" #include "company_base.h" +#include "company_func.h" #include "town.h" #include "newgrf_animation_base.h" @@ -241,7 +242,7 @@ static uint16_t GetAirportTileCallback(CallbackID callback, uint32_t param1, uin return object.ResolveCallback(regs100); } -static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan &dts, Colours colour) +static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan &dts, PaletteID cc_pal) { SpriteID image = dts.ground.sprite; SpriteID pal = dts.ground.pal; @@ -250,11 +251,11 @@ static void AirportDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan & if (image == SPR_FLAT_WATER_TILE && IsTileOnWater(ti->tile)) { DrawWaterClassGround(ti); } else { - DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, GetColourPalette(colour))); + DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, cc_pal)); } } - DrawNewGRFTileSeq(ti, &dts, TO_BUILDINGS, 0, GetColourPalette(colour)); + DrawNewGRFTileSeq(ti, &dts, TO_BUILDINGS, 0, cc_pal); } bool DrawNewAirportTile(TileInfo *ti, Station *st, const AirportTileSpec *airts) @@ -278,7 +279,7 @@ bool DrawNewAirportTile(TileInfo *ti, Station *st, const AirportTileSpec *airts) auto processor = group->ProcessRegisters(object, nullptr); auto dts = processor.GetLayout(); - AirportDrawTileLayout(ti, dts, Company::Get(st->owner)->colour); + AirportDrawTileLayout(ti, dts, GetCompanyPalette(st->owner)); return true; } diff --git a/src/news_gui.cpp b/src/news_gui.cpp index 351f559561..9639848d02 100644 --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -528,7 +528,7 @@ struct NewsWindow : Window { case WID_N_MGR_FACE: { const CompanyNewsInformation *cni = static_cast(this->ni->data.get()); - DrawCompanyManagerFace(cni->face, cni->colour, r); + DrawCompanyManagerFace(cni->face, cni->colour, r); // XXX TODO this should be a palette GfxFillRect(r, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); break; } diff --git a/src/palette.cpp b/src/palette.cpp index b22ac326ea..6fd482fe98 100644 --- a/src/palette.cpp +++ b/src/palette.cpp @@ -15,6 +15,7 @@ #include "landscape_type.h" #include "palette_func.h" #include "settings_type.h" +#include "sprite.h" #include "thread.h" #include "table/palettes.h" @@ -84,6 +85,7 @@ static uint CalculateColourDistance(const Colour &col1, int r2, int g2, int b2) /* 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_CC2_START = 80; ///< Palette index of start of second 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. @@ -368,6 +370,81 @@ TextColour GetContrastColour(PixelColour background, uint8_t threshold) return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK; } +HsvColour ConvertRgbToHsv(Colour rgb) +{ + HsvColour hsv; + + uint8_t rgbmin = std::min({rgb.r, rgb.g, rgb.b}); + uint8_t rgbmax = std::max({rgb.r, rgb.g, rgb.b}); + + hsv.v = rgbmax; + if (hsv.v == 0) { + hsv.h = 0; + hsv.s = 0; + return hsv; + } + + int d = rgbmax - rgbmin; + hsv.s = HsvColour::SAT_MAX * d / rgbmax; + if (hsv.s == 0) { + hsv.h = 0; + return hsv; + } + + int hue; + if (rgbmax == rgb.r) { + hue = HsvColour::HUE_RGN * 0 + HsvColour::HUE_RGN * ((int)rgb.g - (int)rgb.b) / d; + } else if (rgbmax == rgb.g) { + hue = HsvColour::HUE_RGN * 2 + HsvColour::HUE_RGN * ((int)rgb.b - (int)rgb.r) / d; + } else { + hue = HsvColour::HUE_RGN * 4 + HsvColour::HUE_RGN * ((int)rgb.r - (int)rgb.g) / d; + } + if (hue > HsvColour::HUE_MAX) hue -= HsvColour::HUE_MAX; + if (hue < 0) hue += HsvColour::HUE_MAX; + hsv.h = hue; + + return hsv; +} + +Colour ConvertHsvToRgb(HsvColour hsv) +{ + if (hsv.s == 0) return Colour(hsv.v, hsv.v, hsv.v); + if (hsv.h >= HsvColour::HUE_MAX) hsv.h = 0; + + int region = hsv.h / HsvColour::HUE_RGN; + int remainder = (hsv.h - (region * HsvColour::HUE_RGN)) * 6; + int p = (hsv.v * (HsvColour::SAT_MAX - hsv.s)) / HsvColour::SAT_MAX; + int q = (hsv.v * (HsvColour::SAT_MAX - ((hsv.s * remainder) / HsvColour::HUE_MAX))) / HsvColour::SAT_MAX; + int t = (hsv.v * (HsvColour::SAT_MAX - ((hsv.s * (HsvColour::HUE_MAX - remainder)) / HsvColour::HUE_MAX))) / HsvColour::SAT_MAX; + + switch (region) { + case 0: return Colour(hsv.v, t, p); + case 1: return Colour(q, hsv.v, p); + case 2: return Colour(p, hsv.v, t); + case 3: return Colour(p, q, hsv.v); + case 4: return Colour(t, p, hsv.v); + default: return Colour(hsv.v, p, q); + } +} + +/** + * Adjust brightness of an HSV colour. + * @param hsv colour to adjust. + * @param shade shade to apply. + * @param contrast contrast of shade. + * @returns Adjusted HSV colour. + **/ +HsvColour AdjustHsvColourBrightness(HsvColour hsv, ColourShade shade, int contrast) +{ + HsvColour r = hsv; + int amt = (shade - SHADE_NORMAL) * (16 + contrast) / 8; + int overflow = (hsv.v + amt) - HsvColour::VAL_MAX; + r.v = ClampTo(hsv.v + amt); + r.s = ClampTo(hsv.s - std::max(0, overflow)); + return r; +} + + /** * Lookup table of colour shades for all 16 colour gradients. * 8 colours per gradient from darkest (0) to lightest (7) @@ -387,6 +464,10 @@ struct ColourGradients */ PixelColour GetColourGradient(Colours colour, ColourShade shade) { + ColoursPacker cp(colour); + if (cp.IsCustom()) { + return ConvertHsvToRgb(AdjustHsvColourBrightness(cp.Hsv(), shade, cp.GetContrast())); + } return ColourGradients::gradient[colour % COLOUR_END][shade % SHADE_END]; } @@ -402,3 +483,61 @@ void SetColourGradient(Colours colour, ColourShade shade, PixelColour palette_in assert(shade < SHADE_END); ColourGradients::gradient[colour % COLOUR_END][shade % SHADE_END] = palette_index; } + +PixelColour::PixelColour(Colour colour) : r(colour.r), g(colour.g), b(colour.b) +{ + this->p = GetNearestColourIndex(colour); +} + +TextColour PixelColour::ToTextColour() const +{ + TextColour tc = static_cast(this->p) | TC_IS_PALETTE_COLOUR; + if (this->HasRGB()) { + tc |= TC_IS_RGB_COLOUR; + TextColourPacker tcp(tc); + tcp.SetR(this->r); + tcp.SetG(this->g); + tcp.SetB(this->b); + } + return tc; +} + +std::pair GetCompanyColourRGB(Colours colour) +{ + static constexpr uint8_t CC_PALETTE_CONTRAST = 90; + + PaletteID pal = GetColourPalette(colour); + const RecolourSprite *map = GetRecolourSprite(pal); + + Colour rgb = _palette.palette[map->palette[PALETTE_INDEX_CC_START + SHADE_NORMAL]]; + return {rgb, CC_PALETTE_CONTRAST}; +} + +PaletteID CreateCompanyColourRemap(Colours colour1, Colours colour2, bool twocc, PaletteID basemap, PaletteID hint) +{ + DeallocateDynamicSprite(hint); + + PaletteID pal = AllocateDynamicSprite(); + const RecolourSprite *base = GetRecolourSprite(basemap); + RecolourSpriteRGBA *p = new (InjectSprite(SpriteType::Recolour, pal, sizeof(RecolourSpriteRGBA)).data()) RecolourSpriteRGBA(); + + /* Copy base remap */ + p->palette = base->palette; + std::ranges::transform(p->palette, p->rgba.begin(), [](const uint8_t &col) { return _palette.palette[col]; }); + + auto apply_colour = [](RecolourSpriteRGBA &remap, uint8_t index, Colours colour) { + ColoursPacker cp{colour}; + if (!cp.IsCustom()) return; + + HsvColour hsv = cp.Hsv(); + uint8_t con = cp.GetContrast(); + for (ColourShade shade = SHADE_BEGIN; shade != SHADE_END; ++shade) { + remap.rgba[index + shade] = ConvertHsvToRgb(AdjustHsvColourBrightness(hsv, shade, con)); + } + }; + + apply_colour(*p, PALETTE_INDEX_CC_START, colour1); + if (twocc) apply_colour(*p, PALETTE_INDEX_CC2_START, colour2); + + return pal; +} diff --git a/src/palette_func.h b/src/palette_func.h index a5e7dda44c..6c0f325375 100644 --- a/src/palette_func.h +++ b/src/palette_func.h @@ -83,9 +83,16 @@ enum ColourShade : uint8_t { }; DECLARE_INCREMENT_DECREMENT_OPERATORS(ColourShade) +HsvColour ConvertRgbToHsv(Colour rgb); +Colour ConvertHsvToRgb(HsvColour hsv); +HsvColour AdjustHsvColourBrightness(HsvColour hsv, ColourShade shade, int contrast); + PixelColour GetColourGradient(Colours colour, ColourShade shade); void SetColourGradient(Colours colour, ColourShade shade, PixelColour palette_colour); +std::pair GetCompanyColourRGB(Colours colour); +PaletteID CreateCompanyColourRemap(Colours rgb1, Colours rgb2, bool twocc, PaletteID basemap, PaletteID hint); + /** * Return the colour for a particular greyscale level. * @param level Intensity, 0 = black, 15 = white @@ -124,4 +131,84 @@ static constexpr PixelColour PC_FIELDS {0x25}; ///< Light static constexpr PixelColour PC_TREES {0x57}; ///< Green palette colour for trees. static constexpr PixelColour PC_WATER {0xC9}; ///< Dark blue palette colour for water. +/** + * Stretch TNumBits to fill 8 bits. + * The most-significant digits are repeated as least-significant digits so that the full 8 bit range is used, e.g.: + * 000000 -> 00000000, 111100 -> 11110011, 111111 -> 11111111 + * @param v Value of TNumBits to stretch. + * @returns 8 bit stretched value. + */ +template +inline constexpr uint8_t StretchBits(uint8_t v) +{ + return (v << (8 - TNumBits)) | (v >> (8 - (8 - TNumBits) * 2)); +} + +struct ColoursPacker +{ + Colours &c; + + explicit constexpr ColoursPacker(Colours &c) : c(c) { } + + /* + * Constants for the bit packing used by Colours. + */ + static constexpr const uint IS_CUSTOM_BIT = 4; + static constexpr const uint INDEX_START = 0; ///< Packed start of index component + static constexpr const uint INDEX_SIZE = 4; ///< Packed size of index component + static constexpr const uint HUE_START = 7; ///< Packed start of hue component + static constexpr const uint HUE_SIZE = 9; ///< Packed size of hue component + static constexpr const uint SAT_START = 16; ///< Packed start of saturation component + static constexpr const uint SAT_SIZE = 6; ///< Packed size of saturation component + static constexpr const uint VAL_START = 22; ///< Packed start of value component + static constexpr const uint VAL_SIZE = 6; ///< Packed size of value component + static constexpr const uint CON_START = 28; ///< Packed start of contrast component + static constexpr const uint CON_SIZE = 4; ///< Packed size of contrast component + + /* Colours is considered unused and blank if only the I component is set. */ + inline constexpr bool IsCustom() const { return HasBit(this->c, IS_CUSTOM_BIT); } + + inline constexpr uint8_t GetIndex() const { return GB(this->c, INDEX_START, INDEX_SIZE); } + inline constexpr uint16_t GetHue() const { return GB(this->c, HUE_START, HUE_SIZE) * HsvColour::HUE_MAX / (1U << 9); } + inline constexpr uint8_t GetSaturation() const { return StretchBits(GB(this->c, SAT_START, SAT_SIZE)); } + inline constexpr uint8_t GetValue() const { return StretchBits(GB(this->c, VAL_START, VAL_SIZE)); } + inline constexpr uint8_t GetContrast() const { return StretchBits(GB(this->c, CON_START, CON_SIZE)); } + + inline void SetCustom(bool v) { SB(this->c, IS_CUSTOM_BIT, 1, v); } + + inline void SetIndex(uint8_t v) { SB(this->c, INDEX_START, INDEX_SIZE, v); } + inline void SetHue(uint16_t v) { SB(this->c, HUE_START, HUE_SIZE, v * (1U << HUE_SIZE) / HsvColour::HUE_MAX); } + inline void SetSaturation(uint8_t v) { SB(this->c, SAT_START, SAT_SIZE, v >> (8 - SAT_SIZE)); } + inline void SetValue(uint8_t v) { SB(this->c, VAL_START, VAL_SIZE, v >> (8 - VAL_SIZE)); } + inline void SetContrast(uint8_t v) { SB(this->c, CON_START, CON_SIZE, v >> (8 - CON_SIZE)); } + + inline constexpr HsvColour Hsv() const { return {this->GetHue(), this->GetSaturation(), this->GetValue()}; } +}; + +struct TextColourPacker +{ + TextColour &tc; + + constexpr TextColourPacker(TextColour &tc) : tc(tc) { } + + static constexpr uint8_t R_START = 12; ///< Packed start of red component + static constexpr uint8_t R_SIZE = 6; ///< Packed size of red component + + static constexpr uint8_t G_START = 18; ///< Packed start of green component + static constexpr uint8_t G_SIZE = 6; ///< Packed size of green component + + static constexpr uint8_t B_START = 24; ///< Packed start of blue component + static constexpr uint8_t B_SIZE = 6; ///< Packed size of blue component + + inline constexpr uint8_t GetR() const { return StretchBits(GB(this->tc, R_START, R_SIZE)); } + inline constexpr uint8_t GetG() const { return StretchBits(GB(this->tc, G_START, G_SIZE)); } + inline constexpr uint8_t GetB() const { return StretchBits(GB(this->tc, B_START, B_SIZE)); } + + inline constexpr void SetR(uint8_t v) { SB(this->tc, R_START, R_SIZE, v >> (8 - R_SIZE)); } + inline constexpr void SetG(uint8_t v) { SB(this->tc, G_START, G_SIZE, v >> (8 - G_SIZE)); } + inline constexpr void SetB(uint8_t v) { SB(this->tc, B_START, B_SIZE, v >> (8 - B_SIZE)); } + + inline constexpr Colour ToColour() const { return Colour(this->GetR(), this->GetG(), this->GetB()); } +}; + #endif /* PALETTE_FUNC_H */ diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index da1386915f..2881249d76 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3383,6 +3383,8 @@ bool AfterLoadGame() } for (Company *c : Company::Iterate()) { + UpdateLivery(c->livery[LS_DEFAULT], true); + _company_palettes[c->index] = c->livery[LS_DEFAULT].cached_pal_1cc; UpdateCompanyLiveries(c); } diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 679b42bded..32495d9fbf 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -410,9 +410,11 @@ public: class SlCompanyLiveries : public DefaultSaveLoadHandler { public: static inline const SaveLoad description[] = { - SLE_CONDVAR(Livery, in_use, SLE_UINT8, SLV_34, SL_MAX_VERSION), - SLE_CONDVAR(Livery, colour1, SLE_UINT8, SLV_34, SL_MAX_VERSION), - SLE_CONDVAR(Livery, colour2, SLE_UINT8, SLV_34, SL_MAX_VERSION), + SLE_CONDVAR(Livery, in_use, SLE_UINT8, SLV_34, SL_MAX_VERSION), + SLE_CONDVAR(Livery, colour1, SLE_FILE_U8 | SLE_VAR_U32, SLV_34, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(Livery, colour2, SLE_FILE_U8 | SLE_VAR_U32, SLV_34, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(Livery, colour1, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), + SLE_CONDVAR(Livery, colour2, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), }; static inline const SaveLoadCompatTable compat_description = _company_liveries_compat; @@ -511,7 +513,8 @@ static const SaveLoad _company_desc[] = { SLE_CONDVAR(CompanyProperties, current_loan, SLE_INT64, SLV_65, SL_MAX_VERSION), SLE_CONDVAR(CompanyProperties, max_loan, SLE_INT64, SLV_MAX_LOAN_FOR_COMPANY, SL_MAX_VERSION), - SLE_VAR(CompanyProperties, colour, SLE_UINT8), + SLE_CONDVAR(CompanyProperties, colour, SLE_FILE_U8 | SLE_VAR_U32, SL_MIN_VERSION, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(CompanyProperties, colour, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), SLE_VAR(CompanyProperties, money_fraction, SLE_UINT8), SLE_VAR(CompanyProperties, block_preview, SLE_UINT8), diff --git a/src/saveload/group_sl.cpp b/src/saveload/group_sl.cpp index a6f341d3d9..cd7244b076 100644 --- a/src/saveload/group_sl.cpp +++ b/src/saveload/group_sl.cpp @@ -23,8 +23,10 @@ static const SaveLoad _group_desc[] = { SLE_VAR(Group, vehicle_type, SLE_UINT8), SLE_VAR(Group, flags, SLE_UINT8), SLE_CONDVAR(Group, livery.in_use, SLE_UINT8, SLV_GROUP_LIVERIES, SL_MAX_VERSION), - SLE_CONDVAR(Group, livery.colour1, SLE_UINT8, SLV_GROUP_LIVERIES, SL_MAX_VERSION), - SLE_CONDVAR(Group, livery.colour2, SLE_UINT8, SLV_GROUP_LIVERIES, SL_MAX_VERSION), + SLE_CONDVAR(Group, livery.colour1, SLE_FILE_U8 | SLE_VAR_U32, SLV_GROUP_LIVERIES, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(Group, livery.colour2, SLE_FILE_U8 | SLE_VAR_U32, SLV_GROUP_LIVERIES, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(Group, livery.colour1, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), + SLE_CONDVAR(Group, livery.colour2, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), SLE_CONDVAR(Group, parent, SLE_UINT16, SLV_189, SL_MAX_VERSION), SLE_CONDVAR(Group, number, SLE_UINT16, SLV_GROUP_NUMBERS, SL_MAX_VERSION), }; diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp index 7d81e2bf95..b24029bcf2 100644 --- a/src/saveload/industry_sl.cpp +++ b/src/saveload/industry_sl.cpp @@ -179,7 +179,8 @@ static const SaveLoad _industry_desc[] = { SLE_VAR(Industry, type, SLE_UINT8), SLE_VAR(Industry, owner, SLE_UINT8), - SLE_VAR(Industry, random_colour, SLE_UINT8), + SLE_CONDVAR(Industry, random_colour, SLE_FILE_U8 | SLE_VAR_U32, SL_MIN_VERSION, SLV_CUSTOM_COMPANY_COLOURS), + SLE_CONDVAR(Industry, random_colour, SLE_UINT32, SLV_CUSTOM_COMPANY_COLOURS, SL_MAX_VERSION), SLE_CONDVAR(Industry, last_prod_year, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_31), SLE_CONDVAR(Industry, last_prod_year, SLE_INT32, SLV_31, SL_MAX_VERSION), SLE_VAR(Industry, was_cargo_delivered, SLE_UINT8), diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 002e27fef2..e51dcfb21d 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -406,6 +406,7 @@ enum SaveLoadVersion : uint16_t { SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity. SLV_INDUSTRY_NUM_VALID_HISTORY, ///< 356 PR#14416 Store number of valid history records for industries. SLV_INDUSTRY_ACCEPTED_HISTORY, ///< 357 PR#14321 Add per-industry history of cargo delivered and waiting. + SLV_CUSTOM_COMPANY_COLOURS, ///< 358 PR#11634 Custom company colours. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_type.h b/src/settings_type.h index 255038e537..b2da605881 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -210,6 +210,7 @@ struct GUISettings { uint8_t osk_activation; ///< Mouse gesture to trigger the OSK. Colours starting_colour; ///< default color scheme for the company to start a new game with Colours starting_colour_secondary; ///< default secondary color scheme for the company to start a new game with + Colours preset_colours[16]; ///< custom colour presets. bool show_newgrf_name; ///< Show the name of the NewGRF in the build vehicle window bool show_cargo_in_vehicle_lists; ///< Show the cargoes the vehicles can carry in the list windows bool auto_remove_signals; ///< automatically remove signals when in the way during rail construction diff --git a/src/sprite.h b/src/sprite.h index 504bc3a4e7..9ace06fd41 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -185,6 +185,6 @@ inline PaletteID GroundSpritePaletteTransform(SpriteID image, PaletteID pal, Pal * @param colour Colour. * @return Recolour palette. */ -static inline PaletteID GetColourPalette(Colours colour) { return PALETTE_RECOLOUR_START + colour; } +static inline PaletteID GetColourPalette(Colours colour) { return PALETTE_RECOLOUR_START + (colour % COLOUR_END); } #endif /* SPRITE_H */ diff --git a/src/spritecache.cpp b/src/spritecache.cpp index 4387a79a93..7ff4efda6b 100644 --- a/src/spritecache.cpp +++ b/src/spritecache.cpp @@ -406,6 +406,40 @@ static bool ResizeSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels spr return true; } +/** + * Read recolour sprite data. + * @param file SpriteFile to read from. + * @param entries Number of entries to read. + */ +void RecolourSprite::Read(SpriteFile &file, size_t entries) +{ + assert(entries <= RecolourSprite::PALETTE_SIZE); + if (file.NeedsPaletteRemap()) { + /* Remapping from "Windows" to "DOS" requires a temporary buffer as entries are overwritten. */ + std::array tmp{}; + file.ReadBlock(tmp.data(), entries); + + for (uint i = 0; i < entries; ++i) { + this->palette[i] = _palmap_w2d[tmp[_palmap_d2w[i]]]; + } + } else { + file.ReadBlock(this->palette.data(), 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. @@ -416,31 +450,23 @@ static bool ResizeSprites(SpriteLoader::SpriteCollection &sprite, ZoomLevels spr */ static void *ReadRecolourSprite(SpriteFile &file, size_t file_pos, uint num, SpriteAllocator &allocator) { - /* "Normal" recolour sprites are ALWAYS 257 bytes. Then there is a small - * number of recolour sprites that are 17 bytes that only exist in DOS - * GRFs which are the same as 257 byte recolour sprites, but with the last - * 240 bytes zeroed. */ - static const uint RECOLOUR_SPRITE_SIZE = 257; - uint8_t *dest = allocator.Allocate(std::max(RECOLOUR_SPRITE_SIZE, num)); - file.SeekTo(file_pos, SEEK_SET); - if (file.NeedsPaletteRemap()) { - uint8_t *dest_tmp = new uint8_t[std::max(RECOLOUR_SPRITE_SIZE, num)]; - /* Only a few recolour sprites are less than 257 bytes */ - if (num < RECOLOUR_SPRITE_SIZE) std::fill_n(dest_tmp, RECOLOUR_SPRITE_SIZE, 0); - file.ReadBlock(dest_tmp, num); + /* The first byte of the recolour sprite records the number of entries, with + * the caveat that as this is a single byte 256 is recorded as 0. */ + uint entries = file.ReadByte(); + if (entries == 0) entries = 256; + --num; - /* The data of index 0 is never used; "literal 00" according to the (New)GRF specs. */ - for (uint i = 1; i < RECOLOUR_SPRITE_SIZE; i++) { - dest[i] = _palmap_w2d[dest_tmp[_palmap_d2w[i - 1] + 1]]; - } - delete[] dest_tmp; - } else { - file.ReadBlock(dest, num); + if (entries > num) { + Debug(grf, 1, "ReadRecolourSprite: Expected recolour sprite with {} entries but only {} present", entries, num); + entries = num; } - return dest; + num -= entries; + RecolourSprite *rs = (num == entries * 4) ? allocator.New() : allocator.New(); + rs->Read(file, entries); + return rs; } /** @@ -733,7 +759,7 @@ static void DeleteEntriesFromSpriteCache(size_t to_remove) SpriteID i = 0; for (; i != static_cast(_spritecache.size()) && candidate_bytes < to_remove; i++) { const SpriteCache *sc = GetSpriteCache(i); - if (sc->ptr != nullptr) { + if (sc->ptr != nullptr && sc->file != nullptr) { push({ sc->lru, i, sc->length }); if (candidate_bytes >= to_remove) break; } @@ -742,7 +768,7 @@ static void DeleteEntriesFromSpriteCache(size_t to_remove) * only sprites with LRU values <= the maximum (i.e. the top of the heap) need to be considered */ for (; i != static_cast(_spritecache.size()); i++) { const SpriteCache *sc = GetSpriteCache(i); - if (sc->ptr != nullptr && sc->lru <= candidates.front().lru) { + if (sc->ptr != nullptr && sc->file != nullptr && sc->lru <= candidates.front().lru) { push({ sc->lru, i, sc->length }); while (!candidates.empty() && candidate_bytes - candidates.front().size >= to_remove) { pop(); @@ -784,6 +810,10 @@ void IncreaseSpriteLRU() void SpriteCache::ClearSpriteData() { + if (this->ptr == nullptr) return; + + if (this->type == SpriteType::Recolour) reinterpret_cast(this->ptr.get())->~RecolourSprite(); + _spritecache_bytes_used -= this->length; this->ptr.reset(); } @@ -795,6 +825,32 @@ void *UniquePtrSpriteAllocator::AllocatePtr(size_t size) return this->data.get(); } +/** + * Allocate and inject memory for a memory-based sprite. + */ +std::span InjectSprite(SpriteType type, SpriteID load_index, size_t len) +{ + if (SpriteExists(load_index)) GetSpriteCache(load_index)->ClearSpriteData(); + + SpriteCache *sc = AllocateSpriteCache(load_index); + sc->file_pos = SIZE_MAX; + sc->file = nullptr; + sc->id = 0; + sc->lru = 0; + sc->type = type; + sc->warned = false; + sc->control_flags = {}; + + UniquePtrSpriteAllocator cache_allocator; + cache_allocator.Allocate(len); + + sc->ptr = std::move(cache_allocator.data); + sc->length = static_cast(cache_allocator.size); + _spritecache_bytes_used += sc->length; + + return {sc->ptr.get(), sc->length}; +} + /** * Handles the case when a sprite of different type is requested than is present in the SpriteCache. * For SpriteType::Font sprites, it is normal. In other cases, default sprite is loaded instead. @@ -928,3 +984,44 @@ void GfxClearFontSpriteCache() } /* static */ SpriteCollMap> SpriteLoader::Sprite::buffer; + +static SpriteID _sprites_end; ///< First usable free sprite ID. +static std::vector _dynamic_sprites; ///< List of used/free custom sprite slots. + +/** + * Clear custom sprites mapping and set first usable free sprite ID. + */ +void ClearDynamicSprites() +{ + _dynamic_sprites.clear(); + _sprites_end = _spritecache.size(); +} + +/** + * Allocate a custom sprite ID. + */ +SpriteID AllocateDynamicSprite() +{ + /* Find first unused slot, or make one. */ + auto it = std::ranges::find(_dynamic_sprites, 0); + if (it == std::end(_dynamic_sprites)) it = _dynamic_sprites.emplace(it, 0); + + (*it)++; + return _sprites_end + std::distance(std::begin(_dynamic_sprites), it); +} + +/** + * Mark a custom sprite ID as deallocated. + * The sprite slot is merely marked as reusable. + */ +void DeallocateDynamicSprite(SpriteID sprite) +{ + if (sprite >= _sprites_end && sprite < _sprites_end + _dynamic_sprites.size()) { + assert(_dynamic_sprites[sprite - _sprites_end] > 0); + --_dynamic_sprites[sprite - _sprites_end]; + + if (_dynamic_sprites[sprite - _sprites_end] == 0) { + GetSpriteCache(sprite)->ClearSpriteData(); + } + } +} diff --git a/src/spritecache.h b/src/spritecache.h index 4106622c1c..fbb220d127 100644 --- a/src/spritecache.h +++ b/src/spritecache.h @@ -41,10 +41,9 @@ inline const Sprite *GetSprite(SpriteID sprite, SpriteType type) return (Sprite*)GetRawSprite(sprite, type); } -inline const uint8_t *GetNonSprite(SpriteID sprite, SpriteType type) +inline const RecolourSprite *GetRecolourSprite(SpriteID sprite) { - assert(type == SpriteType::Recolour); - return (uint8_t*)GetRawSprite(sprite, type); + return static_cast(GetRawSprite(sprite, SpriteType::Recolour)); } void GfxInitSpriteMem(); @@ -60,5 +59,10 @@ size_t GetGRFSpriteOffset(uint32_t id); bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id); bool SkipSpriteData(SpriteFile &file, uint8_t type, uint16_t num); void DupSprite(SpriteID old_spr, SpriteID new_spr); +std::span InjectSprite(SpriteType type, SpriteID load_index, size_t len); + +void ClearDynamicSprites(); +SpriteID AllocateDynamicSprite(); +void DeallocateDynamicSprite(SpriteID spite); #endif /* SPRITECACHE_H */ diff --git a/src/spritecache_type.h b/src/spritecache_type.h index a6ffbbc6a1..afa4bb7ab1 100644 --- a/src/spritecache_type.h +++ b/src/spritecache_type.h @@ -11,6 +11,8 @@ #define SPRITECACHE_TYPE_H #include "core/enum_type.hpp" +#include "gfx_type.h" +#include "spriteloader/sprite_file_type.hpp" /** Data structure describing a sprite. */ struct Sprite { @@ -21,6 +23,29 @@ struct Sprite { std::byte data[]; ///< Sprite data. }; +/** Data structure describing a palette remap. */ +class RecolourSprite { +public: + static constexpr size_t PALETTE_SIZE = 256; ///< Number of entries in a recolour sprite. + + 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. diff --git a/src/spriteloader/spriteloader.hpp b/src/spriteloader/spriteloader.hpp index fb36cb0294..6b349b0c3f 100644 --- a/src/spriteloader/spriteloader.hpp +++ b/src/spriteloader/spriteloader.hpp @@ -117,6 +117,18 @@ public: return static_cast(this->AllocatePtr(size)); } + /** + * Allocate memory and construct an object in the sprite cache. + * @tparam T Type to construct. + * @tparam Targs The constructor parameter types. + * @param args Parameters for the constructor. + */ + template + T *New(Targs &&... args) + { + return new (this->Allocate(sizeof(T))) T(std::forward(args)...); + } + protected: /** * Allocate memory for a sprite. diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index 3d9ee1b75b..bce6a1b787 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -26,6 +26,7 @@ static const SettingVariant _gui_settings_table[] = { }; [templates] SDTC_BOOL = SDTC_BOOL( $var, SettingFlags({$flags}), $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $from, $to, $cat, $extra, $startup), +SDTC_LIST = SDTC_LIST( $var, $type, SettingFlags({$flags}), $def, $from, $to, $cat, $extra, $startup), SDTC_OMANY = SDTC_OMANY( $var, $type, SettingFlags({$flags}), $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $from, $to, $cat, $extra, $startup), SDTC_VAR = SDTC_VAR( $var, $type, SettingFlags({$flags}), $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $str_cb, $help_cb, $val_cb, $def_cb, $range_cb, $from, $to, $cat, $extra, $startup), @@ -348,7 +349,7 @@ post_cb = InvalidateCompanyLiveryWindow [SDTC_VAR] var = gui.starting_colour -type = SLE_UINT8 +type = SLE_UINT32 flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync, SettingFlag::GuiDropdown def = COLOUR_END min = 0 @@ -359,7 +360,7 @@ strval = STR_COLOUR_DARK_BLUE [SDTC_VAR] var = gui.starting_colour_secondary -type = SLE_UINT8 +type = SLE_UINT32 flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync, SettingFlag::GuiDropdown def = COLOUR_END min = 0 @@ -368,6 +369,13 @@ str = STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY strhelp = STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY_HELPTEXT strval = STR_COLOUR_SECONDARY_DARK_BLUE +[SDTC_LIST] +var = gui.preset_colours +type = SLE_UINT32 +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = """" +cat = SC_BASIC + [SDTC_BOOL] var = gui.auto_remove_signals flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b94670cbac..71c1285c76 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2045,7 +2045,6 @@ LiveryScheme GetEngineLiveryScheme(EngineID engine_type, EngineID parent_engine_ const Livery *GetEngineLivery(EngineID engine_type, CompanyID company, EngineID parent_engine_type, const Vehicle *v, uint8_t livery_setting) { const Company *c = Company::Get(company); - LiveryScheme scheme = LS_DEFAULT; if (livery_setting == LIT_ALL || (livery_setting == LIT_COMPANY && company == _local_company)) { if (v != nullptr) { @@ -2063,11 +2062,12 @@ const Livery *GetEngineLivery(EngineID engine_type, CompanyID company, EngineID * whether any _other_ liveries are in use. */ if (c->livery[LS_DEFAULT].in_use != 0) { /* Determine the livery scheme to use */ - scheme = GetEngineLiveryScheme(engine_type, parent_engine_type, v); + LiveryScheme scheme = GetEngineLiveryScheme(engine_type, parent_engine_type, v); + if (c->livery[scheme].in_use != 0) return &c->livery[scheme]; } } - return &c->livery[scheme]; + return &c->livery[LS_DEFAULT]; } @@ -2080,6 +2080,15 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng const Engine *e = Engine::Get(engine_type); + /* Default livery for spectators */ + static const Livery default_livery = { + 0, COLOUR_GREY, COLOUR_GREY, + PALETTE_RECOLOUR_START, SPR_2CCMAP_BASE, SPR_2CCMAP_BASE, + }; + + bool twocc = e->info.misc_flags.Test(EngineMiscFlag::Uses2CC); + const Livery *livery = Company::IsValidID(company) ? GetEngineLivery(engine_type, company, parent_engine_type, v, _settings_client.gui.liveries) : &default_livery; + /* Check if we should use the colour map callback */ if (e->info.callback_mask.Test(VehicleCallbackMask::ColourRemap)) { uint16_t callback = GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING, 0, 0, engine_type, v); @@ -2090,6 +2099,15 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng /* If bit 14 is set, then the company colours are applied to the * map else it's returned as-is. */ if (!HasBit(callback, 14)) { + /* Test if this is the standard remap/reversed remap */ + if (map == PALETTE_RECOLOUR_START + (livery->colour1 & 0xF)) { + map = livery->cached_pal_1cc; + } else if (map == SPR_2CCMAP_BASE + (livery->colour1 & 0xF) + (livery->colour2 & 0xF) * 16) { + map = livery->cached_pal_2cc; + } else if (map == SPR_2CCMAP_BASE + (livery->colour2 & 0xF) + (livery->colour1 & 0xF) * 16) { + map = livery->cached_pal_2cr; + } + /* Update cache */ if (v != nullptr) const_cast(v)->colourmap = map; return map; @@ -2097,17 +2115,19 @@ static PaletteID GetEngineColourMap(EngineID engine_type, CompanyID company, Eng } } - bool twocc = e->info.misc_flags.Test(EngineMiscFlag::Uses2CC); - if (map == PAL_NONE) map = twocc ? (PaletteID)SPR_2CCMAP_BASE : (PaletteID)PALETTE_RECOLOUR_START; /* Spectator has news shown too, but has invalid company ID - as well as dedicated server */ if (!Company::IsValidID(company)) return map; - const Livery *livery = GetEngineLivery(engine_type, company, parent_engine_type, v, _settings_client.gui.liveries); - - map += livery->colour1; - if (twocc) map += livery->colour2 * 16; + if (map == PALETTE_RECOLOUR_START || map == SPR_2CCMAP_BASE) { + /* Use cached palette when using default remaps */ + map = twocc ? livery->cached_pal_2cc : livery->cached_pal_1cc; + } else { + /* Add offsets for custom NewGRF remaps */ + map += livery->colour1; + if (twocc) map += livery->colour2 * 16; + } /* Update cache */ if (v != nullptr) const_cast(v)->colourmap = map; diff --git a/src/video/opengl.cpp b/src/video/opengl.cpp index 949c17eaea..70541197a9 100644 --- a/src/video/opengl.cpp +++ b/src/video/opengl.cpp @@ -1290,7 +1290,7 @@ void OpenGLBackend::RenderOglSprite(const OpenGLSprite *gl_sprite, PaletteID pal _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, OpenGLSprite::pal_pbo); _glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - _glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, 256, GetNonSprite(GB(pal, 0, PALETTE_WIDTH), SpriteType::Recolour) + 1); + _glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, 256, GetRecolourSprite(GB(pal, 0, PALETTE_WIDTH))->GetPaletteRemap()); _glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RED, GL_UNSIGNED_BYTE, nullptr); _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); diff --git a/src/widget.cpp b/src/widget.cpp index 034c289ea4..545fd91153 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -300,8 +300,6 @@ void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, Fra if (flags.Test(FrameFlag::Transparent)) { GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR); } else { - assert(colour < COLOUR_END); - const PixelColour dark = GetColourGradient(colour, SHADE_DARK); const PixelColour medium_dark = GetColourGradient(colour, SHADE_LIGHT); const PixelColour medium_light = GetColourGradient(colour, SHADE_LIGHTER); diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h index 2964b341c0..a4b2121493 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -75,6 +75,25 @@ enum CompanyFinancesWidgets : WidgetID { }; +/** Widgets of the #SelectCustomColourWindow class. */ +enum SelectCustomColourWidgets { + WID_SCC_CAPTION, ///< Caption of window. + WID_SCC_HUE, + WID_SCC_SCROLLBAR_HUE, ///< Hue scrollbar. + WID_SCC_SAT, + WID_SCC_SCROLLBAR_SAT, ///< Saturation scrollbar. + WID_SCC_VAL, + WID_SCC_SCROLLBAR_VAL, ///< Value scrollbar. + WID_SCC_CON, + WID_SCC_SCROLLBAR_CON, ///< Contrast scrollbar. + WID_SCC_OUTPUT, + WID_SCC_DEFAULT, ///< First default colour. + WID_SCC_DEFAULT_LAST = WID_SCC_DEFAULT + COLOUR_END - 1, + WID_SCC_PRESETS, ///< First preset colour. + WID_SCC_PRESETS_LAST = WID_SCC_PRESETS + COLOUR_END - 1, +}; + + /** Widgets of the #SelectCompanyLiveryWindow class. */ enum SelectCompanyLiveryWidgets : WidgetID { WID_SCL_CAPTION, ///< Caption of window. diff --git a/src/window_type.h b/src/window_type.h index 28f72486b7..735ac3a19b 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -231,6 +231,12 @@ enum WindowClass : uint16_t { */ WC_COMPANY_COLOUR, + /** + * Custom colour selection; %Window numbers: + * - #CompanyID = #SelectCustomColourWidgets + */ + WC_CUSTOM_COLOUR, + /** * Alter company face window; %Window numbers: * - #CompanyID = #SelectCompanyManagerFaceWidgets