HotXLS savavališkas RGB ir temines spalvas sudeda į 56 vietų BIFF8 spalvų paletę dviem sluoksniais: NearestIndexedColor OKLab erdvėje randa suvokimu artimiausią jau esamą paletės įrašą, o BuildBiffPalettePlan su ApplyBiffPalettePlan perrašo laisvas paletės vietas, kad tikrų spalvų darbaknygė išgyventų įrašymą į klasikinį XLS. Scenarijus visada tas pats — palaikymo bilietas. Kažkas XLSX sukuria ataskaitą su įmoniniais tamsiai mėlynais antraštėmis ir švelniu žalsvai mėlynu akcentu, įrašo ją kaip .xls senesnei programai, ir antraštės grįžta grynos juodos, o žalsvai mėlyna virsta rėkiančiu turkio atspalviu. Nieko nesulūžo, jokio perspėjimo neiššoko. Senojo formato spalvų modelis tiesiog negali išlaikyti to, ką aprašė naujasis, o biblioteka turėjo kažką pasirinkti
Kodėl XLS failas gali sutalpinti tik 56 spalvas?
Nes BIFF8 ląstelės formatas niekada nesaugo RGB reikšmės: šriftai, užpildai ir rėmeliai neša spalvos indeksą, o globalus darbaknygės Palette įrašas ($0092, [MS-XLS] §2.4.188) pateikia lygiai 56 neperšviečiamus RGB įrašus indeksams nuo 8 iki 63. Indeksai nuo 0 iki 7 yra fiksuotos aštuonių pagrindinių spalvų kopijos, o reikšmės, didesnės už 63, apskritai nėra spalvos — tai tokenai, tokie kaip sisteminis priekinis planas, sisteminis fonas ir diagramos tekstas. HotXLS paletę atveria per viešąjį ColorIndex nuo 1 iki 56, tai yra fizinį indeksą minus 7, o ResolveIndexedColor per TXLSIndexedColorSpace laiko atskirtas tris numeravimo schemas: xicsPublicColorIndex API reikšmėms 1..56, xicsBiffIcv neapdorotiems diske saugomiems indeksams, kurie tikrinami pagal jūsų perduodamą vaidmenį atitinkančiu IcvFont, IcvXF arba IcvChart poaibiu, ir xicsOoxmlIndexed, kur 64 ir 65 reiškia sisteminį priekinį planą ir foną
var
Res: TXLSIndexedColorResolution;
begin
// $40 yra BIFF icv tokenas, o ne paletės vieta
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // paletės vieta, jei pavyko išspręsti
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
Pastebėkite, kad pavyzdys skirsto pagal Res.Kind ir ignoruoja Boolean grąžinamąją reikšmę. ResolveIndexedColor grąžina True tik tada, kai gavo konkretų ARGB, o trumpoji perkrova niekada nesukreipia į Windows darbalaukį, todėl automatinis ar sisteminis tokenas teisėtai grąžina False, vis tiek likdamas klasifikuotas kaip xickSystem. HotXLS užkliuvo prie to savo pačios darbaknygės serializatoriuje: kodas, laikantis False „nėra spalvos“, tyliai išmeta tokeno Automatic ir System reikšmes. Jei tų tokenų tikrosioms RGB reikšmėms reikia, kvieskite ilguosią perkrovą ir pateikite TXLSTryResolveSystemColor callback, taikantį jūsų pačių UI, eksporto ar headless politiką
Kodėl HotXLS spalvas gretina OKLab, o ne RGB?
Nes sRGB kanalų reikšmės yra gamma užkoduotos, todėl Euklido atstumas RGB neseka to, ką mato žmogus, o klaida didžiausia būtent tamsiuose, sočiuose tonuose, kurių įmoninės paletės pamėgo. Imkime tamsiai mėlyną $000033. RGB atstumas iki juodos yra 51, o iki numatytosios navy įrašo $000080 — 77, tad RGB parinkiklis užtikrintai nudažo jūsų antraštę juodai. OKLab kvadratiniai atstumai yra apie 0.0312 iki juodos ir 0.0235 iki navy, ir HotXLS pasirenka navy — ColorIndex 11 fizinėje vietoje 18; šis tikslus atvejis testų rinkinyje įkaltas abiem varikliais, ir Classic, ir XLSX. Konversija ArgbToOklab viduje kiekvieną sRGB kanalą padaro linijiniu, taiko OKLab LMS matricą, ištraukia kubines šaknis ir projektuoja į L, a ir b, po ko paprastas kvadratinis Euklido atstumas yra pagrįstas suvokiamo skirtumo įvertinimu. OKLab nėra CIEDE2000 ir tos nesistengia būti, bet čia nėra iškilių atspalvio pataisų, kainuoja saują dauginimų vienai spalvai ir užtenka stabilumo varyti klasterizacijos ciklą — būtent ten jis ir atpirkia savo vietą
Ką garantuoja NearestIndexedColor?
NearestIndexedColor garantuoja deterministinį, tik skaitomą atsakymą: vieną įvesties konversiją, vieną fiksuotą peržiūrą per 56 podėlyje saugomus įrašus, o kai du įrašai vienodai arti — mažiausią viešąjį indeksą. Kiekviena darbaknygė podėlyje laiko normalizuotą ARGB ir visų 56 fizinių vietų OKLab koordinates kartu su paletės kartos skaitliuku. Paletės atstatymas perkuria podėlį, vienos vietos pakeitimas atnaujina tik tą vietą, o užklausa prieš pasenusią kartą grąžina False, užuot spėliojusi. Peržiūra naudoja griežtą „mažiau kaip“ palyginimą, pradedant nuo vietos 8, todėl paletė, kurioje ta pati spalva yra du kartus, visada atsako mažesniu indeksu; tai svarbu, kai diffinate du sugeneruotus failus ir tikatės baitas į baitą identiškos išvesties. Įvesties alpha laikosi santūraus kontrakto: nulinis alpha baitas laikomas neperšviečiamu, o iš dalies permatoma reikšmė atmetama su ColorIndex 0 ir PaletteSlot -1, nes paletės įrašai neturi alpha. Classic variklio užpildų ir rėmelių rašytojai įrašymo metu RGB ir temas spalvas į indeksą paverčia ta pačia OKLab parinkimo procedūra, tad API ir saugomas failas sutaria, į kurią vietą spalva pakliūva
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;
Kaip BuildBiffPalettePlan sutalpina tikras spalvas į 56 vietas?
BuildBiffPalettePlan apskaičiuoja pilną pasiūlymą visoms 56 vietoms neliesdamas darbaknygės, tad jį galite apžiūrėti, užregistruoti žurnale ar išmesti. Planuotojas pirmiausia kviečia ScanIndexedColorUsage: kiekviena vieta, kurią indeksu pasiekia šriftas, užpildas, rėmelis, sąlyginis formatas, figūra, komentaras ar darbalapio tinklelio linija, užrakinama, nes vieno paletės įrašo pakeitimas vienu ypu perspalvina visus to indekso vartotojus. Tikslai yra tiesioginės RGB ir išspręstos temos spalvos iš šriftų, užpildų, rėmelių, diferencialinių stilių, duomenų juostų ir spalvų skalių. Kiekvienas tikslas sveriamas didesniuoju iš jo atvaizduotų nuorodų skaičiaus ir apibrėžimų skaičiaus, o sąlyginis formatas skaičiuoja ląsteles, kurias dengia jo diapazonai, tad spalva, nutapyta per visą stulpelį, nusveria vieną, panaudotą viename pastabe. Tada išdėstymas eina fiksuota tvarka:
- Užrakintos vietos besąlygiškai išlaiko savo šaltinio spalvą
- Tikslas, jau egzistuojantis paletėje, paliekamas žemiausioje jam atitinkančioje vietoje, o ta vieta tampa fiksuota
- Jei likę unikalūs tikslai telpa į laisvas vietas, kiekvienas gauna tikslią vietą, priskirtą didėjančia ARGB tvarka
- Kitu atveju nustatomas
Quantized, kiekviena laisva vieta pasėjama tikslu, kurio atstumas iki artimiausio egzistuojančio centro, padaugintas iš jo svorio, didžiausias, ir iki 16 dažniu pasvertų k-means ratų OKLab erdvėje judina tik laisvuosius centrus, kol priskirimai nustoja keistis
Būkite sąžiningi su savimi dėl to, ką duoda perpildymo kelias. Klasterizacija yra apribota lokali optimizacija, o ne globalus optimumas, ir laisva vieta galiausiai laiko centroidą, konvertuotą atgal į sRGB su suvaržymu, kuris gali būti spalva, kurios jokia ląstelė nepanaudojo pažodžiui. Tai, ką tikrai gaunate, yra kartojamumas: ta pati darbaknygė visada duoda tą patį planą, o planas savo žalą praneša per WeightedError, MaxDistanceSquared, ExactTargetWeight ir TotalTargetWeight, tad paketinė užduotis gali atsisakyti rašyti, kai aproksimacija tampa per šiurkšti prekės ženklui
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // tik skaitymas
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;
Kaip ApplyBiffPalettePlan atmeta pasenusį planą?
ApplyBiffPalettePlan patikrina visą planą, dar nesparašiusi nė vienos vietos, ir grąžina False nepalietusi paletės, jei kas nors nesutampa su šia darbaknyge. Planas neša SourcePaletteGeneration ir SourcePaletteHash — 64 bitų FNV-1a maišą per 56 šaltinio spalvas; patikra taip pat dar kartą peržiūri kiekvieną viešąjį ir fizinį indeksą, kiekvieną šaltinio spalvą, tai, kad jokia užrakinta vieta nepažymėta kaip pakeista, užrakintų ir pakeistų skaičius bei tai, kad kiekvienas tikslas neperšviečiamas. Bet koks efektyvus paletės pasikeitimas per tą laiką, įskaitant sėkmingą ankstesnį to paties plano taikymą, padaro planą pasenusį, tad planai praktiškai yra vienkartiai. Teisėtas planas be pakeistų vietų pavyksta nejudinęs kartos, o tikras pakeitimas kartą pakelia ir kartą perkuria OKLab parinkiklį — Classic variklyje perrašant fiksuotą paletės masyvą, XLSX variklyje įstatant paruoštą indeksuotų spalvų perrašymo sąrašą
Įjungimas BIFF8 įrašymams ir XLSX į XLS konversijai
Savybė BiffPaletteSavePolicy pagal nutylėjimą yra xbpsPreserve, tad HotXLS atnaujinimas niekada niekieno paletės neperrašo už nugaros. Nustačius xbpsOptimizeTrueColors, Classic darbaknygė SaveAs viduje sukuria ir pritaiko šviežią planą, bet tik kai tikslinis formatas yra xlExcel97; BIFF5, CSV, HTML, PDF, XLSX ir kiti rašytojai nustatymo ignoruoja. Po sėkmingo įrašymo optimizuota paletė lieka darbaknygės modelyje, tad vėlesnės užklausos ir įrašymai mato tą patį atvaizdavimą. Jei įrašymas nepavyksta ar yra atšaukiamas, originalios 56 spalvos ir originali karta atstatomos. XLSX šaltiniams SaveXLSXWorkbookAsXLS iš lxXlsxExport sukuria vieną planą iš įkeltos darbaknygės ir įrašo jį į paskirties paletę, dar nekonvertavus jokio stiliaus, — tai yra deterministinis tiltas, kurį praveda darbaknygės audito ir konversijos workbench demonstracija. Temų spalvos praeina per tą patį planuotoją, kai jų tint išspręstas į RGB; jei norite, kad temos diagramų užpilduose liktų gyvos, GelFrame temos spalvų diagramų užpildų straipsnis pasakoja, kaip dvejetainis XLS saugo schemos indeksą vietoj išlygintos spalvos
// Classic darbaknygė: įjungiama savo noru, tik BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // paletė jau atstatyta
// XLSX modelis į BIFF8 su vienu deterministiniu paletės planu
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
HotXLS paletės API veikia taip pat su IXLSWorkbook ir TXLSXWorkbook, tiek iš Delphi, tiek iš C++Builder. Parsisiųskite bandomąją versiją ir nukreipkite ją į savo spalvingiausią skaičiuoklę HotXLS Delphi Excel komponento puslapyje