Efektivní přístup k bitmapě ve FireMonkey a jeho využití pro kvalitní převzorkování

vložil Radek Červinka 8. srpna 2013 07:21

Pokud člověk potřeboval rychlý přístup k bitmapě ve VCL tak použil její property ScanLine kdy se dostal přímo k jejím obrazovým datům. Podobný přístup platil i u FireMonkey ve verzi XE2, ale s nástupem většího počtu platform a důraznějšího využití grafického HW (GPU) na různých platformách vznikl požadavek na komplexnější řešení.

Byl jsem požádán o implementaci výstupního filtru, který implementuje resampling, který bude kvalitnější než implementace ve FireMonkey. Implementace ve FMX totiž používá funkce OS, které nejsou pro naše účely dostačující, a navíc pro každou platformu může tím pádem dát kapánek různé výsledky, což je pro nás nepřijatelné. Pro VCL podobné řešení existuje např. ve formě projektu Graphics32, ale to nejde přímo použít protože jsem chtěl něco co bude lehce upravitelné na OSX a mobilní zařízení, což v případě uvedené knihovny by znamenalo dost práce. Naštěstí jsem našel původní knihovnu, která byla pro implementaci v Graphics32 použita - Interpolated Bitmap Resampling using filters - VCL (lokální kopie, protože autor po začlenění ji přestal udržovat, XE4 compatible).

Podle mne to funguje ve FireMonkey následovně: Bitmapa interně používá intenzivně GPU (pokud je to možné) a proto její obrazové data nemusí být dostupné. Metody FMX.Types.TBitmap.Map a UnMap provedou "namapování" (případně zpětnou aktualizaci) dat bitmapy do paměti a vrátí strukturu FMX.Types.TBitmapData a programátor pak s tím může pracovat, ať už přes velmi efektivní volání SetPixel a GetPixel (podle mého průzkumu je jejich použití jen o 1/4 pomalejší než použití ScanLine ve VCL - interně totiž pracují přímo s daty), nebo přímo přes property Data, což je pointer na binární data tj. ekvivalent (s upřesněním dále) ScanLine.

Musím přiznat, že na práci s pixely a binárními daty obrazu je FMX dobře připravena. Data vrácena přes Map jsou sice ve formátu GPU, naštěstí přes FMX.PixelFormats se to dá metodami AlphaColorToScanLine a ScanLineToAlphaColor převést na klasický formát 32bit hodnoty ve formátu AARRGGBB alias TAlphaColor, což je mimochodem základní typ pro práci s barvou ve FireMonkey.

var
  bmp:TBitmap;
  data: TBitmapData;
  i, j: Integer;

begin
  bmp := TBitmap.Create(100, 100);
  if bmp.Map(TMapAccess.maWrite, data) then
  begin
    for i := 0 to 99 do
      for j := 0 to 99 do
        data.SetPixel(i, j,$FFFF0000);

    bmp.Unmap(data);
    ImageControl1.Bitmap.Assign(bmp);
  end;
  bmp.Free;
end;

Uvedený kód vytvoří bitmapu a nakreslí červený čtverec 100x100 a následně ji zobrazí na formuláři. Pro ukázku použití AlphaColorToScanline je dostupné demo:

function TForm1.TestAlphaColorToScanline(ABitmap: TBitmap;
  start, count: integer): TBitmap;
var
  bitdata1, bitdata2: TBitmapData;
begin
  Result := TBitmap.Create(Round(ABitmap.Width), Round(count));
  if (ABitmap.Map(TMapAccess.maRead, bitdata1) and
    Result.Map(TMapAccess.maWrite, bitdata2)) then
  begin
    try
      AlphaColorToScanline(@PAlphaColorArray(bitdata1.Data)
        [start * (bitdata1.Pitch div GetPixelFormatBytes(ABitmap.PixelFormat))],
        bitdata2.Data, Round(Result.Height * Result.Width),
        ABitmap.PixelFormat);
    finally
      ABitmap.Unmap(bitdata1);
      Result.Unmap(bitdata2);
    end;
  end;
end;

Tato funkce vrátí požadované řádky jako novou bitmapu.

FireMonkey TBitmap resample

Pro naše použití v této fázi stačilo dříve uvedený zdrojový kód na resampling mírně upravit tak, že se pracuje místo s TBitmap s TBitmapData a používá se SetPixel a PutPixel + nějaké mírné přetypování. Ve výsledku je proti VCL verzi používající ScanLine o cca 1/5 pomalejší, což slibuje, že v případě nutnosti se bude dát optimalizovat. Upravená jednotka pro FireMonkey Bitmap resampling podporující Lanczos3, Hermite, Mitchell, B-Spline výstupní filtry.

var
  bmp, bmp2: TBitmap;
  bmpdata, bmpdata2: TBitmapData;
  sw: TStopWatch;

begin
  bmp:= TBitmap.CreateFromFile('001.bmp');

  bmp.Map(TMapAccess.maRead, bmpdata);
  bmp2 := TBitmap.Create(800, 600);
  bmp2.Map(TMapAccess.maReadWrite, bmpdata2);
  sw := TStopwatch.Create;
  try
    sw.Start;
    Strecth(bmpdata, bmpdata2, Lanczos3Filter, 3.0);
    sw.Stop;
  finally
    bmp.Unmap(bmpdata);
    bmp2.Unmap(bmpdata2);
  end;

  bmp2.SaveToFile('test.bmp');
  ShowMessage(sw.Elapsed.TotalMilliseconds.ToString());
  bmp.Free;
  bmp2.Free;
end;

Tagy: ,

Komentáře

10.8.2013 15:39:19 #

k

Neco podobneho ve vice modifikacich umoznuje unita DIB.pas z baliku DelphiX, ale lze pouzit snad i samostatne :)

k

11.8.2013 0:01:44 #

Radek

Pochybuji ze by neco takoveho  z DelphiX bylo přeložitelné na MACOSX - DelphiX je velmi svazano s Windows.

Radek

13.8.2013 14:22:14 #

JaroB

No, abych pravdu řekl, opravdu je možné DIB unitu použít samostatně i bez podpory DirectX (DXConsts.pas neobsahuhuje nic než konstanty k možné lokalizaci). Nacpal jsem tam spoustu různých efektů (hlavně je tam conFussion alias DXFusion původně určená pro tuším FastDIB v mojí implementaci a Hibonovy a Sateliho efekty rovněž v mojí úpravě), ale zas tak moc jsem se nezabýval optimalizací na rychlost :(
Něco je primárně určeno jen pro DIB24, ale snažil jsem se nějak efektivně a instantně pracovat i s alpha kanálem, ale to je na posouzení jiných :)

JaroB

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS

Dle měsíců