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
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
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
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