From 5f02b92cd0843026a6bc20743915333041c96cd7 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 24 May 2021 21:24:17 +0200 Subject: [PATCH] - font system overhaul. This eliminates nearly all palette dependencies, most importantly font translation will now be done on True Color data, making translations on True Color font less destructive. --- src/common/2d/v_drawtext.cpp | 15 +- src/common/engine/palettecontainer.h | 14 + src/common/fonts/font.cpp | 314 ++++------------------ src/common/fonts/fontinternals.h | 1 - src/common/fonts/hexfont.cpp | 128 ++++----- src/common/fonts/singlelumpfont.cpp | 221 ++++----------- src/common/fonts/singlepicfont.cpp | 6 +- src/common/fonts/specialfont.cpp | 110 +++----- src/common/fonts/v_font.cpp | 207 +++++++++++++- src/common/fonts/v_font.h | 15 +- src/common/textures/bitmap.h | 5 + src/common/textures/formats/fontchars.cpp | 98 ++----- src/common/textures/formats/fontchars.h | 25 +- src/common/textures/hw_texcontainer.h | 12 +- src/common/textures/texture.cpp | 7 +- 15 files changed, 479 insertions(+), 699 deletions(-) diff --git a/src/common/2d/v_drawtext.cpp b/src/common/2d/v_drawtext.cpp index 9a778eb13..97d418fd7 100644 --- a/src/common/2d/v_drawtext.cpp +++ b/src/common/2d/v_drawtext.cpp @@ -173,9 +173,8 @@ void DrawChar(F2DDrawer *drawer, FFont* font, int normalcolor, double x, double FGameTexture* pic; int dummy; - bool redirected; - if (NULL != (pic = font->GetChar(character, normalcolor, &dummy, &redirected))) + if (NULL != (pic = font->GetChar(character, normalcolor, &dummy))) { DrawParms parms; Va_List tags; @@ -188,7 +187,7 @@ void DrawChar(F2DDrawer *drawer, FFont* font, int normalcolor, double x, double } bool palettetrans = (normalcolor == CR_UNDEFINED && parms.TranslationId != 0); PalEntry color = 0xffffffff; - if (!palettetrans) parms.TranslationId = redirected ? -1 : font->GetColorTranslation((EColorRange)normalcolor, &color); + if (!palettetrans) parms.TranslationId = font->GetColorTranslation((EColorRange)normalcolor, &color); parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255); drawer->AddTexture(pic, parms); } @@ -204,9 +203,8 @@ void DrawChar(F2DDrawer *drawer, FFont *font, int normalcolor, double x, double FGameTexture *pic; int dummy; - bool redirected; - if (NULL != (pic = font->GetChar(character, normalcolor, &dummy, &redirected))) + if (NULL != (pic = font->GetChar(character, normalcolor, &dummy))) { DrawParms parms; uint32_t tag = ListGetInt(args); @@ -214,7 +212,7 @@ void DrawChar(F2DDrawer *drawer, FFont *font, int normalcolor, double x, double if (!res) return; bool palettetrans = (normalcolor == CR_UNDEFINED && parms.TranslationId != 0); PalEntry color = 0xffffffff; - if (!palettetrans) parms.TranslationId = redirected ? -1 : font->GetColorTranslation((EColorRange)normalcolor, &color); + if (!palettetrans) parms.TranslationId = font->GetColorTranslation((EColorRange)normalcolor, &color); parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255); drawer->AddTexture(pic, parms); } @@ -316,11 +314,10 @@ void DrawTextCommon(F2DDrawer *drawer, FFont *font, int normalcolor, double x, d continue; } - bool redirected = false; - if (NULL != (pic = font->GetChar(c, currentcolor, &w, &redirected))) + if (NULL != (pic = font->GetChar(c, currentcolor, &w))) { // if palette translation is used, font colors will be ignored. - if (!palettetrans) parms.TranslationId = redirected? -1 : trans; + if (!palettetrans) parms.TranslationId = trans; SetTextureParms(drawer, &parms, pic, cx, cy); if (parms.cellx) { diff --git a/src/common/engine/palettecontainer.h b/src/common/engine/palettecontainer.h index cac902929..1103f5c34 100644 --- a/src/common/engine/palettecontainer.h +++ b/src/common/engine/palettecontainer.h @@ -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/src/common/fonts/font.cpp b/src/common/fonts/font.cpp index 755ef1129..8421ab92b 100644 --- a/src/common/fonts/font.cpp +++ b/src/common/fonts/font.cpp @@ -83,7 +83,6 @@ FFont::FFont (const char *name, const char *nametemplate, const char *filetempla Next = FirstFont; FirstFont = this; Cursor = '_'; - ActiveColors = 0; SpaceWidth = 0; FontHeight = 0; uint8_t pp = 0; @@ -333,23 +332,13 @@ FFont::FFont (const char *name, const char *nametemplate, const char *filetempla TexMan.AddGameTexture(tex); Chars[i].OriginalPic = tex; - if (!noTranslate) - { - Chars[i].TranslatedPic = MakeGameTexture(new FImageTexture(new FFontChar1(orig->GetImage())), nullptr, ETextureType::FontChar); - Chars[i].TranslatedPic->CopySize(pic, true); - TexMan.AddGameTexture(Chars[i].TranslatedPic); - } - else - { - Chars[i].TranslatedPic = tex; - } - if (sysCallbacks.FontCharCreated) sysCallbacks.FontCharCreated(pic, Chars[i].OriginalPic, Chars[i].TranslatedPic); + if (sysCallbacks.FontCharCreated) sysCallbacks.FontCharCreated(pic, Chars[i].OriginalPic, Chars[i].OriginalPic); - Chars[i].XMove = (int)Chars[i].TranslatedPic->GetDisplayWidth(); + Chars[i].XMove = (int)Chars[i].OriginalPic->GetDisplayWidth(); } else { - Chars[i].TranslatedPic = nullptr; + Chars[i].OriginalPic = nullptr; Chars[i].XMove = INT_MIN; } } @@ -360,7 +349,7 @@ FFont::FFont (const char *name, const char *nametemplate, const char *filetempla { SpaceWidth = spacewidth; } - else if ('N' - FirstChar >= 0 && 'N' - FirstChar < count && Chars['N' - FirstChar].TranslatedPic != nullptr) + else if ('N' - FirstChar >= 0 && 'N' - FirstChar < count && Chars['N' - FirstChar].OriginalPic != nullptr) { SpaceWidth = (Chars['N' - FirstChar].XMove + 1) / 2; } @@ -444,11 +433,7 @@ void FFont::ReadSheetFont(TArray &folderdata, int width, int height Chars[i].OriginalPic = (*lump)->GetUseType() == ETextureType::FontChar? (*lump) : MakeGameTexture(pic, nullptr, ETextureType::FontChar); Chars[i].OriginalPic->SetUseType(ETextureType::FontChar); Chars[i].OriginalPic->CopySize(*lump, true); - Chars[i].TranslatedPic = MakeGameTexture(new FImageTexture(new FFontChar1(pic->GetImage())), nullptr, ETextureType::FontChar); - Chars[i].TranslatedPic->CopySize(*lump, true); - Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar); if (Chars[i].OriginalPic != *lump) TexMan.AddGameTexture(Chars[i].OriginalPic); - TexMan.AddGameTexture(Chars[i].TranslatedPic); } Chars[i].XMove = width; } @@ -458,7 +443,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 +494,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--; @@ -596,75 +581,14 @@ void FFont::RecordAllTextureColors(uint32_t *usedcolors) { for (unsigned int i = 0; i < Chars.Size(); i++) { - if (Chars[i].TranslatedPic) + if (Chars[i].OriginalPic) { - FFontChar1 *pic = static_cast(Chars[i].TranslatedPic->GetTexture()->GetImage()); - if (pic) - { - // The remap must be temporarily reset here because this can be called on an initialized font. - auto sr = pic->ResetSourceRemap(); - RecordTextureColors(pic, usedcolors); - pic->SetSourceRemap(sr); - } + auto pic = Chars[i].OriginalPic->GetTexture()->GetImage(); + if (pic) RecordTextureColors(pic, usedcolors); } } } -//========================================================================== -// -// SetDefaultTranslation -// -// Builds a translation to map the stock font to a mod provided replacement. -// -//========================================================================== - -void FFont::SetDefaultTranslation(uint32_t *othercolors) -{ - uint32_t mycolors[256] = {}; - RecordAllTextureColors(mycolors); - - uint8_t mytranslation[256], othertranslation[256], myreverse[256], otherreverse[256]; - TArray myluminosity, otherluminosity; - - SimpleTranslation(mycolors, mytranslation, myreverse, myluminosity); - SimpleTranslation(othercolors, othertranslation, otherreverse, otherluminosity); - - FRemapTable remap(ActiveColors); - remap.Remap[0] = 0; - remap.Palette[0] = 0; - remap.ForFont = true; - - for (unsigned l = 1; l < myluminosity.Size(); l++) - { - for (unsigned o = 1; o < otherluminosity.Size()-1; o++) // luminosity[0] is for the transparent color - { - if (myluminosity[l] >= otherluminosity[o] && myluminosity[l] <= otherluminosity[o+1]) - { - PalEntry color1 = GPalette.BaseColors[otherreverse[o]]; - PalEntry color2 = GPalette.BaseColors[otherreverse[o+1]]; - double weight = 0; - if (otherluminosity[o] != otherluminosity[o + 1]) - { - weight = (myluminosity[l] - otherluminosity[o]) / (otherluminosity[o + 1] - otherluminosity[o]); - } - int r = int(color1.r + weight * (color2.r - color1.r)); - int g = int(color1.g + weight * (color2.g - color1.g)); - int b = int(color1.b + weight * (color2.b - color1.b)); - - r = clamp(r, 0, 255); - g = clamp(g, 0, 255); - b = clamp(b, 0, 255); - remap.Remap[l] = ColorMatcher.Pick(r, g, b); - remap.Palette[l] = PalEntry(255, r, g, b); - break; - } - } - } - Translations[CR_UNTRANSLATED] = GPalette.StoreTranslation(TRANSLATION_Internal, &remap); - forceremap = true; -} - - //========================================================================== // // compare @@ -688,160 +612,40 @@ static int compare (const void *arg1, const void *arg2) //========================================================================== // -// FFont :: SimpleTranslation -// -// Colorsused, translation, and reverse must all be 256 entry buffers. -// Colorsused must already be filled out. -// Translation be set to remap the source colors to a new range of -// consecutive colors based at 1 (0 is transparent). -// Reverse will be just the opposite of translation: It maps the new color -// range to the original colors. -// *Luminosity will be an array just large enough to hold the brightness -// levels of all the used colors, in consecutive order. It is sorted from -// darkest to lightest and scaled such that the darkest color is 0.0 and -// the brightest color is 1.0. -// The return value is the number of used colors and thus the number of -// entries in *luminosity. +// FFont :: GetLuminosity // //========================================================================== -int FFont::SimpleTranslation (uint32_t *colorsused, uint8_t *translation, uint8_t *reverse, TArray &Luminosity) +int FFont::GetLuminosity (uint32_t *colorsused, TArray &Luminosity, int* minlum, int* maxlum) { double min, max, diver; - int i, j; - memset (translation, 0, 256); - - reverse[0] = 0; - for (i = 1, j = 1; i < 256; i++) - { - if (colorsused[i]) - { - reverse[j++] = i; - } - } - - qsort (reverse+1, j-1, 1, compare); - - Luminosity.Resize(j); + Luminosity.Resize(256); Luminosity[0] = 0.0; // [BL] Prevent uninitalized memory max = 0.0; min = 100000000.0; - for (i = 1; i < j; i++) + for (int i = 1; i < 256; i++) { - translation[reverse[i]] = i; - - Luminosity[i] = RPART(GPalette.BaseColors[reverse[i]]) * 0.299 + - GPART(GPalette.BaseColors[reverse[i]]) * 0.587 + - BPART(GPalette.BaseColors[reverse[i]]) * 0.114; - if (Luminosity[i] > max) - max = Luminosity[i]; - if (Luminosity[i] < min) - min = Luminosity[i]; + if (colorsused[i]) + { + Luminosity[i] = GPalette.BaseColors[i].r * 0.299 + GPalette.BaseColors[i].g * 0.587 + GPalette.BaseColors[i].b * 0.114; + if (Luminosity[i] > max) max = Luminosity[i]; + if (Luminosity[i] < min) min = Luminosity[i]; + } + else Luminosity[i] = -1; // this color is not of interest. } diver = 1.0 / (max - min); - for (i = 1; i < j; i++) + for (int i = 1; i < 256; i++) { - Luminosity[i] = (Luminosity[i] - min) * diver; + if (colorsused[i]) + { + Luminosity[i] = (Luminosity[i] - min) * diver; + } } + if (minlum) *minlum = int(min); + if (maxlum) *maxlum = int(max); - return j; -} - -//========================================================================== -// -// FFont :: BuildTranslations -// -// Build color translations for this font. Luminosity is an array of -// brightness levels. The ActiveColors member must be set to indicate how -// large this array is. Identity is an array that remaps the colors to -// their original values; it is only used for CR_UNTRANSLATED. Ranges -// is an array of TranslationParm structs defining the ranges for every -// possible color, in order. Palette is the colors to use for the -// untranslated version of the font. -// -//========================================================================== - -void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity, - const void *ranges, int total_colors, const PalEntry *palette, std::function post) -{ - int i, j; - const TranslationParm *parmstart = (const TranslationParm *)ranges; - - FRemapTable remap(total_colors); - remap.ForFont = true; - - // Create different translations for different color ranges - Translations.Clear(); - for (i = 0; i < NumTextColors; i++) - { - if (i == CR_UNTRANSLATED) - { - if (identity != nullptr) - { - memcpy(remap.Remap, identity, ActiveColors); - if (palette != nullptr) - { - memcpy(remap.Palette, palette, ActiveColors * sizeof(PalEntry)); - } - else - { - remap.Palette[0] = GPalette.BaseColors[identity[0]] & MAKEARGB(0, 255, 255, 255); - for (j = 1; j < ActiveColors; ++j) - { - remap.Palette[j] = GPalette.BaseColors[identity[j]] | MAKEARGB(255, 0, 0, 0); - } - } - Translations.Push(GPalette.StoreTranslation(TRANSLATION_Internal, &remap)); - } - else - { - Translations.Push(Translations[0]); - } - continue; - } - - assert(parmstart->RangeStart >= 0); - - remap.Remap[0] = 0; - remap.Palette[0] = 0; - remap.ForFont = true; - - for (j = 1; j < ActiveColors; j++) - { - int v = int(luminosity[j] * 256.0); - - // 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 - r = clamp(r, 0, 255); - g = clamp(g, 0, 255); - b = clamp(b, 0, 255); - remap.Remap[j] = ColorMatcher.Pick(r, g, b); - remap.Palette[j] = PalEntry(255,r,g,b); - } - if (post) post(&remap); - Translations.Push(GPalette.StoreTranslation(TRANSLATION_Internal, &remap)); - - // Advance to the next color range. - while (parmstart[1].RangeStart > parmstart[0].RangeEnd) - { - parmstart++; - } - parmstart++; - } + return 256; } //========================================================================== @@ -862,11 +666,10 @@ int FFont::GetColorTranslation (EColorRange range, PalEntry *color) const } if (color != nullptr) *color = retcolor; } - if (ActiveColors == 0 || range == CR_UNDEFINED) + if (range == CR_UNDEFINED) return -1; else if (range >= NumTextColors) range = CR_UNTRANSLATED; - //if (range == CR_UNTRANSLATED && !translateUntranslated) return nullptr; return Translations[range]; } @@ -889,7 +692,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 +706,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 +715,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 +729,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 +747,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; } @@ -961,7 +764,7 @@ int FFont::GetCharCode(int code, bool needpic) const // //========================================================================== -FGameTexture *FFont::GetChar (int code, int translation, int *const width, bool *redirected) const +FGameTexture *FFont::GetChar (int code, int translation, int *const width) const { code = GetCharCode(code, true); int xmove = SpaceWidth; @@ -979,19 +782,8 @@ FGameTexture *FFont::GetChar (int code, int translation, int *const width, bool if (code < 0) return nullptr; - if ((translation == CR_UNTRANSLATED || translation == CR_UNDEFINED) && !forceremap) - { - bool redirect = Chars[code].OriginalPic && Chars[code].OriginalPic != Chars[code].TranslatedPic; - if (redirected) *redirected = redirect; - if (redirect) - { - assert(Chars[code].OriginalPic->GetUseType() == ETextureType::FontChar); - return Chars[code].OriginalPic; - } - } - if (redirected) *redirected = false; - assert(Chars[code].TranslatedPic->GetUseType() == ETextureType::FontChar); - return Chars[code].TranslatedPic; + assert(Chars[code].OriginalPic->GetUseType() == ETextureType::FontChar); + return Chars[code].OriginalPic; } //========================================================================== @@ -1172,31 +964,29 @@ void FFont::LoadTranslations() { unsigned int count = LastChar - FirstChar + 1; uint32_t usedcolors[256] = {}; - uint8_t identity[256]; TArray Luminosity; 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; + GetLuminosity (usedcolors, 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 + TranslationType, minlum, maxlum); } - - BuildTranslations (Luminosity.Data(), identity, &TranslationParms[TranslationType][0], ActiveColors, nullptr); } //========================================================================== diff --git a/src/common/fonts/fontinternals.h b/src/common/fonts/fontinternals.h index dc942319e..b74750c57 100644 --- a/src/common/fonts/fontinternals.h +++ b/src/common/fonts/fontinternals.h @@ -3,7 +3,6 @@ #include #include "tarray.h" -// This structure is used by BuildTranslations() to hold color information. struct TranslationParm { short RangeStart; // First level for this range diff --git a/src/common/fonts/hexfont.cpp b/src/common/fonts/hexfont.cpp index 1c23d3bd0..705195dfd 100644 --- a/src/common/fonts/hexfont.cpp +++ b/src/common/fonts/hexfont.cpp @@ -50,6 +50,8 @@ struct HexDataSource TArray glyphdata; unsigned glyphmap[65536] = {}; + PalEntry ConsolePal[18], SmallPal[18]; + //========================================================================== // // parse a HEX font @@ -83,6 +85,17 @@ struct HexDataSource if (codepoint > LastChar) LastChar = codepoint; } } + + ConsolePal[0] = SmallPal[0] = 0; + for (int i = 1; i < 18; i++) + { + double lum = i == 1 ? 0.01 : 0.5 + (i - 2) * (0.5 / 17.); + uint8_t lumb = (uint8_t(lum * 255)); + + ConsolePal[i] = PalEntry(255, lumb, lumb, lumb); + lumb = i * 255 / 17; + SmallPal[i] = PalEntry(255, lumb, lumb, lumb); + } } }; @@ -95,6 +108,7 @@ public: FHexFontChar(uint8_t *sourcedata, int swidth, int width, int height); TArray CreatePalettedPixels(int conversion) override; + int CopyPixels(FBitmap* bmp, int conversion); protected: int SourceWidth; @@ -159,12 +173,23 @@ TArray FHexFontChar::CreatePalettedPixels(int) return Pixels; } +int FHexFontChar::CopyPixels(FBitmap* bmp, int conversion) +{ + if (conversion == luminance) conversion = normal; // luminance images have no use as an RGB source. + PalEntry* palette = hexdata.ConsolePal; + auto ppix = CreatePalettedPixels(conversion); + bmp->CopyPixelData(0, 0, ppix.Data(), Width, Height, Height, 1, 0, palette, nullptr); + return 0; + +} + class FHexFontChar2 : public FHexFontChar { public: FHexFontChar2(uint8_t *sourcedata, int swidth, int width, int height); TArray CreatePalettedPixels(int conversion) override; + int CopyPixels(FBitmap* bmp, int conversion); }; @@ -228,6 +253,15 @@ TArray FHexFontChar2::CreatePalettedPixels(int) return Pixels; } +int FHexFontChar2::CopyPixels(FBitmap* bmp, int conversion) +{ + if (conversion == luminance) conversion = normal; // luminance images have no use as an RGB source. + PalEntry* palette = hexdata.SmallPal; + auto ppix = CreatePalettedPixels(conversion); + bmp->CopyPixelData(0, 0, ppix.Data(), Width, Height, Height, 1, 0, palette, nullptr); + return 0; +} + class FHexFont : public FFont @@ -267,10 +301,9 @@ public: { auto offset = hexdata.glyphmap[i]; int size = hexdata.glyphdata[offset] / 16; - Chars[i - FirstChar].TranslatedPic = MakeGameTexture(new FImageTexture(new FHexFontChar(&hexdata.glyphdata[offset + 1], size, size * 9, 16)), nullptr, ETextureType::FontChar); - Chars[i - FirstChar].OriginalPic = Chars[i - FirstChar].TranslatedPic; + Chars[i - FirstChar].OriginalPic = MakeGameTexture(new FImageTexture(new FHexFontChar(&hexdata.glyphdata[offset + 1], size, size * 9, 16)), nullptr, ETextureType::FontChar); Chars[i - FirstChar].XMove = size * spacing; - TexMan.AddGameTexture(Chars[i - FirstChar].TranslatedPic); + TexMan.AddGameTexture(Chars[i - FirstChar].OriginalPic); } else Chars[i - FirstChar].XMove = spacing; @@ -285,18 +318,15 @@ public: void LoadTranslations() { - double luminosity[256]; + int minlum = hexdata.ConsolePal[1].r; + int maxlum = hexdata.ConsolePal[17].r; - memset (PatchRemap, 0, 256); - for (int i = 0; i < 18; i++) - { - // Create a gradient similar to the old console font. - PatchRemap[i] = i; - luminosity[i] = i == 1? 0.01 : 0.5 + (i-2) * (0.5 / 17.); + Translations.Resize(NumTextColors); + for (int i = 0; i < NumTextColors; i++) + { + if (i == CR_UNTRANSLATED) Translations[i] = 0; + else Translations[i] = LuminosityTranslation(i * 2 + 1, minlum, maxlum); } - ActiveColors = 18; - - BuildTranslations (luminosity, nullptr, &TranslationParms[1][0], ActiveColors, nullptr); } }; @@ -338,10 +368,9 @@ public: { auto offset = hexdata.glyphmap[i]; int size = hexdata.glyphdata[offset] / 16; - Chars[i - FirstChar].TranslatedPic = MakeGameTexture(new FImageTexture(new FHexFontChar2(&hexdata.glyphdata[offset + 1], size, 2 + size * 8, 18)), nullptr, ETextureType::FontChar); - Chars[i - FirstChar].OriginalPic = Chars[i - FirstChar].TranslatedPic; + Chars[i - FirstChar].OriginalPic = MakeGameTexture(new FImageTexture(new FHexFontChar2(&hexdata.glyphdata[offset + 1], size, 2 + size * 8, 18)), nullptr, ETextureType::FontChar); Chars[i - FirstChar].XMove = size * spacing; - TexMan.AddGameTexture(Chars[i - FirstChar].TranslatedPic); + TexMan.AddGameTexture(Chars[i - FirstChar].OriginalPic); } else Chars[i - FirstChar].XMove = spacing; @@ -356,71 +385,16 @@ public: void LoadTranslations() override { - double luminosity[256]; + int minlum = hexdata.SmallPal[1].r; + int maxlum = hexdata.SmallPal[17].r; - memset(PatchRemap, 0, 256); - for (int i = 0; i < 18; i++) + Translations.Resize(NumTextColors); + for (int i = 0; i < NumTextColors; i++) { - // Create a gradient similar to the old console font. - PatchRemap[i] = i; - luminosity[i] = i / 17.; + if (i == CR_UNTRANSLATED) Translations[i] = 0; + else Translations[i] = LuminosityTranslation(i * 2, minlum, maxlum); } - ActiveColors = 18; - - BuildTranslations(luminosity, nullptr, &TranslationParms[0][0], ActiveColors, nullptr); } - - void SetDefaultTranslation(uint32_t *colors) override - { - double myluminosity[18]; - - myluminosity[0] = 0; - for (int i = 1; i < 18; i++) - { - myluminosity[i] = (i - 1) / 16.; - } - - uint8_t othertranslation[256], otherreverse[256]; - TArray otherluminosity; - - SimpleTranslation(colors, othertranslation, otherreverse, otherluminosity); - - FRemapTable remap(ActiveColors); - remap.Remap[0] = 0; - remap.Palette[0] = 0; - remap.ForFont = true; - - for (unsigned l = 1; l < 18; l++) - { - for (unsigned o = 1; o < otherluminosity.Size() - 1; o++) // luminosity[0] is for the transparent color - { - if (myluminosity[l] >= otherluminosity[o] && myluminosity[l] <= otherluminosity[o + 1]) - { - PalEntry color1 = GPalette.BaseColors[otherreverse[o]]; - PalEntry color2 = GPalette.BaseColors[otherreverse[o + 1]]; - double weight = 0; - if (otherluminosity[o] != otherluminosity[o + 1]) - { - weight = (myluminosity[l] - otherluminosity[o]) / (otherluminosity[o + 1] - otherluminosity[o]); - } - int r = int(color1.r + weight * (color2.r - color1.r)); - int g = int(color1.g + weight * (color2.g - color1.g)); - int b = int(color1.b + weight * (color2.b - color1.b)); - - r = clamp(r, 0, 255); - g = clamp(g, 0, 255); - b = clamp(b, 0, 255); - remap.Remap[l] = ColorMatcher.Pick(r, g, b); - remap.Palette[l] = PalEntry(255, r, g, b); - break; - } - } - } - Translations[CR_UNTRANSLATED] = GPalette.StoreTranslation(TRANSLATION_Internal, &remap); - forceremap = true; - - } - }; diff --git a/src/common/fonts/singlelumpfont.cpp b/src/common/fonts/singlelumpfont.cpp index 3678ee441..dcded6efa 100644 --- a/src/common/fonts/singlelumpfont.cpp +++ b/src/common/fonts/singlelumpfont.cpp @@ -90,26 +90,22 @@ public: void RecordAllTextureColors(uint32_t* usedcolors) override; protected: - void CheckFON1Chars (double *luminosity); - void BuildTranslations2 (); - void FixupPalette (uint8_t *identity, double *luminosity, const uint8_t *palette, - bool rescale, PalEntry *out_palette); + void CheckFON1Chars (); + void FixupPalette (uint8_t *identity, const PalEntry *palette, int* minlum ,int* maxlum); void LoadTranslations (); void LoadFON1 (int lump, const uint8_t *data); void LoadFON2 (int lump, const uint8_t *data); void LoadBMF (int lump, const uint8_t *data); - void CreateFontFromPic (FTextureID picnum); - - static int BMFCompare(const void *a, const void *b); - + enum { FONT1, FONT2, BMFFONT } FontType; - uint8_t PaletteData[768]; + PalEntry Palette[256]; bool RescalePalette; + int ActiveColors = -1; }; @@ -160,29 +156,6 @@ FSingleLumpFont::FSingleLumpFont (const char *name, int lump) : FFont(lump) FirstFont = this; } -//========================================================================== -// -// FSingleLumpFont :: CreateFontFromPic -// -//========================================================================== - -void FSingleLumpFont::CreateFontFromPic (FTextureID picnum) -{ - auto pic = TexMan.GetGameTexture(picnum); - - FontHeight = (int)pic->GetDisplayHeight (); - SpaceWidth = (int)pic->GetDisplayWidth (); - GlobalKerning = 0; - - FirstChar = LastChar = 'A'; - Chars.Resize(1); - Chars[0].TranslatedPic = pic; - Chars[0].OriginalPic = pic; - - // Only one color range. Don't bother with the others. - ActiveColors = 0; -} - //========================================================================== // // FSingleLumpFont :: LoadTranslations @@ -191,28 +164,21 @@ void FSingleLumpFont::CreateFontFromPic (FTextureID picnum) void FSingleLumpFont::LoadTranslations() { - double luminosity[256]; uint8_t identity[256]; - PalEntry local_palette[256]; - bool useidentity = true; - bool usepalette = false; - const void* ranges; unsigned int count = LastChar - FirstChar + 1; + int minlum, maxlum; switch(FontType) { case FONT1: - useidentity = false; - ranges = &TranslationParms[1][0]; - CheckFON1Chars (luminosity); + CheckFON1Chars(); + minlum = 1; + maxlum = 255; break; case BMFFONT: case FONT2: - usepalette = true; - FixupPalette (identity, luminosity, PaletteData, RescalePalette, local_palette); - - ranges = &TranslationParms[0][0]; + FixupPalette (identity, Palette, &minlum, &maxlum); break; default: @@ -223,11 +189,16 @@ void FSingleLumpFont::LoadTranslations() for(unsigned int i = 0;i < count;++i) { - if(Chars[i].TranslatedPic) - static_cast(Chars[i].TranslatedPic->GetTexture()->GetImage())->SetSourceRemap(PatchRemap); + if(Chars[i].OriginalPic) + static_cast(Chars[i].OriginalPic->GetTexture()->GetImage())->SetSourceRemap(Palette); } - BuildTranslations (luminosity, useidentity ? identity : nullptr, ranges, ActiveColors, usepalette ? local_palette : nullptr); + Translations.Resize(NumTextColors); + for (int i = 0; i < NumTextColors; i++) + { + if (i == CR_UNTRANSLATED) Translations[i] = 0; + else Translations[i] = LuminosityTranslation(i * 2 + (FontType == FONT1 ? 1 : 0), minlum, maxlum); + } } //========================================================================== @@ -262,19 +233,20 @@ void FSingleLumpFont::LoadFON1 (int lump, const uint8_t *data) // Move the Windows-1252 characters to their proper place. for (int i = 0x80; i < 0xa0; i++) { - if (win1252map[i-0x80] != i && Chars[i].TranslatedPic != nullptr && Chars[win1252map[i - 0x80]].TranslatedPic == nullptr) + if (win1252map[i-0x80] != i && Chars[i].OriginalPic != nullptr && Chars[win1252map[i - 0x80]].OriginalPic == nullptr) { std::swap(Chars[i], Chars[win1252map[i - 0x80]]); } } + Palette[0] = 0; + for (int i = 1; i < 256; i++) Palette[i] = PalEntry(255, i, i, i); } //========================================================================== // // FSingleLumpFont :: LoadFON2 // -// FON2 is used for everything but the console font. The console font should -// probably use FON2 as well, but oh well. +// FON2 is used for everything but the console font. // //========================================================================== @@ -340,7 +312,11 @@ void FSingleLumpFont::LoadFON2 (int lump, const uint8_t *data) SpaceWidth = totalwidth * 2 / (3 * count); } - memcpy(PaletteData, palette, ActiveColors*3); + Palette[0] = 0; + for (int i = 1; i < ActiveColors; i++) + { + Palette[i] = PalEntry(255, palette[i * 3], palette[i * 3 + 1], palette[i * 3 + 2]); + } data_p = palette + ActiveColors*3; @@ -350,14 +326,12 @@ void FSingleLumpFont::LoadFON2 (int lump, const uint8_t *data) Chars[i].XMove = widths2[i]; if (destSize <= 0) { - Chars[i].TranslatedPic = nullptr; Chars[i].OriginalPic = nullptr; } else { - Chars[i].TranslatedPic = MakeGameTexture(new FImageTexture(new FFontChar2 (lump, int(data_p - data), widths2[i], FontHeight)), nullptr, ETextureType::FontChar); - Chars[i].OriginalPic = Chars[i].TranslatedPic; - TexMan.AddGameTexture(Chars[i].TranslatedPic); + Chars[i].OriginalPic = MakeGameTexture(new FImageTexture(new FFontChar2 (lump, int(data_p - data), widths2[i], FontHeight)), nullptr, ETextureType::FontChar); + TexMan.AddGameTexture(Chars[i].OriginalPic); do { int8_t code = *data_p++; @@ -396,8 +370,6 @@ void FSingleLumpFont::LoadBMF(int lump, const uint8_t *data) int numchars, count, totalwidth, nwidth; int infolen; int i, chari; - uint8_t raw_palette[256*3]; - PalEntry sort_palette[256]; FontType = BMFFONT; FontHeight = data[5]; @@ -439,31 +411,14 @@ void FSingleLumpFont::LoadBMF(int lump, const uint8_t *data) count = LastChar - FirstChar + 1; Chars.Resize(count); // BMF palettes are only six bits per component. Fix that. - for (i = 0; i < ActiveColors*3; ++i) - { - raw_palette[i+3] = (data[17 + i] << 2) | (data[17 + i] >> 4); - } - ActiveColors++; - - // Sort the palette by increasing brightness for (i = 0; i < ActiveColors; ++i) { - PalEntry *pal = &sort_palette[i]; - pal->a = i; // Use alpha part to point back to original entry - pal->r = raw_palette[i*3 + 0]; - pal->g = raw_palette[i*3 + 1]; - pal->b = raw_palette[i*3 + 2]; + int r = (data[17 + i * 3] << 2) | (data[17 + i * 3] >> 4); + int g = (data[18 + i * 3] << 2) | (data[18 + i * 3] >> 4); + int b = (data[19 + i * 3] << 2) | (data[19 + i * 3] >> 4); + Palette[i + 1] = PalEntry(255, r, g, b); // entry 0 (transparent) is not stored in the font file. } - qsort(sort_palette + 1, ActiveColors - 1, sizeof(PalEntry), BMFCompare); - - // Create the PatchRemap table from the sorted "alpha" values. - PatchRemap[0] = 0; - for (i = 1; i < ActiveColors; ++i) - { - PatchRemap[sort_palette[i].a] = i; - } - - memcpy(PaletteData, raw_palette, 768); + ActiveColors++; // Now scan through the characters again, creating glyphs for each one. for (i = chari = 0; i < numchars; ++i, chari += 6 + chardata[chari+1] * chardata[chari+2]) @@ -489,7 +444,6 @@ void FSingleLumpFont::LoadBMF(int lump, const uint8_t *data) -(int8_t)chardata[chari+3], // x offset -(int8_t)chardata[chari+4] // y offset )), nullptr, ETextureType::FontChar); - Chars[chardata[chari] - FirstChar].TranslatedPic = tex; Chars[chardata[chari] - FirstChar].OriginalPic = tex; TexMan.AddGameTexture(tex); } @@ -510,55 +464,33 @@ void FSingleLumpFont::LoadBMF(int lump, const uint8_t *data) FixXMoves(); } -//========================================================================== -// -// FSingleLumpFont :: BMFCompare STATIC -// -// Helper to sort BMF palettes. -// -//========================================================================== - -int FSingleLumpFont::BMFCompare(const void *a, const void *b) -{ - const PalEntry *pa = (const PalEntry *)a; - const PalEntry *pb = (const PalEntry *)b; - - return (pa->r * 299 + pa->g * 587 + pa->b * 114) - - (pb->r * 299 + pb->g * 587 + pb->b * 114); -} - //========================================================================== // // FSingleLumpFont :: CheckFON1Chars // // Scans a FON1 resource for all the color values it uses and sets up -// some tables like SimpleTranslation. Data points to the RLE data for +// some tables. Data points to the RLE data for // the characters. Also sets up the character textures. // //========================================================================== -void FSingleLumpFont::CheckFON1Chars (double *luminosity) +void FSingleLumpFont::CheckFON1Chars() { FileData memLump = fileSystem.ReadFile(Lump); - const uint8_t* data = (const uint8_t*) memLump.GetMem(); + const uint8_t* data = (const uint8_t*)memLump.GetMem(); + const uint8_t* data_p; - uint8_t used[256], reverse[256]; - const uint8_t *data_p; - int i, j; - - memset (used, 0, 256); data_p = data + 8; - for (i = 0; i < 256; ++i) + for (int i = 0; i < 256; ++i) { int destSize = SpaceWidth * FontHeight; - if(!Chars[i].TranslatedPic) + if (!Chars[i].OriginalPic) { - Chars[i].TranslatedPic = MakeGameTexture(new FImageTexture(new FFontChar2 (Lump, int(data_p - data), SpaceWidth, FontHeight)), nullptr, ETextureType::FontChar); - Chars[i].OriginalPic = Chars[i].TranslatedPic; + Chars[i].OriginalPic = MakeGameTexture(new FImageTexture(new FFontChar2(Lump, int(data_p - data), SpaceWidth, FontHeight)), nullptr, ETextureType::FontChar); Chars[i].XMove = SpaceWidth; - TexMan.AddGameTexture(Chars[i].TranslatedPic); + TexMan.AddGameTexture(Chars[i].OriginalPic); } // Advance to next char's data and count the used colors. @@ -567,87 +499,52 @@ void FSingleLumpFont::CheckFON1Chars (double *luminosity) int8_t code = *data_p++; if (code >= 0) { - destSize -= code+1; + destSize -= code + 1; while (code-- >= 0) { - used[*data_p++] = 1; + data_p++; } } else if (code != -128) { - used[*data_p++] = 1; + data_p++; destSize -= 1 - code; } } while (destSize > 0); } - - memset (PatchRemap, 0, 256); - reverse[0] = 0; - for (i = 1, j = 1; i < 256; ++i) - { - if (used[i]) - { - reverse[j++] = i; - } - } - for (i = 1; i < j; ++i) - { - PatchRemap[reverse[i]] = i; - luminosity[i] = (reverse[i] - 1) / 254.0; - } - ActiveColors = j; + ActiveColors = 256; } //========================================================================== // // FSingleLumpFont :: FixupPalette // -// Finds the best matches for the colors used by a FON2 font and sets up -// some tables like SimpleTranslation. +// Finds the best matches for the colors used by a FON2 font and finds thr +// used luminosity range // //========================================================================== -void FSingleLumpFont::FixupPalette (uint8_t *identity, double *luminosity, const uint8_t *palette, bool rescale, PalEntry *out_palette) +void FSingleLumpFont::FixupPalette (uint8_t *identity, const PalEntry *palette, int *pminlum, int *pmaxlum) { - int i; double maxlum = 0.0; double minlum = 100000000.0; - double diver; identity[0] = 0; palette += 3; // Skip the transparent color - for (i = 1; i < ActiveColors; ++i, palette += 3) + for (int i = 1; i < ActiveColors; ++i, palette ++) { - int r = palette[0]; - int g = palette[1]; - int b = palette[2]; + int r = palette->r; + int g = palette->g; + int b = palette->b; double lum = r*0.299 + g*0.587 + b*0.114; identity[i] = ColorMatcher.Pick(r, g, b); - luminosity[i] = lum; - out_palette[i].r = r; - out_palette[i].g = g; - out_palette[i].b = b; - out_palette[i].a = 255; - if (lum > maxlum) - maxlum = lum; - if (lum < minlum) - minlum = lum; + if (lum > maxlum) maxlum = lum; + if (lum < minlum) minlum = lum; } - out_palette[0] = 0; - if (rescale) - { - diver = 1.0 / (maxlum - minlum); - } - else - { - diver = 1.0 / 255.0; - } - for (i = 1; i < ActiveColors; ++i) - { - luminosity[i] = (luminosity[i] - minlum) * diver; - } + if (pminlum) *pminlum = int(minlum); + if (pmaxlum) *pmaxlum = int(maxlum); } //========================================================================== @@ -661,13 +558,11 @@ void FSingleLumpFont::FixupPalette (uint8_t *identity, double *luminosity, const void FSingleLumpFont::RecordAllTextureColors(uint32_t* usedcolors) { - double luminosity[256]; uint8_t identity[256]; - PalEntry local_palette[256]; if (FontType == BMFFONT || FontType == FONT2) { - FixupPalette(identity, luminosity, PaletteData, RescalePalette, local_palette); + FixupPalette(identity, Palette, nullptr, nullptr); for (int i = 0; i < 256; i++) { if (identity[i] != 0) usedcolors[identity[i]]++; diff --git a/src/common/fonts/singlepicfont.cpp b/src/common/fonts/singlepicfont.cpp index aefede946..c8d5614ae 100644 --- a/src/common/fonts/singlepicfont.cpp +++ b/src/common/fonts/singlepicfont.cpp @@ -45,7 +45,7 @@ public: FSinglePicFont(const char *picname); // FFont interface - FGameTexture *GetChar(int code, int translation, int *const width, bool *redirected = nullptr) const override; + FGameTexture *GetChar(int code, int translation, int *const width) const override; int GetCharWidth (int code) const override; protected: @@ -79,7 +79,6 @@ FSinglePicFont::FSinglePicFont(const char *picname) : SpaceWidth = (int)pic->GetDisplayWidth(); GlobalKerning = 0; FirstChar = LastChar = 'A'; - ActiveColors = 0; PicNum = picnum; Next = FirstFont; @@ -94,10 +93,9 @@ FSinglePicFont::FSinglePicFont(const char *picname) : // //========================================================================== -FGameTexture *FSinglePicFont::GetChar (int code, int translation, int *const width, bool *redirected) const +FGameTexture *FSinglePicFont::GetChar (int code, int translation, int *const width) const { *width = SpaceWidth; - if (redirected) *redirected = false; if (code == 'a' || code == 'A') { return TexMan.GetGameTexture(PicNum, true); diff --git a/src/common/fonts/specialfont.cpp b/src/common/fonts/specialfont.cpp index 637cbf866..6ceb38b15 100644 --- a/src/common/fonts/specialfont.cpp +++ b/src/common/fonts/specialfont.cpp @@ -108,26 +108,18 @@ FSpecialFont::FSpecialFont (const char *name, int first, int count, FGameTexture Chars[i].OriginalPic = MakeGameTexture(pic->GetTexture(), nullptr, ETextureType::FontChar); Chars[i].OriginalPic->CopySize(pic, true); TexMan.AddGameTexture(Chars[i].OriginalPic); - - if (!noTranslate) - { - Chars[i].TranslatedPic = MakeGameTexture(new FImageTexture(new FFontChar1 (charlumps[i]->GetTexture()->GetImage())), nullptr, ETextureType::FontChar); - Chars[i].TranslatedPic->CopySize(charlumps[i], true); - TexMan.AddGameTexture(Chars[i].TranslatedPic); - } - else Chars[i].TranslatedPic = Chars[i].OriginalPic; - Chars[i].XMove = (int)Chars[i].TranslatedPic->GetDisplayWidth(); - if (sysCallbacks.FontCharCreated) sysCallbacks.FontCharCreated(pic, Chars[i].OriginalPic, Chars[i].TranslatedPic); + Chars[i].XMove = (int)Chars[i].OriginalPic->GetDisplayWidth(); + if (sysCallbacks.FontCharCreated) sysCallbacks.FontCharCreated(pic, Chars[i].OriginalPic, Chars[i].OriginalPic); } else { - Chars[i].TranslatedPic = nullptr; + Chars[i].OriginalPic = nullptr; Chars[i].XMove = INT_MIN; } } // Special fonts normally don't have all characters so be careful here! - if ('N'-first >= 0 && 'N'-first < count && Chars['N' - first].TranslatedPic != nullptr) + if ('N'-first >= 0 && 'N'-first < count && Chars['N' - first].OriginalPic != nullptr) { SpaceWidth = (Chars['N' - first].XMove + 1) / 2; } @@ -137,11 +129,6 @@ FSpecialFont::FSpecialFont (const char *name, int first, int count, FGameTexture } FixXMoves(); - - if (noTranslate) - { - ActiveColors = 0; - } } //========================================================================== @@ -152,65 +139,56 @@ FSpecialFont::FSpecialFont (const char *name, int first, int count, FGameTexture void FSpecialFont::LoadTranslations() { - int count = LastChar - FirstChar + 1; - uint32_t usedcolors[256] = {}; - uint8_t identity[256]; - TArray Luminosity; - int TotalColors; - int i; - - for (i = 0; i < count; i++) - { - if (Chars[i].TranslatedPic) - { - 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); - } - } - } + FFont::LoadTranslations(); + bool needsnotrans = false; // exclude the non-translated colors from the translation calculation - for (i = 0; i < 256; i++) - if (notranslate[i]) - usedcolors[i] = false; - - TotalColors = ActiveColors = SimpleTranslation (usedcolors, PatchRemap, identity, Luminosity); - - // Map all untranslated colors into the table of used colors - for (i = 0; i < 256; i++) - { + for (int i = 0; i < 256; i++) if (notranslate[i]) { - PatchRemap[i] = TotalColors; - identity[TotalColors] = i; - TotalColors++; + needsnotrans = true; + break; } - } - for (i = 0; i < count; i++) + // If we have no non-translateable colors, we can use the base data as-is. + if (!needsnotrans) { - if(Chars[i].TranslatedPic) - static_cast(Chars[i].TranslatedPic->GetTexture()->GetImage())->SetSourceRemap(PatchRemap); + return; } - BuildTranslations(Luminosity.Data(), identity, &TranslationParms[0][0], TotalColors, nullptr, [=](FRemapTable* remap) - { - // add the untranslated colors to the Ranges tables - if (ActiveColors < TotalColors) - { - for (int j = ActiveColors; j < TotalColors; ++j) - { - remap->Remap[j] = identity[j]; - remap->Palette[j] = GPalette.BaseColors[identity[j]]; - remap->Palette[j].a = 0xff; - } - } - }); + // we only need to add special handling if there's colors that should not be translated. + // Obviously 'notranslate' should only be used on data that uses the base palette, otherwise results are undefined! + for (auto &trans : Translations) + { + if (!IsLuminosityTranslation(trans)) continue; // this should only happen for CR_UNTRANSLATED. - ActiveColors = TotalColors; + FRemapTable remap(256); + remap.ForFont = true; + + uint8_t workpal[1024]; + for (int i = 0; i < 256; i++) + { + workpal[i * 4 + 0] = GPalette.BaseColors[i].b; + workpal[i * 4 + 1] = GPalette.BaseColors[i].g; + workpal[i * 4 + 2] = GPalette.BaseColors[i].r; + workpal[i * 4 + 3] = GPalette.BaseColors[i].a; + } + V_ApplyLuminosityTranslation(trans, workpal, 256); + for (int i = 0; i < 256; i++) + { + if (!notranslate[i]) + { + remap.Palette[i] = PalEntry(workpal[i * 4 + 3], workpal[i * 4 + 2], workpal[i * 4 + 1], workpal[i * 4 + 0]); + remap.Remap[i] = ColorMatcher.Pick(remap.Palette[i]); + } + else + { + remap.Palette[i] = GPalette.BaseColors[i]; + remap.Remap[i] = i; + } + } + trans = GPalette.StoreTranslation(TRANSLATION_Internal, &remap); + } } FFont *CreateSpecialFont (const char *name, int first, int count, FGameTexture **lumplist, const bool *notranslate, int lump, bool donttranslate) diff --git a/src/common/fonts/v_font.cpp b/src/common/fonts/v_font.cpp index 7f54e7bf1..280fd5856 100644 --- a/src/common/fonts/v_font.cpp +++ b/src/common/fonts/v_font.cpp @@ -586,6 +586,176 @@ 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; + } +} + +//========================================================================== +// +// SetDefaultTranslation +// +// Builds a translation to map the stock font to a mod provided replacement. +// This probably won't work that well if the original font is extremely colorful. +// +//========================================================================== + +static void CalcDefaultTranslation(FFont* base, int index) +{ + uint32_t othercolors[256] = {}; + base->RecordAllTextureColors(othercolors); + + TArray otherluminosity; + base->GetLuminosity(othercolors, otherluminosity); + + PalEntry *remap = &paletteptr[index * 256]; + memset(remap, 0, 1024); + + for (unsigned i = 0; i < 256; i++) + { + auto lum = otherluminosity[i]; + if (lum >= 0 && lum <= 1) + { + int index = int(lum * 255); + remap[index] = GPalette.BaseColors[i]; + } + } + + // todo: fill the gaps. + remap[0] = 0; + int lowindex = 0; + int highindex = 1; + + while (lowindex < 255) + { + while (highindex <= 255 && remap[highindex].a == 0) highindex++; + if (lowindex == 0) + { + for (int i = 0; i < highindex; i++) remap[i] = remap[highindex]; + lowindex = highindex++; + } + else if (highindex > 256) + { + for (int i = lowindex + 1; i < highindex; i++) remap[i] = remap[lowindex]; + break; + } + else + { + for (int i = lowindex + 1; i < highindex; i++) + { + PalEntry color1 = remap[lowindex]; + PalEntry color2 = remap[highindex]; + double weight = (i - lowindex) / double(highindex - lowindex); + int r = int(color1.r + weight * (color2.r - color1.r)); + int g = int(color1.g + weight * (color2.g - color1.g)); + int b = int(color1.b + weight * (color2.b - color1.b)); + r = clamp(r, 0, 255); + g = clamp(g, 0, 255); + b = clamp(b, 0, 255); + remap[i] = PalEntry(255, r, g, b); + } + lowindex = highindex++; + } + } + +} + //========================================================================== // // V_LogColorFromColorRange @@ -676,6 +846,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); @@ -827,24 +998,44 @@ void V_LoadTranslations() for (auto font = FFont::FirstFont; font; font = font->Next) { if (!font->noTranslate) font->LoadTranslations(); - else font->ActiveColors = 0; } + if (BigFont) { - uint32_t colors[256] = {}; - BigFont->RecordAllTextureColors(colors); - if (OriginalBigFont != nullptr) OriginalBigFont->SetDefaultTranslation(colors); + CalcDefaultTranslation(BigFont, CR_UNTRANSLATED * 2 + 1); + if (OriginalBigFont != nullptr && OriginalBigFont != BigFont) + { + int sometrans = OriginalBigFont->Translations[0]; + sometrans &= ~(0x3fff << 16); + sometrans |= (CR_UNTRANSLATED * 2 + 1) << 16; + OriginalBigFont->Translations[CR_UNTRANSLATED] = sometrans; + OriginalBigFont->forceremap = true; + } } if (SmallFont) { - uint32_t colors[256] = {}; - SmallFont->RecordAllTextureColors(colors); - if (OriginalSmallFont != nullptr) OriginalSmallFont->SetDefaultTranslation(colors); - NewSmallFont->SetDefaultTranslation(colors); + CalcDefaultTranslation(SmallFont, CR_UNTRANSLATED * 2); + if (OriginalSmallFont != nullptr && OriginalSmallFont != SmallFont) + { + int sometrans = OriginalSmallFont->Translations[0]; + sometrans &= ~(0x3fff << 16); + sometrans |= (CR_UNTRANSLATED * 2) << 16; + OriginalSmallFont->Translations[CR_UNTRANSLATED] = sometrans; + OriginalSmallFont->forceremap = true; + } + if (NewSmallFont != nullptr) + { + int sometrans = NewSmallFont->Translations[0]; + sometrans &= ~(0x3fff << 16); + sometrans |= (CR_UNTRANSLATED * 2) << 16; + NewSmallFont->Translations[CR_UNTRANSLATED] = sometrans; + NewSmallFont->forceremap = true; + } } translationsLoaded = true; } + void V_ClearFonts() { while (FFont::FirstFont != nullptr) diff --git a/src/common/fonts/v_font.h b/src/common/fonts/v_font.h index 37bf049a3..0b17b77e1 100644 --- a/src/common/fonts/v_font.h +++ b/src/common/fonts/v_font.h @@ -97,7 +97,7 @@ public: FFont (const char *fontname, const char *nametemplate, const char *filetemplate, int first, int count, int base, int fdlump, int spacewidth=-1, bool notranslate = false, bool iwadonly = false, bool doomtemplate = false, GlyphSet *baseGlpyphs = nullptr); virtual ~FFont (); - virtual FGameTexture *GetChar (int code, int translation, int *const width, bool *redirected = nullptr) const; + virtual FGameTexture *GetChar (int code, int translation, int *const width) const; virtual int GetCharWidth (int code) const; int GetColorTranslation (EColorRange range, PalEntry *color = nullptr) const; int GetLump() const { return Lump; } @@ -128,22 +128,17 @@ public: void SetKerning(int c) { GlobalKerning = c; } bool NoTranslate() const { return noTranslate; } virtual void RecordAllTextureColors(uint32_t *usedcolors); - virtual void SetDefaultTranslation(uint32_t *colors); void CheckCase(); int GetDisplacement() const { return Displacement; } + static int GetLuminosity(uint32_t* colorsused, TArray& Luminosity, int* minlum = nullptr, int* maxlum = nullptr); protected: FFont (int lump); - void BuildTranslations (const double *luminosity, const uint8_t *identity, - const void *ranges, int total_colors, const PalEntry *palette, std::function post = nullptr); void FixXMoves(); - static int SimpleTranslation (uint32_t *colorsused, uint8_t *translation, - uint8_t *identity, TArray &Luminosity); - void ReadSheetFont(TArray &folderdata, int width, int height, const DVector2 &Scale); EFontType Type = EFontType::Unknown; @@ -161,12 +156,10 @@ protected: bool forceremap = false; struct CharData { - FGameTexture *TranslatedPic = nullptr; // Texture for use with font translations. - FGameTexture *OriginalPic = nullptr; // Texture for use with CR_UNTRANSLATED or font colorization. + FGameTexture *OriginalPic = nullptr; int XMove = INT_MIN; }; TArray Chars; - int ActiveColors = -1; TArray Translations; uint8_t PatchRemap[256]; @@ -192,6 +185,8 @@ EColorRange V_ParseFontColor (const uint8_t *&color_value, int normalcolor, int FFont *V_GetFont(const char *fontname, const char *fontlumpname = nullptr); void V_InitFontColors(); char* CleanseString(char* str); +void V_ApplyLuminosityTranslation(int translation, uint8_t* pixel, int size); void V_LoadTranslations(); +class FBitmap; diff --git a/src/common/textures/bitmap.h b/src/common/textures/bitmap.h index 5ee574b7f..36d520771 100644 --- a/src/common/textures/bitmap.h +++ b/src/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/src/common/textures/formats/fontchars.cpp b/src/common/textures/formats/fontchars.cpp index 107080e35..44a72ca6e 100644 --- a/src/common/textures/formats/fontchars.cpp +++ b/src/common/textures/formats/fontchars.cpp @@ -41,48 +41,6 @@ #include "fontchars.h" #include "engineerrors.h" -//========================================================================== -// -// FFontChar1 :: FFontChar1 -// -// Used by fonts made from textures. -// -//========================================================================== - -FFontChar1::FFontChar1 (FImageSource *sourcelump) -: BaseTexture(sourcelump), SourceRemap (nullptr) -{ - // now copy all the properties from the base texture - assert(BaseTexture != nullptr); - CopySize(*BaseTexture); - bUseGamePalette = false; -} - -//========================================================================== -// -// FFontChar1 :: GetPixels -// -// Render style is not relevant for fonts. This must not use it! -// -//========================================================================== - -TArray FFontChar1::CreatePalettedPixels (int) -{ - // Make the texture as normal, then remap it so that all the colors - // are at the low end of the palette - // Why? It only creates unnecessary work! - auto Pixels = BaseTexture->GetPalettedPixels(normal); - - if (SourceRemap) - { - for (int x = 0; x < Width*Height; ++x) - { - Pixels[x] = SourceRemap[Pixels[x]]; - } - } - return Pixels; -} - //========================================================================== // // FFontChar2 :: FFontChar2 @@ -91,8 +49,8 @@ TArray FFontChar1::CreatePalettedPixels (int) // //========================================================================== -FFontChar2::FFontChar2 (int sourcelump, int sourcepos, int width, int height, int leftofs, int topofs) -: SourceLump (sourcelump), SourcePos (sourcepos), SourceRemap(nullptr) +FFontChar2::FFontChar2(int sourcelump, int sourcepos, int width, int height, int leftofs, int topofs) + : SourceLump(sourcelump), SourcePos(sourcepos) { Width = width; Height = height; @@ -100,17 +58,6 @@ FFontChar2::FFontChar2 (int sourcelump, int sourcepos, int width, int height, in TopOffset = topofs; } -//========================================================================== -// -// FFontChar2 :: SetSourceRemap -// -//========================================================================== - -void FFontChar2::SetSourceRemap(const uint8_t *sourceremap) -{ - SourceRemap = sourceremap; -} - //========================================================================== // // FFontChar2 :: Get8BitPixels @@ -121,7 +68,7 @@ void FFontChar2::SetSourceRemap(const uint8_t *sourceremap) TArray FFontChar2::CreatePalettedPixels(int) { - auto lump = fileSystem.OpenFileReader (SourceLump); + auto lump = fileSystem.OpenFileReader(SourceLump); int destSize = Width * Height; uint8_t max = 255; bool rle = true; @@ -129,23 +76,23 @@ TArray FFontChar2::CreatePalettedPixels(int) // This is to "fix" bad fonts { uint8_t buff[16]; - lump.Read (buff, 4); + lump.Read(buff, 4); if (buff[3] == '2') { - lump.Read (buff, 7); + lump.Read(buff, 7); max = buff[6]; - lump.Seek (SourcePos - 11, FileReader::SeekCur); + lump.Seek(SourcePos - 11, FileReader::SeekCur); } else if (buff[3] == 0x1A) { lump.Read(buff, 13); max = buff[12] - 1; - lump.Seek (SourcePos - 17, FileReader::SeekCur); + lump.Seek(SourcePos - 17, FileReader::SeekCur); rle = false; } else { - lump.Seek (SourcePos - 4, FileReader::SeekCur); + lump.Seek(SourcePos - 4, FileReader::SeekCur); } } @@ -153,7 +100,7 @@ TArray FFontChar2::CreatePalettedPixels(int) int runlen = 0, setlen = 0; uint8_t setval = 0; // Shut up, GCC! - uint8_t *dest_p = Pixels.Data(); + uint8_t* dest_p = Pixels.Data(); int dest_adv = Height; int dest_rew = destSize - 1; @@ -166,11 +113,7 @@ TArray FFontChar2::CreatePalettedPixels(int) if (runlen != 0) { uint8_t color = lump.ReadUInt8(); - color = MIN (color, max); - if (SourceRemap != nullptr) - { - color = SourceRemap[color]; - } + color = MIN(color, max); *dest_p = color; dest_p += dest_adv; x--; @@ -194,11 +137,7 @@ TArray FFontChar2::CreatePalettedPixels(int) { uint8_t color = lump.ReadUInt8(); setlen = (-code) + 1; - setval = MIN (color, max); - if (SourceRemap != nullptr) - { - setval = SourceRemap[setval]; - } + setval = MIN(color, max); } } } @@ -216,10 +155,6 @@ TArray FFontChar2::CreatePalettedPixels(int) { color = max; } - if (SourceRemap != nullptr) - { - color = SourceRemap[color]; - } *dest_p = color; dest_p += dest_adv; } @@ -230,10 +165,17 @@ TArray FFontChar2::CreatePalettedPixels(int) if (destSize < 0) { char name[9]; - fileSystem.GetFileShortName (name, SourceLump); + fileSystem.GetFileShortName(name, SourceLump); name[8] = 0; - I_FatalError ("The font %s is corrupt", name); + I_FatalError("The font %s is corrupt", name); } return Pixels; } +int FFontChar2::CopyPixels(FBitmap* bmp, int conversion) +{ + if (conversion == luminance) conversion = normal; // luminance images have no use as an RGB source. + auto ppix = CreatePalettedPixels(conversion); + bmp->CopyPixelData(0, 0, ppix.Data(), Width, Height, Height, 1, 0, SourceRemap, nullptr); + return 0; +} diff --git a/src/common/textures/formats/fontchars.h b/src/common/textures/formats/fontchars.h index 341fb9445..79baf4a2b 100644 --- a/src/common/textures/formats/fontchars.h +++ b/src/common/textures/formats/fontchars.h @@ -1,21 +1,5 @@ -// This is a font character that loads a texture and recolors it. -class FFontChar1 : public FImageSource -{ -public: - FFontChar1 (FImageSource *sourcelump); - TArray CreatePalettedPixels(int conversion) override; - void SetSourceRemap(const uint8_t *sourceremap) { SourceRemap = sourceremap; } - const uint8_t *ResetSourceRemap() { auto p = SourceRemap; SourceRemap = nullptr; return p; } - FImageSource *GetBase() const { return BaseTexture; } - -protected: - - FImageSource *BaseTexture; - const uint8_t *SourceRemap; -}; - // This is a font character that reads RLE compressed data. class FFontChar2 : public FImageSource { @@ -23,10 +7,15 @@ public: FFontChar2 (int sourcelump, int sourcepos, int width, int height, int leftofs=0, int topofs=0); TArray CreatePalettedPixels(int conversion) override; - void SetSourceRemap(const uint8_t *sourceremap); + int CopyPixels(FBitmap* bmp, int conversion); + + void SetSourceRemap(const PalEntry* sourceremap) + { + SourceRemap = sourceremap; + } protected: int SourceLump; int SourcePos; - const uint8_t *SourceRemap; + const PalEntry *SourceRemap; }; diff --git a/src/common/textures/hw_texcontainer.h b/src/common/textures/hw_texcontainer.h index c36eea38a..91afa526f 100644 --- a/src/common/textures/hw_texcontainer.h +++ b/src/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/src/common/textures/texture.cpp b/src/common/textures/texture.cpp index 503657f00..6d24f1154 100644 --- a/src/common/textures/texture.cpp +++ b/src/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) {