C# Image Objekte vergleichen

ok, lynxx. scheinbar hast du heute mit Peter Lustig geduscht oder einen Clown gefrühstückt.
Und um nen kleinen Pisswettbewerb zu starten: MACHS SCHNELLER!

Naja, ich will ja mal nicht so sein und hier leichter zu lesende Variante schreiben:

Code:
for (int i = 0; i < 128; i++)
   if (x.data[i] != y.data[i])
     return true;
return false;

Nun aber das witzige, der gesamte Bildvergleich mit meinem Monster - Return braucht ca. 5 - 9 ms in der Ruhephase, während die Loop dann ca 10 - 15 ms benötigt. Also wenn der Code bereits ca 90 ms benötigt, dann muss man wohl mit dem Microtuning starten um hier noch Verbesserungen zu erreichen. Wobei die meiste Zeit bei den Drawing Methoden des .Net Frameworks verbraten wird. Hier sollte man dann wohl die WinAPI verwenden, wie z.B. SetDIBitsToDevice, StretchDIBits, GetDIBits um nur einige Verdächtige zu nennen.
 
Zuletzt bearbeitet:
Rossibaer schrieb:
ok, lynxx. scheinbar hast du heute mit Peter Lustig geduscht oder einen Clown gefrühstückt.
Und um nen kleinen Pisswettbewerb zu starten: MACHS SCHNELLER!

Naja, ich will ja mal nicht so sein und hier leichter zu lesende Variante schreiben:

Code:
for (int i = 0; i < 128; i++)
   if (x.data[i] != y.data[i])
     return true;
return false;

Nun aber das witzige, der gesamte Bildvergleich mit meinem Monster - Return braucht ca. 5 - 9 ms in der Ruhephase, während die Loop dann ca 10 - 15 ms benötigt. Also wenn der Code bereits ca 90 ms benötigt, dann muss man wohl mit dem Microtuning starten um hier noch Verbesserungen zu erreichen. Wobei die meiste Zeit bei den Drawing Methoden des .Net Frameworks verbraten wird. Hier sollte man dann wohl die WinAPI verwenden, wie z.B. SetDIBitsToDevice, StretchDIBits, GetDIBits um nur einige Verdächtige zu nennen.
Etwas geht noch:
PHP:
public static bool operator !=(ComparePixel x, ComparePixel y) {
  for (short i = 0; i < 128;)
    if (x.data[i] != y.data[i++])
      return true;
  return false; 
}
:D

Einer der "Schuldigen" ist die eigene Transparente PictureBox mit der größe des Screen nur für die Maus! :eek:
Das frisst jede Menge Zeit .. hier das ist besser und ScreenCapture wird gar nicht mehr gebraucht:
PHP:
bool bScaleMouse = false; // Maus skalieren oder Orginalgröße
private void GetMouse(ref int x, ref int y) {
  if (pbScreen.Image != null) {
    double mouseWidthFactor = ((double)pbScreen.Width) / pbScreen.Image.Width;
    double mouseHeightFactor = ((double)pbScreen.Height) / pbScreen.Image.Height;
    Cursor mouse = Cursor.Current;
    int mousew = bScaleMouse ? (int)(mouse.Size.Width * mouseWidthFactor) : mouse.Size.Width;
    int mouseh = bScaleMouse ? (int)(mouse.Size.Height * mouseHeightFactor) : mouse.Size.Height;
    pbMouse.Size = new Size(mousew, mouseh);
    pbMouse.Image = new Bitmap(mousew, mouseh);
    Graphics g = Graphics.FromImage(pbMouse.Image);
    Rectangle rectDest = new Rectangle(0, 0, mousew, mouseh);
    mouse.DrawStretched(g, rectDest);
    g.Dispose();
    newMouse = (Bitmap)pbMouse.Image;
    x = (int)(Cursor.Position.X * mouseWidthFactor);
    y = (int)(Cursor.Position.Y * mouseHeightFactor);
    pbMouse.Location = new Point(x, y);
  }
}
DrawMouse() wird nicht mehr gebraucht ..
 
Zuletzt bearbeitet:
Einer der schuldigen ist die eigene Transparente PictureBox mit der größe des Screen nur für die Maus!
Das frisst jede Menge Zeit

Muss dir da widersprechen. Gerade eben am Originalcode von Zhen gemessen und siehe da GetMouse() benötigt 4 / 5 ms und DrawMouse() dann nochmal 5 / 6 ms. ca. 40 - 50 ms gehen aber für das pbScreen.Refresh() drauf, da beist die Maus kein Faden ab!
 
Rossibaer schrieb:
Muss dir da widersprechen. Gerade eben am Originalcode von Zhen gemessen und siehe da GetMouse() benötigt 4 / 5 ms und DrawMouse() dann nochmal 5 / 6 ms. ca. 40 - 50 ms gehen aber für das pbScreen.Refresh() drauf, da beist die Maus kein Faden ab!
.. probiers aus, elapsed ist bei mir deutlich runtergegangen, pbMouse wird ja anschliessend noch auf pbScreen gezeichnet ..

Edit:
Am besten noch ein Label für DurchschnittsMs hinzufügen und am Ende von timerScreen_Tick:
PHP:
  long durchschnitt = 0;
  int grabs = 0;
  private void timerScreen_Tick(object sender, EventArgs e) {
    ...
    ...
    long elapsed = stopwatch.ElapsedMilliseconds;
    durchschnitt += elapsed;
    grabs++;
    lblMs.Text = elapsed.ToString();
    lblDurchschnitt.Text = (durchschnitt / grabs).ToString();
  }
 
Zuletzt bearbeitet:
von 5 auf 1 ms (nur GetMouse()) ohne dabei zu berücksichtigen, dass der Mauszeiger sich z.B. bei einem Textfeld auf dem Bildschirm ändert, bei dir ists immer der gleiche Zeiger. Somit zwar von gesamt 11 ms auf 1 ms runter, dafür aber ein Windows-Feature verloren.

Edit: Ich schau gerade mal wie das die CaptureScreen.dll macht...
 
Zuletzt bearbeitet:
Rossibaer schrieb:
von 5 auf 1 ms (nur GetMouse()) ohne dabei zu berücksichtigen, dass der Mauszeiger sich z.B. bei einem Textfeld auf dem Bildschirm ändert, bei dir ists immer der gleiche Zeiger. Somit zwar von gesamt 11 ms auf 1 ms runter, dafür aber ein Windows-Feature verloren.

Edit: Ich schau gerade mal wie das die CaptureScreen.dll macht...
Naja, jedenfalls die Transparente Picturebox in Bildschirmgröße ist unnötig und frisst Zeit, das kann man ja leicht wie in meinem Beispiel gesehen ändern ..

Mir ist noch ein kleines Problem in der GetPartialScreen() aufgefallen, und zwar in der Schleife wird ja der Speicher einfach am Stück durchgeprüft und die Position durch Modulo / Teilung berechnet, bei Bildschirmauflösungen die nicht durch 256 teilbar sind kann dann aber eine maxX rauskommen die der Bildschirmbreite entspricht, obwohl nur eine Änderung am Anfang einer X-Zeile stattfand. 1920 / 256 = 7,5 Also 128 Pixel "Rest", bzw wird dann die letzten 128 Pixel der aktuellen Zeile und 128 Pixel der nächsten Zeile getestet.

Aber wenn man die Schleife so ändert:
PHP:
for (int indx = 0; indx < count; indx++) {
  ...
  if (x > maxX && ((newScreen.Width * (y+1)) % bufferSize == 0)) maxX = x;
Wird beim maxX fall geprüft ob die Bildschirmbreite * (Zeile+1) ohne Rest durch bufferSize (256) geteilt werden kann, nur dann handelt es sich wirklich um das Ende einer Zeile und maxX wird übernommen.

Edit: Kleine Änderung, funktioniert dann auch mit ungewöhnlichen Auflösungen wie 1776 (alle 16 Zeilen teilbar), 1680 (alle 16 Zeilen teilbar), 1600 (alle 4 Zeilen teilbar), 1440 (alle 8 Zeilen teilbar), 1400 (alle 32 Zeilen teilbar) usw .. Bildschirmänderungen am Rechten Rand die kleiner als Anzahl teilbare Zeilen sind gehen dann aber eventuell verloren, man könnte alternativ bei diesen Auflösungen/schlechter teilbarkeit durch 256 die Extra Prüfung weglassen und damit leben das bei kleineren Bildänderungen rechts die gesamte Breite übertragen wird, oder eine Extra-Schleife prüft bei diesen Auflösungen gezielt das Ende der X-Zeilen in einer Schleife.

2nd Edit:
Hier die Prüfung des / 256 Byte teilbaren Bereichs und der letzten 256 Bytes einer Zeile falls es sich um eine der nicht teilbaren Auflösungen handelt und maxX noch nicht Bildschirmbreite - 256 ist, nebenbei ist der Code auch noch etwas schneller. :D
PHP:
// Vergleiche 256 Pixel auf einmal
int* newBase = (int*)newData.Scan0;
int* prevBase = (int*)prevData.Scan0;
int* newLast;
int* prevLast;
int count = byteCount / sizeof(ComparePixel);
int bufferSize = sizeof(ComparePixel) / Bpp;
int x = 0, y = 0;
// Nur / 256 teilbaren Bereich einer Zeile prüfen
int endx = newScreen.Width / bufferSize * bufferSize;
for (x = 0; x < endx; x += bufferSize) {
  // 256 Bytes von oben nach unten prüfen bis erste Differenz (minY finden)
  for (y = 0; y < minY; y++) {
    newLast = newBase + y * newScreen.Width + x;
    prevLast = prevBase + y * newScreen.Width + x;
    if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
      if (x < minX) minX = x;
      // Jeder Treffer muss maxX sein
      maxX = x;
      // Jeder Treffer muss minY sein
      minY = y;
      if (y > maxY) maxY = y;
      break;
    }
  }

  // Hat vorherige Schleife nicht alle Zeilen getestet (nur der Fall wenn schon eine Differenz aufgetreten ist)
  if (y != newScreen.Height) {
    // 256 Bytes von unten nach oben prüfen bis letzte Differenz (maxY finden)
    for (y = newScreen.Height - 1; y > maxY; y--) {
      newLast = newBase + y * newScreen.Width + x;
      prevLast = prevBase + y * newScreen.Width + x;
      if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
        // Jeder Treffer muss maxX sein
        maxX = x;
        // Jeder Treffer muss maxY sein
        maxY = y;
        break;
      }
    }

    // Wurde maxX schon gefunden ?
    if (x != maxX) {
      // 256 Bytes von minY bis maxY prüfen bis erste Differenz (maxX finden)
      for (y = minY; y < maxY; y++) {
        newLast = newBase + y * newScreen.Width + x;
        prevLast = prevBase + y * newScreen.Width + x;
        if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
          if (x < minX) minX = x;
          // Jeder Treffer muss maxX sein
          maxX = x;
          break;
        }
      }
    }
  }
}
// x = letzte 256 Pixel einer Zeile
x = newScreen.Width - bufferSize;
// Falls maxX noch nicht newScreen.Width - bufferSize und newScreen.Width nicht ohne Rest teilbar durch bufferSize
if (maxX < x && newScreen.Width % bufferSize != 0) {
  // Die letzten 256 Pixel jeder Zeile aufsteigend prüfen
  // Pointer ans Ende der Zeile - bufferSize setzen
  newLast = newBase + x;
  prevLast = prevBase + x;
  // Für Rechten Rand minY finden
  for (y = 0; y < minY; y++, newLast += newScreen.Width, prevLast += newScreen.Width) {
    if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
      if (x < minX) minX = x;
      // Jeder Treffer muss maxX sein
      maxX = x;
      // Jeder Treffer muss minY sein
      minY = y;
      if (y > maxY) maxY = y;
      break;
    }
  }
  // Hat vorherige Schleife nicht alle Zeilen getestet und ist maxY noch kleiner Bildschirmhöhe
  if (y != newScreen.Height && maxY < newScreen.Height) {
    // Die letzten 256 Pixel jeder Zeile absteigend prüfen
    // Pointer ans Ende des Bildes - bufferSize setzen
    newLast = newBase + pixelCount - bufferSize;
    prevLast = prevBase + pixelCount - bufferSize;
    // Für Rechten Rand maxY finden
    for (y = newScreen.Height - 1; y > maxY; y--, newLast -= newScreen.Width, prevLast -= newScreen.Width) {
      if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
        // Jeder Treffer muss maxX sein
        maxX = x;
        // Jeder Treffer muss maxY sein
        maxY = y;
        break;
      }
    }

    // Wurde maxX schon gefunden ?
    if (x != maxX) {
      // 256 Bytes von minY bis maxY prüfen bis erste Differenz (maxX finden)
      for (y = minY; y < maxY; y++) {
        newLast = newBase + y * newScreen.Width + x;
        prevLast = prevBase + y * newScreen.Width + x;
        if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {
          if (x < minX) minX = x;
          // Jeder Treffer muss maxX sein
          maxX = x;
          break;
        }
      }
    }
  }
}
// Größe von ComparePixel addieren
maxX += bufferSize;
if (maxX > newScreen.Width)
  maxX = newScreen.Width;
maxY++;
 
Zuletzt bearbeitet:
lynxx: hast recht bei dem zeilenweise Prüfen. Wow hab garnicht daran gedacht. Aber dein Code im 2. Edit sieht sehr groß aus. Dabei hab ich mir folgendes gedacht: Man könnte ja mal alle Zeilen durchlaufen und dabei zuerst von links nach rechts suchen um das minX zu finden, dabei reduziert sich der Durchlauf der Schleife bei jeder Zeile von 0 bis zum aktuellen minX. Anschließend sucht man von rechts nach links und betrachtet dabei das maxX, was sich auch wieder reduziert von Ende bis aktuellen maxX. Analog zu meiner derzeitigen Behandlung vom Rest des gesamten Bildes. minY wäre die erste gefundene Änderung und maxY die letzte gefundene Änderung.

So ala:

Code:
for(int y = 0; y < newScreen.Height; y++)
{
  for(int x = 0; x < minX; x++)
  {
     // Prüfen und auswerten (bei 1. Treffer mit break aussteigen) und beim 1. Treffer minY setzen
  }
  for(int x = newScreen.Width; x > maxX; x--)
  {
     // Prüfen und auswerten (bei 1. Treffer mit break aussteigen) und maxY setzen
  }
}

Dadurch wären auch die krummen Auflösungen kein Problem, was die Geschwindigkeit angeht - keine Ahnung. Vielleicht ist es schneller, was durchaus sein kann, wenn das Bild eben nicht vollständig durchlaufen werden müsste, z.B. bei großen Änderungen, bei kleinen bzw. keinen Änderungen wirds dann halt komplett durchlaufen, d.h. die Ruhephase wird mehr Zeit benötigen als bei Auslastung. So wäre zumindest mein Gedankengang. Aber vielleicht liege ich falsch. Werde das mal bei Gelegenheit ausprobieren.

Grüße
Rossibaer

PS: Sag mal lynxx, schläfst du auch ab und zu mal? Zuerst schreibst du um 00:43 und dann um 05:20 und kommst dabei mit einem fast komplett anderen Code. Also was treibst du so, wenn "normale" Rossibaeren schlafen? :D

EDIT: Um dem Ganzen noch Einen drauf zusetzen, sind im Code massive Speicherlecks drin. Teilweise rauscht der verwendete Speicher auf 700MB hoch bis der GC dann wieder aufräümt. Liegt daran, dass wir so ziemlich jeden Screenshot in ein neues Bitmap werfen. Jedenfalls bremst der GC das Programm immer wieder aus, sodaß die gesamte Ausführungszeit des TimerTicks regelmäßig auf über 150ms springt.
 
Zuletzt bearbeitet:
Rossibaer schrieb:
..minY wäre die erste gefundene Änderung und maxY die letzte gefundene Änderung.

So ala:

Code:
for(int y = 0; y < newScreen.Height; y++)
{
  for(int x = 0; x < minX; x++)
  {
     // Prüfen und auswerten (bei 1. Treffer mit break aussteigen) und beim 1. Treffer minY setzen
  }
  for(int x = newScreen.Width; x > maxX; x--)
  {
     // Prüfen und auswerten (bei 1. Treffer mit break aussteigen) und maxY setzen
  }
}
Das macht mein Code ja, nur nicht in X sondern Y-Richtung, weil dann bedeutend weniger Schleifen gestartet werden müssen, in X-Richtung bei 1920/1080 sind es nur 7 Ausführungen innerhalb der Schleife, bei y aber 1080.
Allerdings müsste bei Deiner Methode auch noch den Bereich zwischen minX & maxX prüfen um ein neues maxY zu finden (z.B eine kleine Grafik hat sich links oben geändert, eine rechts oben und dann eine in der Mitte des Bildschirms ..)
So das es schon 3 Schleifen sind, dann muss noch geprüft werden ob die erste Schleife ohne Break durchgelaufen ist, sonst wird die ganze Zeile vorwärts und rückwärts geprüft, also im schlimmsten Fall alles doppelt ..
Dann hab ich noch 3 Extra-Schleifen für die letzten 256 Pixel, weil da die warscheinlichkeit am größten ist das die Adressen nicht auf Cache/Prefetch-Zeilenanfang liegen (je nach Auflösung), ausserdem werden die ja nur für den Fall aufgerufen das die aktuelle Auflösung nicht / 256 teilbar ist, maxX nicht gefunden usw usf ..

Rossibaer schrieb:
PS: Sag mal lynxx, schläfst du auch ab und zu mal? Zuerst schreibst du um 00:43 und dann um 05:20 und kommst dabei mit einem fast komplett anderen Code. Also was treibst du so, wenn "normale" Rossibaeren schlafen? :D
Hrhr, schlafen kann ich wenn ich tot bin. :D

Rossibaer schrieb:
EDIT: Um dem Ganzen noch Einen drauf zusetzen, sind im Code massive Speicherlecks drin. Teilweise rauscht der verwendete Speicher auf 700MB hoch bis der GC dann wieder aufräümt. Liegt daran, dass wir so ziemlich jeden Screenshot in ein neues Bitmap werfen. Jedenfalls bremst der GC das Programm immer wieder aus, sodaß die gesamte Ausführungszeit des TimerTicks regelmäßig auf über 150ms springt.
Das habe ich mir noch gar nicht angesehen, sollte ich wohl mal. :)
 
Wenn ich es mir recht überlege, ist es eigentlich völlig falsch nach dem Rechteck zu suchen, welches Änderungen hat. Das Problem ist, dass wenn sich unten rechts z.B. die Uhrzeit ändert und oben links auch nur ein Pixel anders ist, dann wird der komplette Screen in Größe von mehreren MB versendet. Das Versenden ist aber der Schritt, der die Geschwindigkeit des RemoteTools (was der TE beabsichtigt) bestimmt. Da nutzt es nichts im ms Bereich den Unterschied von 2 Screens als Rechteck zu finden! Also wäre meine Idee das gesamte Bild mit der ComparePixel Struktur zu durchlaufen und bei denen die eine Änderung haben, Index und Länge bestimmen, dann eine neue Struktur erzeugen aus Index, Länge und Daten der Änderung. Dies würde ich dann versenden und auf der Empfangseite an genau dieser Stelle wieder reinkopieren. Das würde das Datenvolumen drastisch reduzieren und nur noch im Grenzfall der eher selten eintritt ein volles Bild senden. Ich denke, dass mit der ComparePixel Struktur die Bilddaten ausreichend schnell durchlaufen werden können. Damit wird die aufwändige Suche nach minX, maxX, minY und maxY hinfällig und das Datenvolumen reduziert.

Für die Lösung der Speicherlecks würde ich nur 2 Bitmaps erzeugen und diese immer wieder tauschen.
 
Zuletzt bearbeitet:
Rossibaer schrieb:
Wenn ich es mir recht überlege, ist es eigentlich völlig falsch nach dem Rechteck zu suchen, welches Änderungen hat. Das Problem ist, dass wenn sich unten rechts z.B. die Uhrzeit ändert und oben links auch nur ein Pixel anders ist, dann wird der komplette Screen in Größe von mehreren MB versendet. Das Versenden ist aber der Schritt, der die Geschwindigkeit des RemoteTools (was der TE beabsichtigt) bestimmt. Da nutzt es nichts im ms Bereich den Unterschied von 2 Screens als Rechteck zu finden! Also wäre meine Idee das gesamte Bild mit der ComparePixel Struktur zu durchlaufen und bei denen die eine Änderung haben, Index und Länge bestimmen, dann eine neue Struktur erzeugen aus Index, Länge und Daten der Änderung. Dies würde ich dann versenden und auf der Empfangseite an genau dieser Stelle wieder reinkopieren. Das würde das Datenvolumen drastisch reduzieren und nur noch im Grenzfall der eher selten eintritt ein volles Bild senden. Ich denke, dass mit der ComparePixel Struktur die Bilddaten ausreichend schnell durchlaufen werden können. Damit wird die aufwändige Suche nach minX, maxX, minY und maxY hinfällig und das Datenvolumen reduziert.

Für die Lösung der Speicherlecks würde ich nur 2 Bitmaps erzeugen und diese immer wieder tauschen.
VNC macht auch sowas in der Art, Hextile testet imho 32x32 Pixelblöcke, das hat erstmal den Vorteil das praktisch jede Auflösung dadurch teilbar ist und wenn ein Treffer innerhalb des Blocks ist so wird dieser verschickt (evtl. noch komprimiert), statt sich die Mühe zu machen genau herauszufinden welcher Pixel jetzt wo und wieviel. Der ganze Bildschirm wird dann in 32x32 Blöcken durchgetestet ..

Zum Bitmapproblem hab ich keine Lösung, ich hab auch schon versucht native Funktionen einzusetzen, CreateCompatibleBitmap ... BitBlt ... Image.FromHbitmap, klappt auch und schnell, Problem ist nur das beim zweiten Zugriff auf das Image eine Exception geworfen wird, dann habe ich testweise wie ich das schonmal gepostet habe memcpy(Marshal.UnsafeAddrOfPinnedArrayElement(newPicture, 0), picData.Scan0, newPicture.Length * 4); gemacht, das schlägt aber schon mit der "CompatibleBitmap" fehl.
Mit der durch g.CopyFromScreen erzeugten Bitmap funktioniert memcpy, aber das bringt nicht viel da man auch noch 2 Arrays in Bildschirmgröße handeln muss und trotzdem ständig eine neue Bitmap erzeugen muss.

Aber hey, das einfachste von allem klappt natürlich .. :evillol:

in der GetScreen()
if (newScreen == null || prevScreen == null || (newScreen != null & prevScreen != null && ((newScreen.Width * newScreen.Height) != (prevScreen.Width * prevScreen.Height))))
newScreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);

Und da wo nur
prevScreen = newScreen;
steht:
Bitmap tmpBmp = prevScreen;
prevScreen = newScreen;
newScreen = tmpBmp;

:daumen:
 
1. wieder mal entschuldigung dass ich mich am WE nicht melden konnte (Anschluss kommt erst am Freitag =D dann gehen auch die schlaflosen nächste wieder los :D )

2. ich weiß gar nicht wie ich euch danken soll =)
Werd jetzt gleich mal den ganzen Code durchgucken und austesten.

3. Aber wegen dem GC und dem speicher... was wäre wenn man den GC zwangsaufruft nach 10 Wiederholungen z.B. dann sollte der Speicher ja nicht so hoch sein und damit braucht der GC ja auch nicht allzulange dann. Also rein theoretisch gesehen sollte er dann die Anwendung nicht so extrem ausbremsen oder?

EDIT:
hier stand schwachsinn!!

2nd EDIT:
hier ebenfalls!!

bin soweit ganz glücklich und werd mich mal daran machen die nativen Funktionen zu verstehen/implementieren/ausprobieren. Aber ich glaub ganz alleine schaff ich das auch nicht :D hab letzte Woche da auch schon Probleme mit der GetDIBits Methode gehabt :(

3rd EDIT:
Also irgendwie glaub ich, dass in der neuen GetPartialScreen-Methode ein Fehler ist. Es gibt ein Rectangle mit stets den selben Koordinaten und Maßen zurück (nämlich mit Breite und Höhe der Auflösung). Das würde ja aber bedeuten, dass da stets der komplette Screen übertragen wird und nicht nur ein Teil davon oder ?!! => lag an der Exception. Der Code für den partialScreen wurde gar nicht durchlaufen

4th EDIT:
Zwar hab ich meine Fehler eingesehen und wieder rückgängig gemacht, aber mit einer Sache komme ich noch nicht so ganz klar... mit dem Rectangle für den partialScreen! Zwar gibt er mir stets andere Breite und Höhe aus (seitdem die Exception behoben wurde :rolleyes: ) jedoch liegen diese stets im Minusbereich und die X und Y Koordinaten sind trotzdem immer die selben (die Maße der Auflösung).

Das Problem ist aber, dass ich diese Brauche um ein einzelnes Bild allein von der Änderung zu machen (damit ichs über die Leitung schicken kann).
Ergänzung ()

Das gibts echt nicht. Jetzt hab ich den ganzen Tag damit verbracht und dennoch nicht geschafft es richtig zum laufen zu bringen.
Ergänzung ()

Ok... ich habs geschafft den Screen zu übermitteln, einzufügen, aber es gibt doch den ein oder anderen Bug :(

Also 1. Die Methode von lynxx mit dem if(....) und dem Bitmap tmpBmp... funktioniert schon mal nicht. Wirft ne Exception (hab ewig gebraucht bis ich mal drauf gekommen bin).

2. es ist bei weitem nicht mehr so flüßig wie es mit der alten methode war (wieso muss ich echt mal gucken und suchen - aber die cpu last ist immerhin nicht mehr bei 99 % wie früher sondern nur noch bei 60 %).

3. das ist das größte manko... leider bleiben reste von fenstern übrig. das programm aktualisiert also momentan relativ schlecht!

Ich werde jede Neuigkeit hier posten und hoffe, dass wenn euch was auffällt/einfällt wie ich weiterkommen könnte ihr mir helft :) danke schon mal im vorraus und für alles bisherige ;)
 
Zuletzt bearbeitet:
Hi Zhen, willkommen zurück...

Am Besten du packst mal deine Solution so wie sie ist zusammen und schiebst sie hier in den Thread. Ist schwer zu sagen, welche Methode du nun verwendest und wie der Code ausschaut. Immerhin haben lynxx und ich so an den verschiedensten Sachen experimentiert und kräftig gepostet, sodaß es doch recht verwirrend sein kann. Am Ende waren wir uns, glaube ich, einig, dass es schlecht ist, das Rechteck der Änderungen zu ermitteln, weil es zum einen sehr aufwändig, kompliziert und fehleranfällig ist und es viele Situationen geben wird, wo du gezwungenermaßen den kompletten oder fast kompletten Screen schicken musst. Ein Beispiel: Oben links in der Ecke ändert sich ein Pixel, unten Rechts (z.B. die Uhr) ändert mind. ein Pixel, dann sendest du den vollen Screen. Und das wird sehr häufig passieren.

Aber man kann sich auch andere Lösungen überlegen, z.B. jeden Screen in viele kleine Rechtecke (ala 32x32 Pixel) aufteilen, diese prüfen und wenn es Unterschiede gibt, dann senden. Ergebnis wäre, dass auch minimale Änderungen in weit entfernten Bereichen des Screens das zu sendende Datenvolumen relativ klein belassen, was letztendlich der Übertragung und Darstellung auf dem Remoteclient zu Gute kommt. Ein anderer Ansatz wäre, dass man das Bild als gesamtes Array betrachtet, dieses in 256 Pixel lange Ketten zerlegt, diese betrachtet und bei Änderungen nur die geänderten Pixelketten mit dem Index sendet. Beim Prüfen des Screens bin ich da auf meinem Rechner (1280x1024, 32 Bit, 3.4GHz) selbst bei vielen Änderungen im Screen mit ca 6- 10ms dabei. Das Zusammensetzen geht ebenfalls sehr schnell, einfach an dem Index die Pixeldaten drüber bügeln. Hier entscheidet nur die Übertragungsgeschwindigkeit im Netz und das .Net interne Zeichnen des Screens in die Picturebox.

Zum GC: Als ich mit der .Net Sache anfing, hielt ich damals deinen heutigen Vorschlag für eine superdupper Idee. Mittlerweile bin ich persönlich strikt dagegen, weil (a) man besser den GC seine Sachen selbst regeln lässt (Ich kenne nicht den GC in seiner Tiefe und kann somit nicht abschätzen ob es ok ist!), (b) der Programmierfehler, das Speicherleck, weiterhin im Programm ist und somit weiter unabschätzbare Probleme verursachen kann und (c) auf Faulheit / Schlampigkeit des Programmierers hindeutet! Das ist nur das Konsumieren einer Schmerztablette bei Kopfschmerzen, ohne die Ursache zu kennen.
Aber das sind halt sehr subjektive Dinge, die mich von Sachen wie GC.Collect() etc. abhalten. Ausserdem würde es mich verrückt machen, wenn ich da ne Sache programmiert habe, aber nicht genau weiß, wieso ein Problem entsteht.

Das eigentliche Problem ist jedoch sehr leicht zu finden. Mit jedem Screenshot wird durch die CaptureScreen.dll ein neues mehrere MB großes Bitmap im Speicher erzeugt, ohne dass das vorhergehende mit Bitmap.Dispose() für den GC zur Bereinigung freigegeben wird. D.h. mehrere MB große Bitmap liegen im Speicher bis irgendwann der GC selber sagt, nun wech mit dem alten Scheiß. Angenommen der Timer Tick wird alle 100ms ausgelöst (so hatte ich ihn bei mir eingestellt) dann kommen pro Sekunde ca 50 MB nutzloser Speicher dazu. Dann noch das Problem, dass der GC bei diesen "Langzeitobjekten" von Haus aus lange wartet, bis er sie tatsächlich bereinigt. Alles zusammen werden dann sinnlos mehrere 100 MB verballert ohne Sinn und Verstand. Die Lösung: Es werden genau 2 Bitmaps erzeugt, bei jedem Durchgang getauscht und mit der Graphics.CopyScreen Methode neu befüllt.

Siehe lynxx Lösung (Post #70):

Aber hey, das einfachste von allem klappt natürlich ..

in der GetScreen()
if (newScreen == null || prevScreen == null || (newScreen != null & prevScreen != null && ((newScreen.Width * newScreen.Height) != (prevScreen.Width * prevScreen.Height))))
newScreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);

Und da wo nur
prevScreen = newScreen;
steht:
Bitmap tmpBmp = prevScreen;
prevScreen = newScreen;
newScreen = tmpBmp;

Wenn du vorher noch den Speicher beider Bitmaps mit Marshal.AllocHGlobal selbst erzeugst und diesen IntPtr dann an einen der Overloads des Bitmapkonstruktors übergibst, kannst du dir auch das LockBits und UnlockBits beim Vergleich bzw. Zugriff auf die Bilddaten sparen. Im übrigen bleibt bei dieser Variante mein Rechner bei "schlanken" 40MB Speicher und der GC hat so gut wie nichts mehr zu tun. Aber vergiss nicht auch diesen Speicher an entsprechender Stelle (z.B. bei Änderung der Auflösung / Neuerstellen der Bitmaps) mit Marshal.Release wieder freizugeben, sonst hast du das gleiche Problem wie vorher nur mit dem Unterschied dass der GC nichts mehr freigibt und dein Rechner irgendwann swapped und dann das Programm später völlig den Geist aufgibt.

Also, Anregungen gibts genug, lass Taten folgen ...

Grüße
Rossibaer
 
Zuletzt bearbeitet:
Also die Methode von lynxx hat bei mir nicht funktioniert. Die hat mir dann an der stelle

Code:
BitmapData bmpPrevData = prevScreen.Lockbits(...);

eine Exception geworfen, dass prevScreen bereits gesperrt ist.

Zu der Solution, ich werde die mal aufbereiten und dann hier posten.

Das Zerlegen in kleine Rechtecke klingt natürlich gut und würde bestimmt einen Performanceschub bringen und den Traffic senken. Aber es ist doch etwas zu kompliziert für mich gerade. Wie gesagt bin ich jetzt erst ins zweite Lehrjahr gekommen und in .Net programmiere ich erst seit nem halben Jahr (vielleicht 1 Monat mehr). Davor waren es nur kleinere Websiten in PHP =D

Der Vergleich mit den Arrays ist natürlich auch nicht schlecht. Ich werde mal sehen womit ich besser zurecht komme und halt euch auf dem laufenden ;)

Danke aber vielmals für die Tipps und Hilfestellungen :)
Ergänzung ()

mkayy... hab jetzt die Solution so weit aufbereitet und werds gleich mal uppen.
Ergänzung ()

Ach ja... vielleicht sollte noch erwähnt werden, dass ich beim Remote-Tool keinen Timer benutze für den Aufruf der Funktionen sondern eine While-Schleife die ständig aufs neue Durchlaufen wird und nur bei einer Exception die Verbindung kappt und beendet wird. Wenn ichs nämlich mit dem Timer mache, dann hängt sich das Programm ständig auf. Es leckt ziemlich stark. Jetzt mit dem Code von den letzten Tagen (aktuelles Zip siehe Anhang) leckt es auch ganz schön, aber trotzdem nicht so extrem wie wenn ich nen timer benutze.
Ergänzung ()

Ok also es liegt an den Pointern, dass Reste von Fenstern bleiben! Und zwar an den Stellen mit den

Code:
if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {

Anweisungen. Schreibt man stattdessen:

Code:
if (((ComparePixel*)newLast) != ((ComparePixel*)prevLast)) {

dann bleiben keine Überreste von Fenstern mehr übrig in der Picturebox. Aber das ganze ruckelt trotzdem. Zwar nicht so extrem stark, aber das Arbeiten an so einem Tool würde dennoch nicht zum Vergnügen werden :D
 

Anhänge

Zuletzt bearbeitet:
Ergänzung ()

Ok also es liegt an den Pointern, dass Reste von Fenstern bleiben! Und zwar an den Stellen mit den

Code:
if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {

Anweisungen. Schreibt man stattdessen:

Code:
if (((ComparePixel*)newLast) != ((ComparePixel*)prevLast)) {

dann bleiben keine Überreste von Fenstern mehr übrig in der Picturebox. Aber das ganze ruckelt trotzdem. Zwar nicht so extrem stark, aber das Arbeiten an so einem Tool würde dennoch nicht zum Vergnügen werden :D


So ganz richtig ist es nicht! Wenn du folgenden Vergleich machst

Code:
if (*((ComparePixel*)newLast) != *((ComparePixel*)prevLast)) {

dann werden die Inhalte bzw die Pixel verglichen.

Wenn du folgenden Vergleich machst
Code:
if (((ComparePixel*)newLast) != ((ComparePixel*)prevLast)) {

werden nur die Pointer selbst verglichen. Dabei ist das Ergebnis, dass jeder Screen anders ist, weil beide Pointer auf unterschiedliche Bereiche im Arbeitsspeicher zeigen. Zum Schluß wird immer der volle Screen gesendet, was dazu führt dass du keine Fragmente mehr hast. Das klärt dann auch warum es so ruckelt.

Ich denke mit den verschiedenen Vergleichansätzen haben wir das Problem nur verschlimmbessert. Ich habe mir deine letzte Solution angeschaut und denke, dass der Vergleich viel zu komplex ist, als das er tatsächlich funktionieren kann. Ich bin da selber noch nicht dahinter gestiegen, was da alles gemacht wird, aber mehrere Seiten Code in der Vergleichs-Methode sollten es wirklich nicht sein und das Einzelne dann zu überblicken ist für mich wirklich zu viel des Guten.

Ich hatte mal testweise begonnen die Idee vom Arrayvergleich und den Pixelketten Client / Serverseitig zu implementieren. Anbei findest du die Solution, die ich bisher habe. Es fehlen noch ne ganze Reihe von Dingen, z.B.

Allgemein:
1. Protokoll für die Kommunikation an sich

Serverseitig:
1. Öffnen eines Listeners/Sockets für reinkommende Verbindungen des Clients
2. Versand der Screendimensionen, d.h. Breite und Höhe

Clientseitig:
1. Herstellen der Verbindung zum Server
2. Empfang der Screendimensionen, d.h. Breite und Höhe vom Serverscreen
2. Empfang der Datenpakete und Auswertung zu einer Struktur bestehend aus Index und 256 Pixeldaten
3. Merge der Änderung mit vorhergehenden Screen
4. Darstellung auf einem Control oder dem Screen komplett

Also wie du siehst, ist noch viel offen. Dafür wäre aber schonmal das Ganze mit dem Speicherleck, Screenshot machen, Vergleich und Senden serverseitig als "Proof of Concept" implementiert. Schaus dir einfach mal an. Der Client ist noch garnicht realisiert. Aber wenn ich Zeit habe werde ich da einfach mal wieder etwas weiter machen. Ist halt noch eine große Baustelle.
 

Anhänge

Zuletzt bearbeitet: (Kleine Änderungen an der Solution)
Ich hab mich gestern auch mit der Byte-Array-Methode gespielt und naja, bin aber noch nicht so weit, dass ichs hab testen können.

Für die Verbindung benutze ich übrigens den WCF Dienst. Damit ist das ganze viel einfacher zu realisieren als mit Sockets und Sicherheit bietet es auch (zumindestens laut dem was ich bisher so über WCF gelesen hab :D ) :)

Die Tasten und Maussteuerung hab ich bereits, genauso wie die Verbindung über WCF. Was mir fehlt ist ein zuverlässiger Vergleich der Screenshots ;)
Ergänzung ()

Ich hab gestern übrigens auch mit der anderen Methode bisschen rumprobiert. Die würde sogar funktionieren (glaub ich zumindestens), aber ich bekomm relativ weit am Ende des Bildes dann eine Exception (speicherzugriff) geworfen. Ich vermutte es liegt an dem Stride dass ich da was falsch mache. Mir happerts an dieser Stelle jedoch noch etwas am Verständniss, deswegen hab ich gestern das Problem nicht lösen können.

Ich poste mal den Code und vielleicht kann mir jemand helfen? Danke shcon mal im vorraus.

Code:
int xPixel = _curScreen.Width;
int yPixel = _curScreen.Height;

const int pixel = 32;

int Spalten = xPixel / pixel;
int Zeilen = yPixel / pixel;

int Bpp = Bitmap.GetPixelFormatSize(_curScreen.PixelFormat) / 8;
int pixelcount = _curScreen.Width * _curScreen.Height;
int stride = bmpDataCur.Stride / Bpp;

int sizeOfInt = sizeof(int);

unsafe {
	int* curPtr = (int*)bmpDataCur.Scan0;
	int* prevPtr = (int*)bmpDataPrev.Scan0;

	for (int zeile = 0; zeile < Zeilen; zeile++) {
		for (int spalte = 0; spalte < Spalten; spalte++) {
			curPtr = (int*)bmpDataCur.Scan0;
			prevPtr = (int*)bmpDataPrev.Scan0;

			for (int y = zeile * pixel; y < (zeile * pixel + pixel); y++) {
				for (int x = spalte * pixel; x < (spalte * pixel + pixel); x++) {
					if (curPtr[x * y] != prevPtr[x * y]) {
						byte[] zeileBytes = BitConverter.GetBytes(zeile);
						byte[] spalteBytes = BitConverter.GetBytes(spalte);
						byte[] imgData = new byte[pixel * pixel];
						byte[] result = new byte[sizeOfInt * 2 + imgData.Length];

						for (int i = 0, j = ((zeile * pixel) * (spalte * pixel)); i < imgData.Length; i++, j++) {
							imgData[i] = (byte)curPtr[j];
						}

						Array.Copy(zeileBytes, 0, result, 0, zeileBytes.Length);
						Array.Copy(spalteBytes, 0, result, sizeOfInt, spalteBytes.Length);
						Array.Copy(imgData, 0, result, sizeOfInt * 2, imgData.Length);

						_curScreen.UnlockBits(bmpDataCur);
						_prevScreen.UnlockBits(bmpDataPrev);

						return result;
					}
				}

				curPtr += stride;
				prevPtr += stride;
			}
		}
	}
}
Ergänzung ()

Naja... meien Methode mit dem Array hab ich nun zum laufen gebracht, aber es funktioniert bislang extrems schlecht. Ziemlich große Verzögerung (1-3 Sekunden und mehr) und der Traffic ist sogar größer als bei der Rechteck-Methode. Das Programm verschickt byte-Arrays die sind so groß wie ein ganzer Screenshot obwohl nur vielleicht die Hälfte vom ganzen Screen gesendet wird :-/

EDIT: ok korrigiere mich. Früher war ein ganzer Screenshot gerade mal max. 200 kb groß. Jetzt versendet er nur Teile vom Screen als byte-Array das 500 kb und weit aus mehr groß ist.
 
Zuletzt bearbeitet:
Zur Beschleunigung kannst du ja mal meine bisher immer wieder verwendete Methodik probieren:

Betrachte die 32 Pixel (a 32 Bit) als 16 Long Werte innerhalb einer PixelBlock Struktur (siehe meine letzte Solution da sind es 256 Pixel, einfach das Array auf 16 longs begrenzen und den != Operator so anpassen das auch nur diese 16 Elemente getestet werden). Dann kannst du dir schonmal die Schleife sparen in der du von rechts nach links innerhalb eines Vierecks von 32 Pixel scannst.

Die Erzeugung deiner Datenstruktur sieht mir auch langsam aus, da jedesmal erst Byte Arrays für zeileBytes, dann spalteBytes, dann imgData und zum Schluß result gebildet werden. Das ist ineffizient, besser die 4 Arrays am Anfang vor dem gesamten Scanvorgang bilden und dann die Daten in diese Arrays reinfeuern. Besser wäre noch wenn du mit einem einzigen Array auskommen würdest, das spart dann schonmal die langsame Kopieraktion. Du könntest die ScreenChangeToBytes Methode aus meiner letzten Solution anpassen und das ZielArray als ref an die Methode übergeben. Dann gibts nicht so viele Objekte im Arbeitsspeicher, die der GC wieder bereinigen muss.

Zur Exception: Einerseits wäre die Frage ob bei deiner eingestellten Auflösung die Höhe und Breite jeweils durch 32 ohne Rest teilbar sind. Falls nicht, wäre das die Ursache da am Ende dann nur ein Teil des Blocks gescannt werden kann, während beim Zugriff auf den Rest die Exception gefeuert wird. Das wäre zumindest mein erster Gedanke...

EDIT
Mir fällt noch ein, dass du mit deinem gewählten Lösungsansatz, um das Bild in 32x32 Pixel zu unterteilen, sehr viele Schleifendurchläufe hast. Damit erhöhst du natürlich den Suchaufwand massiv, was wiederum zu der miesen Performance führt. Ein Beispiel: Bei einer 1280x1024 Auflösung wären es dann 40 x 32 Pixelblöcke was dann insgesamt für die Prüfung aller Pixel eines Bildes folgende Rechnung ergibt:

32 (Breite des Pixelblocks) *
32 (Höhe des Pixelblocks) *
40 (Breite des Bildes in Pixelblöcken) *
32 (Höhe des Bildes in Pixelblöcken)
= 1.310.720 Durchläufe

Das ist definitiv zu viel und sorgt für lange Laufzeiten.

Wenn du jetzt eine auf 16 longs angepasste PixelBlock Struktur von mir nimmst, dann würden es nur noch 32 * 32 * 40 = 40.960 Durchläufe werden. Wenn du dann noch die Struktur weiter verfeinerst, kannst du das Ganze auch so laufen lassen, dass tatsächlich nur noch 40 * 32 = 1.280 Durchläufe nötig wären.
 
Zuletzt bearbeitet:
Naja ich glaub ich mache bei dem Stride etwas verkehrt. Meine Auflösung ist ja 1280 x 1024 Pixel (also durch 32 * 32 Pixel teilbar). Dennoch bekomme ich irgendwann (so bei Y 960-980 Pixel und X 1024-1060) ne Exception geworfen. Hmm... frage mich nur was da falsch ist.

PS: Danke für die Tipps zur Verbesserung. Werd das gleich mal ausprobieren.
 
Suche mal nach Pointerarithmetik, scheint mir nen Fehler im Verständnis zu sein
 
Zuletzt bearbeitet:
Zurück
Oben