Jakub Florczyk - Blog o programowaniu .NET i Android

Programista praktyczny

Month: May, 2009

BlipNet 0.2.2.2

Kolejna paczka drobnych poprawek w BlipNet. Zmiany obejmują implementację stronicowania kokpitu, poprawki na pobieranie statusów oraz literówki.

Wydajność GDI w .NET Compact Framework – rysowanie grafik

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ęć:
screen3

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