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;