Od kilku dni walczę z wydajnością w Pocket Blip. Użytkownicy zaczęli skarżyć się na powolne skrolowanie paneli, które bezpośrednio wynika z prędkości rysowania. Prace zacząłem od testów czasów rysowania paneli. Najprostsza forma licznika poniżej:
// zegar Stopwatch sw = new Stopwatch(); sw.Start(); // --- kod rysujący --- // podgląd Debug.WriteLine(sw.ElapsedMilliseconds.ToString());
Na warsztat wziąłem panel zdjęć:

Do testu ustawiłem panel na 10 zdjęć i po ściągnięciu ich na telefon zacząłem wymuszać odświeżanie rysowania. Pierwotna metoda rysowania wykorzystywała poniższą metodę:
// Summary:
// Draws the specified portion of the specified System.Drawing.Image at the
// specified location and with the specified size.
public void DrawImage(Image image, Rectangle destRect, Rectangle srcRect, GraphicsUnit srcUnit);
Potrzeba użycia tej metody wynika z dopasowania rysowania na telefonach o rozdzielczościach VGA. W takim przypadku po prostu zwiększałem destRect aby grafiki dopasować do rozdzielczości ekranu.
Wynik po testach okazał się jednak bardzo słaby. Średnie przerysowania zajmowało ponad 100ms – czyli poniżej 10 klatek na sekundę, przez co użytkownicy odczuwali szarpanie panelu.
Jednak z drugiej strony wszystkim wiadomo, że klasy .NET nie należą do demonów szybkości, a więc czas na OpenNETCF i podgląd Reflector-em. Po krótkim szukaniu odnajduje:
public void DrawImage(BitmapEx image, Rectangle destRect, Rectangle srcRect)
{
IntPtr hDC = GDIPlus.CreateCompatibleDC(this.hDC);
IntPtr hObject = GDIPlus.SelectObject(hDC, image.hBitmap);
GDIPlus.StretchBlt(this.hDC, destRect.Left, destRect.Top, destRect.Width, destRect.Height, hDC, srcRect.Left, srcRect.Top, srcRect.Width, srcRect.Height, 0xcc0020);
GDIPlus.SelectObject(hDC, hObject);
GDIPlus.DeleteDC(hDC);
}
[DllImport("coredll.dll", SetLastError=true)]
public static extern int StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, uint dwRop);
Szybka przeróbka pod Pocket Blip-a i otrzymuje:
public static void DrawImage(this Graphics graphics, Image image, Rectangle destRect, Rectangle srcRect)
{
using (Graphics graphicsImage = Graphics.FromImage(image))
{
IntPtr graphicsHdc = graphics.GetHdc();
IntPtr graphicsImageHdc = graphicsImage.GetHdc();
StretchBlt(graphicsHdc, destRect.Left, destRect.Top, destRect.Width, destRect.Height, graphicsImageHdc, srcRect.Left, srcRect.Top, srcRect.Width, srcRect.Height, SRCCOPY);
graphicsImage.ReleaseHdc(graphicsImageHdc);
graphics.ReleaseHdc(graphicsHdc);
}
}
[DllImport("coredll.dll")]
private static extern int StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, uint dwRop);
Testy okazały się dość ciekawe:
- 30ms – dla zdjęć skalowanych do krotności wielkości
- 60ms – dla zdjęć skalowanych dowolnie
Ten wynik natchnął mnie do testu poniższej metody:
// Summary:
// Draws the specified image, using its original physical size, at the location
// specified by a coordinate pair.
public void DrawImage(Image image, int x, int y);
Wynik okazał się rewelacyjny – około 2ms!
Podsumowując:
- jak ognia unikaj skalowania grafik
- jeżeli już musisz skalować, staraj się skalować do n-tych krotności i rysuj wykorzystując OpenNETCF lub bezpośrednio StretchBlt
- jeżeli musisz skalować używając mnożnika ułamkowego używaj OpenNETCF lub StretchBlt
- pomyśl o pre-skalowaniu grafik o ile to możliwe – oczywiście wykorzystaj OpenNETCF lub StretchBlt
- o wbudowanych metodach skalujących w Compact Framework .NET zapomnij



Kelly Brown
/ 2009-06-13The article is very good. Write please more