HotXLS разпределя произволни RGB и theme цветове върху BIFF8 цветовата палитра с 56 слота на два слоя: NearestIndexedColor намира перцептивно най-близкия съществуващ запис в палитрата в OKLab пространството, а BuildBiffPalettePlan заедно с ApplyBiffPalettePlan пренаписва свободните слотове, така че true-color работна книга да оцелее при запис в класически XLS. Спусъкът е винаги един и същ support ticket. Някой прави отчет в XLSX с корпоративни тъмносини header-и и мек тюркоазен акцент, записва го като .xls за някоя стара система, а header-ите се връщат чисто черни, а тюркоазеното се превръща в крещящ бирюзов цвят. Нищо не се е сринало и никакво предупреждение не се е запалило. Цветовият модел на стария формат просто не може да побере онова, което новият описва, а библиотеката е трябвало да избере нещо
Защо XLS файл може да побере само 56 цвята?
Защото BIFF8 cell формат никога не съхранява RGB стойност: шрифтовете, fill-овете и рамките носят color index, а workbook-глобалният Palette record ($0092, [MS-XLS] §2.4.188) подава точно 56 непрозрачни RGB записа за индекси 8 до 63. Индекси 0 до 7 са фиксирани копия на осемте основни цвята, а стойностите над 63 изобщо не са цветове, а токени като system foreground, system background и chart text. HotXLS излага палитрата чрез публичен ColorIndex от 1 до 56, тоест физическият индекс минус 7, а ResolveIndexedColor държи трите схеми на номерация разделени чрез TXLSIndexedColorSpace: xicsPublicColorIndex за API стойностите 1..56, xicsBiffIcv за суровите on-disk индекси, валидирани спрямо подмножеството IcvFont, IcvXF или IcvChart според ролята, която подавате, и xicsOoxmlIndexed, където 64 и 65 значат system foreground и background
var
Res: TXLSIndexedColorResolution;
begin
// $40 е BIFF icv токен, не слот в палитрата
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // слот в палитрата, ако се разреши
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
Обърнете внимание, че примерът разклонява по Res.Kind и игнорира Boolean връщаната стойност. ResolveIndexedColor връща True само когато е получил конкретен ARGB, а краткият overload изобщо не чете Windows десктопа, така че automatic или system токен законно се връща като False, докато продължава да е класифициран като xickSystem. HotXLS удари това в собствения си workbook serializer: код, който третира False като „няма цвят“, тихо изхвърля Automatic и System значението на токена. Ако ви трябват реални RGB стойности за тези токени, викайте дългия overload и подавайте TXLSTryResolveSystemColor callback, който прилага вашата собствена UI, export или headless политика
Защо HotXLS съпоставя цветове в OKLab вместо в RGB?
Защото sRGB каналните стойности са gamma-кодирани, така че евклидовото разстояние в RGB не следи това, което човек вижда, а грешката е най-голяма точно в тъмните, наситени тонове, които корпоративните палитри обичат. Вземете тъмносиния $000033. В RGB разстоянието до черното е 51, а разстоянието до navy записа по подразбиране $000080 е 77, така че RGB matcher уверено ви боядисва header-а в черно. В OKLab квадратичните разстояния са около 0.0312 до черното и 0.0235 до navy, и HotXLS избира navy, ColorIndex 11 на физически слот 18; точно този случай е закован в тестовия suite и за двата engine-а, Classic и XLSX. Конверсията вътре в ArgbToOklab линеаризира всеки sRGB канал, прилага OKLab LMS матрицата, вади кубични корени и проектира върху L, a и b, след което обикновено квадратично евклидово разстояние е разумен заместител на възприетата разлика. OKLab не е CIEDE2000 и не се преструва, че е, но няма piecewise hue корекции, струва шепа умножения на цвят и е достатъчно стабилен да задвижва clustering цикъл — точно там си изкарва мястото
Какво гарантира NearestIndexedColor?
NearestIndexedColor гарантира детерминистичен, read-only отговор: една входна конверсия, едно фиксирано сканиране на 56 кеширани записа и най-ниският публичен индекс, когато два записа са еднакво близки. Всяка работна книга кешира нормализирания ARGB и OKLab координатите на всичките 56 физически слота заедно с брояч на palette generation. Reset на палитрата преизгражда кеша, промяна на един слот обновява само него, а заявка срещу остаряло поколение връща False вместо да гадае. Сканирането ползва строго „по-малко от“ сравнение, започвайки от слот 8, затова палитра, съдържаща един и същ цвят два пъти, винаги отговаря с по-ниския индекс; това има значение, когато diff-вате два генерирани файла и очаквате byte-identical изход. Входящият alpha следва тесен договор: нулев alpha байт се третира като непрозрачен, а частично прозрачна стойност се отхвърля с ColorIndex 0 и PaletteSlot -1, защото записите в палитрата нямат alpha. Fill и border writer-ите на Classic engine-а конвертират RGB и theme цветове в индекс със същата OKLab рутина при запис, така че API-ят и записаният файл се разбират в кой слот каца даден цвят
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;
Как BuildBiffPalettePlan побира true цветовете в 56 слота?
BuildBiffPalettePlan изчислява пълно предложение за всичките 56 слота, без да пипа работната книга, така че можете да го инспектирате, да го логнете или да го изхвърлите. Planner-ът първо вика ScanIndexedColorUsage: всеки слот, който шрифт, fill, рамка, conditional format, shape, коментар или gridline на лист реферира по индекс, се заключва, защото промяната на запис в палитрата преоцветява наведнъж всеки консуматор на този индекс. Целите са директните RGB и разрешените theme цветове от шрифтове, fills, рамки, differential styles, data bars и color scales. Всяка цел тежи според по-голямото от броя си на рендерирани справки и броя си на дефиниции, а conditional format брои клетките, които диапазоните му покриват, така че цвят, размазан по цяла колона, тежи повече от ползван в една бележка. Разпределянето после върви в фиксиран ред:
- Заключените слотове пазят своя source цвят безусловно
- Цел, която вече съществува в палитрата, се запазва на най-ниския си съвпадащ слот, а този слот става фиксиран
- Ако останалите уникални цели се побират в свободните слотове, всяка получава точен слот, разпределен във възходящ ARGB ред
- Иначе
Quantizedсе вдига, всеки свободен слот се сее с целта, чие разстояние до най-близкия ѝ съществуващ център, умножено по тежестта ѝ, е най-голямо, и до 16 рунда frequency-weighted k-means в OKLab местят само свободните центрове, докато разпределенията спрат да се менят
Бъдете честни със себе си какво доставя overflow пътят. Clustering-ът е ограничена локална оптимизация, не глобален оптимум, а свободен слот най-накрая държи центроид, конвертиран обратно в sRGB с clamping, което може да е цвят, който нито една клетка не е ползвала буквално. Това, което реално получавате, е повторяемост: същата работна книга винаги дава същия план, а планът докладва собствения си ущърб чрез WeightedError, MaxDistanceSquared, ExactTargetWeight и TotalTargetWeight, така че batch job може да откаже запис, когато апроксимацията стане твърде груба за brand guideline
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // read-only
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;
Как ApplyBiffPalettePlan отхвърля остарял план?
ApplyBiffPalettePlan валидира целия план, преди да запише дори един слот, и връща False с недокосната палитра, ако нещо не съвпада с текущата работна книга. Планът носи SourcePaletteGeneration и SourcePaletteHash, 64-битов FNV-1a hash върху 56-те source цвята; валидацията преизпитва също всеки публичен и физически индекс, всеки source цвят, че нито един заключен слот не е маркиран като променен, заключените и променените бройки, и че всяка цел е непрозрачна. Всяка ефективна промяна на палитрата междувременно, включително успешно приложението на същия план по-рано, прави плана остарял, затова плановете са де факто еднократни. Валиден план без променени слотове успява без да вдига поколението, а истинска промяна вдига поколението веднъж и преизгражда OKLab matcher-а веднъж — на Classic engine-а чрез пренаписване на фиксирания palette array, а на XLSX engine-а чрез сменяне с подготвен indexed-color override списък
Включване за BIFF8 записи и XLSX-to-XLS конверсия
Свойството BiffPaletteSavePolicy по подразбиране е xbpsPreserve, така че ъпгрейд на HotXLS никога не пренапва ничия палитра зад гърба ви. Задаването на xbpsOptimizeTrueColors кара Classic работна книга да изгради и приложи свеж план вътре в SaveAs, но само когато целевият формат е xlExcel97; BIFF5, CSV, HTML, PDF, XLSX и останалите writer-и игнорират настройката. След успешен запис оптимизираната палитра остава в модела на работната книга, така че по-късните заявки и записи виждат същото съответствие. Ако записът се провали или бъде отменен, оригиналните 56 цвята и оригиналното поколение се възстановяват. За XLSX източници SaveXLSXWorkbookAsXLS в lxXlsxExport строи един план от заредената работна книга и го записва в целевата палитра, преди да е конвертиран какъвто и да е style, което е детерминистичният мост, който демото на workbook audit и conversion workbench упражнява. Theme цветовете минават през същия planner, след като tint-ът им се разреши до RGB; ако предпочитате да пазите theme-овете живи в chart fills, статията за GelFrame theme color chart fills обяснява как binary XLS съхранява scheme индекс вместо изгладен цвят
// Classic работна книга: opt in, само BIFF8
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // палитрата вече е възстановена
// XLSX модел към BIFF8 с един детерминистичен palette план
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
Palette API-ите на HotXLS работят по същия начин на IXLSWorkbook и TXLSXWorkbook, както от Delphi, така и от C++Builder. Свалете trial-а и го насочете към най-цветната ви електронна таблица от страницата на HotXLS Delphi Excel компонента