Teknisk artikel

BIFF8 56-farve-paletten: OKLab-farvetilknytning i HotXLS

HotXLS mapper vilkårlige RGB- og temafarver ned på BIFF8-farvepalettens 56 pladser i to lag: NearestIndexedColor finder den perceptuelt nærmeste eksisterende paletpost i OKLab-rummet, og BuildBiffPalettePlan med ApplyBiffPalettePlan omskriver de ledige paletpladser, så en true-color-arbejdsbog overlever en gemning til klassisk XLS. Udløseren er altid samme supportsag. Nogen bygger en rapport i XLSX med navyblå firmaheadere og en dæmpet teal-accent, gemmer den som .xls til en forbruger på ældre software, og headerne kommer tilbage i ren sort, mens tealen bliver til en højtråbende turkis. Intet gik ned, og ingen advarsel kom. Det gamle formats farvemodel kan simpelthen ikke rumme, hvad det nye beskrev, og biblioteket måtte vælge noget

Hvorfor kan en XLS-fil kun rumme 56 farver?

Fordi et BIFF8-celformat aldrig gemmer en RGB-værdi: fonte, fyld og kanter bærer et farveindeks, og den arbejdsbog-globale Palette-record ($0092, [MS-XLS] §2.4.188) leverer præcis 56 uigennemsigtige RGB-poster til indeks 8 til 63. Indeks 0 til 7 er faste kopier af de otte grundfarver, og værdierne over 63 er slet ikke farver, men tokens som systemforgrund, systembaggrund og diagramtekst. HotXLS eksponerer paletten gennem en offentlig ColorIndex fra 1 til 56, som er det fysiske indeks minus 7, og ResolveIndexedColor holder de tre nummereringsskemaer adskilt gennem TXLSIndexedColorSpace: xicsPublicColorIndex til API-værdierne 1..56, xicsBiffIcv til rå indeks på disken, som valideres mod IcvFont-, IcvXF- eller IcvChart-delmængden for den rolle, du giver med, og xicsOoxmlIndexed, hvor 64 og 65 betyder systemforgrund og systembaggrund

HotXLS holder de tre indekserede farveskemaer adskilt gennem TXLSIndexedColorSpace: rå BIFF-icv-værdier, 0 til 7 fastlåst til de otte grundfarver, de 56 paletpladser 8 til 63 i Palette-record $0092, tokens over 63 som systemforgrund, offentlig ColorIndex 1 til 56 forskudt med minus 7, og xicsOoxmlIndexed, hvor 64 og 65 betyder systemforgrund og systembaggrund
Samme farveindeks betyder forskellige tal i hvert skema, så HotXLS sender enhver værdi gennem ResolveIndexedColor i stedet for at lade en rå BIFF-token udgive sig for at være en offentlig ColorIndex
var
  Res: TXLSIndexedColorResolution;
begin
  // $40 er en BIFF-icv-token, ikke en paletplads
  Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
  case Res.Kind of
    xickPalette:   UseArgb(Res.ARGB);   // paletplads, hvis den resolves
    xickAutomatic,
    xickSystem:    UseSystemColor(Res.SystemColorRole);
    xickInvalid:   RejectToken(Res.RawIndex);
  end;
end;

Bemærk, at eksemplet switcher på Res.Kind og ignorerer den booleske returværdi. ResolveIndexedColor returnerer kun True, når den har skaffet en konkret ARGB, og den korte overload læser aldrig Windows-skrivebordet, så en automatic- eller system-token kommer med rette tilbage som False, mens den stadig er klassificeret som xickSystem. HotXLS ramte det i sin egen workbook-serializer: kode, der behandler False som "ingen farve", smider diskret tokenens Automatic- og System-betydning væk. Har du brug for reelle RGB-værdier til de tokens, så kald den lange overload og giv en TXLSTryResolveSystemColor-callback med, der anvender din egen UI-, eksport- eller headless-politik

Hvorfor matcher HotXLS farver i OKLab i stedet for RGB?

Fordi sRGB-kanalværdier er gamma-kodede, så den euklidiske afstand i RGB ikke følger med i, hvad et menneske ser, og fejlen er værst i netop de mørke, mættede toner, som firmapaletter elsker. Tag den mørkeblå $000033. I RGB er afstanden til sort 51 og afstanden til standard-navy-posten $000080 77, så en RGB-matcher maler din header selvsikkert sort. I OKLab er de kvadrerede afstande omkring 0,0312 til sort og 0,0235 til navy, og HotXLS vælger navy, ColorIndex 11 på fysisk plads 18; netop det tilfælde er fastlåst i test-suitten for både Classic- og XLSX-motoren. Konverteringen inde i ArgbToOklab lineariserer hver sRGB-kanal, anvender OKLab LMS-matrixen, tager kubikrødder og projicerer på L, a og b, hvorefter en almindelig kvadreret euklidisk afstand er en rimelig proxy for den opfattede forskel. OKLab er ikke CIEDE2000 og foregiver det heller ikke, men den har ingen piecewise hue-korrektioner, koster et par multiplikationer pr. farve og er stabil nok til at drive en clustering-løkke, og dér tjener den virkelig sin plads

Hvordan HotXLS matcher den mørkeblå $000033 på paletten: euklidisk afstand i gamma-kodet RGB måler 51 til sort og 77 til navy og ville male headeren sort, mens ArgbToOklab kvadrerede afstande på 0,0312 og 0,0235 lader NearestIndexedColor vælge navy, ColorIndex 11 på fysisk plads 18
Gamma-kodede kanalværdier gør RGB-afstand til en dårlig proxy for, hvad et menneske ser, så HotXLS konverterer én gang til OKLab og lader en almindelig kvadreret euklidisk sammenligning drive paletscannet

Hvad garanterer NearestIndexedColor?

NearestIndexedColor garanterer et deterministisk, read-only svar: én inputkonvertering, ét fast sweep over 56 cachede poster og det laveste offentlige indeks, når to poster er lige tæt på. Hver arbejdsbog cacher den normaliserede ARGB og OKLab-koordinaterne for alle 56 fysiske pladser sammen med en palette-generationstæller. Et palette-reset genopbygger cachen, en ændring af en enkelt plads opdaterer kun dén plads, og en forespørgsel mod en forældet generation returnerer False i stedet for at gætte. Scannet bruger en stræng mindre end-sammenligning fra plads 8, og det er grunden til, at en palet med samme farve to gange altid svarer med det lavere indeks; det tæller, når du diff'er to genererede filer og forventer byte-identisk output. Input-alpha følger en snæver kontrakt: en alpha-byte på nul behandles som uigennemsigtig, og en delvist gennemsigtig værdi afvises med ColorIndex 0 og PaletteSlot -1, da paletposter ikke har alpha. Classic-motorens fyld- og kanteskrivere konverterer RGB- og temafarver til et indeks med samme OKLab-matching-rutine ved gemning, så API'en og den gemte fil er enige om, hvilken plads en farve lander på

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;

Hvordan får BuildBiffPalettePlan true colors ind på 56 pladser?

BuildBiffPalettePlan beregner et komplet forslag til alle 56 pladser uden at røre arbejdsbogen, så du kan inspicere, logge eller kassere det. Planlæggeren kalder først ScanIndexedColorUsage: enhver plads, som en font, et fyld, en kant, en betinget formatering, en shape, en kommentar eller en gitterlinje i regnearket refererer til via indeks, er låst, fordi en ændring af en paletpost omfarver alle forbrugere af det indeks på én gang. Målene er de direkte RGB- og resolverte temafarver fra fonte, fyld, kanter, differential styles, data bars og color scales. Hvert mål vægtes efter det største af sit renderede referencetal og sit definitionstal, og en betinget formatering tæller de celler, dens områder dækker, så en farve malet ud over en hel kolonne vejer mere end én brugt i en enkelt note. Placeringen forløber derefter i fast rækkefølge:

  • Låste pladser beholder deres kildefarve betingelsesløst
  • Et mål, der allerede findes i paletten, bevares på dets laveste matchende plads, og den plads bliver fastlåst
  • Hvis de resterende unikke mål kan være i de ledige pladser, får hver enkelt en eksakt plads, tildelt i stigende ARGB-rækkefølge
  • Ellers sættes Quantized, hver ledig plads seedes med det mål, hvis afstand til sit nærmeste eksisterende center, ganget med dets vægt, er størst, og op til 16 runder af frekvensvægtet k-means i OKLab flytter kun de ledige centra, indtil tildelingerne holder op med at ændre sig

Vær ærlig over for dig selv om, hvad overflow-stien leverer. Clusteringen er en begrænset lokal optimering, ikke et globalt optimum, og en ledig plads ender med at holde et centroid konverteret tilbage til sRGB med clamping, hvilket kan være en farve, ingen celle brugte ordret. Det, du får, er gentagelighed: samme arbejdsbog giver altid samme plan, og planen rapporterer sit eget tab gennem WeightedError, MaxDistanceSquared, ExactTargetWeight og TotalTargetWeight, så et batch-job kan nægte at gemme, når approksimationen bliver for grov for en brand-guideline

HotXLS paletpipeline for en true-color-arbejdsbog: ScanIndexedColorUsage låser hver plads, en font, et fyld, en kant, en betinget formatering, en shape, en kommentar eller en gitterlinje refererer til, BuildBiffPalettePlan placerer eksakte farver i stigende ARGB-rækkefølge eller kører op til 16 runder af frekvensvægtet k-means i OKLab, og ApplyBiffPalettePlan validerer generationen og FNV-1a-hashen før skrivning
Planlægning er read-only og gentagelig, planen rapporterer sit eget tab gennem WeightedError og MaxDistanceSquared, og en forældet plan afvises med paletten urørt, fordi planer i praksis er single-use
var
  Plan: TXLSBiffPalettePlan;
  I: Integer;
begin
  Plan := Workbook.BuildBiffPalettePlan;   // skrivebeskyttet
  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;

Hvordan afviser ApplyBiffPalettePlan en forældet plan?

ApplyBiffPalettePlan validerer hele planen, før den skriver en eneste plads, og returnerer False med paletten urørt, hvis noget er i uoverensstemmelse med den aktuelle arbejdsbog. Planen bærer SourcePaletteGeneration og SourcePaletteHash, en 64-bit FNV-1a-hash over de 56 kildefarver; valideringen genkontrollerer også hvert offentligt og fysisk indeks, hver kildefarve, at ingen låst plads er markeret som ændret, de låste og ændrede antal, og at hvert mål er uigennemsigtigt. Enhver effektiv palette-ændring imellem, inklusive en tidligere vellykket anvendelse af samme plan, gør planen forældet, så planer i praksis er single-use. En gyldig plan uden ændrede pladser lykkes uden at rykke generationen, og en reel ændring bump'er generationen én gang og genopbygger OKLab-matcheren én gang — på Classic-motoren ved at omskrive det faste palette-array og på XLSX-motoren ved at skifte en forberedt indexed-color-override-liste ind

Aktivering ved BIFF8-gemninger og XLSX-til-XLS-konvertering

Egenskaben BiffPaletteSavePolicy står som standard til xbpsPreserve, så en opgradering af HotXLS aldrig omskriver nogens palet bag deres ryg. Sætter man den til xbpsOptimizeTrueColors, bygger og anvender en Classic-arbejdsbog en frisk plan inde i SaveAs, men kun når målformatet er xlExcel97; BIFF5, CSV, HTML, PDF, XLSX og de andre writers ignorerer indstillingen. Efter en vellykket gemning bliver den optimerede palet i arbejdsbogsmodellen, så senere forespørgsler og gemninger ser samme mapping. Fejler gemningen, eller den annulleres, gendannes de originale 56 farver og den originale generation. For XLSX-kilder bygger SaveXLSXWorkbookAsXLS i lxXlsxExport én plan fra den indlæste arbejdsbog og skriver den til destinationspaletten, før nogen style konverteres, hvilket er den deterministiske bro, som workbook audit and conversion workbench-demoen træner. Temafarver passerer den samme planlægger, efter deres tint er resolvet til RGB; vil man hellere holde temaer live i diagramfyld, dækker artiklen om GelFrame theme color diagramfyld, hvordan binær XLS gemmer et skemaindeks i stedet for en fladtrykt farve

// Classic-arbejdsbog: opt-in, kun BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
  HandleSaveFailure;   // paletten allerede gendannet

// XLSX-model til BIFF8 med én deterministisk paletplan
XWorkbook := TXLSXWorkbook.Create;
try
  if XWorkbook.Open('report.xlsx') = 1 then
    SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
  XWorkbook.Free;
end;

HotXLS' palet-API'er opfører sig ens på IXLSWorkbook og TXLSXWorkbook, fra Delphi så vel som fra C++Builder. Download prøveversionen, og peg den på dit mest farverige regneark fra HotXLS Delphi Excel component-siden