Używanie bitmap w C#

2

Nie znam się zbytnio na komputerowej (tzn z poziomu kodu) obróbce grafiki. Mam pytanie: mój program nieco "mrugał" (Jest odświażany 5 razy na sekundę) więc postanowiłem użyć bitmapy. Niestety jest jeszcze gorzej (Przepraszem za enigmatyczny wstęp, ale już dwa razy pisałem obszerniejszy, i dwa razy firefox mi się zacinał...)

Otóż było:

Graphics g = e.Graphics;
                Pen pioro = new Pen(Color.Black);
                g.DrawImage(Obrazek, X, Y;
                g.DrawLine(pioro, 0, 500, 1280, 500);

(W rzeczywistym programie wygląda to nieco inaczej, ale tak będzie czytelniej a sens taki sam)

Teraz jest:

w public Form1():
Bitmap bitmap = new Bitmap(this.Size.Width, this.Size.Height);

i w Form1.Paint():

                
Graphics ImageGraphics = Graphics.FromImage(bitmap);
                Graphics g = e.Graphics;
                Pen pioro = new Pen(Color.Black);
                ImageGraphics.Clear(Color.White);
                ImageGraphics.DrawImage(Obrazek, X, Y);
                ImageGraphics.DrawLine(pioro, 0, 500, 1280, 500);
                g.DrawImage(bitmap, 0, 0);

Zacina się niemiłosiernie.

Próbowałem (Dla testów jeno) zamienić ostatnią linijkę na "g.DrawImage(Obrazek, X, Y);" - i potwierdziło się to, co podejrzewałem - program działał płynnie i bez zawieszeń (jedynie z tym lekkim mruganiem co na początku), czyli nie owe nowe linijki go zwalniały, tylko samo rysowanie bitmapy. Ma ktoś pomysł co z tym zrobić?

PS. Możliwe że część winy ponosi to, że do każdorazowego odświeżania obrazu używam Refresh(), ale kiedy próbowałem go zamienić na this.Form1_Paint(this, null) wyskakiwały błędy (podczas debugowania, program się uruchamiał).

0

Jeśli obraz miga to zastosuj podwójne buforowanie. Na sieci znajdziesz wiele przykładowych rozwiązań.

0

ustaw sobie w OnPaint:
e.Graphics.CompositingMode = CompositingMode.SourceCopy;

dzięki temu kopiowanie bitmapy odbędzie się szybciej- domyślny tryb to CompositingMode.SourceOverlay - co powoduje że piksele kopiowanej bitmapy są blendowane z pikselami formy. Tobie blendowanie w tym przypadku nie jest potrzebne.

1

Hmm... No więc tak:
Zgodnie z jakimś przykładem w sieci dopisałem obok InitializeComponent(); jeszcze takie linijki jak:

                SetStyle(ControlStyles.AllPaintingInWmPaint, true);
                SetStyle(ControlStyles.UserPaint, true);
                SetStyle(ControlStyles.ResizeRedraw, true);
                this.DoubleBuffered = true;

i rzeczywiście: program już nie miga, lecz dalej się 'tnie'.

Form1.Paint:

     Graphics g = e.Graphics;
                Pen pioro = new Pen(Color.Black);
                Graphics ImageGraphics = Graphics.FromImage(bitmap);

                ImageGraphics.Clear(Color.White);
                ImageGraphics.DrawImage(Obrazek, X, Y);
                ImageGraphics.DrawLine(pioro, 0, 500, 1280, 500);
                g.DrawImage(bitmap, 0, 0);
  1. Nie jestem pewien, ale na pewno zwalniają prace programu 3 pierwsze linijki. Czy dałoby się coś zrobić żeby były wykonywane tylko raz, a nie przy każdej klatce?

  2. Da się zrobić coś jeszcze?

  3. Przepraszam za takie pytania, ale jest jak mówiłem -> jeśli chodzi o grafikę, to jestem zielony jak jabłka w czerwcu.


Edit: 
Próbowałem zastosować się do rady i wpisać: 
e.Graphics.CompositingMode = CompositingMode.SourceCopy, ale wyskakuję błąd:
The name 'CompositingMode' does not exist in the current context (Odnośnie drugiego CompositingMode).
"e.Graphics.CompositingMode.SourceCopy" też nie działa.
0
Pen pioro = new Pen(Color.Black);
                Graphics ImageGraphics = Graphics.FromImage(bitmap);

                ImageGraphics.Clear(Color.White);
                ImageGraphics.DrawImage(Obrazek, X, Y);
                ImageGraphics.DrawLine(pioro, 0, 500, 1280, 500);

A musisz Tworzyć za każdym razem nowy Graphics. Nie możesz tego zrobić raz ? Ten obrazek jest zmienny ?

0
MSM napisał(a)

Próbowałem zastosować się do rady i wpisać:
e.Graphics.CompositingMode = CompositingMode.SourceCopy, ale wyskakuję błąd:
The name 'CompositingMode' does not exist in the current context (Odnośnie drugiego CompositingMode).
"e.Graphics.CompositingMode.SourceCopy" też nie działa.

dodaj:
e.Graphics.CompositingMode = CompositingMode.SourceCopy - jest ok tylko dodaj sobie:
using System.Drawing.Drawing2D;

0

i jeszcze jedno - MS sugeruje by przy prostym kopiowaniu korzystać z DrawImage w wersji z paramerami szerokości i wysokości czyli:

e.Graphics.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);

choć ma to głównie znaczenie przy rysowaniu obrazów wczytanych z pliku - jak np. jpeg

0

A poza tym mały przykład jak rysować w oddzielnym wątku. Nie będzie cięło.</url>

1

A musisz Tworzyć za każdym razem nowy Graphics. Nie możesz tego zrobić raz ? Ten obrazek jest zmienny ?

Sam o to pytałem (Pytanie numer 1...), problem polega na tym że... gdzie je niby mam umieścić? Włączam je do Paint, bo tam jest "e", jeśli rozumiesz o co mi chodzi.

Obrazek owszem, jest zmienny. Konkretnie jest to "ImageDictionary[Grafika.Animation]" gdzie ImageDictionary jest słownikiem, Grafika jest klasą, a Animation właśnie zmienną.

e.Graphics.CompositingMode = CompositingMode.SourceCopy - Rzeczywiście działa. Mimo że jest to kolejna (już czwarta...) linia którą wystarczyłoby raz napisać a nie cały czas, wyniki są lepsze średnio o 300 ms. (na 100 wykonań)

No i na końcu "Gwóźdź programu": rysowanie w oddzielnym wątku -> Strrasznie skomplikowane - jest powodem dla którego wczoraj się nie odezwałem ^^.

Tzn nie tyle skomplikowane, co odmienne od mojego. A gdzie konkretnie mam problem?
Tak: w programie Hassa przed stworzeniem nowego wątku jest wyrażenie:

if (WątekRysujący != null && WątekRysujący.IsAlive)
                button1.Text = "Bez przesady! Da się zrobić, żeby dwa wątki rysowały po jednej bitmapie, ale ten przykład ma być prosty!";
            else
 (i tutaj tworzenie wątku itd.)

U mnie zaś, kiedy próbowałem wprowadzić tą instrukcję if() za każdym razem (prawie) wychodzi że wątek IsAlive. Przeglądnąłem kod Hassa -> nigdzie nie widzę tam instrukcji usuwającej wątek czy coś takiego.

Jedyna różnica jest taka, że u niego nowy wątek tworzony jest po długim czasie, a u mnie co 20 ms.

PS. NIENAWIDZĘ się tak pytać o każdą głupotę. To tyle...

Edit: Jednak jeszcze jedno: czy to że obraz u Hassa jest rysowany na pictureboxie ma znaczenie? Bo na wszelki wypadek zmieniłem kod właśnie tak, by rysował na pictureboxie a nie po formie.

0
MSM napisał(a)

Tak: w programie Hassa przed stworzeniem nowego wątku jest wyrażenie:

if (WątekRysujący != null && WątekRysujący.IsAlive)
                button1.Text = "Bez przesady! Da się zrobić, żeby dwa wątki rysowały po jednej bitmapie, ale ten przykład ma być prosty!";
            else
 (i tutaj tworzenie wątku itd.)

U mnie zaś, kiedy próbowałem wprowadzić tą instrukcję if() za każdym razem (prawie) wychodzi że wątek IsAlive. Przeglądnąłem kod Hassa -> nigdzie nie widzę tam instrukcji usuwającej wątek czy coś takiego.

Wątek "umiera" jeśli zostanie aktywnie ubity (mniej lub bardziej grzecznie), ale też jeśli "się skończy", czyli jeśli główna metoda wątku dobiegnie końca. Oczywiście można napisać nieskończoną pętlę, wtedy metoda nie dobiegnie końca, jednak moja ma określoną liczbę iteracji - po tym wątek umiera śmiercią naturalną.

MSM napisał(a)

Jedyna różnica jest taka, że u niego nowy wątek tworzony jest po długim czasie, a u mnie co 20 ms.

Jeśli co 20 ms tworzysz nowy wątek, to cała idea raczej siada - samo stworzenie wątku kosztuje, nie mówiąc już o przełączaniu kontekstu... Generalnie chodzi o to, żeby jeden wątek poboczny chodził sobie przez dłuższy czas i coś tam przetwarzał, a przy okazji od czasu do czasu karmił główny wątek wynikami do wyświetlenia, czyli niech wykonuje w kółko taki schemat:

  • pobierz dane (skądkolwiek je bierzesz, jakiekolwiek dane, ...)
  • na podstawie danych narysuj coś w buforze
  • każ wyświetlić bufor wątkowi głównemu (to możesz dokładnie skopiować ode mnie)

Wklej kod, to poradzę bardziej szczegółowo.

MSM napisał(a)

Edit: Jednak jeszcze jedno: czy to że obraz u Hassa jest rysowany na pictureboxie ma znaczenie? Bo na wszelki wypadek zmieniłem kod właśnie tak, by rysował na pictureboxie a nie po formie.

Zauważ, że ja nie tyle rysuję po PictureBox, co podaję mu bitmapę do wyświetlenia. Można oczywiście zamiast tego rysować tą bitmapę np. na formie, ale wtedy trzeba się zatroszczyć np. o przerysowywanie (OnPaint). PictureBox sam dba o to, żeby zawsze wyświetlać i odmalowywać bitmapę, którą ma wsadzoną w property Image (oprócz tego potrafi sam skalować obrazek w zależności od swojej wielkości!) - dlatego go wybrałem.

1

Zauważ, że ja nie tyle rysuję po PictureBox, co podaję mu bitmapę do wyświetlenia.

O to mi chodziło, źle się wysłowiłem...

Wklej kod, to poradzę bardziej szczegółowo.

Raczej będzie ciężko, bo obecnie dążę do przyśpieszenia programu (tzn użycia bitmapy, teraz jeszcze wątków) "po trupach", nie troszcząc się o coś tak przyziemnego jak czytelność (już nie wspominając że, oprócz formy, używam klas).

niech wykonuje w kółko taki schemat:

  • pobierz dane (skądkolwiek je bierzesz, jakiekolwiek dane, ...)
  • na podstawie danych narysuj coś w buforze
  • każ wyświetlić bufor wątkowi głównemu (to możesz dokładnie skopiować ode mnie)

Czyli żeby jeden wątek (cały czas ten sam) wykonywał te wszystkie operacje? Nie mogę tego cyklu dać do instrukcji for (tak jak ty), bo dopóki nie minie te 20 ms. to wątek nie ma co robić (mówiąc inaczej - wyświetlany obraz zmienia się co 20 ms.).

Można byłoby rysowanie umieścić w osobnej klasie? To (jeśli się nie mylę) pozwoliłoby jednocześnie cały czas mieć dostęp do bitmapy z poziomu formy, a jednocześnie pozwoliłoby kazać (również z poziomu formy) odświeżać tą bitmapę co jakiś czas bez tworzenia nowego wątku. Mam nadzieję że piszę zrozumiale... Chyba że da się to zrobić prościej?

0
MSM napisał(a)

Czyli żeby jeden wątek (cały czas ten sam) wykonywał te wszystkie operacje? Nie mogę tego cyklu dać do instrukcji for (tak jak ty), bo dopóki nie minie te 20 ms. to wątek nie ma co robić (mówiąc inaczej - wyświetlany obraz zmienia się co 20 ms.).

Też łatwo zrobić:

while(1) // w metodzie wątku nieskończona pętla (albo skończona jeśli to bardziej stosowne)
{
 // rysowanie...
 // przerzucanie bufora
 Thread.Sleep(20);
}

Ciągle działa jeden wątek. Można go ubić za pomocą thread.Abort() z wątku głównego.

MSM napisał(a)

Można byłoby rysowanie umieścić w osobnej klasie?
Umieszczenie rysowania w osobnej klasie nie wpłynie na wydajność ani "wątkowość"- co najwyżej lepiej zorganizuje kod. Ale oczywiście jeśli rysowanie jest skomplikowane to warto je wywalić do oddzielnej klasy.

MSM napisał(a)

To (jeśli się nie mylę) pozwoliłoby jednocześnie cały czas mieć dostęp do bitmapy z poziomu formy, a jednocześnie pozwoliłoby kazać (również z poziomu formy) odświeżać tą bitmapę co jakiś czas bez tworzenia nowego wątku. Mam nadzieję że piszę zrozumiale... Chyba że da się to zrobić prościej?
Całe podwójne buforowanie polega na tym, że ciągle masz do dyspozycji bitmapę. I jest ona odświeżana co pewien czas. Naprawdę całe rozwiązanie już zostało przedstawione - nie jest to tak skomplikowane jak sądzisz.

1

Naprawdę całe rozwiązanie już zostało przedstawione - nie jest to tak skomplikowane jak sądzisz.

Rzeczywiście nie było.

Wszystko kompiluje się, działa, nawet całkiem nieźle, chociaż dalej się zacina (1: nieregularnie, 2: Procesor nie jest używany w 100 %, czytaj: błąd leży gdzie indziej). Jednak skoro już znam teorię to sobie jakoś poradzę... ^^ W końcu napisałem ten temat żeby nauczyć się programować grafikę, a nie żeby wyciągnąć cały kod źródłowy od innych :)

Kiedy trafię na jakiś mur którego nie będę w stanie przekroczyć, znowu poproszę o pomoc, ale nie nastąpi to szybko, gdyż zepsuł mi się jakiś wentylatorek w "środku" i tego posta piszę z procesorem rozgrzanym do 65 stopni (podczas pracy rozgrzewa się nawet do 75).

PS i Edit: A tak w ogóle to dziękuję wszystkim za pomoc...

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