- redid font translation so that it doesn't need to crush the font characters' color set to the base palette.

Right now it creates a special type of luminance translation that can operate on a true color bitmap.
This commit is contained in:
Christoph Oelckers 2021-05-24 13:16:50 +02:00
parent 9382a62aa1
commit 0bab333f36
9 changed files with 189 additions and 44 deletions

View file

@ -32,7 +32,7 @@ struct FRemapTable
PalEntry Palette[256]; // The ideal palette this maps to
int crc32;
int Index;
int NumEntries; // # of elements in this table (usually 256)
int NumEntries; // # of elements in this table (usually 256), if this is -1 it is a luminosity translation.
bool Inactive = false; // This table is inactive and should be treated as if it was passed as NULL
bool TwodOnly = false; // Only used for 2D rendering
bool ForFont = false; // Mark font translations because they may require different handling than the ones for sprites-
@ -70,12 +70,26 @@ inline constexpr uint32_t TRANSLATION(uint8_t a, uint32_t b)
{
return (a << TRANSLATION_SHIFT) | b;
}
inline constexpr uint32_t LuminosityTranslation(int range, uint8_t min, uint8_t max)
{
// ensure that the value remains positive.
return ( (1 << 30) | ((range&0x3fff) << 16) | (min << 8) | max );
}
inline constexpr bool IsLuminosityTranslation(int trans)
{
return trans > 0 && (trans & (1 << 30));
}
inline constexpr int GetTranslationType(uint32_t trans)
{
assert(!IsLuminosityTranslation(trans));
return (trans & TRANSLATIONTYPE_MASK) >> TRANSLATION_SHIFT;
}
inline constexpr int GetTranslationIndex(uint32_t trans)
{
assert(!IsLuminosityTranslation(trans));
return (trans & TRANSLATION_MASK);
}

View file

@ -458,7 +458,7 @@ void FFont::ReadSheetFont(TArray<FolderEntry> &folderdata, int width, int height
// Move the Windows-1252 characters to their proper place.
for (int i = 0x80; i < 0xa0; i++)
{
if (win1252map[i - 0x80] != i && Chars[i - minchar].TranslatedPic != nullptr && Chars[win1252map[i - 0x80] - minchar].TranslatedPic == nullptr)
if (win1252map[i - 0x80] != i && Chars[i - minchar].OriginalPic != nullptr && Chars[win1252map[i - 0x80] - minchar].OriginalPic == nullptr)
{
std::swap(Chars[i - minchar], Chars[win1252map[i - 0x80] - minchar]);
}
@ -509,24 +509,24 @@ void FFont::CheckCase()
}
if (myislower(chr))
{
if (Chars[i].TranslatedPic != nullptr) lowercount++;
if (Chars[i].OriginalPic != nullptr) lowercount++;
}
else
{
if (Chars[i].TranslatedPic != nullptr) uppercount++;
if (Chars[i].OriginalPic != nullptr) uppercount++;
}
}
if (lowercount == 0) return; // This is an uppercase-only font and we are done.
// The ß needs special treatment because it is far more likely to be supplied lowercase only, even in an uppercase font.
if (Chars[0xdf - FirstChar].TranslatedPic != nullptr)
if (Chars[0xdf - FirstChar].OriginalPic != nullptr)
{
if (LastChar < 0x1e9e)
{
Chars.Resize(0x1e9f - FirstChar);
LastChar = 0x1e9e;
}
if (Chars[0x1e9e - FirstChar].TranslatedPic == nullptr)
if (Chars[0x1e9e - FirstChar].OriginalPic == nullptr)
{
std::swap(Chars[0xdf - FirstChar], Chars[0x1e9e - FirstChar]);
lowercount--;
@ -705,7 +705,7 @@ static int compare (const void *arg1, const void *arg2)
//
//==========================================================================
int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_t *reverse, TArray<double> &Luminosity)
int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_t *reverse, TArray<double> &Luminosity, int* minlum, int* maxlum)
{
double min, max, diver;
int i, j;
@ -744,6 +744,8 @@ int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_
{
Luminosity[i] = (Luminosity[i] - min) * diver;
}
if (minlum) *minlum = int(min);
if (maxlum) *maxlum = int(max);
return j;
}
@ -889,7 +891,7 @@ int FFont::GetCharCode(int code, bool needpic) const
// regular chars turn negative when the 8th bit is set.
code &= 255;
}
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].OriginalPic != nullptr))
{
return code;
}
@ -903,7 +905,7 @@ int FFont::GetCharCode(int code, bool needpic) const
if (myislower(code))
{
code = upperforlower[code];
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].OriginalPic != nullptr))
{
return code;
}
@ -912,7 +914,7 @@ int FFont::GetCharCode(int code, bool needpic) const
while ((newcode = stripaccent(code)) != code)
{
code = newcode;
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].OriginalPic != nullptr))
{
return code;
}
@ -926,7 +928,7 @@ int FFont::GetCharCode(int code, bool needpic) const
while ((newcode = stripaccent(code)) != code)
{
code = newcode;
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].OriginalPic != nullptr))
{
return code;
}
@ -944,7 +946,7 @@ int FFont::GetCharCode(int code, bool needpic) const
while ((newcode = stripaccent(code)) != code)
{
code = newcode;
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].TranslatedPic != nullptr))
if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].OriginalPic != nullptr))
{
return code;
}
@ -979,7 +981,7 @@ FGameTexture *FFont::GetChar (int code, int translation, int *const width, bool
if (code < 0) return nullptr;
if ((translation == CR_UNTRANSLATED || translation == CR_UNDEFINED) && !forceremap)
if ((translation == CR_UNTRANSLATED || translation == CR_UNDEFINED || translation >= NumTextColors) && !forceremap)
{
bool redirect = Chars[code].OriginalPic && Chars[code].OriginalPic != Chars[code].TranslatedPic;
if (redirected) *redirected = redirect;
@ -990,6 +992,11 @@ FGameTexture *FFont::GetChar (int code, int translation, int *const width, bool
}
}
if (redirected) *redirected = false;
if (IsLuminosityTranslation(Translations[translation]))
{
assert(Chars[code].OriginalPic->GetUseType() == ETextureType::FontChar);
return Chars[code].OriginalPic;
}
assert(Chars[code].TranslatedPic->GetUseType() == ETextureType::FontChar);
return Chars[code].TranslatedPic;
}
@ -1177,26 +1184,25 @@ void FFont::LoadTranslations()
for (unsigned int i = 0; i < count; i++)
{
if (Chars[i].TranslatedPic)
if (Chars[i].OriginalPic)
{
FFontChar1 *pic = static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetTexture()->GetImage());
if (pic)
{
pic->SetSourceRemap(nullptr); // Force the FFontChar1 to return the same pixels as the base texture
RecordTextureColors(pic, usedcolors);
}
auto pic = Chars[i].OriginalPic->GetTexture()->GetImage();
if (pic) RecordTextureColors(pic, usedcolors);
}
}
ActiveColors = SimpleTranslation (usedcolors, PatchRemap, identity, Luminosity);
int minlum = 0, maxlum = 0;
ActiveColors = SimpleTranslation (usedcolors, PatchRemap, identity, Luminosity, &minlum, &maxlum);
for (unsigned int i = 0; i < count; i++)
// Here we can set everything to a luminosity translation.
// Create different translations for different color ranges
Translations.Resize(NumTextColors);
for (int i = 0; i < NumTextColors; i++)
{
if(Chars[i].TranslatedPic)
static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetTexture()->GetImage())->SetSourceRemap(PatchRemap);
if (i == CR_UNTRANSLATED) Translations[i] = 0;
else Translations[i] = LuminosityTranslation(i*2, minlum, maxlum);
}
BuildTranslations (Luminosity.Data(), identity, &TranslationParms[TranslationType][0], ActiveColors, nullptr);
}
//==========================================================================

View file

@ -161,14 +161,10 @@ void FSpecialFont::LoadTranslations()
for (i = 0; i < count; i++)
{
if (Chars[i].TranslatedPic)
if (Chars[i].OriginalPic)
{
FFontChar1 *pic = static_cast<FFontChar1 *>(Chars[i].TranslatedPic->GetTexture()->GetImage());
if (pic)
{
pic->SetSourceRemap(nullptr); // Force the FFontChar1 to return the same pixels as the base texture
RecordTextureColors(pic, usedcolors);
}
auto pic = Chars[i].OriginalPic->GetTexture()->GetImage();
if (pic) RecordTextureColors(pic, usedcolors);
}
}

View file

@ -586,6 +586,107 @@ EColorRange V_FindFontColor (FName name)
return CR_UNTRANSLATED;
}
//==========================================================================
//
// CreateLuminosityTranslationRanges
//
// Create universal remap ranges for hardware rendering.
//
//==========================================================================
static PalEntry* paletteptr;
static void CreateLuminosityTranslationRanges()
{
paletteptr = (PalEntry*)ImageArena.Alloc(256 * ((NumTextColors * 2)) * sizeof(PalEntry));
for (int l = 0; l < 2; l++)
{
auto parmstart = &TranslationParms[l][0];
// Put the data into the image arena where it gets deleted with the rest of the texture data.
for (int p = 0; p < NumTextColors; p++)
{
// Intended storage order is Range 1, variant 1 - Range 1, variant 2, Range 2, variant 1, and so on.
// The storage of the ranges forces us to go through this differently...
PalEntry* palette = paletteptr + p * 512 + l * 256;
for (int v = 0; v < 256; v++)
{
palette[v].b = palette[v].g = palette[v].r = (uint8_t)v;
}
if (p != CR_UNTRANSLATED) // This table skips the untranslated entry. Do I need to say that the stored data format is garbage? >)
{
for (int v = 0; v < 256; v++)
{
// Find the color range that this luminosity value lies within.
const TranslationParm* parms = parmstart - 1;
do
{
parms++;
if (parms->RangeStart <= v && parms->RangeEnd >= v)
break;
} while (parms[1].RangeStart > parms[0].RangeEnd);
// Linearly interpolate to find out which color this luminosity level gets.
int rangev = ((v - parms->RangeStart) << 8) / (parms->RangeEnd - parms->RangeStart);
int r = ((parms->Start[0] << 8) + rangev * (parms->End[0] - parms->Start[0])) >> 8; // red
int g = ((parms->Start[1] << 8) + rangev * (parms->End[1] - parms->Start[1])) >> 8; // green
int b = ((parms->Start[2] << 8) + rangev * (parms->End[2] - parms->Start[2])) >> 8; // blue
palette[v].r = (uint8_t)clamp(r, 0, 255);
palette[v].g = (uint8_t)clamp(g, 0, 255);
palette[v].b = (uint8_t)clamp(b, 0, 255);
}
// Advance to the next color range.
while (parmstart[1].RangeStart > parmstart[0].RangeEnd)
{
parmstart++;
}
parmstart++;
}
}
}
}
//==========================================================================
//
// V_ApplyLuminosityTranslation
//
// Applies the translation to a bitmap for texture generation.
//
//==========================================================================
void V_ApplyLuminosityTranslation(int translation, uint8_t* pixel, int size)
{
int colorrange = (translation >> 16) & 0x3fff;
if (colorrange >= NumTextColors * 2) return;
int lum_min = (translation >> 8) & 0xff;
int lum_max = translation & 0xff;
int lum_range = (lum_max - lum_min + 1);
PalEntry* remap = paletteptr + colorrange * 256;
for (int i = 0; i < size; i++, pixel += 4)
{
// we must also process the transparent pixels here to ensure proper filtering on the characters' edges.
int gray = PalEntry(255, pixel[2], pixel[1], pixel[0]).Luminance();
int lumadjust = (gray - lum_min) * 255 / lum_range;
int index = clamp(lumadjust, 0, 255);
PalEntry newcol = remap[index];
// extend the range if we find colors outside what initial analysis provided.
if (gray < lum_min)
{
newcol.r = newcol.r * gray / lum_min;
newcol.g = newcol.g * gray / lum_min;
newcol.b = newcol.b * gray / lum_min;
}
else if (gray > lum_max)
{
newcol.r = clamp(newcol.r * gray / lum_max, 0, 255);
newcol.g = clamp(newcol.g * gray / lum_max, 0, 255);
newcol.b = clamp(newcol.b * gray / lum_max, 0, 255);
}
pixel[0] = newcol.b;
pixel[1] = newcol.g;
pixel[2] = newcol.r;
}
}
//==========================================================================
//
// V_LogColorFromColorRange
@ -676,6 +777,7 @@ EColorRange V_ParseFontColor (const uint8_t *&color_value, int normalcolor, int
void V_InitFonts()
{
CreateLuminosityTranslationRanges();
V_InitCustomFonts();
FFont *CreateHexLumpFont(const char *fontname, int lump);

View file

@ -142,7 +142,7 @@ protected:
void FixXMoves();
static int SimpleTranslation (uint32_t *colorsused, uint8_t *translation,
uint8_t *identity, TArray<double> &Luminosity);
uint8_t *identity, TArray<double> &Luminosity, int* minlum = nullptr, int* maxlum = nullptr);
void ReadSheetFont(TArray<FolderEntry> &folderdata, int width, int height, const DVector2 &Scale);
@ -193,5 +193,6 @@ FFont *V_GetFont(const char *fontname, const char *fontlumpname = nullptr);
void V_InitFontColors();
char* CleanseString(char* str);
void V_LoadTranslations();
class FBitmap;

View file

@ -198,6 +198,11 @@ public:
return Pitch;
}
int GetBufferSize() const
{
return Pitch * Height;
}
const uint8_t *GetPixels() const
{
return data;

View file

@ -65,8 +65,16 @@ private:
// This is needed for allowing the client to allocate slots that aren't matched to a palette, e.g. Build's indexed variants.
if (translation >= 0)
{
auto remap = GPalette.TranslationToTable(translation);
translation = remap == nullptr ? 0 : remap->Index;
if (!IsLuminosityTranslation(translation))
{
auto remap = GPalette.TranslationToTable(translation);
translation = remap == nullptr ? 0 : remap->Index;
}
else
{
// only needs to preserve the color range plus an identifier for marking this a luminosity translation.
translation = ((translation >> 16) & 0x3fff) | 0xff0000;
}
}
else translation &= ~0x7fffffff;

View file

@ -321,6 +321,7 @@ bool FTexture::ProcessData(unsigned char* buffer, int w, int h, bool ispatch)
// Initializes the buffer for the texture data
//
//===========================================================================
void V_ApplyLuminosityTranslation(int translation, uint8_t *buffer, int size);
FTextureBuffer FTexture::CreateTexBuffer(int translation, int flags)
{
@ -356,7 +357,7 @@ FTextureBuffer FTexture::CreateTexBuffer(int translation, int flags)
buffer = new unsigned char[W * (H + 1) * 4];
memset(buffer, 0, W * (H + 1) * 4);
auto remap = translation <= 0 ? nullptr : GPalette.TranslationToTable(translation);
auto remap = translation <= 0 || IsLuminosityTranslation(translation) ? nullptr : GPalette.TranslationToTable(translation);
if (remap && remap->Inactive) remap = nullptr;
if (remap) translation = remap->Index;
FBitmap bmp(buffer, W * 4, W, H);
@ -364,6 +365,10 @@ FTextureBuffer FTexture::CreateTexBuffer(int translation, int flags)
int trans;
auto Pixels = GetBgraBitmap(remap ? remap->Palette : nullptr, &trans);
bmp.Blit(exx, exx, Pixels);
if (IsLuminosityTranslation(translation))
{
V_ApplyLuminosityTranslation(translation, buffer, W * H);
}
if (remap == nullptr)
{

View file

@ -331,21 +331,29 @@ bool PickTexture(FRenderState *state, FGameTexture* tex, int paletteid, TextureP
{
if (!tex->isValid() || tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0) return false;
int usepalette = paletteid == 0? 0 : GetTranslationType(paletteid) - Translation_Remap;
int usepalswap = GetTranslationIndex(paletteid);
int usepalette = 0, useremap = 0;
if (!IsLuminosityTranslation(paletteid))
{
usepalette = paletteid == 0 ? 0 : GetTranslationType(paletteid) - Translation_Remap;
useremap = GetTranslationIndex(paletteid);
}
bool foggy = state && (state->GetFogColor() & 0xffffff);
int TextureType = hw_int_useindexedcolortextures && !foggy? TT_INDEXED : TT_TRUECOLOR;
pick.translation = paletteid;
pick.basepalTint = 0xffffff;
auto& h = lookups.tables[usepalswap];
auto& h = lookups.tables[useremap];
bool applytint = false;
// Canvas textures must be treated like hightile replacements in the following code.
int hipalswap = usepalette >= 0 ? usepalswap : 0;
int hipalswap = usepalette >= 0 ? useremap : 0;
auto rep = (hw_hightile && !(h.tintFlags & TINTF_ALWAYSUSEART)) ? FindReplacement(tex->GetID(), hipalswap, false) : nullptr;
if (rep || tex->GetTexture()->isHardwareCanvas())
if (IsLuminosityTranslation(paletteid))
{
// For a luminosity translation we only want the plain texture as-is.
}
else if (rep || tex->GetTexture()->isHardwareCanvas())
{
if (usepalette > 0)
{
@ -371,9 +379,9 @@ bool PickTexture(FRenderState *state, FGameTexture* tex, int paletteid, TextureP
if (h.tintFlags & (TINTF_ALWAYSUSEART | TINTF_USEONART))
{
applytint = true;
if (!(h.tintFlags & TINTF_APPLYOVERPALSWAP)) usepalswap = 0;
if (!(h.tintFlags & TINTF_APPLYOVERPALSWAP)) useremap = 0;
}
pick.translation = paletteid == 0? 0 : TRANSLATION(usepalette + Translation_Remap, usepalswap);
pick.translation = paletteid == 0? 0 : TRANSLATION(usepalette + Translation_Remap, useremap);
}
else pick.translation |= 0x80000000;
}