Tekninen artikkeli

BIFF8:n 56 värin paletti ja OKLab-sovitus HotXLS:ssä

HotXLS sovittaa mielivaltaiset RGB- ja teemavärit 56-paikkaiseen BIFF8-väripalettiin kahdessa kerroksessa: NearestIndexedColor etsii aistillisesti lähimmän olemassa olevan palettimerkinnän OKLab-avaruudessa, ja BuildBiffPalettePlan yhdessä ApplyBiffPalettePlan-metodin kanssa kirjoittaa vapaat palettipaikat uudelleen, jotta true color -työkirja selviää tallennuksesta klassiseen XLS-muotoon. Laukaisija on aina sama tukipyyntö. Joku rakentaa XLSX-raportin yrityksen tummansinisillä otsikoilla ja pehmeällä teal-korostuksella, tallentaa sen .xls-muodossa vanhan kuluttajakoodin vuoksi, ja otsikot palaavat puhtaana mustana kun taas teal muuttuu kirkkaaksi turkoosiksi. Mikään ei kaatunut eikä yhtään varoitusta lauennut. Vanhan formaatin värimalli ei yksinkertaisesti pysty taltioimaan sitä mitä uusi kuvasi, ja kirjaston piti valita jotakin

Miksi XLS-tiedosto voi taltioida vain 56 väriä?

Siksi että BIFF8-solun muotoilu ei koskaan tallenna RGB-arvoa: fontit, täytöt ja reunukset kantavat väri-indeksiä, ja koko työkirjan kattava Palette-tietue ($0092, [MS-XLS] §2.4.188) tarjoaa täsmälleen 56 läpinäkymätöntä RGB-merkintää indekseille 8–63. Indeksit 0–7 ovat kahdeksan perusvärin kiinteät kopiot, ja 63:aa suuremmat arvot eivät ole värejä lainkaan vaan tokeneita kuten system foreground, system background ja chart text. HotXLS avaa paletin julkisena ColorIndex-arvona 1–56, joka on fyysinen indeksi miinus 7, ja ResolveIndexedColor pitää kolme numerointijärjestelmää erillään TXLSIndexedColorSpace-tyypin kautta: xicsPublicColorIndex 1–56:n API-arvoille, xicsBiffIcv raaoille levillä matkustaville indekseille, jotka validoidaan antamaasi roolia vasten IcvFont-, IcvXF- tai IcvChart-joukossa, sekä xicsOoxmlIndexed, jossa 64 ja 65 tarkoittavat system foregroundia ja system backgroundia

HotXLS pitää kolme indeksoitujen värien järjestelmää erillään TXLSIndexedColorSpacen kautta: raat BIFF-icv-arvot, 0–7 kiinteänä kahdeksana perusvärinä, Palette-tietueen $0092:n 56 palettipaikkaa 8–63, yli 63:n tokenit kuten system foreground, julkinen ColorIndex 1–56 siirtona miinus 7 sekä xicsOoxmlIndexed, jossa 64 ja 65 tarkoittavat system foregroundia ja system backgroundia
Sama väri-indeksi tarkoittaa eri lukuja kussakin järjestelmässä, joten HotXLS ohjaa jokaisen arvon ResolveIndexedColorin kautta sen sijaan että raaka BIFF-token esiintyisi julkisena ColorIndexinä
var
  Res: TXLSIndexedColorResolution;
begin
  // $40 on BIFF-icv-token, ei palettipaikka
  Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
  case Res.Kind of
    xickPalette:   UseArgb(Res.ARGB);   // palettipaikka, jos ratkesi
    xickAutomatic,
    xickSystem:    UseSystemColor(Res.SystemColorRole);
    xickInvalid:   RejectToken(Res.RawIndex);
  end;
end;

Huomaa, että esimerkki haarautuu Res.Kind-arvon mukaan ja sivuuttaa Boolean-paluarvon. ResolveIndexedColor palauttaa arvon True vain silloin kun se sai konkreettisen ARGB:n, ja lyhyt overload ei koskaan lue Windowsin työpöytää, joten automatic- tai system-token palautuu laillisesti arvoon False luokiteltuna silti xickSystem-luokkaan. HotXLS kompastui tähän omassa työkirjaserialisoijassaan: koodi joka tulkitsee arvon False ”ei väriä” -merkitykseen heittää hiljaisena pois tokenin Automatic- ja System-merkitykset. Jos tarvitset kyseisille tokeneille oikeita RGB-arvoja, kutsu pitkää overloadia ja toimita TXLSTryResolveSystemColor-callback, joka soveltaa omaa UI-, export- tai headless-käytäntöäsi

Miksi HotXLS sovittaa värit OKLabissa eikä RGB:ssä?

Siksi että sRGB-kanava-arvot ovat gamma-koodattuja, joten euklidinen etäisyys RGB:ssä ei seuraa sitä mitä ihminen näkee, ja virhe on pahimmillaan juuri niissä tummissa, tyydyttyneissä sävyissä joita yrityspaletit rakastavat. Ota tummansininen $000033. RGB:ssä etäisyys mustaan on 51 ja etäisyys oletusarvoiseen navy-merkintään $000080 on 77, joten RGB-vertailija maalaa otsikkosi mustaksi täysin varmasti. OKLabissa neliöidyt etäisyydet ovat noin 0,0312 mustaan ja 0,0235 navyyn, ja HotXLS valitsee navyn, ColorIndex 11 fyysisellä paikalla 18; juuri tämä tapaus on lukittu testipakettiin sekä Classic- että XLSX-koneelle. ArgbToOklab-konversio linearisoi jokaisen sRGB-kanavan, soveltaa OKLabin LMS-matriisia, ottaa kuutiojuuret ja projisoi L-, a- ja b-akseleille, minkä jälkeen pelkkä neliöity euklidinen etäisyys on kohtuullinen likiarvo havaitulle erotukselle. OKLab ei ole CIEDE2000 eikä esitä olevaankaan, mutta sillä ei ole paloittaisia sävykorjauksia, se maksaa kourallisen kertolaskuja väriä kohti, ja se on tarpeeksi vakaa ohjaamaan klusterointisilmukkaa, ja siinä se oikeasti ansaitsee paikkansa

Miten HotXLS sovittaa tummansinisen $000033 palettiin: euklidinen etäisyys gamma-koodatussa RGB:ssä on 51 mustaan ja 77 navyyn ja maalaisi otsikon mustaksi, kun taas ArgbToOklabin neliöidyt etäisyydet 0,0312 ja 0,0235 antavat NearestIndexedColorin valita navyn, ColorIndex 11 fyysisellä paikalla 18
Gamma-koodatut kanava-arvot tekevät RGB-etäisyydestä huonon likiarvon sille mitä ihminen näkee, joten HotXLS konvertoi kerran OKLabiksi ja antaa pelkän neliöidyn euklidisen vertailun ohjata palettiskannausta

Mitä NearestIndexedColor takaa?

NearestIndexedColor takaa deterministisen, vain lukevan vastauksen: yhden syötteen konversion, yhden kiinteän skannauksen 56 välimuistissa pidettävän merkinnän yli ja pienimmän julkisen indeksin aina kun kaksi merkintää ovat yhtä lähellä. Jokainen työkirja välimuistittaa normalisoidun ARGB:n ja kaikkien 56 fyysisen paikan OKLab-koordinaatit yhdessä paletin generatiolaskurin kanssa. Paletin nollaus rakentaa välimuistin uudelleen, yksittäisen paikan muutos päivittää vain kyseisen paikan, ja kysely vanhentunutta generatiota vastaan palauttaa arvon False sen sijaan että arvaisi. Skannaus käyttää tiukkaa pienempi kuin -vertailua aloittaen paikasta 8, minkä vuoksi paletti joka sisältää saman värin kahdesti vastaa aina pienemmällä indeksillä; se merkitsee silloin kun vertailet kahta generoitua tiedostoa ja odotat tavutarkkaa tulosta. Syötteen alfa noudattaa kapeaa sopimusta: nollan alfatavu tulkitaan peittäväksi, ja osittain läpinäkyvä arvo hylätään palauttamalla ColorIndex 0 ja PaletteSlot -1, koska palettimerkinnöillä ei ole alfaa. Classic-koneen täyttö- ja reunuskirjoittajat konvertoivat RGB- ja teemavärit indeksiksi samalla OKLab-vertailurutiinilla tallennushetkellä, joten API ja talletettu tiedosto ovat samaa mieltä siitä, mille paikalle väri laskeutuu

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;

Miten BuildBiffPalettePlan mahduttaa true color -värit 56 paikkaan?

BuildBiffPalettePlan laskee valmiin ehdotuksen kaikille 56 paikalle koskematta työkirjaan, joten voit tutkia, lokittaa tai hylätä sen. Suunnittelija kutsuu ensin ScanIndexedColorUsage-metodia: jokainen paikka, johon fontti, täyttö, reunus, ehdollinen muotoilu, kuvio, kommentti tai worksheetin ruudukko viittaa indeksin kautta lukitaan, koska palettimerkinnän muuttaminen värää jokaisen kyseisen indeksin kuluttajan kerralla uudelleen. Kohteet ovat suorat RGB- ja ratkaistut teemavärit fonteista, täytöistä, reunuksista, differentiaalisista tyyleistä, datapalkkeista ja väriasteikoista. Jokainen kohde painotetaan suuremmalla sen renderöityvien viittausten määrästä ja sen määritelmien määrästä, ja ehdollinen muotoilu laskee alueittensa kattamat solut, joten koko sarakkeen yli maalattu väri voittaa yhdessä kommentissa käytetyn. Sijoitus etenee sitten kiinteässä järjestyksessä:

  • Lukitut paikat säilyttävät lähdevärinsä ehdottomasti
  • Kohde joka on jo paletissa säilytetään pienimmällä vastaavalla paikallaan ja kyseinen paikka muuttuu kiinteäksi
  • Jos jäljellä olevat uniikit kohteet mahtuvat vapaisiin paikkoihin, jokainen saa tarkan paikan, osoitettuna nousevassa ARGB-järjestyksessä
  • Muuten asetetaan Quantized, jokainen vapaa paikka alustetaan sillä kohteella jonka etäisyys lähimpään olemassa olevaan keskukseen kerrottuna painollaan on suurin, ja enintään 16 kierrosta frekvenssipainotettua k-means-klusterointia OKLabissa siirtää vain vapaita keskuksia kunnes sijoitukset lakkaavat muuttumasta

Ole rehellinen itsellesi siitä, mitä ylivuotopolku tuottaa. Klusterointi on rajattu paikallinen optimointi, ei globaali optimi, ja vapaa paikka päätyy pitämään keskuspunktia joka on konvertoitu takaisin sRGB:hen katkaisulla, ja se voi olla väri jota mikään solu ei käyttänyt sellaisenaan. Sen sijaan saat toistettavuuden: sama työkirja tuottaa aina saman suunnitelman, ja suunnitelma raportoi oman vahingonsa WeightedError-, MaxDistanceSquared-, ExactTargetWeight- ja TotalTargetWeight-arvojen kautta, joten eräajo voi kieltäytyä tallentamasta kun likiarvo muuttuu liian karkeaksi brändiohjeistukselle

HotXLS:n palettiputki true color -työkirjalle: ScanIndexedColorUsage lukitsee jokaisen paikan johon fontti, täyttö, reunus, ehdollinen muotoilu, kuvio, kommentti tai ruudukko viittaa, BuildBiffPalettePlan sijoittaa tarkat värit nousevassa ARGB-järjestyksessä tai ajaa enintään 16 kierrosta frekvenssipainotettua k-means-klusterointia OKLabissa, ja ApplyBiffPalettePlan validoi generatiot ja FNV-1a-tiivisteen ennen kirjoitusta
Suunnittelu on vain luku -laatuista ja toistettavaa, suunnitelma raportoi oman vahingonsa WeightedError- ja MaxDistanceSquared-arvojen kautta, ja vanhentunut suunnitelma hylätään palettia koskematta koska suunnitelmat ovat käytännössä kertakäyttöisiä
var
  Plan: TXLSBiffPalettePlan;
  I: Integer;
begin
  Plan := Workbook.BuildBiffPalettePlan;   // vain luku
  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;

Miten ApplyBiffPalettePlan hylkää vanhentuneen suunnitelman?

ApplyBiffPalettePlan validoi koko suunnitelman ennen kuin se kirjoittaa yhtään paikkaa, ja palauttaa arvon False palettia koskematta jos mikä tahansa on ristiriidassa nykyisen työkirjan kanssa. Suunnitelma kantaa mukanaan SourcePaletteGeneration- ja SourcePaletteHash-kentät, 64-bittisen FNV-1a-tiivisteen 56 lähdeväristä; validointi tarkistaa myös jokaisen julkisen ja fyysisen indeksin, jokaisen lähdevärin, ettei yhtään lukittua paikkaa ole merkitty muuttuneeksi, lukittujen ja muuttuneiden määrät sekä että jokainen kohde on peittävä. Mikä tahansa toteutunut palettimuutos väliin jäävältä ajalta, myös saman suunnitelman aiempi onnistunut soveltaminen, tekee suunnitelmasta vanhentuneen, joten suunnitelmat ovat käytännössä kertakäyttöisiä. Kelvollinen suunnitelma ilman muuttuneita paikkoja onnistuu siirtämättä generatiota, ja todellinen muutos nostaa generatiota kerran ja rakentaa OKLab-vertailijan kerran uudelleen, Classic-koneella ylikirjoittamalla kiinteän palettitaulukon ja XLSX-koneella vaihtamalla paikalleen valmistellun indeksoitujen värien ohituslistan

Käyttöönotto BIFF8-tallennuksissa ja XLSX–XLS-muunnoksissa

BiffPaletteSavePolicy-ominaisuuden oletusarvo on xbpsPreserve, joten HotXLS:n päivittäminen ei koskaan kirjoita kenenkään palettia selän takana. Asetus arvoon xbpsOptimizeTrueColors saa Classic-työkirjan rakentamaan ja soveltamaan tuoreen suunnitelman SaveAs-kutsun sisällä, mutta vain kun kohdemuoto on xlExcel97; BIFF5, CSV, HTML, PDF, XLSX ja muut kirjoittajat ohittavat asetuksen. Onnistuneen tallennuksen jälkeen optimoitu paletti jää työkirjamalliin, joten myöhemmät kyselyt ja tallennukset näkevät saman kuvauksen. Jos tallennus epäonnistuu tai perutaan, alkuperäiset 56 väriä ja alkuperäinen generatio palautetaan. XLSX-lähteille SaveXLSXWorkbookAsXLS lxXlsxExportissa rakentaa yhden suunnitelman ladatusta työkirjasta ja kirjoittaa sen kohdepalettiin ennen minkään tyylin konversiota, mikä on deterministinen silta jota työkirjan auditointi- ja muunnostyökalun demo harjoittelee. Teemavärit kulkevat saman suunnittelijan läpi sen jälkeen kun niiden tint on ratkaistu RGB:ksi; jos haluat pitää teemat elävinä chart-täytöissä, GelFrame-teemavärien chart-täyttöartikkeli kertoo miten binääri-XLS tallentaa skeemaindeksin litistyneen värin sijaan

// Classic-työkirja: valitse käyttöön, vain BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
  HandleSaveFailure;   // paletti on jo palautettu

// XLSX-malli BIFF8:ksi yhdellä deterministisellä palettiluonnoksella
XWorkbook := TXLSXWorkbook.Create;
try
  if XWorkbook.Open('report.xlsx') = 1 then
    SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
  XWorkbook.Free;
end;

HotXLS:n paletti-API:t toimivat samalla tavalla IXLSWorkbook- ja TXLSXWorkbook-rajapinnoilla Delphista ja C++Builderista käsin. Lataa kokeiluversio ja kokeile sitä värikkäimmällä taulukkolaskentatiedostollasi; saat sen HotXLS Delphi Excel -komponentin sivulta