Tehnički članak

BIFF8 paleta od 56 boja: OKLab mapiranje boja u HotXLS-u

HotXLS u dva sloja preslikava proizvoljne RGB i theme boje na BIFF8 paletu od 56 mjesta: NearestIndexedColor u OKLab prostoru nalazi perceptivno najbliži postojeći unos palete, a BuildBiffPalettePlan s ApplyBiffPalettePlan prepisuju slobodna mjesta palete tako da radna knjiga u pravim bojama preživi spremanje u klasični XLS. Okidač je uvijek isti support ticket. Netko sastavi izvještaj u XLSX-u s korporativnim tamnoplavim naslovima i blagim teal akcentom, spremit ga kao .xls za legacy potrošača, i naslovi se vrate potpuno crni dok teal postane vrišteći tirkiz. Ništa se nije srušilo i nijedno upozorenje nije zasviralo. Model boja starog formata jednostavno ne može nositi ono što je opisao novi, a biblioteka je morala nešto odabrati

Zašto XLS datoteka može držati samo 56 boja?

Zato što BIFF8 format ćelije nikad ne sprema RGB vrijednost: fontovi, ispune i okviri nose indeks boje, a Palette zapis na razini cijele radne knjige ($0092, [MS-XLS] §2.4.188) daje točno 56 neprozirnih RGB unosa za indekse 8 do 63. Indeksi 0 do 7 fiksne su kopije osam osnovnih boja, a vrijednosti iznad 63 uopće nisu boje nego tokeni poput system foreground, system background i chart text. HotXLS paletu izlaže kroz javni ColorIndex od 1 do 56, što je fizički indeks minus 7, a ResolveIndexedColor drži tri sheme numeracije odvojene kroz TXLSIndexedColorSpace: xicsPublicColorIndex za API vrijednosti 1..56, xicsBiffIcv za sirove indekse s diska, koje provjerava protiv podskupa IcvFont, IcvXF ili IcvChart za ulogu koju Vi proslijedite, i xicsOoxmlIndexed, gdje 64 i 65 znače system foreground i background

HotXLS drži tri sheme indeksiranih boja odvojene kroz TXLSIndexedColorSpace: sirove BIFF icv vrijednosti, 0 do 7 fiksirano na osam osnovnih boja, 56 mjesta palete 8 do 63 zapisa Palette $0092, tokeni iznad 63 poput system foreground, javni ColorIndex 1 do 56 pomaknut minus 7, i xicsOoxmlIndexed gdje 64 i 65 znače system foreground i background
Isti indeks boje u svakoj shemi znači drugačiji broj, pa HotXLS svaku vrijednost usmjerava kroz ResolveIndexedColor umjesto da sirovi BIFF token glumi javni ColorIndex
var
  Res: TXLSIndexedColorResolution;
begin
  // $40 je BIFF icv token, ne mjesto palete
  Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
  case Res.Kind of
    xickPalette:   UseArgb(Res.ARGB);   // mjesto palete, ako je razriješeno
    xickAutomatic,
    xickSystem:    UseSystemColor(Res.SystemColorRole);
    xickInvalid:   RejectToken(Res.RawIndex);
  end;
end;

Obratite pozornost na to da primjer grana na Res.Kind i ignorira Booleovu povratnu vrijednost. ResolveIndexedColor vraća True samo kada je dobila konkretan ARGB, a kratka overload verzija nikad ne čita Windows desktop, pa automatski ili sistemski token legitimno dolazi natrag kao False dok je i dalje klasificiran kao xickSystem. HotXLS je to pogodio u vlastitom serializatoru radne knjige: kod koji tretira False kao "bez boje" tiho odbacuje Automatic i System značenje tokena. Ako trebate stvarne RGB vrijednosti za te tokene, pozovite dugi overload i dostavite TXLSTryResolveSystemColor callback koji primjenjuje Vašu vlastitu UI, export ili headless politiku

Zašto HotXLS poklapa boje u OKLabu umjesto u RGB-u?

Zato što su sRGB vrijednosti kanala gamma-kodirane, pa euklidska udaljenost u RGB-u ne prati ono što čovjek vidi, a pogreška je najgora upravo u tamnim, zasićenim tonovima koje korporativne palete obožavaju. Uzmite tamnoplavu $000033. U RGB-u je udaljenost do crne 51, a do zadanog navy unosa $000080 77, pa RGB matcher samouvjereno oboji Vaše naslove crno. U OKLabu su kvadrirane udaljenosti oko 0.0312 do crne i 0.0235 do navy, pa HotXLS bira navy, ColorIndex 11 na fizičkom mjestu 18; taj točan slučaj pribijen je u test suite za i Classic i XLSX engine. Konverzija unutar ArgbToOklab linearizira svaki sRGB kanal, primjenjuje OKLab LMS matricu, izvlači kubne korijene i projicira na L, a i b, nakon čega je obična kvadrirana euklidska udaljenost razumna zamjena za percipiranu razliku. OKLab nije CIEDE2000 i ne pretvara se da jest, ali nema piecewise ispravke nijanse, košta par množenja po boji i dovoljno je stabilan da pokreće klaster petlju — i tamo zaista zaslužuje svoje mjesto

Kako HotXLS preslikava tamnoplavu $000033 na paletu: euklidska udaljenost u gamma-kodiranom RGB-u iznosi 51 do crne i 77 do navy te bi naslov obojila crno, dok kvadrirane ArgbToOklab udaljenosti 0.0312 i 0.0235 puštaju NearestIndexedColor da izabere navy, ColorIndex 11 na fizičkom mjestu 18
Gamma-kodirane vrijednosti kanala čine RGB udaljenost lošom zamjenom za ono što čovjek vidi, pa HotXLS jednom konvertira u OKLab i pušta da obična kvadrirana euklidska usporedba vodi skeniranje palete

Što garantira NearestIndexedColor?

NearestIndexedColor garantira determinističan odgovor samo za čitanje: jedna konverzija ulaza, jedno fiksno skeniranje 56 keširanih unosa i najniži javni indeks kad god su dva unosa jednako blizu. Svaka radna knjiga kešira normalizirani ARGB i OKLab koordinate svih 56 fizičkih mjesta zajedno s brojačem generacije palete. Reset palete gradi keš iznova, promjena jednog mjesta ažurira samo to mjesto, a upit nad zastarjelom generacijom vraća False umjesto da nagađa. Skeniranje koristi strogu manje-od usporedbu počevši od mjesta 8, zato paleta koja istu boju sadrži dvaput uvijek odgovara nižim indeksom; to je bitno kad diffate dvije generirane datoteke i očekujete izlaz identičan do bajta. Ulazna alfa vrijednost slijedi uzak ugovor: alfa bajt nula tretira se kao neproziran, a djelomično prozirna vrijednost odbija se s ColorIndex 0 i PaletteSlot -1, jer unosi palete nemaju alfu. Fill i border pisci Classic enginea konvertiraju RGB i theme boje u indeks istom OKLab rutinom poklapanja u trenutku spremanja, pa se API i spremljena datoteka slažu u koje mjesto boja slijeće

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;

Kako BuildBiffPalettePlan ugura prave boje u 56 mjesta?

BuildBiffPalettePlan računa potpuni prijedlog za svih 56 mjesta ne dirajući radnu knjigu, pa ga možete pregledati, zapisati u log ili odbaciti. Planer najprije zove ScanIndexedColorUsage: svako mjesto koje font, ispuna, okvir, conditional format, oblik, komentar ili gridline radnog lista referencira indeksom zaključano je, jer promjena unosa palete prebojava sve potrošače tog indeksa u jednom mahu. Mete su izravne RGB i razriješene theme boje iz fontova, ispuna, okvira, diferencijalnih stilova, data barova i color scaleova. Svaka meta teži većim od broja referenci u renderu i broja definicija, a conditional format broji ćelije koje njegovi rasponi pokrivaju, pa boja preko cijelog stupca prevagne nad onom u jednoj napomeni. Smještaj zatim ide fiksnim redoslijedom:

  • Zaključana mjesta bezuvjetno zadržavaju svoju izvornu boju
  • Meta koja već postoji u paleti zadržava se na njenom najnižem mjestu poklapanja, a to mjesto postaje fiksno
  • Ako se preostale jedinstvene mete uklapaju u slobodna mjesta, svaka dobiva točno mjesto, dodijeljeno u uzlaznom ARGB redoslijedu
  • Inače se postavlja Quantized, svako slobodno mjesto zasije se metom čija je udaljenost do najbližeg postojećeg centra, pomnožena njenom težinom, najveća, a do 16 krugova frekvencijski-težinskih k-means u OKLabu pomiče samo slobodne centre dok dodjele ne stanu

Budite iskreni prema sebi oko toga što overflow putanja isporučuje. Klasteriranje je ograničena lokalna optimizacija, ne globalni optimum, i slobodno mjesto na kraju drži centroid konvertiran natrag u sRGB uz clamping, što može biti boja koju nijedna ćelija nije koristila doslovno. Ono što dobivate je ponovljivost: ista radna knjiga uvijek daje isti plan, a plan vlastitu štetu javlja kroz WeightedError, MaxDistanceSquared, ExactTargetWeight i TotalTargetWeight, pa batch posao može odbiti spremanje kad aproksimacija postane pregruba za smjernice brenda

HotXLS pipeline palete za radnu knjigu u pravim bojama: ScanIndexedColorUsage zaključava svako mjesto koje referencira font, ispuna, okvir, conditional format, oblik, komentar ili gridline, BuildBiffPalettePlan smješta točne boje u uzlaznom ARGB redoslijedu ili pokreće do 16 krugova frekvencijski-težinskih k-means u OKLabu, a ApplyBiffPalettePlan provjerava generaciju i FNV-1a hash prije pisanja
Planiranje je samo za čitanje i ponovljivo, plan vlastitu štetu javlja kroz WeightedError i MaxDistanceSquared, a zastarjeli plan se odbija s paletom netaknutom jer su planovi u praksi jednokratni
var
  Plan: TXLSBiffPalettePlan;
  I: Integer;
begin
  Plan := Workbook.BuildBiffPalettePlan;   // samo za čitanje
  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;

Kako ApplyBiffPalettePlan odbija zastarjeli plan?

ApplyBiffPalettePlan provjerava cijeli plan prije nego napiše ijedno mjesto i vraća False s netaknutom paletom ako se bilo što ne slaže s trenutnom radnom knjigom. Plan nosi SourcePaletteGeneration i SourcePaletteHash, 64-bitni FNV-1a hash nad 56 izvornih boja; validacija dodatno ponovno provjerava svaki javni i fizički indeks, svaku izvornu boju, da nijedno zaključano mjesto nije označeno kao promijenjeno, brojače zaključanih i promijenjenih, te da je svaka meta neprozirna. Svaka stvarna promjena palete u međuvremenu, uključujući uspješnu raniju primjenu istog plana, čini plan zastarjelim, pa su planovi u praksi jednokratni. Valjan plan bez promijenjenih mjesta uspijeva bez napredovanja generacije, a stvarna promjena podiže generaciju jednom i gradi OKLab matcher iznova jednom — na Classic engineu prepisivanjem fiksnog polja palete, a na XLSX engineu umetanjem pripremljenog indexed-color override popisa

Uključivanje za BIFF8 spremanja i konverziju XLSX u XLS

Svojstvo BiffPaletteSavePolicy zadano je na xbpsPreserve, pa nadogradnja HotXLS-a nikome ne prepisuje paletu iza leđa. Postavljanje na xbpsOptimizeTrueColors natjera Classic radnu knjigu da izgradi i primijeni svjež plan unutar SaveAs, ali samo kad je ciljni format xlExcel97; BIFF5, CSV, HTML, PDF, XLSX i ostali pisci ignoriraju postavku. Nakon uspješnog spremanja optimizirana paleta ostaje u modelu radne knjige, pa kasniji upiti i spremanja vide isto mapiranje. Ako spremanje padne ili se otkaže, vraćaju se izvornih 56 boja i izvorna generacija. Za XLSX izvore, SaveXLSXWorkbookAsXLS u lxXlsxExport gradi jedan plan iz učitane radne knjige i upisuje ga u odredišnu paletu prije konverzije ijednog stila, i to je deterministički most koji vježba demo za reviziju i konverziju radne knjige. Theme boje prolaze kroz isti planer nakon što se njihov tint razriješi u RGB; ako Vi radije želite theme žive u chart ispunama, članak o GelFrame theme bojama za chart ispune pokriva kako binarni XLS sprema indeks sheme umjesto spljoštene boje

// Classic radna knjiga: isključivo uključivanje, samo BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
  HandleSaveFailure;   // paleta je već vraćena

// XLSX model u BIFF8 s jednim determinističkim planom palete
XWorkbook := TXLSXWorkbook.Create;
try
  if XWorkbook.Open('report.xlsx') = 1 then
    SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
  XWorkbook.Free;
end;

HotXLS API palete rade isto na IXLSWorkbook i TXLSXWorkbook, iz Delphija i C++Buildera jednako. Probnu verziju preuzmite s stranice HotXLS Delphi Excel komponente i usmjerite je na svoju najšareniju proračunsku tablicu