diff --git a/source/common/engine/palettecontainer.h b/source/common/engine/palettecontainer.h index cac902929..c284ae7f8 100644 --- a/source/common/engine/palettecontainer.h +++ b/source/common/engine/palettecontainer.h @@ -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); } diff --git a/source/common/fonts/font.cpp b/source/common/fonts/font.cpp index 755ef1129..d8dbc5045 100644 --- a/source/common/fonts/font.cpp +++ b/source/common/fonts/font.cpp @@ -458,7 +458,7 @@ void FFont::ReadSheetFont(TArray &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 &Luminosity) +int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_t *reverse, TArray &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(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(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); } //========================================================================== diff --git a/source/common/fonts/specialfont.cpp b/source/common/fonts/specialfont.cpp index 637cbf866..d57efda02 100644 --- a/source/common/fonts/specialfont.cpp +++ b/source/common/fonts/specialfont.cpp @@ -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(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); } } diff --git a/source/common/fonts/v_font.cpp b/source/common/fonts/v_font.cpp index 7f54e7bf1..f568de7ef 100644 --- a/source/common/fonts/v_font.cpp +++ b/source/common/fonts/v_font.cpp @@ -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); diff --git a/source/common/fonts/v_font.h b/source/common/fonts/v_font.h index 37bf049a3..747ae71c8 100644 --- a/source/common/fonts/v_font.h +++ b/source/common/fonts/v_font.h @@ -142,7 +142,7 @@ protected: void FixXMoves(); static int SimpleTranslation (uint32_t *colorsused, uint8_t *translation, - uint8_t *identity, TArray &Luminosity); + uint8_t *identity, TArray &Luminosity, int* minlum = nullptr, int* maxlum = nullptr); void ReadSheetFont(TArray &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; diff --git a/source/common/textures/bitmap.h b/source/common/textures/bitmap.h index 5ee574b7f..36d520771 100644 --- a/source/common/textures/bitmap.h +++ b/source/common/textures/bitmap.h @@ -198,6 +198,11 @@ public: return Pitch; } + int GetBufferSize() const + { + return Pitch * Height; + } + const uint8_t *GetPixels() const { return data; diff --git a/source/common/textures/hw_texcontainer.h b/source/common/textures/hw_texcontainer.h index c36eea38a..91afa526f 100644 --- a/source/common/textures/hw_texcontainer.h +++ b/source/common/textures/hw_texcontainer.h @@ -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; diff --git a/source/common/textures/texture.cpp b/source/common/textures/texture.cpp index 503657f00..6d24f1154 100644 --- a/source/common/textures/texture.cpp +++ b/source/common/textures/texture.cpp @@ -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) { diff --git a/source/core/textures/hightile.cpp b/source/core/textures/hightile.cpp index 87d8eaf7c..85a809016 100644 --- a/source/core/textures/hightile.cpp +++ b/source/core/textures/hightile.cpp @@ -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; }