HotXLS mapează culori RGB și de temă oarecare pe paleta de culori BIFF8 cu 56 de sloturi în două straturi: NearestIndexedColor găsește în spațiul OKLab intrarea de paletă existentă perceptual cea mai apropiată, iar BuildBiffPalettePlan împreună cu ApplyBiffPalettePlan rescriu sloturile libere ale paletei, ca un registru de lucru în culori reale să supraviețuiască unei salvări în XLS clasic. Declanșatorul este mereu același tichet de suport. Cineva construiește un raport în XLSX cu antete bleumarin corporatist și un accent turcoaz discret, îl salvează ca .xls pentru un consumator legacy, iar antetele revin negru pur, în timp ce turcoazul se transformă într-un turcoaz strident. Nimic nu s-a prăbușit și niciun avertisment nu a fost emis. Modelul de culori al formatului vechi pur și simplu nu poate reține ce descria cel nou, iar biblioteca a trebuit să aleagă ceva
De ce poate un fișier XLS reține doar 56 de culori?
Pentru că un format de celulă BIFF8 nu stochează niciodată o valoare RGB: fonturile, umplerile și bordurile poartă un index de culoare, iar înregistrarea Palette ($0092, [MS-XLS] §2.4.188), globală la nivelul registrului de lucru, furnizează exact 56 de intrări RGB opace pentru indecșii 8 până la 63. Indecșii 0 până la 7 sunt copii fixe ale celor opt culori de bază, iar valorile peste 63 nu sunt deloc culori, ci token-uri precum prim-planul de sistem, fundalul de sistem și textul graficelor. HotXLS expune paleta printr-un ColorIndex public de la 1 la 56, adică indexul fizic minus 7, iar ResolveIndexedColor ține separate cele trei scheme de numerotare prin TXLSIndexedColorSpace: xicsPublicColorIndex pentru valorile API 1..56, xicsBiffIcv pentru indecșii brute de pe disc, care sunt validați față de submulțimea IcvFont, IcvXF sau IcvChart pentru rolul pe care îl pasați, și xicsOoxmlIndexed, unde 64 și 65 însemnă prim-plan și fundal de sistem
var
Res: TXLSIndexedColorResolution;
begin
// $40 este un token BIFF icv, nu un slot de paletă
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // slot de paletă, dacă s-a rezolvat
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
Rețineți că exemplul comută după Res.Kind și ignoră valoarea Booleană întoarsă. ResolveIndexedColor întoarce True doar când a obținut un ARGB concret, iar overload-ul scurt nu citește niciodată desktopul Windows, deci un token automatic sau de sistem vine în mod legitim înapoi ca False, rămânând totodată clasificat ca xickSystem. HotXLS a lovit asta în propriul serializer de registru de lucru: codul care tratează False ca „fără culoare" aruncă în tăcere sensul Automatic și System al token-ului. Dacă aveți nevoie de valori RGB reale pentru token-urile acelea, apelați overload-ul lung și furnizați un callback TXLSTryResolveSystemColor care aplică propria politică de UI, export sau headless
De ce potrivește HotXLS culorile în OKLab în loc de RGB?
Pentru că valorile de canal sRGB sunt encodate gamma, deci distanța euclidiană în RGB nu urmărește ce vede o persoană, iar eroarea este cea mai mare exact în tonurile închise și saturate de care se îndrăgostesc paletele corporatiste. Luați albastrul închis $000033. În RGB distanța până la negru este 51, iar cea până la intrarea navy implicită $000080 este 77, deci un potrivitor RGB vă vântește antetul în negru cu toată convingerea. În OKLab distanțele pătrate sunt în jur de 0.0312 până la negru și 0.0235 până la navy, iar HotXLS alege navy, ColorIndex 11 la slotul fizic 18; exact cazul acesta este fixat în suita de teste atât pentru motorul Classic, cât și pentru cel XLSX. Conversia din interiorul lui ArgbToOklab linearizează fiecare canal sRGB, aplică matricea LMS a OKLab, extrage rădăcini cubice și proiectează pe L, a și b, după care o distanță euclidiană pătrată simplă este un proxy rezonabil pentru diferența percepută. OKLab nu este CIEDE2000 și nu se dă drept el, dar nu are corecții de nuanță pe bucăți, costă câteva înmulțiri pe culoare și este suficient de stabil ca să conducă o buclă de clusterizare, ceea ce este locul în care își câștigă cu adevărat existența
Ce garantează NearestIndexedColor?
NearestIndexedColor garantează un răspuns determinist și doar în citire: o conversie a intrării, o scanare fixă peste 56 de intrări cachetate și cel mai mic index public ori de câte ori două intrări sunt la fel de apropiate. Fiecare registru de lucru cachetează ARGB-ul normalizat și coordonatele OKLab ale tuturor celor 56 de sloturi fizice împreună cu un contor de generație a paletei. Un reset al paletei reconstruiește cache-ul, o schimbare pe un singur slot actualizează doar slotul acela, iar o interogare contra unei generații învechite întoarce False în loc să ghicească. Scanarea folosește o comparație strictă de mai mic, pornind de la slotul 8, motiv pentru care o paletă care conține aceeași culoare de două ori răspunde mereu cu indexul mai mic; contează când comparați două fișiere generate și vă așteptați la o ieșire identică byte cu byte. Alfa-ul de intrare urmează un contract îngust: un byte alfa zero este tratat ca opac, iar o valoare parțial transparentă este respinsă cu ColorIndex 0 și PaletteSlot -1, pentru că intrările de paletă nu au alfa. Scriitorii de umpleri și borduri ai motorului Classic convertesc culorile RGB și de temă într-un index cu aceeași rutină de potrivire OKLab la momentul salvării, deci API-ul și fișierul stocat cad de acord asupra slotului în care aterizează o culoare
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;
Cum încadrează BuildBiffPalettePlan culorile reale în 56 de sloturi?
BuildBiffPalettePlan calculează o propunere completă pentru toate cele 56 de sloturi fără să atingă registrul de lucru, deci o puteți inspecta, jurnaliza sau arunca. Planificatorul apelează întâi ScanIndexedColorUsage: orice slot la care un font, o umplere, o bordură, un format condițional, o formă, un comentariu sau o linie de grilă a foii de lucru face referință prin index este blocat, pentru că schimbarea unei intrări de paletă recolorează dintr-o lovitură fiecare consumator al indexului aceluia. Țintele sunt culorile RGB directe și culorile de temă rezolvate din fonturi, umpleri, borduri, stiluri diferențiale, bare de date și scale de culori. Fiecare țintă este ponderată cu cel mai mare dintre numărul de referințe randate și numărul de definiții, iar un format condițional numără celulele pe care le acoperă intervalele lui, deci o culoare întinsă peste o coloană întreagă cântărește mai mult decât una folosită într-o singură notă. Plasarea apoi decurge într-o ordine fixă:
- Sloturile blocate își păstrează necondiționat culoarea sursă
- O țintă care există deja în paletă este reținută la slotul ei de potrivire cel mai mic, iar slotul acela devine fix
- Dacă țintele unice rămase încape în sloturile libere, fiecare primește un slot exact, alocat în ordine ARGB crescătoare
- Altfel se setează
Quantized, fiecare slot liber este inițializat cu ținta a cărei distanță până la cel mai apropiat centru existent, înmulțită cu ponderea ei, este cea mai mare, iar până la 16 runde de k-means ponderat frecvențial în OKLab mută doar centrele libere până când atribuirile încetează să se mai schimbe
Fiți sinceri cu voi înșivă în privința a ceea ce livrează calea de depășire. Clusterizarea este o optimizare locală mărginită, nu un optimum global, iar un slot liber ajunge să țină un centroid convertit înapoi în sRGB cu clampare, ceea ce poate fi o culoare pe care nicio celulă n-a folosit-o ca atare. Ceea ce obțineți este repetabilitatea: același registru de lucru produce întotdeauna același plan, iar planul își raportează propriile daune prin WeightedError, MaxDistanceSquared, ExactTargetWeight și TotalTargetWeight, deci un job de batch poate refuza salvarea când aproximarea devine prea grosieră pentru un ghid de brand
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // doar în citire
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;
Cum respinge ApplyBiffPalettePlan un plan învechit?
ApplyBiffPalettePlan validează planul întreg înainte să scrie un singur slot și întoarce False cu paleta neatinsă dacă ceva nu se potrivește cu registrul de lucru curent. Planul poartă SourcePaletteGeneration și SourcePaletteHash, un hash FNV-1a pe 64 de biți peste cele 56 de culori sursă; validarea reverifică fiecare index public și fizic, fiecare culoare sursă, că niciun slot blocat nu este marcat ca schimbat, contoarele de blocate și de schimbate, și că fiecare țintă este opacă. Orice schimbare efectivă de paletă între timp, inclusiv o aplicare reușită anterioară a aceluiași plan, face planul învechit, deci planurile sunt practic de o singură folosință. Un plan valid fără sloturi schimbate reușește fără să avanseze generația, iar o schimbare reală mărește generația o dată și reconstruiește potrivitorul OKLab o dată, pe motorul Classic prin rescrierea tabloului fix de paletă, iar pe motorul XLSX prin înlocuirea cu o listă pregătită de override de culori indexate
Activarea pentru salvările BIFF8 și pentru conversia XLSX în XLS
Proprietatea BiffPaletteSavePolicy are implicit valoarea xbpsPreserve, deci actualizarea HotXLS nu rescrie niciodată paleta nimănui pe furiș. Setarea ei la xbpsOptimizeTrueColors face ca un registru de lucru Classic să construiască și să aplice un plan proaspăt în interiorul lui SaveAs, dar doar când formatul țintă este xlExcel97; scriitorii BIFF5, CSV, HTML, PDF, XLSX și ceilalți ignoră setarea. După o salvare reușită, paleta optimizată rămâne în modelul registrului de lucru, deci interogările și salvările ulterioare văd aceeași mapare. Dacă salvarea eșuează sau este anulată, cele 56 de culori originale și generația originală sunt restaurate. Pentru sursele XLSX, SaveXLSXWorkbookAsXLS din lxXlsxExport construiește un singur plan din registrul de lucru încărcat și îl scrie în paleta destinației înainte ca vreun stil să fie convertit, ceea ce este puntea deterministă pe care o exersează demonstrația de audit și conversie a registrelor de lucru. Culorile de temă trec prin același planificator după ce tentă lor este rezolvată în RGB; dacă preferați să păstrați temele vii în umplerile graficelor, articolul despre umpluturile de grafice cu culori de temă GelFrame acoperă cum XLS-ul binar stochează un index de schemă în locul unei culori aplatizate
// Registru de lucru clasic: activare explicită, doar BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // paleta este deja restaurată
// model XLSX către BIFF8 cu un singur plan de paletă determinist
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
API-urile de paletă HotXLS funcționează la fel pe IXLSWorkbook și TXLSXWorkbook, atât din Delphi, cât și din C++Builder. Descărcați versiunea de probă și îndreptați-o spre foaia de calcul cea mai colorată de a dumneavoastră, de pe pagina componentei Excel HotXLS pentru Delphi