/** * @file palette.cpp * * Implementation of functions for handling the engines color palette. */ #include "dx.h" #include "options.h" #include "render.h" #include "storm/storm.h" #include "utils/display.h" #include "utils/sdl_compat.h" namespace devilution { SDL_Color logical_palette[256]; SDL_Color system_palette[256]; SDL_Color orig_palette[256]; Uint8 paletteTransparencyLookup[256][256]; //Lookup table for transparency /* data */ /** Specifies whether the palette has max brightness. */ bool sgbFadedIn = true; void palette_update() { assert(palette); if (SDLC_SetSurfaceAndPaletteColors(pal_surface, palette, system_palette, 0, 256) < 0) { ErrSdl(); } pal_surface_palette_version++; } void ApplyGamma(SDL_Color *dst, const SDL_Color *src, int n) { int i; double g; g = sgOptions.Graphics.nGammaCorrection / 100.0; for (i = 0; i < n; i++) { dst[i].r = pow(src[i].r / 256.0, g) * 256.0; dst[i].g = pow(src[i].g / 256.0, g) * 256.0; dst[i].b = pow(src[i].b / 256.0, g) * 256.0; } force_redraw = 255; } static void LoadGamma() { int gamma_value = sgOptions.Graphics.nGammaCorrection; if (gamma_value < 30) { gamma_value = 30; } else if (gamma_value > 100) { gamma_value = 100; } sgOptions.Graphics.nGammaCorrection = gamma_value - gamma_value % 5; } void palette_init() { LoadGamma(); memcpy(system_palette, orig_palette, sizeof(orig_palette)); InitPalette(); } /** * @brief Generate lookup table for transparency * * This is based of the same technique found in Quake2. * * To mimic 50% transparency we figure out what colors in the existing palette are the best match for the combination of any 2 colors. * We save this into a lookup table for use during rendering. * * @param palette The colors to operate on * @param skipFrom Do not use colors between this index and skipTo * @param skipTo Do not use colors between skipFrom and this index * @param toUpdate Only update the first n colors */ static void GenerateBlendedLookupTable(SDL_Color *palette, int skipFrom, int skipTo, int toUpdate = 256) { for (int i = 0; i < 256; i++) { for (int j = 0; j < 256; j++) { if (i == j) { // No need to calculate transparency between 2 identical colors paletteTransparencyLookup[i][j] = j; continue; } if (i > j) { // Half the blends will be mirror identical ([i][j] is the same as [j][i]), so simply copy the existing combination. paletteTransparencyLookup[i][j] = paletteTransparencyLookup[j][i]; continue; } if (i > toUpdate && j > toUpdate) { continue; } Uint8 r = ((int)palette[i].r + (int)palette[j].r) / 2; Uint8 g = ((int)palette[i].g + (int)palette[j].g) / 2; Uint8 b = ((int)palette[i].b + (int)palette[j].b) / 2; Uint8 best; Uint32 bestDiff = SDL_MAX_UINT32; for (int k = 0; k < 256; k++) { if (k >= skipFrom && k <= skipTo) continue; int diffr = palette[k].r - r; int diffg = palette[k].g - g; int diffb = palette[k].b - b; int diff = diffr * diffr + diffg * diffg + diffb * diffb; if (bestDiff > diff) { best = k; bestDiff = diff; } } paletteTransparencyLookup[i][j] = best; } } } void LoadPalette(const char *pszFileName) { int i; void *pBuf; BYTE PalData[256][3]; assert(pszFileName); SFileOpenFile(pszFileName, &pBuf); SFileReadFile(pBuf, (char *)PalData, sizeof(PalData), NULL, NULL); SFileCloseFile(pBuf); for (i = 0; i < 256; i++) { orig_palette[i].r = PalData[i][0]; orig_palette[i].g = PalData[i][1]; orig_palette[i].b = PalData[i][2]; #ifndef USE_SDL1 orig_palette[i].a = SDL_ALPHA_OPAQUE; #endif } if (sgOptions.Graphics.bBlendedTransparancy) { if (leveltype == DTYPE_CAVES || leveltype == DTYPE_CRYPT) { GenerateBlendedLookupTable(orig_palette, 1, 31); } else if (leveltype == DTYPE_NEST) { GenerateBlendedLookupTable(orig_palette, 1, 15); } else { GenerateBlendedLookupTable(orig_palette, -1, -1); } } } void LoadRndLvlPal(int l) { int rv; char szFileName[MAX_PATH]; if (l == DTYPE_TOWN) { LoadPalette("Levels\\TownData\\Town.pal"); } else { rv = random_(0, 4) + 1; sprintf(szFileName, "Levels\\L%iData\\L%i_%i.PAL", l, l, rv); if (l == 5) { sprintf(szFileName, "NLevels\\L5Data\\L5Base.PAL"); } if (l == 6) { if (!gbNestArt) { rv++; } sprintf(szFileName, "NLevels\\L%iData\\L%iBase%i.PAL", 6, 6, rv); } LoadPalette(szFileName); } } void ResetPal() { } void IncreaseGamma() { if (sgOptions.Graphics.nGammaCorrection < 100) { sgOptions.Graphics.nGammaCorrection += 5; if (sgOptions.Graphics.nGammaCorrection > 100) sgOptions.Graphics.nGammaCorrection = 100; ApplyGamma(system_palette, logical_palette, 256); palette_update(); } } void DecreaseGamma() { if (sgOptions.Graphics.nGammaCorrection > 30) { sgOptions.Graphics.nGammaCorrection -= 5; if (sgOptions.Graphics.nGammaCorrection < 30) sgOptions.Graphics.nGammaCorrection = 30; ApplyGamma(system_palette, logical_palette, 256); palette_update(); } } int UpdateGamma(int gamma) { if (gamma) { sgOptions.Graphics.nGammaCorrection = 130 - gamma; ApplyGamma(system_palette, logical_palette, 256); palette_update(); } return 130 - sgOptions.Graphics.nGammaCorrection; } void SetFadeLevel(DWORD fadeval) { int i; for (i = 0; i < 256; i++) { // BUGFIX: should be 256 (fixed) system_palette[i].r = (fadeval * logical_palette[i].r) >> 8; system_palette[i].g = (fadeval * logical_palette[i].g) >> 8; system_palette[i].b = (fadeval * logical_palette[i].b) >> 8; } palette_update(); } void BlackPalette() { SetFadeLevel(0); } void PaletteFadeIn(int fr) { int i; ApplyGamma(logical_palette, orig_palette, 256); DWORD tc = SDL_GetTicks(); for (i = 0; i < 256; i = (SDL_GetTicks() - tc) / 2.083) { // 32 frames @ 60hz SetFadeLevel(i); SDL_Rect SrcRect = { BUFFER_BORDER_LEFT, BUFFER_BORDER_TOP, gnScreenWidth, gnScreenHeight }; BltFast(&SrcRect, NULL); RenderPresent(); } SetFadeLevel(256); memcpy(logical_palette, orig_palette, sizeof(orig_palette)); sgbFadedIn = true; } void PaletteFadeOut(int fr) { int i; if (sgbFadedIn) { DWORD tc = SDL_GetTicks(); for (i = 256; i > 0; i = 256 - (SDL_GetTicks() - tc) / 2.083) { // 32 frames @ 60hz SetFadeLevel(i); SDL_Rect SrcRect = { BUFFER_BORDER_LEFT, BUFFER_BORDER_TOP, gnScreenWidth, gnScreenHeight }; BltFast(&SrcRect, NULL); RenderPresent(); } SetFadeLevel(0); sgbFadedIn = false; } } /** * @brief Cycle the given range of colors in the palette * @param from First color index of the range * @param to First color index of the range */ static void CycleColors(int from, int to) { SDL_Color col = system_palette[from]; for (int i = from; i < to; i++) { system_palette[i] = system_palette[i + 1]; } system_palette[to] = col; if (!sgOptions.Graphics.bBlendedTransparancy) return; for (int i = 0; i < 256; i++) { Uint8 col = paletteTransparencyLookup[i][from]; for (int j = from; j < to; j++) { paletteTransparencyLookup[i][j] = paletteTransparencyLookup[i][j + 1]; } paletteTransparencyLookup[i][to] = col; } Uint8 colRow[256]; memcpy(colRow, &paletteTransparencyLookup[from], sizeof(*paletteTransparencyLookup)); for (int i = from; i < to; i++) { memcpy(&paletteTransparencyLookup[i], &paletteTransparencyLookup[i + 1], sizeof(*paletteTransparencyLookup)); } memcpy(&paletteTransparencyLookup[to], colRow, sizeof(colRow)); } /** * @brief Cycle the given range of colors in the palette in reverse direction * @param from First color index of the range * @param to First color index of the range */ static void CycleColorsReverse(int from, int to) { SDL_Color col = system_palette[to]; for (int i = to; i > from; i--) { system_palette[i] = system_palette[i - 1]; } system_palette[from] = col; if (!sgOptions.Graphics.bBlendedTransparancy) return; for (int i = 0; i < 256; i++) { Uint8 col = paletteTransparencyLookup[i][to]; for (int j = to; j > from; j--) { paletteTransparencyLookup[i][j] = paletteTransparencyLookup[i][j - 1]; } paletteTransparencyLookup[i][from] = col; } Uint8 colRow[256]; memcpy(colRow, &paletteTransparencyLookup[to], sizeof(*paletteTransparencyLookup)); for (int i = to; i > from; i--) { memcpy(&paletteTransparencyLookup[i], &paletteTransparencyLookup[i - 1], sizeof(*paletteTransparencyLookup)); } memcpy(&paletteTransparencyLookup[from], colRow, sizeof(colRow)); } void palette_update_caves() { CycleColors(1, 31); palette_update(); } int dword_6E2D58; int dword_6E2D54; void palette_update_crypt() { if (dword_6E2D58 > 1) { CycleColorsReverse(1, 15); dword_6E2D58 = 0; } else { dword_6E2D58++; } if (dword_6E2D54 > 0) { CycleColorsReverse(16, 31); palette_update(); dword_6E2D54++; } else { dword_6E2D54 = 1; } } int dword_6E2D5C; int dword_6E2D60; void palette_update_hive() { if (dword_6E2D60 == 2) { CycleColorsReverse(1, 8); dword_6E2D60 = 0; } else { dword_6E2D60++; } if (dword_6E2D5C == 2) { CycleColorsReverse(9, 15); palette_update(); dword_6E2D5C = 0; } else { dword_6E2D5C++; } } void palette_update_quest_palette(int n) { int i; for (i = 32 - n; i >= 0; i--) { logical_palette[i] = orig_palette[i]; } ApplyGamma(system_palette, logical_palette, 32); palette_update(); GenerateBlendedLookupTable(logical_palette, 1, 31, 32 - n); // Possible optimization would be to only update color 0 as only the UI can overlap with transparency in this quest } } // namespace devilution