raze/source/core/palette.cpp

481 lines
15 KiB
C++
Raw Normal View History

/*
** palette.cpp
**
**---------------------------------------------------------------------------
** Copyright 2019-2020 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "build.h"
#include "imagehelpers.h"
#include "palette.h"
#include "superfasthash.h"
#include "memarena.h"
#include "palettecontainer.h"
#include "palutil.h"
#include "colormatcher.h"
2020-05-23 12:40:54 +00:00
#include "m_swap.h"
#include "v_colortables.h"
#include "v_font.h"
#include "printf.h"
#include "v_draw.h"
2020-05-27 20:19:02 +00:00
LookupTableInfo lookups;
2021-03-25 08:13:16 +00:00
int numshades;
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// Adds a palette to the global list of base palettes
//
//==========================================================================
2020-05-23 12:40:54 +00:00
void paletteSetColorTable(int32_t id, uint8_t const* table, bool notransparency, bool twodonly)
{
if (id == 0)
{
GPalette.SetPalette(table, 255);
GPalette.BaseColors[255] = 0;
BuildTransTable(GPalette.BaseColors);
}
FRemapTable remap;
remap.AddColors(0, 256, table, -1);
if (!notransparency)
{
remap.Palette[255] = 0;
remap.Remap[255] = 255;
}
2020-07-05 08:15:55 +00:00
remap.TwodOnly = twodonly; //
remap.NoTransparency = notransparency;
GPalette.UpdateTranslation(TRANSLATION(Translation_BasePalettes, id), &remap);
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
2020-05-23 12:40:54 +00:00
// loads the main palette file.
//
2020-05-23 12:40:54 +00:00
//==========================================================================
void paletteLoadFromDisk(void)
{
for (auto& x : glblend)
{
x = defaultglblend;
}
auto fil = fileSystem.OpenFileReader("palette.dat");
if (!fil.isOpen())
return;
// Base palette
uint8_t palette[768];
if (768 != fil.Read(palette, 768))
return;
for (auto & pe : palette)
pe <<= 2;
2020-05-23 12:40:54 +00:00
paletteSetColorTable(0, palette, false, false);
paletteloaded |= PALETTE_MAIN;
// LameDuke and Witchaven use an older variant.
if (fil.GetLength() == 41600)
{
numshades = 32;
2020-05-23 12:40:54 +00:00
}
else
{
// Shade tables
numshades = fil.ReadInt16();
if (numshades <= 1)
{
Printf("Warning: Invalid number of shades in \"palette.dat\"!\n");
numshades = 0;
return;
}
}
// Read base shade table (lookuptables 0).
unsigned length = numshades * 256;
auto buffer = fil.Read(length);
2023-08-19 16:08:27 +00:00
if (buffer.size() != length) return;
lookups.setTable(0, buffer.data());
2020-06-11 07:15:44 +00:00
paletteloaded |= PALETTE_SHADE | PALETTE_TRANSLUC;
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// postprocess the palette data after everything has been loaded
//
//==========================================================================
2020-05-27 20:19:02 +00:00
void LookupTableInfo::postLoadTables(void)
{
2020-05-27 20:19:02 +00:00
GPalette.GenerateGlobalBrightmapFromColormap(getTable(0), numshades);
2020-05-27 20:19:02 +00:00
// Try to detect fullbright translations. Unfortunately this cannot be used to detect fade strength because of loss of color precision in the palette map.
for (int j = 0; j < MAXPALOOKUPS; j++)
{
if (tables[j].FadeColor) continue;
2020-05-27 20:19:02 +00:00
auto lookup = tables[j].Shades;
if (lookup.Len() > 0)
2020-05-23 12:40:54 +00:00
{
2020-05-27 20:19:02 +00:00
auto basetable = (uint8_t*)lookup.GetChars();
auto midtable = basetable + ((numshades / 2) - 1) * 256;
int lumibase = 0, lumimid = 0;
for (int i = 1; i < 255; i++) // intentionally leave out 0 and 255, because the table here is not translucency adjusted to the palette.
{
lumibase += GPalette.BaseColors[basetable[i]].Amplitude();
lumimid += GPalette.BaseColors[midtable[i]].Amplitude();
}
float divider = float(lumimid) / float(lumibase);
bool isbright = false;
if (divider > 0.9)
{
tables[j].ShadeFactor = 1 / 10000.f; // this translation is fullbright.
2020-05-27 20:19:02 +00:00
}
else
{
if (tables[j].ShadeFactor == 0) tables[j].ShadeFactor = 1.f;
2020-05-27 20:19:02 +00:00
// Fullbright lookups do not need brightmaps.
auto fog = tables[j].FadeColor;
if (GPalette.HasGlobalBrightmap && fog.r == 0 && fog.g == 0 && fog.b == 0)
{
isbright = true;
// A translation is fullbright only if all fullbright colors in the base table are mapped to another fullbright color.
auto brightmap = GPalette.GlobalBrightmap.Remap;
for (int i = 1; i < 255; i++) // This also ignores entries 0 and 255 for the same reason as above.
{
int map = basetable[i];
if (brightmap[i] == GPalette.WhiteIndex && brightmap[map] != GPalette.WhiteIndex)
{
isbright = false;
break;
}
}
}
}
tables[j].hasBrightmap = isbright;
DPrintf(DMSG_NOTIFY, "Lookup %d is %sbright\n", j, isbright ? "" : "not ");
2020-05-23 12:40:54 +00:00
}
}
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
2020-05-23 12:40:54 +00:00
// load the lookup tables from lookup.dat
//
2020-05-23 12:40:54 +00:00
//==========================================================================
2020-05-27 20:19:02 +00:00
int32_t LookupTableInfo::loadTable(FileReader &fp)
{
uint8_t buffer[256];
2020-05-23 12:40:54 +00:00
int numlookups = fp.ReadUInt8();
if (numlookups < 1)
return -1;
2020-05-23 12:40:54 +00:00
for (int j=0; j<numlookups; j++)
{
2020-05-23 12:40:54 +00:00
int palnum = fp.ReadUInt8();
if (256 != fp.Read(buffer, 256))
return -1;
if (palnum < 0 || palnum >= 256 - RESERVEDPALS)
{
Printf("ERROR: attempt to load lookup at invalid index %d\n", palnum);
}
2020-05-23 12:40:54 +00:00
else
makeTable(palnum, buffer, 0, 0, 0, 0);
}
return 0;
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// Find a gap of four consecutive unused pal numbers to generate fog shade tables.
//
//==========================================================================
2020-05-27 20:19:02 +00:00
void LookupTableInfo::setupDefaultFog(void)
{
2020-05-23 12:40:54 +00:00
for (int j = 1; j <= 255 - 3; j++)
{
2020-05-27 20:19:02 +00:00
if (tables[j].Shades.IsEmpty() && tables[j+1].Shades.IsEmpty() && tables[j + 2].Shades.IsEmpty() && tables[j + 3].Shades.IsEmpty())
{
2020-05-27 20:19:02 +00:00
makeTable(j, NULL, 60, 60, 60, 1);
makeTable(j + 1, NULL, 60, 0, 0, 1);
makeTable(j + 2, NULL, 0, 60, 0, 1);
makeTable(j + 3, NULL, 0, 0, 60, 1);
break;
}
}
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// post process the lookup tables once everything has been loaded
//
//==========================================================================
2020-05-27 20:19:02 +00:00
void LookupTableInfo::postLoadLookups()
{
2020-05-23 12:40:54 +00:00
int numpalettes = GPalette.NumTranslations(Translation_BasePalettes);
if (numpalettes == 0) return;
2020-05-24 22:46:46 +00:00
for (int i = 0; i < numpalettes; i++)
{
2020-05-24 22:46:46 +00:00
auto palette = GPalette.GetTranslation(Translation_BasePalettes, i);
if (!palette) continue;
2020-07-05 08:15:55 +00:00
if (palette->TwodOnly)
2020-05-24 22:46:46 +00:00
{
GPalette.CopyTranslation(TRANSLATION(Translation_Remap + i, 0), TRANSLATION(Translation_BasePalettes, i));
}
else
2020-05-23 12:40:54 +00:00
{
2020-05-24 22:46:46 +00:00
for (int l = 0; l < MAXPALOOKUPS; l++)
2020-05-23 12:40:54 +00:00
{
2020-05-27 20:19:02 +00:00
if (!tables[l].Shades.IsEmpty())
2020-05-23 12:40:54 +00:00
{
2020-05-27 20:19:02 +00:00
const uint8_t* lookup = (uint8_t*)tables[l].Shades.GetChars();
2020-05-24 22:46:46 +00:00
FRemapTable remap;
memcpy(remap.Remap, lookup, 256);
for (int j = 0; j < 256; j++)
2020-05-23 12:40:54 +00:00
{
remap.Palette[j] = palette->Palette[remap.Remap[j]];
2020-05-23 12:40:54 +00:00
}
remap.NumEntries = 256;
GPalette.UpdateTranslation(TRANSLATION(i + Translation_Remap, l), &remap);
2020-05-23 12:40:54 +00:00
}
}
}
}
// Swap colors 0 and 255. Note that color 255 may not be translucent!
auto colorswap = [](FRemapTable* remap)
{
std::swap(remap->Palette[255], remap->Palette[0]);
std::swap(remap->Remap[255], remap->Remap[0]);
for (auto& c : remap->Remap)
{
if (c == 0) c = 255;
else if (c == 255) c = 0;
}
2020-07-05 08:15:55 +00:00
if (!remap->NoTransparency)
{
remap->Remap[0] = 0;
remap->Palette[0] = 0;
}
};
for (auto remap : GPalette.uniqueRemaps)
{
if (!remap->ForFont) colorswap(remap);
}
colorswap(&GPalette.GlobalBrightmap);
std::swap(GPalette.BaseColors[0], GPalette.BaseColors[255]);
GPalette.Remap[0] = 255;
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// set a lookup table from external data
//
//==========================================================================
2020-05-27 20:19:02 +00:00
int32_t LookupTableInfo::setTable(int palnum, const uint8_t *shtab)
{
if (shtab != NULL)
{
2020-05-23 12:40:54 +00:00
int length = numshades * 256;
2020-05-27 20:19:02 +00:00
tables[palnum].Shades = FString((const char*)shtab, length);
}
return 0;
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
2020-05-23 12:40:54 +00:00
// creates a lookup table from scratch
//
2020-05-23 12:40:54 +00:00
//==========================================================================
2020-05-27 20:19:02 +00:00
void LookupTableInfo::makeTable(int palnum, const uint8_t *remapbuf, int r, int g, int b, bool noFloorPal)
{
uint8_t idmap[256];
// NOTE: palnum==0 is allowed
2020-05-23 12:40:54 +00:00
if (paletteloaded == 0 || (unsigned)palnum >= MAXPALOOKUPS)
return;
2020-05-27 20:19:02 +00:00
tables[palnum].noFloorPal = noFloorPal;
2020-05-23 12:40:54 +00:00
if (remapbuf == nullptr)
{
if (r == 0 && g == 0 && b == 0)
{
2020-05-27 20:19:02 +00:00
clearTable(palnum);
return;
}
2020-05-23 12:40:54 +00:00
for (int i = 0; i < 256; i++) idmap[i] = i;
remapbuf = idmap;
}
2020-05-23 12:40:54 +00:00
int length = numshades * 256;
2020-05-27 20:19:02 +00:00
auto p = tables[palnum].Shades.LockNewBuffer(length);
// "black fog"/visibility case -- only remap color indices
auto src = getTable(0);
2020-05-23 12:40:54 +00:00
for (int j = 0; j < numshades; j++)
{
for (int i = 0; i < 256; i++)
{
p[256 * j + i] = src[256 * j + remapbuf[i]];
}
}
2020-05-27 20:19:02 +00:00
tables[palnum].FadeColor.r = r;
tables[palnum].FadeColor.g = g;
tables[palnum].FadeColor.b = b;
tables[palnum].FadeColor.a = 1;
}
2020-05-23 12:40:54 +00:00
//==========================================================================
//
// hicsetpalettetint(pal,r,g,b,sr,sg,sb,effect)
// The tinting values represent a mechanism for emulating the effect of global sector
// palette shifts on true-colour textures and only true-colour textures.
// effect bitset: 1 = greyscale, 2 = invert
//
//==========================================================================
void LookupTableInfo::setPaletteTint(int palnum, int r, int g, int b, int sr, int sg, int sb, int flags)
{
if ((unsigned)palnum >= MAXPALOOKUPS) return;
auto &lookup = tables[palnum];
lookup.tintColor = PalEntry(r, g, b);
lookup.tintShade = PalEntry(sr, sg, sb);
if (flags == 0) flags = TINTF_COLORIZE;
lookup.tintFlags = flags | TINTF_ENABLE;
}
//==========================================================================
//
// todo: everything below belongs elsewhere. Move it out
//
//==========================================================================
#include "v_2ddrawer.h"
uint8_t curbasepal;
2022-08-03 13:50:27 +00:00
int32_t r_scenebrightness;
PalEntry palfadergb;
static int32_t tint_blood_r = 0, tint_blood_g = 0, tint_blood_b = 0;
glblend_t glblend[MAXBLENDTABS];
void videoSetPalette(int palid)
2020-05-23 12:40:54 +00:00
{
curbasepal = (GPalette.GetTranslation(Translation_BasePalettes, palid) == nullptr)? 0 : palid;
2020-05-23 12:40:54 +00:00
}
//==========================================================================
//
2020-05-23 12:40:54 +00:00
// map Build blend definitions to actual render style / alpha combos.
//
2020-05-23 12:40:54 +00:00
//==========================================================================
glblend_t const nullglblend =
{
2020-05-23 12:40:54 +00:00
{
{ 1.f, STYLEALPHA_One, STYLEALPHA_Zero, 0 },
{ 1.f, STYLEALPHA_One, STYLEALPHA_Zero, 0 },
},
};
glblend_t const defaultglblend =
{
{
{ 2.f / 3.f, STYLEALPHA_Src, STYLEALPHA_InvSrc, 0 },
{ 1.f / 3.f, STYLEALPHA_Src, STYLEALPHA_InvSrc, 0 },
},
};
2020-05-23 12:40:54 +00:00
FRenderStyle GetRenderStyle(int blend, int def)
{
FRenderStyle rs;
rs.BlendOp = STYLEOP_Add;
auto glbdef = &glblend[blend].def[def];
rs.SrcAlpha = glbdef->src;
rs.DestAlpha = glbdef->dst;
rs.Flags = 0;
return rs;
}
2020-05-23 12:40:54 +00:00
float GetAlphaFromBlend(uint32_t method, uint32_t blend)
2020-04-12 05:44:55 +00:00
{
2020-05-23 12:40:54 +00:00
return method == DAMETH_TRANS1 || method == DAMETH_TRANS2 ? glblend[blend].def[method - DAMETH_TRANS1].alpha : 1.f;
}
//==========================================================================
//
// Fullscreen blend effects
//
//==========================================================================
void DrawFullscreenBlends()
{
// These get prepended to the 2D drawer so they must be submitted in reverse order of drawing.
if (tint_blood_r | tint_blood_g | tint_blood_b)
{
PalEntry color2(255, max(-tint_blood_r, 0), max(-tint_blood_g, 0), max(-tint_blood_b, 0));
twod->AddColorOnlyQuad(0, 0, twod->GetWidth(), twod->GetHeight(), color2, &LegacyRenderStyles[STYLE_Subtract], true);
PalEntry color(255, max(tint_blood_r, 0), max(tint_blood_g, 0), max(tint_blood_b, 0));
twod->AddColorOnlyQuad(0, 0, twod->GetWidth(), twod->GetHeight(), color, &LegacyRenderStyles[STYLE_Add], true);
}
if (palfadergb.a > 0)
{
twod->AddColorOnlyQuad(0, 0, twod->GetWidth(), twod->GetHeight(), palfadergb, &LegacyRenderStyles[STYLE_Translucent], true);
}
}
void videoTintBlood(int32_t r, int32_t g, int32_t b)
{
tint_blood_r = r;
tint_blood_g = g;
tint_blood_b = b;
}