Autor Beitrag
Chiyoko
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Mi 18.06.14 10:23 
Huhu,

nach langer Zeit melde ich mich mal wieder zurueck :shock:

Ich habe momentan ein großes Projekt am laufen und arbeite mit Bildern.
Mein Ziel war es, ein Array aus Bildern in einer bestimmten Reihenfolge darzustellen, was soweit auch sehr gut funktioniert.


ausblenden C#-Quelltext
1:
2:
3:
4:
Bitmap[,] bmapArray;

// Code in einer Schleife durchgehen und dem Array x,y Werte zuweisen
// Im OnPaint Event Bereiche neuzeichnen



Den Code habe ich momentan nicht bei mir, aber das kann ich spaeter gerne nachtragen.

Problem:
Das Problem ist die Masse an Bildern, die sich auf gute 4000 belaufen.4000 Bilder a 256 Pixel x 24 Bit.
In einem Graphics Objekt werden diese Bilder gerendert und durch OnPaint neu gezeichnet.
Beim Debuggen sind das etwa 1,3 GB an Speicher, die verbraucht werden.
- die Menge an Bildern kann und soll nicht geändert werden
- die Bildergröße kann und soll nicht geändert werden (aus Detailgründen)
- die Farbtiefe kann und soll nicht geändert werden (aus Detailgründen, auch wenn das viell. nicht auffallen wuerde)
- das Bild vorher zu speichern und dann zu laden, ist auch keine gute Alternative ( zu unflexibel)

Frage:
Ist es moeglich, das neu zeichnen zu unterbinden, sodass jedes Objekt im BmapArray gelöscht werden kann?
Ich möchte die Bilder sozusagen nur einmal auf eine Oberfläche rendern, denn danach sind sie statisch und
verändern sich nicht mehr.
Oder spielt das keine Rolle, weil das Bild, was gerendert wurde, genauso viel Speicher verlangt?

Gibt es sonstige Optimierungsmoeglichkeiten?
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4009
Erhaltene Danke: 824

Win7
C++, C# (VS 2015/17)
BeitragVerfasst: Mi 18.06.14 11:07 
Hallo,

dann rendere die Teilbilder doch einmalig in ein (in-memory) Bitmap und zeichne dann im OnPaint-Ereignis den entsprechenden Teilausschnitt mittels der passenden DrawImage-Methode (s.a. Wie, Teil einer Sprite-Grafik anzeigen?).

Es kommt natürlich auch darauf an, wieviel von dem Gesamtbild immer angezeigt wird, so daß man evtl. ein lazy-load als auch Caching (z.B. last recent used) implementieren könnte.

Für diesen Beitrag haben gedankt: Chiyoko
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Mi 18.06.14 11:29 
Wie meinst du das genau?
D.h. alle einzlnen Bilder sollen in einer Bitmap erstellt werden, die sich dann noch im Speicher befindet?
Würde dann speichertechnisch nicht das Selbe bei raus kommen?Dann bringt auch der Teilausschnitt nicht viel.

Bei meinem Bild handelt es sich um eine 32000 x 9000 Pixel große Bilddatei, wovon nur maximal die Bildschirmbreiten/-längen angezeigt werden.
Der Rest wird durch Bildaufbauleisten ausgeblendet.

Momentan wird auch nur der entsprechende Bildausschnitt gerendert.
Wenn auf dem Bildschirm nur 500 Pixel sichtbar sind, dann werden maximal 4 Bilder mit DrawImage ueber OnPaint gezeichnet, die sich bei diesen Clientkoordinaten befinden.
Demirel Automaten
Hält's aus hier
Beiträge: 4
Erhaltene Danke: 1



BeitragVerfasst: Mi 18.06.14 14:11 
An Chiyoko,

wenn ich Dich richtig verstehe möchtest Du einen Teil einer großen Grafik auf dem Bildschirm ausgeben ?
Dafür verwende ich eigentlich immer die AlphaBlend Funktion der GDI.
Hier bei wird über das Handle des Canvas nur der angegebene Teil aus der Quellgrafik an das Handle des Zielcanvas übergeben und dann neu gezeichnet. Eine Aufteilung in Teilgrafiken ist dann nicht mehr nötig, denn sowohl Ausschnitt als auch Ziel können Pixelweise gesetzt werden.
Wenn Du den Ausschnitt jetzt nicht mehr verändern musst, kannst Du natürlich die Quellgrafik aus dem Speicher entfernen.

Leider arbeite ich in Delphi und nicht in C, so dass ich Dir nur den MSDN Code ans Herz legen kann.

AlphaBlend : msdn.microsoft.com/e...51%28v=vs.85%29.aspx
Blendfunktion : msdn.microsoft.com/e...93%28v=vs.85%29.aspx

Die Blendfunktion wird von AlphaBlend verwendet und muss vorher erzeugt werden.
Setze hierbei den Wert SourceConstantAlpha auf 255, dann wird das Bild deckend gezeichnet.

Es gibt auch noch eine neuere Funktion aus der GDI+ die extra für dotNet gedacht ist.

DrawAlphaBlend msdn.microsoft.com/e...53%28v=vs.85%29.aspx

Über Vor- oder Nachteile kann ich nichts sagen, da ich noch mit den GDI Funktionen arbeite.

Wenn Du Dich mit extrem speicheraufwendigen Grafiken rumschlagen musst, solltest Du Dir aufjedenfall die GDI bzw GDI+ mal anschauen. Die Funktionen sind deutlich schneller als jedes OnPaint.

Wenn Du unbedingt an dem Array festhalten möchtest würde ich Dir raten ein kleines Array mit +/- 10 Zellen gemessen am Bildschirmausschnitt im Speicher zu halten und lieber per neuen Thread den geänderten Teilausschnitt in ein zweites Array nachzuladen.

Ich hoffe die Antwort hilft Dir weiter.

Für diesen Beitrag haben gedankt: Chiyoko
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Mi 18.06.14 15:33 
@Demirel

Nein, du hast das falsch verstanden. :)

Nochmal in Kurzfassung:

Bereits implementiert sind :arrow: :arrow:

- einlesen und zuweisen der Bmap-Array-Matrix von 4000 Bildern
- diese BMaps nehmen 1,3 GB an RAM ein
- diese BMaps werden in einem OnPaint Event in ein Graphics Objekt gerendert, wobei in Abhaengigkeit der
Bildaufbauleisten die Bilder aus dem Array neu gezeichnet werden(also nur der sichtbare Bereich).
D.h. 4000 Bilder im Speicher, aber 3-4 davon sind nur sichtbar und werden neu gezeichnet.

Meine Frage war:
Wie opimiere ich den Speicherverbrauch von 1,3GB?
ODER
Wie kann ich die Bilder auf ein Panel/eine Form etc rendern, ohne das OnPaint Ereignis zu nutzen?
D.h. ich möchte letztlich nur 1 Objekt im Speicher haben und nicht 4000.
Wenn das funktionieren sollte, ist die Datei dann auch 1,3 GB groß?

Natuerlich könnte ich das Bild auch erstellen und dann einfach laden lassen.(Bereits geschehen).
Aber die Datei ist riesig und unflexibel.



EDIT:
Dein letzter Satz ist vielleicht ein guter Ansatz.Aber die Bilder werden in einer bestimmten Reihenfolge
geladen und das wäre vielleicht etwas umstaendlich. Stell dir das mal wie ein Puzzle vor :D
Dynamisches Nachladen hat auch den Nachteil von Geschwindigkeitseinbusen. Trotzdem sieht mir das nach einer vielversprechenden Alternative aus.

Danke euch beiden.Ich lass die Frage trotzdem noch stehen, falls es doch noch eine bessere Moeglichkeit geben sollte.


@Th69
Jetzt habe ich deine Nachricht auch verstanden:)
avoid
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 206
Erhaltene Danke: 4

WinXP32, Win764
msl, html, php, Java, Basic, C# (VS 2010 Pro)
BeitragVerfasst: Do 19.06.14 10:21 
auch wenn ich nicht genug Ahnung habe um dir bei deinem Problem zu helfen,
will ich mal meinen Senf dazu geben. ;)

wenn ich das richtig verstanden habe hast du eine Grafik die größer als der Anzeigebereich ist.
du hast diese Grafik in ein Raster eingeteilt, warum verstehe ich nicht wirklich
aber evtl. weil du nur die im Anzeigebereich sichtbaren Fragmente laden willst.

da du aber etwas von 1,3 GB Speicher verbrauch schreibst gehe ich mal davon aus,
das du, egal ob die komplette Grafik oder Fragmente, immer alles in den Ram packst
damit du keine Verzögerung bekommst wenn sich der Anzeigebereich auf einen anderen Teil der Grafik ändert.

im Grunde erinnert mich die Geschichte etwas an Google-Maps. ^^

---------------------------------------------------------------

leider hab ich keine Ahnung ob die Grafikfragmente bei dir als Recource eingebunden sind
oder ob sie erst zur Laufzeit von deinem Programm erstellt werden aber ich würde sie auslagern.
Dann hast du die Möglichkeit deine Anwendung zu starten und nur die wirklich angezeigten
Fragmente zu laden und danach die unmittelbar angrenzenden Fragmente schon mal vor zu laden
um beim verschieben des Anzeigebereich die Performance bei zu behalten.

so liegt immer nur ein Bruchteil im Speicher.
oder nicht?

---nachtrag---
Demirel Automaten hatte das ja schon vorgeschlagen. ^^

naja aber auf jeden fall musst du dir im klaren darüber sein das alles was du renderst auch im Speicher liegt.
also rendere nur einen teil oder speichere dein gerendertes Ergebnis zwischen und lade dann nur den benötigten teil.
in welcher form du die Zwischenspeicherung vornimmst bleibt dir überlassen.

Gruß,
avoid

_________________
Gute Fragen sind wie ein wissenschaftliches Experiment. Sie setzen eine Menge Wissen bereits voraus.
bitcoin:1J5dgQQp8eUy8wkUxyztBUVCkCpo5MQEQs?label=Danke

Für diesen Beitrag haben gedankt: Chiyoko
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Do 19.06.14 11:22 
Hehe, alle Antworten sind gerne gesehen:)

ich entwickle seit mittlerweile 3 Jahren(mehr oder weniger) ein Projekt, dass immer wieder verbessert wird.
Projekte werden bei mir nie fertig, dass war schon immer so. Ich habe Freude am basteln und optimieren.
Damals wollte ich eine Karte realisieren, die der Art von Google-Maps gleicht.Das hast du gut erkannt :D
Leider fehlten mir die Kenntnisse dazu, nur Ansatzweise etwas in dieser Art zu erschaffen.
Zudem sind die Windows Forms mit Webseiten nicht zu vergleichen.

Wie schon erwähnt, hatte ich es bereits in Erwägung gezogen, die Bilder dynamisch nach zu laden.
Meine Sorge dabei war immer nur die Berechnung der Positionen.Woher weis ich denn, wo Map X liegt?

Die Bilder sind alle durcheinander und folgen einer Reihenfolge im Namen.
Z.b. 123x50 (Pos x 123, Pos y 50).
Das bedeutet einen Radius zu haben, der sich von z.b. 100 bis 170 (x) und 50 bis 90 (y) bewegt.
Dazwischen gibt es Leerraeume. Wahrscheinlich wäre es das einfachste, die Dateinamen alle umzubenennen
(dafür habe ich bereits ein Programm entwickelt, was mir die Bilddaten von DDS auf Bmp konvertiert und umbenennt)

Dies bedeutet wiederum, 2400 Bilder zu haben die einen Bereich ausfüllen und 1600 schwarze Bereiche, die aber
nur als 1 Bild existieren.
Dann werd ich mir wohl mal die Arbeit machen und die 1600 Bilder erstellen , um die Lücken zu schliessen.
Hat auch den Vorteil, dass meine Berechnungen wegfallen würden, hehe.

Danke.Ich mach mich an die Arbeit. Wenn es fertig ist, mach ich mal ein Bild davon.
avoid
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 206
Erhaltene Danke: 4

WinXP32, Win764
msl, html, php, Java, Basic, C# (VS 2010 Pro)
BeitragVerfasst: Do 19.06.14 17:59 
klingt gut.
aber kein 1,3GB Bild ok? ;)

viel Erfolg!

_________________
Gute Fragen sind wie ein wissenschaftliches Experiment. Sie setzen eine Menge Wissen bereits voraus.
bitcoin:1J5dgQQp8eUy8wkUxyztBUVCkCpo5MQEQs?label=Danke
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Di 24.06.14 22:05 
Es war doch einfacher als ich angenommen hatte.
5 Minuten überlegt aber ein par Tage zum schreiben gebraucht :D

Die Bilder sind jetzt nicht in der richtigen Reihenfolge und das puffern fehlt auch noch, aber es geht!

user defined image

Der wichtigste Codepart:
Die ersten beiden Schleifen durchlaufen die Flächen, die gezeichnet werden sollen.
ListPics ist eine Liste mit Bildpfaden und dessen Koordinaten.
Jetzt muss ich das Ganze nur noch optimieren...was alles andere als einfach wird, da das rendern der Bilder noch der einfachste Teil war.
Aber dazu später mehr:)

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Graphics dc = e.Graphics;
    dc.ScaleTransform(1.0F, 1.0F);
    Size scrollOffset = new Size(AutoScrollPosition);

    int startX = Math.Min(mSizeX, (e.ClipRectangle.Left - scrollOffset.Width) / 256);
    int startY = Math.Min(mSizeY, (e.ClipRectangle.Top - scrollOffset.Height) / 256);
    int endX = Math.Min(mSizeX, (e.ClipRectangle.Right - scrollOffset.Width + 255) / 256);
    int endY = Math.Min(mSizeY, (e.ClipRectangle.Bottom - scrollOffset.Height + 255) / 256);
    Image imgPic;

    for (int x = startX; x < endX; x++)
    {
        for (int y = startY; y < endY; y++)
        {
            for (int i = 0; i < listPics.Count; i++)
            { 
                if(listPics[i].Cx == x && listPics[i].Cy == y)
                {
                    imgPic = Bitmap.FromFile(listPics[i].FullName);
                    BmpMatrix[x,y] = new Bitmap(imgPic, 256256);
                    dc.DrawImage(BmpMatrix[x, y], new Point(x * 256 + scrollOffset.Width, y * 256 + scrollOffset.Height));
                }
            }             
        }
    }
}


Edit:
- Speicherverbrauch < 100Mb (durchgehend)
- Geschwindigkeit: Etwas langsamer aber kaum merkbar zum vorherigen Rendern mit allen Bildern


Edit2:

Auch das letzte Problem ist jetzt geloest und vielleicht ist das Spiel jetzt erkennbar, von dem diese Bilder stammen.
Die Bilder werden von oben nach unten gerendert.Darum erschien alles in der falschen Reihenfolge.

Problemloesung:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
// Bereich von Y bertrifft 81-110
// Um nullbasierende Werte zu erhalten reicht es eigendlich aus, wenn y -81 genommen wird.
// Da die Werte dann aber falsch herum grendert werden, muss die Rechnung so aussehen:

int cx = outX - 45;      // aktuelle X Pos - 45(minimal Wert von X)
int temp = outY - 110;   // aktuelle Y Pos - 110 (maximal Wert von Y)
int cy = ~temp + 1;      // Vorzeichen entfernen, um die umgekehrte Pos zu bekommen


Ergebnis:

Links steht der nullbasierte Wert, daneben jeweils der Wert des Dateinamens.

user defined image
avoid
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 206
Erhaltene Danke: 4

WinXP32, Win764
msl, html, php, Java, Basic, C# (VS 2010 Pro)
BeitragVerfasst: Sa 28.06.14 21:09 
nice :)
und das geht jetzt auch mit dem verschieben des Anzeigebereich?

_________________
Gute Fragen sind wie ein wissenschaftliches Experiment. Sie setzen eine Menge Wissen bereits voraus.
bitcoin:1J5dgQQp8eUy8wkUxyztBUVCkCpo5MQEQs?label=Danke
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Di 01.07.14 11:33 
Alles kein Problem.Die Bilder laden dynamisch nach, sobald sich der Anzeigebereich(noch ueber Scrollbalken, spaeter per Maus) ändert.
Das einzige, was jetzt noch zu tun ist, wäre die Bilder nach ein par Sekunden oder wie auch immer aus dem Speicher zu nehmen.
Ansonsten erhöht sich der Verbrauch bis auf die vorherigen 1,3 GB.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4009
Erhaltene Danke: 824

Win7
C++, C# (VS 2015/17)
BeitragVerfasst: Di 01.07.14 12:47 
Ich habe mal ein bißchen gesucht und A Least Recently Used (LRU) Cache in C# gefunden. Das sollte dir bei deinem Speicherproblem helfen.
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Fr 04.07.14 09:29 
Danke, hat sich aber durch oben beschriebene Loesung bereits geloest.
Mehr Arbeit will ich in das Projekt nicht stecken (abgesehen vom betreiben von Clean Code)
avoid
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 206
Erhaltene Danke: 4

WinXP32, Win764
msl, html, php, Java, Basic, C# (VS 2010 Pro)
BeitragVerfasst: Sa 05.07.14 17:37 
du hast doch jetzt schon für jedes Bild ein flag, ob es im Anzeigebereich ist oder nicht ...
wenn das so ist, frage es doch einfach einmal pro Sekunde ab
und lösche danach alle Bilder aus dem Speicher die nicht mehr im Anzeigebereich sind.

damit du wirklich nur die raus nimmst die nicht gebraucht werden
und im gleichen Zuge kannst du die nächsten umliegenden wieder nachladen.

_________________
Gute Fragen sind wie ein wissenschaftliches Experiment. Sie setzen eine Menge Wissen bereits voraus.
bitcoin:1J5dgQQp8eUy8wkUxyztBUVCkCpo5MQEQs?label=Danke
Chiyoko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 295
Erhaltene Danke: 7

Win 98, Win Xp, Win 10
C# (VS 2017)
BeitragVerfasst: Mo 07.07.14 09:06 
Ja, so oder so ähnlich hatte ich das auch geplant.
Ein Flag habe ich momentan nicht, aber wie du schon beschrieben hast, wird das wohl für die Abfragen zum löschen der Speicherreferenz gebraucht.
Eins nach dem anderen.Jetzt will ich erstmal herausfinden, welche Koordinatenbreite/laenge die Map hat.Danke.