Odborný článok

HotXLS Image Geometry in Delphi: EMU, cm, and Scaling

Do hlavičky generovanej faktúry vložíte logo 600×400 pixelov, na vašom 96-DPI vývojovom monitore vyzerá správne a o týždeň zákazník na notebooku s vysokým DPI hlási, že sa vytlačí vo veľkosti poštovej známky. Pixely sa nezmenili. Zmenil sa predpoklad, že počet pixelov znamená fyzickú veľkosť, a v OOXML to neplatí. Obrázok v tabuľke nesie svoje rozmery v EMU a kým nezačnete uvažovať v EMU, alebo v reálnych jednotkách, ktoré sa naň mapujú bez zvyšku, vaše rozloženie závisí od DPI, ktoré si vykresľujúci stroj práve zvolí

HotXLS je natívny komponent VCL pre Delphi a C++Builder, ktorý číta a zapisuje XLS a XLSX bez Excelu a bez závislosti na COM. Od verzie v2.91.0 vás objekt obrázka v XLSX už nenúti robiť jednotkovú aritmetiku ručne: popri surovom EMU sprístupňuje šírku a výšku v centimetroch, palcoch a bodoch, plus metódu Scale, ktorá mení veľkosť percentom s voliteľným zachovaním pomeru strán. Tento článok vysvetľuje, čo EMU naozaj je, prečo si ho DrawingML zvolilo a ako nový geometrický povrch používať tak, aby ste obrázky umiestňovali podľa fyzickej veľkosti, nie podľa počtu pixelov, ktorému sa nedá veriť

Čo je EMU a prečo ho DrawingML používa

EMU znamená English Metric Unit a je základnou dĺžkovou jednotkou DrawingML, kresliacej vrstvy zdieľanej naprieč celou rodinou Office Open XML. Jedno EMU je definované tak, aby existovalo presne 914400 EMU per inch and 360000 EMU per centimetre. Those two constants are the entire reason the unit exists. 914400 is divisible by 2, 3, 4, 5, 6, 8, 9, 10, 12, and many more; it factors as 26 × 32 × 52. Tieto dve konštanty sú celý dôvod existencie tejto jednotky. Keďže 1 inch = 2.54 cm presne, jednotka deliteľná hodnotami 360000 aj 914400 umožňuje formátu vyjadriť palce, centimetre aj body ako celé čísla bez zaokrúhľovania na hranici jednotiek. Kde by hodnota ako „1.27 cm“ vo floating pointe driftovala, EMU uloží 457200 a zostane presné

Ďalšia dôležitá jednotka je bod. Typografický bod je 1/72 palca, takže existuje 12700 EMU per point (914400 / 72). Excel práve v bodoch interne uvažuje o výškach riadkov, veľkostiach písma a okrajoch, preto je geometria obrázka v bodoch užitočná vtedy, keď chcete obrázok zarovnať na textové metriky, nie na tlačené pravítko. HotXLS kóduje všetky štyri vzťahy ako konštanty jednotiek v knižnici:

const
  XlsxEmuPerInch  = 914400;  // 1 inch
  XlsxEmuPerCm    = 360000;  // 1 centimetre
  XlsxEmuPerPoint = 12700;   // 1 point (1/72 inch)
  XlsxEmuPerPixel = 9525;    // 1 pixel at 96 DPI (914400 / 96)

Posledný riadok je jadrom chyby s poštovou známkou. Pixel má fyzickú veľkosť až vtedy, keď zafixujete DPI, a 9525 EMU je veľkosť jedného pixelu at 96 DPI specifically. Predvolené vykresľovacie DPI Excelu je 96, takže 100-pixelový obrázok skončí ako 100 × 9525 = 952500 EMU, približne 2.54 cm v štandardnom nastavení, ale nič v súbore negarantuje, že konzument použije práve 96. Tvorte v reálnych jednotkách a táto nejednoznačnosť zmizne: 4 cm sú 4 cm, či má obrazovka 96 alebo 220 DPI

Geometrický povrch TXLSXImage

Vložený obrázok v HotXLS je TXLSXImage. Its canonical storage is two integer fields, WidthEMU and HeightEMU, anchored at a one-based Row and Col. Vlastnosti v reálnych jednotkách sú len odvodené pohľady na tieto polia EMU, nie samostatný stav. Pri čítaní WidthCM sa EMU delí hodnotou 360000 a pri zápise sa násobí a zaokrúhli späť. Každý rozmer, ktorý nastavíte, je preto len iný zápis tej istej podkladovej hodnoty EMU:

  • WidthInch / HeightInch — EMU ÷ 914400
  • WidthCM / HeightCM — EMU ÷ 360000
  • WidthPt / HeightPt — EMU ÷ 12700
  • WidthEMU / HeightEMU celočíselný zdroj pravdy

Obrázok pridáte cez AddImage(ARow, ACol, AData, AFormat), passing the raw encoded bytes and a TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, or xlsxImageBmp; výsledkom je zero-based kolekcie worksheetu. K dispozícii je aj Images collection. There is also AddImageFromFile(ARow, ACol, AFileName), ktorý odvodí formát z prípony súboru. Pozor na bázu indexu: AddImage returns zero-based and Images[] is zero-based, which is a deliberate contrast with the one-based Cells[Row, Col] grid, so do not assume the two agree

var
  Sheet: TXLSXWorksheet;
  Img: TXLSXImage;
  Idx: Integer;
begin
  Sheet := Workbook.Sheets.Add('Images');

  // Anchor a PNG at row 3, column 2; AddImage returns a 0-based index.
  Idx := Sheet.AddImage(3, 2, LogoBytes, xlsxImagePng);

  Img := Sheet.Images[Idx];
  Img.WidthCM := 4.0;    // 4 cm wide  -> 1440000 EMU
  Img.HeightCM := 3.0;   // 3 cm tall  -> 1080000 EMU

  // Same geometry, read back in other units.
  // Img.WidthPt  is now 113.39 pt, Img.WidthInch is 1.5748 in.
end;

. Novo vytvorený obrázok má predvolene 100×100 pixelov, teda štvorec 952500 EMU, približne 2.54 cm pri 96 DPI. Toto predvolené nastavenie existuje preto, aby bol obrázok viditeľný aj vtedy, keď zabudnete nastaviť veľkosť, ale pre akékoľvek reálne rozloženie by ste mali nastaviť explicitnú fyzickú veľkosť, nie sa spoliehať na hodnotu odvodenú od pixelov

Zmena mierky a príznak pomeru strán

Keď chcete meniť veľkosť relatívne k aktuálnym rozmerom namiesto absolútneho cieľa, napríklad zmenšiť obrázok grafu na 60 % toho, v čom bol importovaný, použite Scale:

procedure Scale(APercent: Double; AKeepAspect: Boolean = True);

APercent je percento, kde 100 znamená bez zmeny, 150 zväčšenie o polovicu a 50 zmenšenie na polovicu. Pri AKeepAspect at its default True, both width and height multiply by the same factor, so the proportions hold and a 4×3 cm image becomes 6×4.5 cm after Scale(150) sa šírka aj výška násobia rovnakým faktorom, takže proporcie zostanú zachované. Ak odovzdáte False and only the width scales—the height is left exactly as it was. That asymmetry is intentional: when you want to stretch one axis independently, the right tool is the explicit WidthCM<, zmení sa len šírka a výška zostane presne taká, aká bola. Táto asymetria je zámerná: keď chcete jeden rozmer natiahnuť nezávisle od druhého, správnym nástrojom sú explicitné settery code>/HeightCM setters, and the non-aspect branch of Scale, a neproporcionálna vetva Scale(150, False) slúži len na užší prípad úpravy samotnej šírky. Pri skutočne nezávislých rozmeroch siahnite po setteroch

Img.WidthCM := 4.0;
Img.HeightCM := 3.0;

Img.Scale(150);          // aspect locked: now 6.0 x 4.5 cm
Img.Scale(100);          // no-op, returns immediately

Img.Scale(50, False);    // width only: 3.0 cm wide, height unchanged at 4.5 cm

Jedno drobné správanie sa oplatí poznať: Scale(100) short-circuits and returns without touching either field, so it is safe to call unconditionally in a loop where the percentage might be 100. And because the geometry is stored as integer sa skratovo vráti bez zmeny oboch polí, takže ho môžete bezpečne volať aj bez podmienky. Keďže geometria je uložená ako WidthEMUceločíselnéHeightEMU EMU, každý setter zaokrúhľuje. Pri round-tripe cez zlomkové centimetre preto môže vzniknúť odchýlka o zlomok EMU. Ak potrebujete pixelovo presnú kontrolu, nastavte

Čítanie geometrie späť

priamo a konverziu jednotiek vynechajte.Images.CountČítanie geometrie späťImages[i] indexes them zero-based, and FindAt(ARow, ACol) returns the image anchored at a specific cell—or nil if none is. There is also IndexOfCell for the index rather than the object, and DeleteAt / DeleteInRange na odstránenie

var
  i: Integer;
  Img: TXLSXImage;
begin
  for i := 0 to Sheet.Images.Count - 1 do
  begin
    Img := Sheet.Images[i];
    Writeln(Format('[%d] R%dC%d  %.2f x %.2f cm  (%d x %d EMU)',
      [i, Img.Row, Img.Col, Img.WidthCM, Img.HeightCM,
       Img.WidthEMU, Img.HeightEMU]));
  end;

  Img := Sheet.Images.FindAt(3, 2);   // nil-check before use
  if Img <> nil then
    Img.Scale(80);
end;

Keďže vlastnosti v reálnych jednotkách sú živé pohľady, obrázok importovaný z iného nástroja s určitým rozmerom EMU okamžite hlási svoju geometriu v centimetroch, bez ďalšieho prevodu z vašej strany. Ak okrem rastrových obrázkov umiestňujete aj grafy a tvary, sprievodný článok HotXLS charts, images, and Excel drawings in Delphi opisuje model ukotvenia, ktorý tieto objekty zdieľajú

Okraje nastavenia strany v metrických jednotkách

To isté napätie medzi EMU a reálnymi jednotkami sa objaví o úroveň vyššie, na stránke. OOXML a Excel ukladajú tlačové okraje v palcoch, čo je nepraktické, ak sú vaše šablóny reportov definované v milimetroch. Verzia v2.91.0 pridáva centimetrové obaly nad palcovými okrajmi: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, and MarginFooterCM. Každý z nich je tenká pohodlná vrstva nad zodpovedajúcou vlastnosťou v palcoch s presným prevodom 1 inch = 2.54 cm

Sheet.MarginLeftCM := 2.0;     // 2 cm  == 0.7874 inch
Sheet.MarginRightCM := 2.0;
Sheet.MarginTopCM := 2.5;
Sheet.MarginBottomCM := 2.5;
Sheet.MarginHeaderCM := 1.0;
Sheet.MarginFooterCM := 1.0;

Vlastnosti v palcoch (MarginLeft a podobné) zostávajú kanonickým uložením, takže ich môžete miešať. Je to tá istá filozofia metrického pohodlia ako pri geometrii obrázka: formát pod kapotou hovorí imperiálne a knižnica vám dovolí tvoriť v jednotkách, v ktorých je napísaná vaša špecifikácia. Ak rozkladáte zvyšok reportu, pozrite si merged cells and report template layout in HotXLS

Poznámka k tomu, čo geometria garantuje a čo nie

Vlastnosti geometrie riadia deklarovanú veľkosť obrázka v súbore, teda veľkosť, pri ktorej ho kompatibilný konzument vykreslí. Nevzorkujú znovu bajty obrázka. PNG s rozlíšením 50×50 pixelov nastavený na 8 cm sa zväčší a bude kockatý presne tak, ako v Exceli. Knižnica tiež formáty znova nekóduje: bajty, ktoré odovzdáte do AddImage, sa uložia a zapíšu tak, ako sú. Ak odovzdáte JPEG bajty, ale označíte ich ako TXLSXImageFormat you declare. Pass JPEG bytes but tag them xlsxImagePng, vytvoríte súbor, ktorý Excel neotvorí, preto keď môžete, nechajte AddImageFromFile odvodiť formát z prípony

V OOXML je skutočnou veličinou fyzická veľkosť a pixely sú len odvodený tieň závislý od DPI. Tvorte obrázky aj okraje v centimetroch, palcoch alebo bodoch, nechajte HotXLS namapovať ich na presné EMU a vaše faktúry aj reporty sa budú tlačiť rovnako veľké na každom stroji, ktorý ich otvorí

API pre geometriu obrázkov, zmenu mierky a metrické okraje opísané tu sú súčasťou HotXLS Delphi spreadsheet component, ktorá číta a zapisuje XLS a XLSX z Delphi a C++Buildera bez potreby inštalácie Excelu