HotXLS zet willekeurige RGB- en themakleuren in twee lagen om naar het BIFF8-kleurenpalet van 56 slots: NearestIndexedColor zoekt in OKLab-ruimte de perceptueel dichtstbijzijnde bestaande paletinvoer, en BuildBiffPalettePlan met ApplyBiffPalettePlan herschrijft de vrije paletslots zodat een true-color-werkboek een opslag als klassiek XLS overleeft. De aanleiding is altijd hetzelfde supportticket. Iemand bouwt in XLSX een rapport met marineblauwe kopregels in huisstijl en een zacht teal accent, slaat het als .xls op voor een consument op oudere software, en de kopregels komen er als zuiver zwart uit terwijl het teal verandert in een luidruchtig turquoise. Er is niets gecrasht en er is geen enkele waarschuwing afgegaan. Het kleurmodel van het oude formaat kan simpelweg niet bevatten wat het nieuwe beschreef, en de library moest iets kiezen
Waarom kan een XLS-bestand maar 56 kleuren bevatten?
Omdat een BIFF8-celformaat nooit een RGB-waarde opslaat: lettertypen, vullingen en randen dragen een kleurindex, en het werkboekglobale Palette-record ($0092, [MS-XLS] §2.4.188) levert precies 56 ondoorzichtige RGB-invoeren voor de indexen 8 tot en met 63. Indexen 0 tot 7 zijn vaste kopieën van de acht basiskleuren, en de waarden boven 63 zijn helemaal geen kleuren maar tokens zoals systeemvoorgrond, systeemachtergrond en charttekst. HotXLS stelt het palet beschikbaar via een publieke ColorIndex van 1 tot 56, de fysieke index min 7, en ResolveIndexedColor houdt de drie nummeringsschema's uit elkaar via TXLSIndexedColorSpace: xicsPublicColorIndex voor de API-waarden 1..56, xicsBiffIcv voor rauwe indexen op schijf, die worden gevalideerd tegen de subset IcvFont, IcvXF of IcvChart voor de rol die u doorgeeft, en xicsOoxmlIndexed, waar 64 en 65 systeemvoorgrond en -achtergrond betekenen
var
Res: TXLSIndexedColorResolution;
begin
// $40 is een BIFF-icv-token, geen paletslot
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // paletslot, indien opgelost
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
Merk op dat het voorbeeld op Res.Kind schakelt en de Booleaanse returnwaarde negeert. ResolveIndexedColor geeft alleen True terug als er een concrete ARGB is verkregen, en de korte overload leest nooit het Windows-bureaublad, dus een automatic- of system-token komt terecht met False terug terwijl het nog steeds als xickSystem is geclassificeerd. HotXLS trof dit aan in zijn eigen workbook-serializer: code die False als "geen kleur" behandelt gooit stilletjes de Automatic- en System-betekenis van het token weg. Als u echte RGB-waarden voor die tokens nodig hebt, roept u de lange overload aan en levert u een TXLSTryResolveSystemColor-callback aan die uw eigen UI-, export- of headless-beleid toepast
Waarom matcht HotXLS kleuren in OKLab in plaats van RGB?
Omdat sRGB-kanaalwaarden gamma-gecodeerd zijn, volgt de Euclidische afstand in RGB niet wat een mens ziet, en de fout is het grootst in precies de donkere, verzadigde tonen waar huisstijlpaletten van houden. Neem het donkerblauwe $000033. In RGB is de afstand tot zwart 51 en de afstand tot de standaard marineblauwe invoer $000080 is 77, dus een RGB-matcher verft uw kopregel vol overtuiging zwart. In OKLab zijn de gekwadrateerde afstanden ongeveer 0.0312 tot zwart en 0.0235 tot marineblauw, en HotXLS kiest marineblauw, ColorIndex 11 op fysiek slot 18; precies dat geval ligt vast in de testsuite voor zowel de Classic- als de XLSX-engine. De conversie in ArgbToOklab lineariseert elk sRGB-kanaal, past de OKLab-LMS-matrix toe, trekt derdemachtswortels en projecteert op L, a en b, waarna een gewone gekwadrateerde Euclidische afstand een redelijke proxy is voor het waargenomen verschil. OKLab is niet CIEDE2000 en doet ook alsof niet, maar het kent geen stuksgewijze hue-correcties, kost een handvol vermenigvuldigingen per kleur en is stabiel genoeg om een clusteringslus aan te drijven, en daar verdient het echt zijn plek
Wat garandeert NearestIndexedColor?
NearestIndexedColor garandeert een deterministisch, alleen-lezen antwoord: één invoerconversie, één vaste scan over 56 gecachte invoeren, en de laagste publieke index zodra twee invoeren even dichtbij zijn. Elk werkboek cachet de genormaliseerde ARGB en de OKLab-coördinaten van alle 56 fysieke slots samen met een paletgeneratieteller. Een paletreset bouwt de cache opnieuw, een wijziging van één slot actualiseert alleen dat slot, en een query tegen een verouderde generatie geeft False terug in plaats van te gokken. De scan gebruikt een strikte kleiner-dan-vergelijking vanaf slot 8, en daarom antwoordt een palet dat dezelfde kleur twee keer bevat altijd met de lagere index; dat telt wanneer u twee gegenereerde bestanden diffed en byte-identieke uitvoer verwacht. De invoer-alpha volgt een smal contract: een alpha-byte van nul geldt als dekkend, en een gedeeltelijk transparante waarde wordt geweigerd met ColorIndex 0 en PaletteSlot -1, want paletinvoeren hebben geen alpha. De fill- en borderwriters van de Classic-engine converteren RGB- en themakleuren bij het opslaan met dezelfde OKLab-matchingroutine naar een index, zodat de API en het opgeslagen bestand het eens zijn over het slot waar een kleur terechtkomt
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;
Hoe past BuildBiffPalettePlan true colors in 56 slots?
BuildBiffPalettePlan berekent een compleet voorstel voor alle 56 slots zonder het werkboek aan te raken, zodat u het kunt inspecteren, loggen of weggooien. De planner roept eerst ScanIndexedColorUsage aan: elk slot waarnaar een lettertype, vulling, rand, voorwaardelijke opmaak, shape, opmerking of werkbladrasterlijn via een index verwijst, wordt vergrendeld, want het wijzigen van een paletinvoer kleurt in één keer alle consumenten van die index opnieuw. Targets zijn de directe RGB- en opgeloste themakleuren uit lettertypen, vullingen, randen, differentiële stijlen, databalken en kleurschalen. Elke target wordt gewogen op het grotere van het aantal weergegeven verwijzingen en het aantal definities, en een voorwaardelijke opmaak telt de cellen die zijn bereiken bestrijken, dus een kleur over een hele kolom weegt zwaarder dan één die in een enkele notitie voorkomt. De plaatsing verloopt daarna in een vaste volgorde:
- Vergrendelde slots behouden hun bronkleur onvoorwaardelijk
- Een target dat al in het palet bestaat, blijft op zijn laagste matchende slot staan en dat slot wordt vastgelegd
- Als de resterende unieke targets in de vrije slots passen, krijgt elk daarvan een exact slot, toegekend in oplopende ARGB-volgorde
- Anders wordt
Quantizedgezet, wordt elk vrij slot geseed met de target waarvan de afstand tot het dichtstbijzijnde bestaande middelpunt, vermenigvuldigd met zijn gewicht, het grootst is, en verschuiven maximaal 16 rondes van frequentiegewogen k-means in OKLab alleen de vrije middelpunten tot de toewijzingen niet meer veranderen
Wees eerlijk over wat het overlooppad oplevert. De clustering is een begrensde lokale optimalisatie, geen globaal optimum, en een vrij slot houdt uiteindelijk een centroid vast die met clamping terug naar sRGB is geconverteerd, wat een kleur kan zijn die geen enkele cel letterlijk gebruikte. Wat u wél krijgt is herhaalbaarheid: hetzelfde werkboek levert altijd hetzelfde plan op, en het plan rapporteert zijn eigen schade via WeightedError, MaxDistanceSquared, ExactTargetWeight en TotalTargetWeight, zodat een batchtaak de opslag kan weigeren zodra de benadering te grof wordt voor een huisstijlrichtlijn
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // alleen-lezen
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;
Hoe wijst ApplyBiffPalettePlan een verouderd plan af?
ApplyBiffPalettePlan valideert het volledige plan voordat er ook maar één slot wordt weggeschreven, en geeft False terug met het palet onaangeroerd als er ook maar iets niet klopt met het huidige werkboek. Het plan draagt SourcePaletteGeneration en SourcePaletteHash, een 64-bit FNV-1a-hash over de 56 bronkleuren; de validatie controleert bovendien elke publieke en fysieke index, elke bronkleur, dat geen vergrendeld slot als gewijzigd staat gemarkeerd, de aantallen vergrendeld en gewijzigd, en dat elke target dekkend is. Elke effectieve paletwijziging tussendoor, inclusief een eerdere succesvolle toepassing van hetzelfde plan, maakt het plan verouderd, dus plannen zijn in de praktijk eenmalig bruikbaar. Een geldig plan zonder gewijzigde slots slaagt zonder de generatie op te schuiven, en een echte wijziging verhoogt de generatie één keer en bouwt de OKLab-matcher één keer opnieuw, op de Classic-engine door de vaste paletarray te herschrijven en op de XLSX-engine door een voorbereide indexed-color-overridelijst in te wisselen
Aanzetten voor BIFF8-opslag en XLSX-naar-XLS-conversie
De eigenschap BiffPaletteSavePolicy staat standaard op xbpsPreserve, dus een upgrade van HotXLS herschrijft nooit iemands palet achter zijn rug om. Zet u hem op xbpsOptimizeTrueColors, dan bouwt en past een Classic-werkboek binnen SaveAs een vers plan toe, maar alleen als het doelformaat xlExcel97 is; BIFF5, CSV, HTML, PDF, XLSX en de andere writers negeren de instelling. Na een geslaagde opslag blijft het geoptimaliseerde palet in het werkboekmodel zitten, zodat latere queries en opslagen dezelfde mapping zien. Mislukt de opslag of wordt die geannuleerd, dan worden de oorspronkelijke 56 kleuren en de oorspronkelijke generatie hersteld. Voor XLSX-bronnen bouwt SaveXLSXWorkbookAsXLS in lxXlsxExport één plan vanuit het geladen werkboek en schrijft dat in het bestemmingspalet voordat er ook maar één stijl wordt geconverteerd — de deterministische brug die de workbook audit- en conversie-workbenchdemo belichaamt. Themakleuren gaan na het omrekenen van hun tint naar RGB door dezelfde planner; wilt u thema's liever levend houden in chartvullingen, dan legt het artikel over GelFrame-themakleur-chartvullingen uit hoe binair XLS een schema-index opslaat in plaats van een afgevlakte kleur
// Klassiek werkboek: opt-in, alleen BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // palet al hersteld
// XLSX-model naar BIFF8 met één deterministisch paletplan
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
De HotXLS-palet-API's werken hetzelfde op IXLSWorkbook en TXLSXWorkbook, vanuit Delphi net zo goed als vanuit C++Builder. Download de trial en richt die op uw kleurrijkste spreadsheet vanaf de HotXLS Delphi Excel-componentpagina