Teknik Makale

BIFF8 56 Renk Paleti: HotXLS'te OKLab Renk Eşleme

Koyu mavi $000033, uzaklık RGB kanal değerleri üzerinden düz Öklid uzaklığı olarak ölçüldüğünde siyaha 51 birim, klasik lacivert girdisi $000080'e ise 77 birim uzaktadır. En yakın RGB eşlemesiyle klasik .xls dosyaları yazan bir dönüştürücü bu yüzden koyu mavi bir başlık satırını siyaha boyar; palet indeksine tek başına bakan her okuyucu da siyahı görüntüler. Format yazıcıya hiçbir alan bırakmaz: bir hücre formatı bir palet indeksi saklar ve palet 56 girdi tutar. Başlığı doğru tutturmak iki şey ister: insanların gördüğünü izleyen bir uzaklık ölçüsü ve bir çalışma kitabı paletin tutabileceğinden daha fazla farklı renk kullandığında paletin kendisini yeniden yazmanın bir yolu

Bir XLS dosyası neden yalnızca 56 renk tutabiliyor?

Bir BIFF8 font, dolgu ya da kenarlık kaydı asla RGB değeri taşımaz. Bir renk indeksi taşır; çalışma kitabı genelindeki Palette kaydı ($0092, [MS-XLS] §2.4.188), 8 ile 63 arasındaki indeksler için tam olarak 56 opak RGB girdisi sağlar. 0 ile 7 arasındaki indeksler sekiz temel rengin sabit kopyalarıdır. 63 üzerindeki değerler renk değil token'dır: $40 sistem ön planı, $41 sistem arka planı, $4D ile $4F grafik metni, grafik arka planı ve nötr grafik rengidir, $7FFF ise otomatik demektir. Excel 2007, bir hücre formatına tam RGB ya da tema rengi ekleyebilen XFExt kaydını ($087D, §2.4.355) ekledi ama Excel 97-2003 ve yalnızca XF kaydını okuyan her ayrıştırıcı, indeks dışında hiçbir şey görmez

HotXLS paleti, fiziksel indeksin eksi 7'si olan 1-56 arası herkese açık bir ColorIndex üzerinden açar. Üç numaralandırma şeması devrededir ve ResolveIndexedColor, onları TXLSIndexedColorSpace ile ayrı tutar: 1..56 API değerleri için xicsPublicColorIndex, ham disk-üzeri indeksler için xicsBiffIcv ve 64 ile 65'in sistem ön planı ile arka planını ifade ettiği xicsOoxmlIndexed. Ham BIFF indeksleri ayrıca TXLSIndexedColorRole olarak geçtiğiniz role karşı denetlenir; çünkü bir font, bir dolgu ve bir grafik farklı token kümeleri kabul eder: $51 bir font için geçerli, bir dolgu için geçersizdir

HotXLS üç indeksli renk şemasını TXLSIndexedColorSpace ile ayrı tutuyor: ham BIFF icv değerleri, sekiz temel renge sabitlenmiş 0-7, Palette kaydı $0092'nin 8-63 arası 56 palet yuvası, sistem ön planı gibi 63 üstü token'lar, eksi 7 kaydırmalı genel ColorIndex 1-56 ve 64 ile 65'in sistem ön planı ile arka planı ifade ettiği xicsOoxmlIndexed
Aynı renk indeksi her şemada farklı sayılar ifade eder, bu yüzden HotXLS ham bir BIFF token'ının genel ColorIndex kılığına girmesine izin vermek yerine her değeri ResolveIndexedColor'dan geçiriyor

ResolveIndexedColor'un Boolean sonucu, çağıranların tökezlediği yerdir. Yalnızca somut bir ARGB değeri elde ettiğinde True döner ve kısa overload masaüstü renkleri için işletim sistemine hiç soru sormaz. Bir sistem ya da otomatik token bu yüzden Res.Kind xickSystem ya da xickAutomatic ayarlıyken False döner ve False'ı "renk yok" okuyan kod, token'ın anlamını sessizce çöpe atar. Tek istisna $4F'dir, nötr grafik rengi; hiç yardım almadan siyaha çözümlenir. Öteki token'lar için gerçek RGB değerleri istiyorsanız, uzun overload'a bir TXLSTryResolveSystemColor metodu geçirin ve UI, bir dışa aktarma ya da headless bir hizmet için kendi politikanızı uygulayın:

type
  TExportColors = class
    function ResolveSystemColor(AColor: TXLSSystemColorRole;
      ARole: TXLSIndexedColorRole; out AArgb: LongWord): Boolean;
  end;

function TExportColors.ResolveSystemColor(AColor: TXLSSystemColorRole;
  ARole: TXLSIndexedColorRole; out AArgb: LongWord): Boolean;
begin
  // Headless dışa aktarma: güncel masaüstü teması yerine sabit renkler
  Result := True;
  case AColor of
    xscrWindowText, xscrChartText, xscrToolTipText:
      AArgb := $FF000000;
    xscrWindowBackground, xscrChartBackground:
      AArgb := $FFFFFFFF;
  else
    Result := False;
  end;
end;

var
  Colors: TExportColors;   // dışa aktarma kodunuz tarafından bir kez kurulur
  Res: TXLSIndexedColorResolution;
begin
  // $41 sistem arka planı token'ıdır, bir dolgu için geçerlidir
  if Workbook.ResolveIndexedColor($41, xicsBiffIcv, xicrFillBackground,
    Colors.ResolveSystemColor, Res) then
    PaintBackground(Res.ARGB)        // kendi çizim yordamınız
  else if Res.Kind = xickInvalid then
    RejectToken(Res.RawIndex);       // kendi hata işlemeniz
end;

RGB uzaklığı neden yanlış palet girdisini seçiyor?

sRGB kanal değerleri gamma-kodlanmıştır; sayılardaki eşit adımlar, bir insanın gördüğünde eşit adımlar değildir ve hata, tam da kurumsal şablonların sevdiği koyu, doygun tonlarda en büyüktür. RGB'de $000033 siyaha 51, laciverte 77 uzaktadır; siyah kazanır. Her kanal doğrusallaştırılıp renk OKLab'a izdüşürüldüğünde karesel uzaklıklar siyaha yaklaşık 0.0312, laciverte 0.0235'tir ve lacivert kazanır. Eşleme, fiziksel yuva 18'de ColorIndex 11'dir ve Classic ile XLSX motorları aynı cevabı verir

HotXLS koyu mavi $000033'ü palete nasıl eşliyor: gamma-kodlanmış RGB'de Öklid uzaklığı siyaha 51, laciverte 77 ölçer ve başlığı siyaha boyardı; ArgbToOklab'ın 0.0312 ve 0.0235 karesel uzaklıkları ise NearestIndexedColor'ın laciverti, fiziksel yuva 18'deki ColorIndex 11'i seçmesine izin veriyor
Gamma-kodlanmış kanal değerleri RGB uzaklığını insanın gördüğünün zayıf bir vekili yapar, bu yüzden HotXLS bir kez OKLab'a dönüştürüp palet taramasını düz bir karesel Öklid karşılaştırmasına bırakıyor

Varsayılan palet bir kıvrım ekler. Lacivert iki kez görünür, ColorIndex 11'de ve yine 25'te; yani $000033 ikisine de tam olarak aynı uzaktadır. Farklı bir sırada tarayan ya da küçüktür-eşittir kullanan bir eşleştirici bir derlemede 25'i, ötekinde 11'i seçer ve iki üretilmiş dosya diff'te temiz eşleşmeyi bırakır. HotXLS yuva 8'den yukarı doğru katı küçüktür karşılaştırmasıyla tarar; bir beraberlik daima daha düşük indekse gider

OKLab bilinçli bir takastır. CIEDE2000 bazı ton çiftlerini daha doğru sıralardı ama parçalı düzeltmeleri onu yavaş yapar ve yinelemeli bir döngünün içinde akıl yürütmesi zorlaşır. OKLab renk başına bir doğrusallaştırma, iki küçük matris çarpımı ve üç küp köküne mal olur; sonrasında düz bir karesel uzaklık, algılanan fark için yeterince iyi bir vekildir. Her renk atamasında ve bir kümeleme geçişinin içinde koşan 56 girdili palet taraması için daha ucuz ve daha akıcı ölçüt doğru seçimdir

NearestIndexedColor ne döndürür?

NearestIndexedColor, bir ARGB değeri alır, çalışma kitabını değiştirmeden bir TXLSNearestIndexedColorMatch kaydını doldurur. Her çalışma kitabı 56 palet rengini OKLab koordinatlarıyla ve bir palet üretim sayacıyla birlikte tutar; bir sorgu, bir girdi dönüşümü ve bir sabit taramaya mal olur. Paleti sıfırlamak o önbelleği yeniden kurar, tek bir girdiyi değiştirmek yalnızca o yuvayı günceller ve güncel üretimle uyumsuz bir önbelleğe yapılan sorgu tahmin yürütmek yerine False döner

Alfa dar bir kural izler. Sıfır alfa baytı opak sayılır; bu, Delphi kodunun çoğunun düz RGB değerlerini nasıl geçtiğiyle uyumludur ve $FF tanım gereği opaktır. Aradaki her şey reddedilir: fonksiyon ColorIndex 0 ve PaletteSlot -1 ile False döner, çünkü bir palet girdisinin saydamlığı saklayacak yeri yoktur

var
  Match: TXLSNearestIndexedColorMatch;
begin
  if Workbook.NearestIndexedColor($FF000033, Match) then
  begin
    // Varsayılan palet: Match.ColorIndex = 11, Match.PaletteSlot = 18,
    // Match.ARGB = $FF000080
    if not Match.ExactMatch then
      LogApproximation(Match.InputARGB, Match.ARGB,   // kendi günlükçünüz
        Match.DistanceSquared);
  end;
end;

Aynı eşleştirici RGB atayıcılarının arkasındadır. Classic bir çalışma kitabında Interior.Color, Font.Color ya da Border.Color atamak, tam rengi XFExt kaydı için tutar ve XF kaydına yazılan indeksi bu taramayla seçer; dolgu ve kenarlık yazarları RGB ile tema renklerini aynı yolla indekse çözer. NearestIndexedColor bir renk için ne rapor ediyorsa, yalnızca indeks okuyan bir okuyucu o yuvayı gösterecektir

Gerçek renkli bir çalışma kitabını 56 yuvaya sığdırmak

Varsayılan palete karşı eşleme bir avuç renk için yeterlidir. Bir düzine birbirine çok yakın mavisi olan bir marka şablonu daha fazlasını ister: paletin kendisinin değişmesi gerekir. BuildBiffPalettePlan 56 yuvanın tamamı için eksiksiz bir öneri hesaplar ve çalışma kitabına dokunmaz; günlüğe yazabilir, bir eşikle karşılaştırabilir ya da çöpe atabilirsiniz

Planlayıcı ScanIndexedColorUsage ile başlar. Bir font, dolgu, kenarlık, koşullu biçimlendirme, şekil, yorum, grafik fontu ya da çalışma sayfası kılavuz çizgisinin indeksle başvurduğu her yuva kilitlenir; çünkü o girdiyi değiştirmek indeksin tüm tüketicilerini tek seferde yeniden renklendirir. Hedefler, fontlardan, dolgulardan, gradyan duraklarından, kenarlıklardan, koşullu biçimlendirme stillerinden, veri çubuklarından ve renk ölçeklerinden gelen doğrudan RGB ve çözülmüş tema renkleridir. Her hedef, başvuru sayısı ile tanım sayısından büyüğüyle ağırlıklandırılır ve bir koşullu biçimlendirme, aralıklarının kapladığı her hücreyi sayar; tüm bir sütuna boyanan dolgu, tek bir yorumda kullanılan bir rengi geride bırakır. Yerleştirme sonra sabit bir sırayı izler:

  • Kilitli yuvalar güncel renklerini koşulsuz korur
  • Palette zaten var olan bir hedef, en düşük eşleşen yuvasını korur ve o yuva sabitlenir
  • Kalan farklı hedefler boş yuvalara sığıyorsa her biri, artan ARGB sırasıyla atanan tam bir yuva alır
  • Aksi hâlde Quantized set edilir, her boş yuva, en yakın mevcut merkezine uzaklığının ağırlığıyla çarpımı en büyük olan hedefle tohumlanır ve OKLab'da 16 tura kadar ağırlıklı k-means, atamalar değişmeyi bırakana dek yalnızca boş merkezleri oynatır
Gerçek renkli çalışma kitabı için HotXLS palet hattı: ScanIndexedColorUsage font, dolgu, kenarlık, koşullu biçimlendirme, şekil, yorum veya kılavuz çizgisinin başvurduğu her yuvayı kilitliyor, BuildBiffPalettePlan tam renkleri artan ARGB sırasıyla yerleştiriyor ya da OKLab'da 16 tura kadar frekans-ağırlıklı k-means çalıştırıyor, ApplyBiffPalettePlan ise yazmadan önce üretimi ve FNV-1a hash'ini doğruluyor
Planlama salt-okunur ve tekrarlanabilir, plan kendi zararını WeightedError ve MaxDistanceSquared ile raporluyor, bayat plan ise palet hiç değiştirilmeden reddediliyor çünkü planlar fiilen tek kullanımlık

Taşma yolunu gözü açık okumak gerekir. Buradaki k-means sınırlı bir yerel optimizasyondur; bir boş yuva sonunda, kelepçelenerek sRGB'ye geri çevrilmiş bir küme merkezi taşır ve bu, hiçbir hücrenin aynen kullanmadığı bir renk olabilir. Planlayıcının gerçekten sunduğu şey tekrarlanabilirliktir: aynı çalışma kitabı daima aynı planı üretir. Plan, ne kadar bükülmek zorunda kaldığını da WeightedError, MaxDistanceSquared, ExactTargetWeight ve TotalTargetWeight ile raporlar; yaklaşım bir marka kılavuzu için fazla kabalaştığında bir toplu işin kaydı reddetmesine yeter. Targets dizisi her girdi renk için hangi yuvaya eşlendiğini ve karesel uzaklığını gösterir

const
  MaxAcceptedError = 0.002;   // sizin eşiğiniz, karesel OKLab birimi cinsinden
var
  Plan: TXLSBiffPalettePlan;
  I: Integer;
begin
  Plan := Workbook.BuildBiffPalettePlan;   // salt-okunur
  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);           // kendi günlükçünüz
  if not Workbook.ApplyBiffPalettePlan(Plan) then
    raise Exception.Create('The palette changed after planning');
end;

ApplyBiffPalettePlan bayat bir planı nasıl reddediyor?

ApplyBiffPalettePlan, tek bir yuva yazmadan önce planın tamamını denetler ve herhangi bir şey güncel çalışma kitabıyla uyuşmazsa palet hiç dokunulmadan False döner. Plan, SourcePaletteGeneration ile 56 kaynak renk üzerinden 64 bitlik bir FNV-1a hash'i olan SourcePaletteHash taşır. Doğrulama ayrıca her genel ve fiziksel indeksi, her kaynak rengi yeniden denetler; kilitli hiçbir yuvanın değişmiş olarak işaretlenmediğini, kilitli ve değişmiş sayılarının toplandığını ve her hedefin opak olduğunu kontrol eder

Planlama ile uygulama arasındaki her gerçek palet değişikliği planı bayatlatır; aynı planın daha önceki başarılı bir uygulaması dâhil. Planlar pratikte tek kullanımlıktır: kurun, inceleyin, uygulayın; bir tane daha gerekiyorsa yeniden kurun. Değişmiş yuvası olmayan geçerli bir plan, üretimi ilerletmeden başarılı olur. Gerçek bir değişiklik üretimi bir kez ilerletir ve eşleştiriciyi bir kez tazeler. Classic motorunda yeni renkler doğrudan palete girer; XLSX motorunda özel indeksli renklere dönüşürler, HasCustomIndexedColor ile CustomIndexedColorCount bunları rapor eder ve XLSX yazıcısı bunları bir indexedColors bloğu olarak yazar

XLS kayıtları ve XLSX'ten XLS'e dönüşüm için isteğe bağlı açmak

BiffPaletteSavePolicy özelliği varsayılan olarak xbpsPreserve gelir; bir çalışma kitabının paleti yalnızca siz istediğinde değişir. xbpsOptimizeTrueColors yapmak, Classic bir çalışma kitabının SaveAs içinde taze bir plan kurup uygulamasını sağlar ve yalnızca hedef format xlExcel97 olduğunda; BIFF5, CSV, HTML, PDF, XLSX ve öteki yazıcılar bu ayarı yok sayar. Başarılı bir kayıttan sonra optimize edilmiş palet çalışma kitabında kalır; sonraki sorgular ve kayıtlar aynı eşlemeyi görür. Kayıt başarısız olursa, iptal edilirse ya da istisna fırlatırsa özgün 56 renk ve özgün üretim geri yüklenir

Üretilen raporlar için optimize edici politikayı açın. O çalışma kitapları RGB ve tema renklerinden kurulur, paleti kimse elle ayarlamamıştır ve yalnızca indeks okuyan bir okuyucu, politikayla birlikte onları özgüne çok daha yakın gösterir. xbpsPreserve'i, paletin sözleşmenin parçası olduğu çalışma kitapları için koruyun; alt akış kodun ColorIndex ile düzenlediği bir şablon gibi, bir yuvanın rengini sessizce değiştirmesi o kodu kıran yerlerde

XLSX kaynakları başka bir güzergâh izler. lxXlsxExport içindeki SaveXLSXWorkbookAsXLS, yüklenen TXLSXWorkbook'dan tek bir plan kurar ve herhangi bir stili dönüştürmeden önce onu hedef palete yazar; böylece her dönüştürülen hücre nihai palete karşı eşlenir. Çalışma kitabı denetimi ve dönüşüm workbench demosu tam olarak bu yolu koşar. Tema renkleri, tint'leri RGB'ye çözüldükten sonra aynı planlayıcıdan geçer; canlı tema referansı olarak kalmaları gereken grafik dolguları ayrı bir durumdur ve GelFrame tema renkli grafik dolguları yazısında ele alınır; ikili XLS orada düzleştirilmiş renk yerine bir şema indeksi saklar

// Classic çalışma kitabı: opt-in, yalnızca BIFF8 kayıtlarına uygulanır
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
  HandleSaveFailure;   // kendi işleyiciniz; palet çoktan geri yüklendi

// XLSX modelden BIFF8'e, tek deterministik palet planıyla
XWorkbook := TXLSXWorkbook.Create;
try
  if XWorkbook.Open('report.xlsx') = 1 then
    if SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls') <> 1 then
      HandleSaveFailure;
finally
  XWorkbook.Free;
end;

Palet çağrıları (ResolveIndexedColor, NearestIndexedColor, ScanIndexedColorUsage, BuildBiffPalettePlan ve ApplyBiffPalettePlan), Delphi ve C++Builder'da IXLSWorkbook ile TXLSXWorkbook üzerinde aynı imzalara sahiptir. HotXLS Delphi Excel component sayfasındaki deneme derlemesi, hiçbir şey yazılmadan önce en renkli şablonunuza karşı bir plan koşmaya ve MaxDistanceSquared'i okumaya yeter