[C#][Grafika]: Szybkie DrawImage?

1

Tym razem bardzo krótko:

Fragment kodu źródłowego:

            Graphics g = e.Graphics;
//Renderowanie
            g.DrawImage(drawing.GetBitmap, 0, 0);

Ten sam fragment zmieniony na potrzeby testów:

            Graphics g = e.Graphics;
            int TC = Environment.TickCount;
            for (int i = 0; i < 1000; i++)
            {
// Tutaj renderowanie właściwe, w klasie drawng.
            }
            string s = "1: " + (Environment.TickCount - TC).ToString() + Environment.NewLine;
            TC = Environment.TickCount;
            for (int i = 0; i < 1000; i++)
            g.DrawImage(drawing.GetBitmap, 0, 0);
            s += "2: " + (Environment.TickCount - TC).ToString() + Environment.NewLine;
            MessageBox.Show(s);

Wszystko byłoby OK, tylko że test daje taki wynik:

1: 11058
2: 24500

I nie chodzi tu o sam wynik (tryb debugowania, komputer już nie najnowszy) ale o proporcję. Czy da się jakoś przyśpieszyć funkcję DrawImage?

0

Nie wiadomo co jest w pierwszej petli (wiec nie wiadomo z czym porownujesz), nie wiadomo co i jak duzego rysujesz. Troche wiecej szczegolow.

PS. Do liczenia czasu zalecam klase System.Diagnostics.Stopwatch.

1

nie wiadomo co i jak duzego rysujesz

Rzeczywiście, nie wiadomo... :) Kiedy pierwszy raz pisałem ten temat, to w ostatnim zdaniu nacisnąłem coś przez przypadek i usunąłem siobie wszytko :D I przy powtórnym pisaniu widać zapomniałem dodać. Bitmapa ma szerokość i wysokość okna w którym jest rysowana, czyli obecnie 1000x1000. A jeśli chodzi o to CO rysuję, to... nie ma to aż takiego znaczenia - musiałbym umieścić i udokumentować całą klasę. W każdym razie w pominiętym fragmencie obiekt drawing rysuje sobie coś na bitmapie (parę lini oraz 10 - 20 obiektów typu DrawPath), A drawing.GetBitmap zwraca tą bitmapę.

Nie wiadomo co jest w pierwszej petli (wiec nie wiadomo z czym porownujesz)

-->

I nie chodzi tu o sam wynik (tryb debugowania, komputer już nie najnowszy) ale o proporcję.

Przecież chyba nie jest konieczne przy samym prostym (bez przekształceń) rysowaniu bitmapy 1000x1000 aż tyle czasu! 24500 milisekund daje ćwierć sekundy, a żeby było 100% płynnie powinno być około 10 fps...

POST II:

Hmm... znalazłem coś takiego jab BitBlt (BitBlockTransfer) -> podobno jest szybka. Problem w tym że u mnie nie chce działać ;(

       [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        public static extern int BitBlt(
            IntPtr hDestDC,
            int xDest,
            int yDest,
            int nWidth,
            int nHeight,
            IntPtr hSrcDC,
            int xSrc,
            int ySrc,
            System.Int32 dwRop);

Próbowałem:

            Graphics g = e.Graphics;
            Graphics d = Graphics.FromImage(drawing.Bitmap);

            //...

            IntPtr hDestDC = g.GetHdc();
            IntPtr hSrcDC = d.GetHdc();
            BitBlt(hDestDC, 0, 0, 500, 500, hSrcDC, 0, 0, 0x00CC0020);
            g.ReleaseHdc(hDestDC);
            d.ReleaseHdc(hSrcDC);

Ale wyświetla po prostu czarny prostokąt! (no dobra, kwadrat)!!!

POST III

AAACH!!!

Po 4 godzinach szukania i próbowanie (im dłużej patrzę na zegar systemowy tym bardziej wydaje mi się że to było 5 godzin...) wreszcie mi się udało!!!

Pracowałem w osobnym projekcie, stworzonym tylko do testów i musiałem napisać mniej więcej coś takiego:

        Bitmap bit = new Bitmap(100, 100);

        public Form1()
        {
            InitializeComponent();
            Graphics graphicsBit = Graphics.FromImage(bit);
            graphicsBit.FillRectangle(Brushes.Red, new Rectangle(new Point(0, 0), bit.Size));
        }

        const int SRCCOPY = 0xcc0020;

        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        private static extern int BitBlt(
          IntPtr hdcDest,     // handle to destination DC (device context)
          int nXDest,         // x-coord of destination upper-left corner
          int nYDest,         // y-coord of destination upper-left corner
          int nWidth,         // width of destination rectangle
          int nHeight,        // height of destination rectangle
          IntPtr hdcSrc,      // handle to source DC
          int nXSrc,          // x-coordinate of source upper-left corner
          int nYSrc,          // y-coordinate of source upper-left corner
          System.Int32 dwRop  // raster operation code
          );

        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hDC,
            IntPtr hObject);

        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        public static extern bool DeleteDC(IntPtr hdc);



        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics graphTarget = e.Graphics;
            IntPtr GraphTargetDC = graphTarget.GetHdc();

            IntPtr pSource = CreateCompatibleDC(GraphTargetDC);

            SelectObject(pSource, bit.GetHbitmap());

            int x = BitBlt(GraphTargetDC, 0, 0, 100, 100, pSource, 0, 0, 0xCC0020);


            DeleteDC(pSource);
            graphTarget.ReleaseHdc(GraphTargetDC);

            pictureBox1.Image = bit;
        }
    }

Edit: tak, wiem... 3 posty pod rząd... Może trochę przesadziłem, ale zrobiłem to w nadzieji że pomogę komuś kto będzie miał identyczny problem jak ja...

POST IV

Tak, wiem...

Najpierw kod:

                Bitmap bit = (Bitmap)drawing.bitmap.Clone();
                Graphics targetGraph = e.Graphics;
                IntPtr GraphTargetDC = targetGraph.GetHdc();
                IntPtr SourcePtr = GDIfxs.CreateCompatibleDC(GraphTargetDC);
                GDIfxs.SelectObject(SourcePtr, bit.GetHbitmap());

                GDIfxs.BitBlt(GraphTargetDC, 0, 0, ClientSize.Width, ClientSize.Height, SourcePtr, 0, 0, 0x00CC0020);

                GDIfxs.DeleteDC(SourcePtr);
                targetGraph.ReleaseHdc(GraphTargetDC);
                bit.Dispose();

Wszystko działa ładnie i szybko, ale... tylko przez chwilę. Później zaczyna się zacinać. Powód? Jako że jeden obraz wart tysiąca słów, oto informacja od menadżera zadań windows.

user image

Wszystkie te GDI funkcje umieściłem jako statyczne w osobnej klasie (czy to może być powodem?). Domyślam się że nie zwalniam jakiegoś niezarządzanego zasobu, ale nie wiem jakiego. Kilka razy sprawdzałem kod, i nie mogę znaleźć czegoś, co nie byłoby zwalniane. W kodzie nie występuje błąd, czyli nie jest tak że nie dochodzi do momentu zwolnienia. Wewnętrzne funkcje GDI też wykonują się poprawnie (zwraca jeden, czyli true, czyli sukces). Skonsultowałem się z paroma innymi przykładami (chociaż żaden z niech nie polegał na kopiowaniu z bitmapy do formy).
Jedyne co mi przychodzi do głowy to: [???]

Czy ktoś wie, o co może chodzć?

Drobna uwaga: żeby nie załamać moderetorów, których wytrzymałość psychiczną i tak już nadwyrężyły 3 poprzednie posty, połączyłem je wszystkie w jeden. Jak tak dalej pójdzie to wydrukuje ten temat i będę sprzedawał w księgarniach pod swoim nazwiskiem.

I jeszcze jedna edycja: Zastanawiam się czy jest to powodowane przez niezarządzane zasoby, czy po prostu przez ciągłe tworzenie zarządzanych, z którymi GC sobie po prostu nie radzi.

0

To może spróbuj gdzieś wywołać GC.Collect() i obserwuj zużycie pamięci.

1

Collect niestety nie wpływa znacząco na zużycie pamięci :/

Za to czas obliczeń przedstawia się... ciekawie (każdy czas to 10 iteracji fragmentu z bitbltem)

Czas 1: 359
Czas 2: 375
Czas 3: 359
Czas 4: 359
Czas 5: 375
Czas 6: 359
Czas 7: 360
Czas 8: 359
Czas 9: 359
Czas 10: 375
Czas 11: 375
Czas 12: 375
Czas 13: 407
Czas 14: 406
Czas 15: 422
Czas 16: 406
Czas 17: 437
Czas 18: 422
Czas 19: 406
Czas 20: 422
Czas 21: 421
Czas 22: 454     //Dotąd wszystko OK...
Czas 23: 7046
Czas 24: 6218
Czas 25: 3110
Czas 26: 3938
Czas 27: 5547
Czas 28: 11093   // 0_o?!?
Czas 29: 7062
Czas 30: 7000
Czas 31: 7172

Naprawdę nie mam pojęcia o co tutaj chodzi :/

I jeszcze jedno - stare dobre g.DrawImage działa dobrze. Czyli to na pewno tutaj.</u>

0
MSM napisał(a)
                Bitmap bit = (Bitmap)drawing.bitmap.Clone();
                Graphics targetGraph = e.Graphics;
                IntPtr GraphTargetDC = targetGraph.GetHdc();
                IntPtr SourcePtr = GDIfxs.CreateCompatibleDC(GraphTargetDC);
                GDIfxs.SelectObject(SourcePtr, bit.GetHbitmap());

                GDIfxs.BitBlt(GraphTargetDC, 0, 0, ClientSize.Width, ClientSize.Height, SourcePtr, 0, 0, 0x00CC0020);

                GDIfxs.DeleteDC(SourcePtr);
                targetGraph.ReleaseHdc(GraphTargetDC);
                bit.Dispose();

Wszystko działa ładnie i szybko, ale... tylko przez chwilę. Później zaczyna się zacinać. Powód? Jako że jeden obraz wart tysiąca słów, oto informacja od menadżera zadań windows.
user image

wiem, ze to dosc stary watek, ale tez sie kiedys z tym bilem i ciekawy jestem

GDI.SelectObject zwraca kod powodzenia operacji, ale może zwracać także uchwyt do starego obiektu który był poprzednio używany. Może to on wycieka i powieneś go zwalniać? Zdziwil mnie ten kod lekko, gdyż jak dla mnie zawsze selectobject szlo w parze ze sprawdzaniem co on zwraca, ot tak, na wypadek.. por. np. http://www.codeguru.com/forum/showthread.php?t=462084

Jesli jeszczcze masz ten kod pod reka, to sprawdz to:)

1 użytkowników online, w tym zalogowanych: 0, gości: 1