HotXLS mappar godtyckliga RGB- och temafärger till BIFF8-palettens 56 färgplatser i två lager: NearestIndexedColor hittar den perceptuellt närmaste befintliga palettposten i OKLab-färgrummet, och BuildBiffPalettePlan med ApplyBiffPalettePlan skriver om de lediga palettplatserna så att en arbetsbok i äkta färger överlever att sparas som klassisk XLS. Utlösaren är alltid densamma, ett supportärende. Någon bygger en rapport i XLSX med marinblå företagsrubriker och en mjuk turkos accent, sparar den som .xls för en äldre konsument, och rubrikerna kommer tillbaka helsvarta medan turkosen har blivit en skrikig turkos. Inget kraschade och ingen varning larmade. Färgmodellen i gamla formatet kan helt enkelt inte rymma vad den nya beskrev, och biblioteket fick välja något
Varför kan en XLS-fil bara rymma 56 färger?
Därför att ett BIFF8-cellformat aldrig lagrar ett RGB-värde: teckensnitt, fyllningar och kanter bär ett färgindex, och den arbetsboksglobala Palette-posten ($0092, [MS-XLS] §2.4.188) levererar exakt 56 opaka RGB-poster för index 8 till 63. Index 0 till 7 är fasta kopior av de åtta grundfärgerna, och värdena över 63 är inte färger alls utan tokens som systemförgrund, systembakgrund och diagramtext. HotXLS exponerar paletten genom ett publikt ColorIndex på 1 till 56, vilket är det fysiska indexet minus 7, och ResolveIndexedColor håller de tre numreringsschemana isär genom TXLSIndexedColorSpace: xicsPublicColorIndex för API-värdena 1..56, xicsBiffIcv för råa index på disken, som valideras mot delmängden IcvFont, IcvXF eller IcvChart för den roll du skickar in, samt xicsOoxmlIndexed, där 64 och 65 betyder systemförgrund respektive systembakgrund
var
Res: TXLSIndexedColorResolution;
begin
// $40 är en BIFF-icv-token, inte en palettplats
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // palettplats, om den gick att lösa upp
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
Notera att exemplet testar Res.Kind i en case och struntar i det booleska returvärdet. ResolveIndexedColor returnerar True bara när den fick fram en konkret ARGB, och korta överlagringen läser aldrig Windows-skrivbordet, så en automatisk eller systemtoken kommer tillbaka som False med klassificeringen xickSystem intakt, och det är helt legitimt. HotXLS råkade ut för detta i sin egen arbetsboksserialisering: kod som tolkar False som "ingen färg" kastar tyst bort en tokens Automatic- och System-betydelse. Behöver du riktiga RGB-värden för dessa tokens anropar du den långa överlagringen och skickar in ett TXLSTryResolveSystemColor-återanrop som tillämpar din egen policy för UI, export eller körning utan gränssnitt
Varför matchar HotXLS färger i OKLab i stället för RGB?
Därför att sRGB-kanalvärden är gammakodade, så euklidiskt avstånd i RGB följer inte vad en människa ser, och felet är som sämst i exakt de mörka, mättade toner som företagspaletter älskar. Ta den mörkblå $000033. I RGB är avståndet till svart 51 och avståndet till standardposten marinblått $000080 är 77, så en RGB-matchare målar din rubrik svart med full självsäkerhet. I OKLab är de kvadrerade avstånden omkring 0,0312 till svart och 0,0235 till marinblått, och HotXLS väljer marinblått, ColorIndex 11 på fysisk plats 18; exakt det fallet är nålat fast i testsviten för både Classic- och XLSX-motorn. Konverteringen inuti ArgbToOklab lineariserar varje sRGB-kanal, tillämpar OKLab LMS-matrisen, drar kubikrötter och projicerar på L, a och b, varefter ett vanligt kvadrerat euklidiskt avstånd är en rimlig proxy för upplevd skillnad. OKLab är inte CIEDE2000 och låtsas inte vara det, men det har inga styckvisa nyanskorrigeringar, kostar en näve multiplikationer per färg och är stabilt nog att driva en klusterloop, och där förtjänar det sin plats på riktigt
Vad garanterar NearestIndexedColor?
NearestIndexedColor garanterar ett deterministiskt, skrivskyddat svar: en konvertering av indata, en fast genomgång av 56 cachade poster, och det lägsta publika indexet närhelst två poster är lika nära. Varje arbetsbok cachar normaliserad ARGB och OKLab-koordinaterna för alla 56 fysiska platser tillsammans med en generationsräknare för paletten. En palettåterställning bygger om cachen, en enskild platsändring uppdaterar bara den platsen, och en fråga mot en inaktuell generation returnerar False i stället för att gissa. Genomgången använder en strikt mindre än-jämförelse med start på plats 8, vilket är varför en palett som innehåller samma färg två gånger alltid svarar med det lägre indexet; det spelar roll när du diffar två genererade filer och förväntar dig byteidentisk utdata. Indata-alfa följer ett smalt kontrakt: en alfabyte på noll behandlas som opak, och ett partiellt transparent värde avvisas med ColorIndex 0 och PaletteSlot -1, eftersom palettposter saknar alfa. Classic-motorns fyllnads- och kantskrivare konverterar RGB- och temafärger till ett index med samma OKLab-matchningsrutin vid sparningen, så API:et och den lagrade filen är överens om vilken plats en färg hamnar 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;
Hur får BuildBiffPalettePlan plats med äkta färger i 56 platser?
BuildBiffPalettePlan räknar fram ett komplett förslag för alla 56 platser utan att röra arbetsboken, så du kan inspektera, logga eller kassera det. Planeraren anropar först ScanIndexedColorUsage: varje plats som ett teckensnitt, en fyllning, en kant, ett villkorsformat, en form, en kommentar eller ett kalkylbladsrutnät refererar till via index låses, för en ändring av en palettpost färgar om varje konsument av indexet på en gång. Målen är direkta RGB- och upplösta temafärger från teckensnitt, fyllningar, kanter, differentiella format, databar och färgskalor. Varje mål viktas av det större av dess antal renderade referenser och dess antal definitioner, och ett villkorsformat räknar cellerna som dess områden täcker, så en färg som målar en hel kolumn väger tyngre än en som används i en enda anteckning. Placeringen går sedan vidare i fast ordning:
- Låsta platser behåller sin källfärg villkorslöst
- Ett mål som redan finns i paletten behålls på sin lägsta matchande plats, och den platsen blir låst
- Om de återstående unika målen ryms i de lediga platserna får var och en en exakt plats, tilldelad i stigande ARGB-ordning
- Annars sätts
Quantized, varje ledig plats seedas med det mål vars avstånd till närmast befintligt centrum, multiplicerat med dess vikt, är störst, och upp till 16 rundor frekvensviktad k-means i OKLab flyttar bara de lediga centrumen tills tilldelningarna slutar ändras
Var ärlig mot dig själv om vad vägen för överloppsmålen levererar. Klustringen är en begränsad lokal optimering, inte ett globalt optimum, och en ledig plats hamnar med ett centroid konverterat tillbaka till sRGB med klämning, vilket kan vara en färg ingen cell använde ordagrant. Det du får är repeterbarhet: samma arbetsbok ger alltid samma plan, och planen rapporterar sin egen skada genom WeightedError, MaxDistanceSquared, ExactTargetWeight och TotalTargetWeight, så ett batchjobb kan vägra att spara när approximationen blir för grov för en varumärkesmanual
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // skrivskyddad
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;
Hur avvisar ApplyBiffPalettePlan en inaktuell plan?
ApplyBiffPalettePlan validerar hela planen innan den skriver en enda plats, och returnerar False med paletten orörd om något inte stämmer med den aktuella arbetsboken. Planen bär SourcePaletteGeneration och SourcePaletteHash, en 64-bitars FNV-1a-hash över de 56 källfärgerna; valideringen kontrollerar också varje publikt och fysiskt index, varje källfärg, att ingen låst plats är markerad som ändrad, antalet låsta och ändrade platser, och att varje mål är opakt. En effektiv palettändring däremellan, inklusive en framgångsrik tidigare tillämpning av samma plan, gör planen inaktuell, så en plan används i praktiken bara en gång. En giltig plan utan ändrade platser lyckas utan att flytta fram generationen, och en riktig ändring flyttar fram generationen en gång och bygger om OKLab-matcharen en gång, på Classic-motorn genom att skriva om den fasta palettarrayen och på XLSX-motorn genom att byta in en förberedd override-lista för indexerade färger
Slå på policyn för BIFF8-sparningar och XLSX-till-XLS-konvertering
Egenskapen BiffPaletteSavePolicy har xbpsPreserve som standard, så en uppgradering av HotXLS skriver aldrig om någons palett i smyg. Sätter du den till xbpsOptimizeTrueColors bygger en Classic-arbetsbok och tillämpar en färsk plan inuti SaveAs, men bara när målformatet är xlExcel97; BIFF5, CSV, HTML, PDF, XLSX och de andra skrivarna ignorerar inställningen. Efter en lyckad sparning stannar den optimerade paletten kvar i arbetsboksmodellen, så senare frågor och sparningar ser samma mappning. Misslyckas sparningen eller avbryts den återställs de ursprungliga 56 färgerna och den ursprungliga generationen. För XLSX-källor bygger SaveXLSXWorkbookAsXLS i lxXlsxExport en plan från den laddade arbetsboken och skriver in den i destinationspaletten innan någon stil konverteras, vilket är den deterministiska bryggan som demon för arbetsboksgranskning och konvertering tränar. Temafärger går genom samma planerare efter att deras tint lösts till RGB; vill du hellre behålla teman levande i diagramfyllningar tar artikeln om GelFrame-temafärgade diagramfyllningar upp hur binär XLS lagrar ett schemaindex i stället för en utplattad färg
// Classic-arbetsbok: opt-in, endast BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // paletten redan återställd
// XLSX-modell till BIFF8 med en enda deterministisk palettplan
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
HotXLS-palett-API:erna fungerar likadant på IXLSWorkbook och TXLSXWorkbook, från Delphi likaväl som från C++Builder. Ladda ner utvärderingsversionen och rikta den mot ditt mest färgstarka kalkylblad från sidan för HotXLS Delphi Excel-komponenten