Műszaki cikk

BIFF8-os 56 színű paletta: OKLab színillesztés a HotXLS-ben

A HotXLS két rétegben képezi le a tetszőleges RGB- és témaszíneket az 56 rekeszes BIFF8-színpalettára: a NearestIndexedColor a perceptuálisan legközelebbi meglévő palettabejegyzést találja meg az OKLab-térben, a BuildBiffPalettePlan és az ApplyBiffPalettePlan párosa pedig átírja a szabad palettarekeszeket, hogy egy valódi színeket hordozó munkafüzet túlélessen egy klasszikus XLS-mentést. A kiváltó ok mindig ugyanaz a támogatási hibajegy. Valaki XLSX-ben rak össze egy jelentést vállalati sötétkék fejlécekkel és halvány teal kiemelőszínnel, .xls-ként menti el egy régi fogyasztó miatt, és a fejlécek tisztán feketeként jönnek vissza, a teal pedig harsány türkizzé alakul. Semmi nem omlott össze, egyetlen figyelmeztetés sem sült el. A régi formátum színmodellje egyszerűen nem képes tárolni, amit az új leírt, és a könyvtárnak valamelyik oldalra kellett voksolnia

Miért csak 56 színt tud tárolni egy XLS-fájl?

Mert a BIFF8 cellaformátuma soha nem tárol RGB-értéket: a fontok, a kitöltések és a szegélyek színindexet hordoznak, a munkafüzetszintű Palette rekord ($0092, [MS-XLS] §2.4.188) pedig pontosan 56, alfa nélküli RGB-bejegyzést ad a 8-tól 63-ig terjedő indexekhez. A 0-tól 7-ig terjedő indexek a nyolc alapszín rögzített másai, a 63 feletti értékek pedig nem is színek, hanem jelölők, például a rendszerelőtér, a rendszerháttér és a diagramszöveg. A HotXLS a palettát egy 1-től 56-ig terjedő nyilvános ColorIndex-en keresztül mutatja ki, ami a fizikai index mínusz 7, a ResolveIndexedColor pedig a TXLSIndexedColorSpace révén tartja elkülönítve a három számozási sémát: xicsPublicColorIndex az 1..56 API-értékekhez, xicsBiffIcv a nyers, lemezen tárolt indexekhez, amelyeket az átadott szerepkörnek megfelelő IcvFont, IcvXF vagy IcvChart részhalmazon érvényesít, valamint xicsOoxmlIndexed, ahol a 64 és a 65 a rendszerelőteret illetve a rendszerhátteret jelenti

A HotXLS a TXLSIndexedColorSpace-en keresztül tartja elkülönítve a három indexelt színsémát: nyers BIFF icv értékek, 0-tól 7-ig a nyolc alapszínhez rögzítve, a $0092-es Palette rekord 8-tól 63-ig terjedő 56 palettarekesze, a 63 feletti jelölők, például a rendszerelőtér, a nyilvános ColorIndex 1-től 56-ig mínusz 7-es eltolással, valamint az xicsOoxmlIndexed, ahol a 64 és a 65 a rendszerelőteret és a rendszerhátteret jelenti
Ugyanaz a színindex minden sémában más számot takar, ezért a HotXLS minden értéket a ResolveIndexedColor-on vezet át, ahelyett hogy egy nyers BIFF-jelölő nyilvános ColorIndexnek adná ki magát
var
  Res: TXLSIndexedColorResolution;
begin
  // A $40 egy BIFF icv jelölő, nem palettarekes
  Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
  case Res.Kind of
    xickPalette:   UseArgb(Res.ARGB);   // palettarekes, ha feloldódott
    xickAutomatic,
    xickSystem:    UseSystemColor(Res.SystemColorRole);
    xickInvalid:   RejectToken(Res.RawIndex);
  end;
end;

Figyeljen rá, hogy a példa a Res.Kind alapján dönt, és figyelmen kívül hagyja a logikai visszatérési értéket. A ResolveIndexedColor csak akkor ad True-t, ha konkrét ARGB-t sikerült szereznie, a rövid túlterhelés pedig soha nem olvassa be a Windows asztalt, így egy automatic vagy system jelölő joggal térhet vissza False-szal, miközben továbbra is xickSystem-ként van besorolva. A HotXLS a saját munkafüzet-szerializálójában futott bele ebbe: a False-t „nincs szín" jelentésűként kezelő kód csendben kidobja a jelölő Automatic és System jelentését. Ha valós RGB-értékre van szüksége ezeknél a jelölőknél, hívja a hosszú túlterhelést, és adjon át egy TXLSTryResolveSystemColor callbacket, amely a saját UI-, export- vagy fejnélküli szabályzatát alkalmazza

Miért OKLab-térben egyeztet a HotXLS színeket az RGB helyett?

Mert az sRGB csatornaértékei gammakódoltak, így az RGB-beli euklideszi távolság nem követi, amit az emberi szem lát, a hiba pedig épp azokban a sötét, telített tónusokban a legnagyobb, amelyeket a vállalati paletták annyira szeretnek. Vegyük a sötétkék $000033-at. RGB-ben a távolsága a feketéhez 51, az alapértelmezett $000080-es navy bejegyzéshez pedig 77, tehát egy RGB-illesztő magabiztosan feketére festi a fejlécet. OKLab-ban a négyzetes távolságok nagyjából 0,0312 a feketéhez és 0,0235 a navyhez, a HotXLS tehát a navyre voksol, ColorIndex 11, a 18-as fizikai rekeszen; ezt a pontos esetet a tesztkészlet rögzíti a Classic és az XLSX motornál egyaránt. Az ArgbToOklab belső konverziója minden sRGB csatornát linearizál, alkalmazza az OKLab LMS mátrixát, kockagyököt von, majd az L, a és b tengelyekre vetít, ezután már egy sima négyzetes euklideszi távolság is elfogadható közelítése az érzékelt különbségnek. Az OKLab nem CIEDE2000, és nem is adja ki magát annak, nincsenek darabonkénti árnyalatkorrekciók, színenként néhány szorzásba kerül, és elég stabil ahhoz, hogy egy klaszterező ciklust hajtson — és itt váltja valóban be magát

Hogyan illeszti a HotXLS a $000033-as sötétkéket a palettára: a gammakódolt RGB-beli euklideszi távolság 51 a feketéhez és 77 a navyhez, így a fejléc feketére festődne, az ArgbToOklab 0,0312-es és 0,0235-ös négyzetes távolságai alapján a NearestIndexedColor viszont a navyre voksol, ColorIndex 11, a 18-as fizikai rekeszen
A gammakódolt csatornaértékek miatt az RGB-távolság rossz közelítése annak, amit az ember lát, ezért a HotXLS egyszer konvertál OKLab-ra, és a paletta átvizsgálását sima négyzetes euklideszi összehasonlításra bízza

Mit garantál a NearestIndexedColor?

A NearestIndexedColor determinisztikus, csak olvasható választ garantál: egyetlen bemeneti konverzió, egy rögzített végigfutás az 56 gyorsítótárazott bejegyzésen, és egyenlő közelség esetén mindig a legalacsonyabb nyilvános index. Minden munkafüzet gyorsítótárazza mind az 56 fizikai rekesz normalizált ARGB-jét és OKLab-koordinátáit, egy palettagenerációs számlálóval együtt. A paletta visszaállítása újjáépíti a gyorsítótárat, egyetlen rekesz változása csak azt a rekeszt frissíti, az elavult generációval szembeni lekérdezés pedig False-szal tér vissza, nem találgat. A végigfutás szigorú kisebb-összehasonlítással indul a 8-as rekesznél, ezért válaszol mindig az alacsonyabb indexszel egy olyan paletta is, amely kétszer tartalmazza ugyanazt a színt; ez akkor fontos, amikor két legenerált fájlt diffel, és bájtonként azonos kimenetet vár. A bemeneti alfa szűk szerződést követ: a nulla alfabájt fedettként kezelendő, a részben átlátszó érték pedig elutasítandó, ColorIndex 0-val és PaletteSlot -1-gyel, hiszen a palettabejegyzéseknek nincs alfája. A Classic motor kitöltés- és szegélyírói mentéskor ugyanazzal az OKLab-illesztő rutinnal alakítják indexszé az RGB- és témaszíneket, így az API és a tárolt fájl ugyanabban a rekeszben állapodik meg

var
  Match: TXLSNearestIndexedColorMatch;
begin
  if Workbook.NearestIndexedColor($FF000033, Match) then
  begin
    // Match.ColorIndex = 11, Match.PaletteSlot = 18, Match.ARGB = $FF000080
    if not Match.ExactMatch then
      LogApproximation(Match.InputARGB, Match.ARGB, Match.DistanceSquared);
  end;
end;

Hogyan zsúfolja be a BuildBiffPalettePlan a valódi színeket 56 rekeszbe?

A BuildBiffPalettePlan a teljes 56 rekeszre kiszámol egy komplett javaslatot, a munkafüzetet nem érinti, így a tervet megvizsgálhatja, naplózhatja vagy elvetheti. A tervező először meghívja a ScanIndexedColorUsage-t: minden olyan rekesz, amelyre font, kitöltés, szegély, feltételes formázás, alakzat, megjegyzés vagy munkalap-rácsvonal index alapján hivatkozik, zárolt, mert egy palettabejegyzés megváltoztatása egyszerre színezi át az adott index minden felhasználóját. A célok a fontok, kitöltések, szegélyek, differenciális stílusok, adatsávok és színskálák közvetlen RGB- és feloldott témaszínei. Minden célt a megjelenített hivatkozási száma és a definíciós száma közül a nagyobb súlyoz, egy feltételes formázás pedig a tartományai által lefedett cellákat számolja be, így egy egész oszlopon áthúzódó szín nehezebb egyetlen megjegyzésben használtnál. Az elhelyezés ezután rögzített sorrendben halad:

  • A zárolt rekeszek feltétel nélkül megtartják a forrásszínüket
  • A palettában már meglévő cél a legalacsonyabb egyező rekeszén marad, és az a rekesz rögzítetté válik
  • Ha a megmaradó, különböző célok beleférnek a szabad rekeszekbe, mindegyik pontos rekeszt kap, növekvő ARGB-sorrendben kiosztva
  • Ellenkező esetben beállításra kerül a Quantized, minden szabad rekeszt az a cél fogja el, amelynek a legközelebbi meglévő közepéhez mért, súlyával megszorozott távolsága a legnagyobb, majd legfeljebb 16 környi, gyakorisággal súlyozott k-means az OKLab-ban csak a szabad középpontokat mozgatja, amíg a hozzárendelések változni nem hagynak

Legyen őszinte önmagához arról, mit nyújt a túlcsordulási út. A klaszterezés korlátos lokális optimalizálás, nem globális optimum, és egy szabad rekesz végül egy olyan, visszakonvertált, levágott sRGB középpontot tart, amely lehet olyan szín is, amelyet egyetlen cella sem használt szó szerint. Amit viszont kapni lehet, az a megismételhetőség: ugyanaz a munkafüzet mindig ugyanazt a tervet adja, a terv pedig a saját kárát is jelenti WeightedError, MaxDistanceSquared, ExactTargetWeight és TotalTargetWeight révén, így egy kötegelt feladat visszautasíthatja a mentést, ha a közelítés egy márkaarculati irányelvhez képest túl durva lesz

A HotXLS palettafolyamat egy valódi színeket hordozó munkafüzetnél: a ScanIndexedColorUsage zárol minden rekeszt, amelyre font, kitöltés, szegély, feltételes formázás, alakzat, megjegyzés vagy rácsvonal hivatkozik, a BuildBiffPalettePlan pontos színeket helyez el növekvő ARGB-sorrendben, vagy legfeljebb 16 kört fut a gyakorisággal súlyozott OKLab k-meansből, az ApplyBiffPalettePlan pedig írás előtt érvényesíti a generációt és az FNV-1a hash értéket
A tervezés csak olvasható és megismételhető, a terv a saját kárát a WeightedError és a MaxDistanceSquared révén jelenti, az elavult tervet pedig a paletta érintetlen hagyásával utasítják el, mert a tervek gyakorlatilag egyszer használatosak
var
  Plan: TXLSBiffPalettePlan;
  I: Integer;
begin
  Plan := Workbook.BuildBiffPalettePlan;   // csak olvasható
  if Plan.Quantized and (Plan.MaxDistanceSquared > MaxAcceptedError) then
    raise Exception.Create('Too many distinct colors for a BIFF8 palette');
  for I := 0 to High(Plan.Slots) do
    if Plan.Slots[I].Changed then
      LogSlot(Plan.Slots[I].ColorIndex, Plan.Slots[I].SourceARGB,
        Plan.Slots[I].TargetARGB);
  if not Workbook.ApplyBiffPalettePlan(Plan) then
    raise Exception.Create('The palette changed after planning');
end;

Hogyan utasítja el az ApplyBiffPalettePlan az elavult tervet?

Az ApplyBiffPalettePlan még egyetlen rekesz megírása előtt érvényesíti a teljes tervet, és ha bármi ütközik az aktuális munkafüzettel, False-szal tér vissza, a paletta érintetlenül. A terv magán hordozza a SourcePaletteGeneration-t és a SourcePaletteHash-t, egy 64 bites FNV-1a hash értéket az 56 forrásszín felett; az érvényesítés emellett újraellenőrzi minden nyilvános és fizikai indexet, minden forrásszínt, azt, hogy egyetlen zárolt rekesz sincs változottként megjelölve, a zárolási és változási darabszámokat, valamint hogy minden cél fedett. Bármilyen tényleges palettaváltozás a két lépés között — beleértve ugyanazon terv egy korábbi, sikeres alkalmazását is — elavulttá teszi a tervet, tehát a tervek gyakorlatilag egyszer használatosak. Egy érvényes, változatlan rekeszű terv a generáció léptetése nélkül teljesül, egy valódi változás pedig egyszer lépteti a generációt, és egyszer építi újra az OKLab-illesztőt, a Classic motornál a rögzített palettatömb átírásával, az XLSX motornál egy előkészített indexeltszín-felülbíráló lista betöltésével

Bekapcsolás BIFF8-mentéseknél és XLSX-ből XLS-be váltásnál

A BiffPaletteSavePolicy tulajdonság alapértelmezése xbpsPreserve, így a HotXLS frissítése soha nem írja át senki palettáját a háta mögött. xbpsOptimizeTrueColors-ra állítva egy Classic munkafüzet friss tervet épít és alkalmaz a SaveAs belül, de csak akkor, ha a célformátum xlExcel97; a BIFF5, CSV, HTML, PDF, XLSX és a többi író figyelmen kívül hagyja a beállítást. Sikeres mentés után az optimalizált paletta a munkafüzet modelljében marad, így a későbbi lekérdezések és mentések ugyanazt a leképezést látják. Ha a mentés elbukik vagy megszakad, az eredeti 56 szín és az eredeti generáció visszaáll. XLSX forrásoknál a lxXlsxExport SaveXLSXWorkbookAsXLS metódusa egyetlen tervet épít a betöltött munkafüzetből, és még bármely stílus konvertálása előtt beírja a célpalettába, ez az a determinisztikus híd, amelyet a munkafüzet-auditáló és -konvertáló workbench demó be is jár. A témaszínek ugyanazon a tervezőn mennek át, miután a tintájuk RGB-re oldódott; ha inkább azt szeretné, hogy a témák élve maradjanak a diagramkitöltésekben, a GelFrame témaszínes diagramkitöltésekről szóló cikk bemutatja, hogyan tárol a bináris XLS sémaindexet lapított szín helyett

// Klasszikus munkafüzet: feliratkozás, csak BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
  HandleSaveFailure;   // a paletta már visszaállt

// XLSX modell BIFF8-ba, egyetlen determinisztikus palettatervvel
XWorkbook := TXLSXWorkbook.Create;
try
  if XWorkbook.Open('report.xlsx') = 1 then
    SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
  XWorkbook.Free;
end;

A HotXLS paletta-API-jai ugyanúgy működnek IXLSWorkbook-on és TXLSXWorkbook-on, Delphiből és C++Builderből egyaránt. Töltse le a próbaverziót, és a HotXLS Delphi Excel komponens oldaláról irányítsa rá a legszínesebb táblázatára