# Bildqualität verringern



## Mr.Bross (30. Apr 2010)

Hi,

Ich schreibe an einem Programm, das ähnlich wie TeamViewer funktionieren soll(Also Desktop übertragung usw.). Momentan erstelle ich immer Screenshots mit Robot.createScreenCapture(Rectangle) und verschicke diese dann zu dem Server. Jedoch dauert dies sehr lang und braucht pro Bild etwa 4 Sekunden um es su verschicken. 
Gibt es in Java eine Möglichkeit, das Bild zu verkleinern (Also z.B. nur jeden zweiten Pixel verschicken o.ä.), dass es dann schneller wird?? Oder kann man dieses Problem auch anders lösen??


----------



## Marco13 (30. Apr 2010)

Komprimierst du das ganze schon, als PNG zum Beispiel?

Man kann einen ByteArrayOutputStream an ImageIO.write übergeben, und dort dann den byte array mit den PNG-Daten rausholen...


----------



## Mr.Bross (30. Apr 2010)

Was bringt mir das, wenn ich es als PNG abspeichere?? Es wird ja dadurch nicht schneller oder?? Dann müsste ich es abspeichern, laden und verschicken, sind mehr Schritte als vorher. Oder wird die Größe dadurch stark verkleinert und insgesammt ist es dann doch schneller????


----------



## Wildcard (30. Apr 2010)

Netzwerkübertragung ist immer wesentlich teuerer als Speicheroperationen. PNG Bilder sind um viele Faktoren kleiner als unkromprimierte Bitmaps, ein Bildkompressionsverfahren wie eben zB PNG reduziert den Traffic also schon deutlich.
Übrigens, nicht als PNG (physikalisch) abspeichern, sondern nur In-Memory und die komprimierten Bits dann verschicken.


----------



## Mr.Bross (30. Apr 2010)

Tut mir leid, aber kannst du bitte ein Beispiel zeigen, wie du das meinst mit physikalisch abspeichern?? Ich hab davon leider noch nichts gehört.


----------



## ice-breaker (30. Apr 2010)

auch ein PNG wird dich nicht großartig weiterbringen, du müsstest nur den Bereich senden, der sich seit dem letzten Screenshot auch wirklich verändert.
TeamViewer versendet es dann zeilenweise (also jede 4. Zeile, dann jede 3 ...) und erreicht damit dass das Bild scheinbar schneller aufgebaut wird.


----------



## Mr.Bross (30. Apr 2010)

Und wie soll man prüfen was sich verändert hat?? Geht das überhaupt in Java??


----------



## Mr.Bross (1. Mai 2010)

Also ich habe jetzt mal mit getRGB(x,y) jedes einzelne Pixel beider Bilder(das jetztige und das vorherige) ausgelesen und in einem zweidimensionalen int-Array gespeichert. Dann habe ich es durch 


```
if (pixel1[x][y] != pixel2[x][y])
				{
					image2.setRGB(x, y, 0);
				}
```

ausgelesen. Doch nun möchte ich ja nur den Teil verschicken, der sich verändert hat. Wie kann man das jetzt machen? Wenn ich es jetzt so verschicke, habe ich trotzdem noch das ganze Bild, nur dass überall eine "0" steht, wo sich etwas verändert hat (Also Farbwert 0).

PS: Wenns geht auch ein kleines Beispiel dazu schreiben


----------



## Mr.Bross (1. Mai 2010)

Tut mir leid nicht ausgelesen, sondern verglichen. Hier nochmal das ganze:


```
for (int x = 0; x < breite; x++)
		{
			for (int y = 0; y < hoehe; y++)
			{
				pixel1[x][y] = image1.getRGB(x, y);
				pixel2[x][y] = image2.getRGB(x, y);

				if (pixel1[x][y] != pixel2[x][y])
				{
					image2.setRGB(x, y, 0);
				}

			}

		}
```


----------



## Marco13 (1. Mai 2010)

Je nach Bildtyp und Bildgröße kann das getRGB so lange dauern, dass es sich nicht lohnt. Im schlimmsten Fall stellt man ohnehin fest, dass sich ALLES geändert hat, und man auch ALLES übertragen muss.

Man könnte auch sagen, dass die Entscheidung über die Frage, WAS übertragen werden muss, unabhängig von der Frage ist, WIE die Daten dann übertragen werden.

Die Übertragung des Bildes könnte man mit ImageIO (Java Platform SE 6) etwa so machen:

```
// Senden
BufferedImage image = ...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
byte data[] = baos.toByteArray();

verschicke(data, networkOutputStream);

// Empfangen:
BufferedImage image = ImageIO.read(networkInputStream);
```


Die Bestimung der Bereiche, die sich geändert haben (könnte man als "Dirty Rectangle Detection" bezeichnen) kann beliebig kompliziert sein. Ein einfacher Pixelvergleich ist zwar einfach, aber ziemlich langsam. Ob und wie man für sowas Techniken wie KD-Trees oder irgendwas mit Hashing einsetzen kann, muss man sich noch überlegen. Mit ziemlicher Sicherheit wird das aber nur "schnell", wenn man ein bißchen in den Innereien eines BufferedImages rumwühlt (d.h. sicherstellen, dass es vom TYPE_INT_RGB ist, und sich den DataArray als int[] array holt oder so).


----------



## Mr.Bross (1. Mai 2010)

Hi,

ich habe es jetzt einfach mal das ganze int-Array zu verschicken(Da ich nicht wusste wie das andere geht). Als ich es innerhalbt des Routers (2 Rechner unter einem Router) brauchte es pro Bild etwa 300 millisekunden, bis es an kam. 
Jetzt habe ich es jedoch zwar innerhalb eine Routers probiert, aber über die externe IP-Adresse. 
Jetzt gab es schon sofort das erste Problem: Alleine zum Verbinden mit dem Server, hat es 2 Minuten gedauert. Und dann sind die Bilder nicht gleichmäßig angekommen, sondern einmal 20, dann 30 Sekunden nichts, dann 30 usw. . 

Kann mir jemand erklären, warum diese nur so gebündelt ankommen und zwischendrin gar nichts ist?? Ich benutze doch flush() da dürfte dies eigentlich nicht passieren... Und wieso dauert es fast 2 minuten zum verbinden?? Ich habe eine recht schnelle Leitung ( 34000 MB / s)


----------



## Mr.Bross (1. Mai 2010)

Hi,

Ich hab noch ein Problem gefunden  : Wenn ich mit createScreenCapture(rect) mehrer Screenshot mache, sind diese nicht aktuell.
Dieser wird nur alle 2 Sekunden oder so aktualisiert. Kann man diesen vielleicht irgendwie manuell aktualisieren??


----------



## Marco13 (1. Mai 2010)

Klingt insgesamt nach mehreren Baustellen... vielleicht solltest du die Probleme der Reihe nach lösen. Erstmal sicherstellen, dass die Netwerksache funktioniert. Warum dein Router manchmal 2 Minuten braucht, weiß hier niemand, und das hat vermutlich auch nichts mit Java zu tun. Dann das mit den Screenshots. Erklären, wie die bisher gemacht werden, und wie sie weiterverarbeitet werden. Dann überlegen, wie man die Sachen überträgt....


----------



## Mr.Bross (1. Mai 2010)

Wenn es wirklich am Router liegt, kann ich da wohl nichts ändern. Aber wie ich das mit den Screenshots hinkriege, weiß ich nicht. Ich weiß leider nicht wie ich es sonst machen soll.


----------



## Marco13 (2. Mai 2010)

Beschreib' mal wie du die Screenshots im Moment machst. Läuft da irgendein Thread, der dumpf einfach so viele Screenshots macht, wie möglich, und die ins Netzwerk stopft? Oder hast du dir da einen Mechanismus überlegt, der dafür sorgt, dass ein neuer Screenshot erst dann versendet wird, wenn der alte verschickt wurde? 

Ich hatte mal ein ähnliches Programm geschrieben, und da lief es auf sowas raus wie

```
1s. Server wartet auf Bildanfrage
1c. Client sendet Bildanfrage
2c. Client wartet auf Bild
                                   2s. Server empfängt Bildanfrage
                                   3s. Server Schickt Bild raus
                                   4s. Server geht zu 1s
3c. Client empfängt Bild
4c. Client verarbeitet Bild
5c. Client geht zu 1c
```

Um sicherzustellen, dass Bilder nur so schnell rausgeschickt werden, wie sie auch empfangen und verarbeitet werden können. Das ganze auch für mehrere Clients und unterschiedliche Bildtypen ... also in Wirklichkeit etwas komplizierter, als es hier aussieht


----------



## Mr.Bross (2. Mai 2010)

Ich mache meine Screenshots so:


```
while (state == 1)
                 {
						if (x == 20)
						{

							x = 0;

							ooutputs.reset();

							image = null;

							try
							{
								System.gc();
								Thread.sleep(100);
								Thread.yield();

							}
							catch (InterruptedException e)
							{
								e.printStackTrace();
							}

						}

						try
						{
							Thread.sleep(200);
						}
						catch (InterruptedException e)
						{
							e.printStackTrace();
						}

						image = robot.createScreenCapture(rect);

						for (int xx = 0; xx < breite1; xx++)
						{
							for (int yy = 0; yy < hoehe1; yy++)
							{
								pixel[xx][yy] = image.getRGB(xx, yy);

							}

						}

						ooutputs.writeObject(pixel);

						ooutputs.flush();

						Thread.yield();

						x++;

		          }
```

(rect = Rectangle)

Ich habe auch schon probiert, immer einen neuen Robot zu machen, geht leider auch nicht.

Schritte:

1c. Client wartet auf Bildanfrage
                                                        1s. Server sendet Bildanfrage
                                                        2s. Server wartet auf Bilder
2c. Client empfängt Bildanfrage
3c. (Code) :


                 1. Thread schläft 200 millisekunden
                 2. Robot erzeugt Screenshot und speichert ihn im BufferedImage image
                 3. BufferedImage wird in Pixel aufgeteilt und in dem zweidimensionalen int-Array gespeichert.
                 4. ObjectOutputStream verschickt das Bild(Eigentlich das int-Array)


                                                         3s. Server bekommt Bild und zeigt es an  

4c. Code wird so lange ausgeführt (und Server empfängt so lange) , bis der Server schreibt, dass der Client aufhören soll.


----------



## Marco13 (2. Mai 2010)

Ja, was der Code mit dem x==20 und dem System.gc() und so dort soll erschließt sich mir nicht so ganz, aber... dort erkennt man jetzt keine Vorkehrungen, die verhindern, dass z.B. pro Sekunde 5 Bilder gemacht und verschickt werden sollen, obwohl z.B. nur 2 Bilder pro Sekunde verschickt und auf der anderen Seite verarbeitet werden können...


----------



## ice-breaker (2. Mai 2010)

Sollte x=20 sein sind das ja für den Snippet jeweils schon mind. 300ms Wartezeit.
Das bedeutet in eine Sekunde würden 3 solche Wartezeiten reinpassen, wenn wir davon ausgehen, dass das Bilder erzeugen und verschicken für jedes Bild länger als 33msec dauert, dann komme ich ziemlich genau auf 2 Bilder pro Sekunde und per Maximalwert sind überhaupt nur 3 Bilder pro Sekunde möglich.

Du solltest die Sleep-Befehle komplett loswerden, du möchtest eine möglichst Echtzeitübertragung der Bilder und bindest dann Wartezeiten ein? Das passt nicht zusammen.
Die yield-Aufrufe sollten da auch raus.


----------



## Mr.Bross (2. Mai 2010)

Die sleep(), yield() und gc() -Befehle waren eigentlich dazu da, dass die Bilder aktuell sind(Wenn ich mehrere Bilder schneller verschicke (bei langsamer Leitung), dann werden die Bilder immer mehr verzögert) und dass es nicht so auf den Arbeitsspeicher drückt  . Ich habe sie jetzt trotzdem weg gemacht, was leider mein Problem nicht ändert. Die Bilder sind leider nicht aktuell. Es werden etwa 6 Bilder pro Sekunde verschickt (Innerhalb des Routers). Doch wenn ich beim Clienten z.B. ein Programm starte und man es dort schon sieht,  dauert es noch etwa  5 Sekunden, bis man es beim Server sieht. (Obwohl die Bilder fast gleich losgeschickt werden und ankommen)


----------



## Marco13 (2. Mai 2010)

Ich bin kein Netzwerkexperte, aber ... wenn der Client Bilder schneller rausschicken will, als das Netzwerk bwz. der Server verarbeiten kann, dann liegen liegen diese Bilder ja noch irgendwo in einem Puffer. D.h. wenn man dann ein neues, geändertes Bild (mit der gestarteten Applikation) rausschickt, dann KÖNNTE es sein, dass erstmal noch ein paar Sekunden lang "alte" Bilder beim Server ankommen. Aus genau diesem Grund hatte ich damals dieses strenge, "Handshake-artige" Verfahren eingebaut, wo der Empfänger jedes Bild explizit anfordert (und das NUR wenn er das letzte Bild schon fertig verarbeitet hat) und der Sender NUR Bilder rausschickt, wenn vorher eine Anfrage kam.
Etwas ... drastisch formuliert: Wenn in deinem Code kein "import java.util.concurrent.*" steht, ist das (für mich) schon dubios. Aber vielleicht gibt es auch eine gaaanz einfache Alternative, auf die ich wegen mangelnder Netzwerkkenntnisse damals nicht gekommen bin


----------



## Mr.Bross (2. Mai 2010)

Erstmal danke, ich werde mal dein Verfahren ausprobieren, ob das besser klappt. Aber was du mit "import java.util.concurrent.*" meinst, weiß ich nicht. Das seh ich zum ersten mal...


----------



## Marco13 (2. Mai 2010)

Das ist ein package mit einiges Hilfsklassen, u.a. für synchronisation. Das KANN man verwenden, um eben genau das nachzubauen: Ein Thread beim Sender wartet so lange, bis ein "Image Request" gelesen wurde. Dann wird ein Bild rausgeschickt, und wieder in den Wartezustand gegangen. Auf der anderen Seite wurde der Image Request rausgeschickt, und dann auf das Bild gewartet. Sobald das Bild empfangen und verarbeitet ist, wird ein neuer Image Request gesendet.


----------



## Mr.Bross (2. Mai 2010)

Hi,

Ich habe ein Testprogramm gechrieben, das immer eine Anfrage stellt und daraufhin der Screenshot geschickt wird. Doch leider sind die Bilder trotzdem nicht aktuell :-( Das sehe ich schon alleine durch die Ausgabe(Wieveiel Bilder es sind) beim Clienten. Die Ausgabe ist ja auch beim Server auf dem Screenshot zu erkennen, so kann man es vergleichen. 
Die Bilder kommen wieder fast sofort an (vllt 100ms), jedoch bleibt die Ausgabe immer ein paar Sekunden stehen. 
Wenn man beim Clienten auf die Ausgabe schaut, läuft es durchgehend weiter. Doch beim Server (Screenshot) bleibt es immer bei z.B. 20 stehen bis komischerweise output.reset() aufgerufen wird. Dann wird das Bild erst aktualisiert. Habe es getestet, indem ich alle 2 Bilder aktualisiert habe.Es kann aber auch daran liegen, dass es immer 4-5 Sekunden dauert, bis reset() fertig ist.
Wenn ich es jedoch mit "alle 20 mal" reset() mache, ist das Bild, das aktualisiert ist (also das wo anders aussieht), trotzdem nicht aktuell. Es liegt immer zwischen 15 und 20 Bildern zurück. Aber wie bereits gesagt kommen die Bilder fast synchron an.

Hier mal der Quellcode von meinem Testprogramm: (Vielleicht wills ja mal jemand testen, obs bei ihm geht)

Server:


```
package de.kuehne.writeimage;

import java.awt.AWTException;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Server {

	private int x;
	private int y;

	public static void main(String args[]) throws IOException,
			InterruptedException
	{

		new Server().los();

	}

	public void los() throws IOException, InterruptedException
	{

		ServerSocket sock = new ServerSocket(5005);

		Socket socket = sock.accept();

		ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

		PrintWriter writer = new PrintWriter(socket.getOutputStream());

		int width = 1024;
		int height = 768;

		JFrame frame = new JFrame();
		frame.setSize(width, height);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		JPanel panel = new JPanel();

		Graphics g;

		frame.add(panel);

		System.out.println("erstellt");

		BufferedImage image = null;

		try
		{
			image = new Robot().createScreenCapture(new Rectangle(0, 0, width,
					height));
		}
		catch (AWTException e1)
		{
			e1.printStackTrace();
		}

		while (!Thread.interrupted())
		{

			writer.println("-50");
			writer.flush();

			try
			{

				x++;

				int pixel[][] = (int[][]) in.readObject();

				for (int x = 0; x < (width - 1); x++)
				{
					for (int y = 0; y < (height - 1); y++)
					{
						image.setRGB(x, y, pixel[x][y]);

					}

				}

				y++;

				System.out.println("vor zeichnen");

				g = panel.getGraphics();

				g.drawImage(image, 0, 0, null);

				System.out
						.println("empfangen : " + x + "     gezeichnet: " + y);

			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace();
			}

		}

	}
}
```

Client:


```
package de.kuehne.writeimage;

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.ImageIcon;

public class Client {

	public static void main(String args[]) throws AWTException, IOException
	{

		new Client().los();

	}

	public void los() throws AWTException, IOException
	{

		Robot robot = new Robot();

		Socket sock;

		sock = new Socket("localhost", 5000);

		Rectangle rect = new Rectangle();
		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		rect.x = 0;
		rect.y = 0;
		rect.width = dim.width;
		rect.height = dim.height;

		ObjectOutputStream ob = new ObjectOutputStream(sock.getOutputStream());

		BufferedReader reader = new BufferedReader(new InputStreamReader(sock
				.getInputStream()));

		int x = 0;

		while (!Thread.interrupted())
		{

			while (!reader.ready())
			{

			}

			if (x == 20)
			{

				x = 0;

				ob.reset();

			}

			String s = reader.readLine();

			if (s.equals("-50"))
			{

				Image image = robot.createScreenCapture(rect);

				ImageIcon images = new ImageIcon(image);

				ob.writeObject(images);
			}

			x++;

		}

	}

}
```


----------



## Marco13 (3. Mai 2010)

Mit einigen Anpassungen (Port, ImageIcon statt int[][]) gehts. Das Zeichnen sollte eigentlich nur in einer Überschriebenen paintComponent-Methode passieren (nicht getGraphics auf einer Component aufrufen). Inwieweit es dort Probleme geben kann, weil das alles im selben Thread passiert, weiß ich nicht. Jedenfalls könnte ein Grund für die Aussetzer auch sein, dass der Speicher volläuft (schau dir mal die Speicherauslastung an....)


----------



## Mr.Bross (3. Mai 2010)

Tut mir Leid, die Klassen haben leider nicht ganz gestimmt. Ich nehme ein int[][] auch beim Clienten (Ist auf dem anderen Rechner). Und der Port stimmt natürlich auch überein. Probier es mal bitte mit einem int[][]-Array beim Clienten (Und natürlich Server).


----------



## Marco13 (3. Mai 2010)

Hab' gerade nicht so viel Zeit (und bin auch die nächsten Tage nicht so oft hier) aber vielleicht kann man das Problem eingrenzen, indem man es erstmal mit kleineren Bildern (300x300 oder 500x500) testet. Das ganze mal in einem Profiler (VisualVM oder so) laufen lassen könnte auch Rückschlüsse darauf erlauben, wo er denn genau hängt. Das mit dem x==20 hat sich mir aber immernoch nicht erschlossen...?


----------



## Mr.Bross (3. Mai 2010)

Durch x == 20 (genau ooutputs.reset() ) lösche ich den gespeicherten Inhalt des ObjectOutputStreams. Wenn ich das nicht mache, kommt nach einiger Zeit OutOfMemorieException. Und ich werde es mal mit kleineren Bilder prbieren


----------



## Marco13 (3. Mai 2010)

Wenn es einen OutOfMemoryError gibt, ist da mit ziemlicher Sicherheit noch irgendwo ein Fehler... Falls es nicht sooo sehr eilt: Ab nächsten Montag könnt' ich nochmal genauer schauen... Ggf. den Thread dann nochmal uppen oder mit dem aktuellen Stand neu aufmachen, falls bis dahin niemand anderes was cleveres dazu sagen kann....


----------



## Mr.Bross (4. Mai 2010)

Ok ich werds auf jedenfall mal weiterhin probieren


----------



## Mr.Bross (7. Mai 2010)

Hi,

Ich habe mal einen Test gemacht, indem ich beim Clienten und beim Server jedes einzelne Bild abgespeichert habe. Jetzt weiß ich zumindest was das Problem ist:

Der Client macht alle Bilder richtig, doch beim verschicken stimmt etwas nicht. Das Bild bleibt beim Server immer wie das erste (egal wie es sich beim Clienten verändert), bis beim Clienten ObjectOutputStream.reset() aufgerufen wird. Dann stimmt es wieder perfekt überein. (Also dieses eine Bild) Die nächsten sind dann wieder alle wie das, was nach dem reset() verschickt wurde. Erst wenn dann wieder reset() aufgerufen wird, stimmt es wieder usw.

Jetzt habe ich das Problem gefunden, jetzt fehlt nur noch die Lösung  Nach jedem mal reset() aufrufen kann man fast vergessen, da es immer etwa 4-5 sekunden braucht, bis es das gemacht hat. Dann ist es ja keine richtige Bild-zu-Bild - Übertragung, sondern einfach nur Screenshots...


----------



## Marco13 (9. Mai 2010)

Auf dem OutputStream mal .flush() zu machen wäre nicht verkehrt... werd's bei Gelegenheit nochmal testen...


----------



## Mr.Bross (9. Mai 2010)

Das mache ich nach jedem mal und die Bilder kommen ja auch an (zähle es mit einem int).


----------



## Marco13 (9. Mai 2010)

Also, ich habe jetzt nochmal das, was du zuletzt gepostet hattests, korrigiert und mit 300x300-Bildern über localhost getestet - und das funktioniert bei mir erstmal :bahnhof: Keine Verzögerungen, keine falschen Bilder...


```
import java.awt.AWTException;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class RobotServer {

    private int x;
    private int y;

    public static void main(String args[]) throws IOException,
            InterruptedException
    {

        new RobotServer().los();

    }

    public void los() throws IOException, InterruptedException
    {

        ServerSocket sock = new ServerSocket(5005);

        Socket socket = sock.accept();

        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

        PrintWriter writer = new PrintWriter(socket.getOutputStream());

        int width = 300;
        int height = 300;

        JFrame frame = new JFrame();
        frame.setSize(width, height);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();

        Graphics g;

        frame.add(panel);

        System.out.println("erstellt");

        BufferedImage image = null;

        try
        {
            image = new Robot().createScreenCapture(new Rectangle(0, 0, width,
                    height));
        }
        catch (AWTException e1)
        {
            e1.printStackTrace();
        }

        while (!Thread.interrupted())
        {

            writer.println("-50");
            writer.flush();

            try
            {

                x++;

                int pixel[][] = (int[][]) in.readObject();

                for (int x = 0; x < (width - 1); x++)
                {
                    for (int y = 0; y < (height - 1); y++)
                    {
                        image.setRGB(x, y, pixel[x][y]);

                    }

                }

                y++;

                System.out.println("vor zeichnen");

                g = panel.getGraphics();

                g.drawImage(image, 0, 0, null);

                System.out
                        .println("empfangen : " + x + "     gezeichnet: " + y);

            }
            catch (ClassNotFoundException e)
            {
                e.printStackTrace();
            }

        }

    }
}
```


```
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.ImageIcon;

public class RobotClient {

    public static void main(String args[]) throws AWTException, IOException
    {

        new RobotClient().los();

    }

    public void los() throws AWTException, IOException
    {

        Robot robot = new Robot();

        Socket sock;

        sock = new Socket("localhost", 5005);

        Rectangle rect = new Rectangle();
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        rect.x = 0;
        rect.y = 0;
        rect.width = 300; //dim.width;
        rect.height = 300; //dim.height;

        ObjectOutputStream ob = new ObjectOutputStream(sock.getOutputStream());

        BufferedReader reader = new BufferedReader(new InputStreamReader(sock
                .getInputStream()));

        int x = 0;

        int width = 300;
        int height = 300;

        while (!Thread.interrupted())
        {

            while (!reader.ready())
            {

            }

            if (x == 20)
            {

                x = 0;

                ob.reset();

            }

            String s = reader.readLine();

            if (s.equals("-50"))
            {

                Image image = robot.createScreenCapture(rect);
                BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                bi.getGraphics().drawImage(image,0,0,null);

                int pixel[][] = new int[width][height];

                for (int xx = 0; xx < (width - 1); xx++)
                {
                    for (int y = 0; y < (height - 1); y++)
                    {
                        pixel[xx][y] = bi.getRGB(xx, y);

                    }

                }


                //ImageIcon images = new ImageIcon(image);
                //ob.writeObject(images);
                ob.writeObject(pixel);
                ob.flush();
            }

            x++;

        }

    }

}
```


----------



## Mr.Bross (9. Mai 2010)

Ja das geht auch bei mir  Was hat es mit diesem Abschnitt zu tun??


```
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                bi.getGraphics().drawImage(image,0,0,null);
```

Liegt es nur an dem?? Was macht der anders :/


----------



## Marco13 (9. Mai 2010)

Wie hast du denn den "pixel[][]"-Array aus dem "Image" geholt? ???:L


----------



## Mr.Bross (10. Mai 2010)

Ich habe einfach immer getRGB an dem BufferedImage angewandt... 


```
BufferedImage image =new Robot().createScreenCapture(new Rectangle(0, 0, width,
                    height));
```

Und dann die for-Schleife. War/ist das falsch??


----------



## Marco13 (10. Mai 2010)

Ach, sorry, ich dachte der Robot liefert nur ein Image zurück. Aber eigentlich sollte das mit jeder Art von BufferedImage funktionieren. Woran es genau lag, dass es nicht funktioniert hat, wenn man direkt das BI vom Robot nimmt, weiß ich spontan leider auch nicht ???:L


----------



## Mr.Bross (10. Mai 2010)

Wie bekommt es Teamviewer hin, die Bilder so schnell zu übertragen?? Ich habe es mal mit einem Kumpel getestet und es hat für ein Bild über 10min gedauert... Die Übertragung mit Teamviewer klappt jedoch recht gut...


----------



## Marco13 (10. Mai 2010)

Da könnte man ein ganzes Buch drüber schreiben  Ein paar der Punkte wurden witer oben ja schon genannt:
- Nur übertragen, was sich auch geändert hat - dafür gibt's etliche potentiell geeignete Datenstrukturen (hashing, KD-Trees, usw)
- Die Bilddaten komprimieren - die meisten Flächen auf einem "normalen" Desktop sind einfarbig
- Etwas schnelleres als den Robot verwende (direktes auslesen des Grafikkartenspeichers per JNI oder so)
- Keine Konvertierung von Image in int[][] und zurück
- Etwas schnelleres als einen ObjectOutputStream verwenden (d.h. einen einfachen OutputStream, mit einem eigenen Protokoll)
- ...
- ...
- ...


----------



## Mr.Bross (10. Mai 2010)

Ist ein komprimiertes Bild kleiner, als das int-array?? 
Und mit dem nur übertragen was sich geändert hat. Wie soll man das machen?? Ich könnte ein int-array nehmen und alles was gleich ist durch eine -1 kennzeichnen, da wird es aber nicht viel kleiner... 
Und wie schreibt man denn ein eigenes Protokoll über einen OutputStream?? Der muss ja dann auch Objekte nehmen... Und wie soll das dadurch schneller werden?? Es bleibt ja das gleiche Objekt über den gleichen OutputStream...
Und mit der schnelligkeit am Clienten (erstellen usw.) bin ich eigentlich zufrieden, braucht nur etwa 5 millisekunden.


----------



## Marco13 (10. Mai 2010)

_Ist ein komprimiertes Bild kleiner, als das int-array??_

Im allgemeinen: Ja, und zwar deutlich. Im Moment werden ja alle Pixel verschickt. Bei 1000x1000 Pixeln mit RGBA sind das pro Bild ca. 4MB. Wenn man das ganze stattdessen komprimiert, z.B. als PNG, und dann nur "die PNG-Datei" verschickt, sind es - je nachdem ... vielleicht nur 500kb oder noch weniger.

_Und mit dem nur übertragen was sich geändert hat. Wie soll man das machen??_

Da gäbe es beliebig ausgefeilte Möglichkeiten. Man könnte (vereinfacht gesagt) den aktuellen und den vorheigen Screenshot vergleichen, und dann feststellen, dass sich von dem 1000x1000-Bild nur ein Bereich der größe 100x100 verändert hat. NUR diesen Bereich könnte man dann als PNG übertragen (mit der Zusatzinformation, an welcher Stelle auf dem Bildschirm der Client dieses "Tile" (Kachel) dann malen soll). So ein "Tiling" könnte (!) ohnehin die "gefühlte Geschwindigkeit" verbessern: Wenn man immer das komplette Bild überträgt, braucht das jedesmal sehr lange. Wenn man stattdessen den Bildschirm z.B. in 10x10 Kacheln aufteilt, und die einzeln überträgt, kommt beim Client zumindest schonmal was an - noch nicht das ganze Bild, aber man sieht immerhin schon mal was...


_Und wie schreibt man denn ein eigenes Protokoll über einen OutputStream?? Der muss ja dann auch Objekte nehmen... Und wie soll das dadurch schneller werden?? Es bleibt ja das gleiche Objekt über den gleichen OutputStream..._

Das bezog sich auf die obigen Punkte: Man weiß nicht genau, was ein ObjectOutputStream genau verschickt, wenn man ihm einen int[][] array gibt. Es wird sicher sehr kompakt und effizient sein, aber stattdessen ein PNG (als Rohdaten, also einfach als ein byte[] array) wird vermutlich schneller sein. Falls man Tiling verwendet, muss man aber zusätzlich noch die Positionen und Größen der gesendeten Tiles mitschicken, damit der Client sie richtig anzeigen kann. Das würde man dann ggf. mit einem eigenen Protokoll machen. Alternativ dazu mit einer Klasse, die einfach "Serializable" ist, und DIE schickt man dann über einen ObjectOutputStream

```
class Tile implements Serializable
{
    private int x,y,width,height;
    private byte[] pngData;
    ...
}
```

Alles nur GANZ grobe Ideen. Da kann man eine Menge Arbeit reinstecken, wenn man es "vernünftig" machen will...


----------



## ice-breaker (13. Mai 2010)

ice-breaker hat gesagt.:


> auch ein PNG wird dich nicht großartig weiterbringen, du müsstest nur den Bereich senden, der sich seit dem letzten Screenshot auch wirklich verändert.
> *TeamViewer* versendet es dann zeilenweise (also jede 4. Zeile, dann jede 3 ...) und erreicht damit dass das Bild scheinbar schneller aufgebaut wird.





Mr.Bross hat gesagt.:


> Wie bekommt es *Teamviewer* hin, die Bilder so schnell zu übertragen?? Ich habe es mal mit einem Kumpel getestet und es hat für ein Bild über 10min gedauert... Die Übertragung mit *Teamviewer* klappt jedoch recht gut...


----------



## Mr.Bross (16. Mai 2010)

Danke für die Antworten. Tut mir leid dass ich es erst jetzt lese, aber ich habe die Seite immer aktualisiert und erst jetzt mitbekommen dass eine neue Seite angefangen wurde >.< . Ich werde mal ausprobieren was du geschrieben hast.


----------



## Mr.Bross (16. Mai 2010)

Also wenn ich es richtig verstanden habe, soll ich die Bilder (durchgehend?) vergleichen(mit den RGB-Werten?). Und wenn sich etwas verändert hat, verschicke ich die Daten des Ortes, der sich verändert hat(genaue Position und Länge/Breite) und dann das PNG(nur der Teil vom Screenshot). Und der Server zeichnet dann nur diese Fläche an den richtigen Fleck .

Wie kann ich jetzt ein PNG-Bild virtuell speichern?? Also ich kann es auf der Festplatte speichern klar, aber virtuell??(wenn man das so nennen kann)
Und verstehe ich das richtig mit dem RGB-vergleich?? Oder wäre es anders besser...


----------



## Marco13 (16. Mai 2010)

Das mit dem RGB-Vergleich wäre eine Möglichkeit. Erstmal wäre das evtl. noch etwas langsam. Wenn man genauer weiß, von welchem Typ das Bild ist, kann man sich die Pixeldaten evtl. direkt als int[] array abholen, womit der Vergleich u.U. schneller werden könnte, aber das ist nur eine Optimierung, die man nachträglich machen kann (sofern man den Vergleich schön in eine eigene Methode gepackt hat).

Um ein Bild "virtuell" zu speichern, kann man einen ByteArrayOutputStream verwenden, grob sowas wie

```
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
byte pngData[] = baos.toByteArray();
```


----------



## Mr.Bross (16. Mai 2010)

Danke, aber wie kann ich das Bild dann wieder aus dem byte-Array auslesen??


----------



## Marco13 (16. Mai 2010)

Indem man auf der anderen Seite genau das umgekehrte mit ImageIO.read und einen ByteArray_Input_Stream macht


----------



## Mr.Bross (16. Mai 2010)

Achso hätte ich auch selbst draufkommen können :-D


----------



## Mr.Bross (17. Mai 2010)

Hätte da noch eine Frage zum vergleichen. Ich speichere mom jeden momentanen Screenshot in pixel[][] und das Bild zuvor in pixel2[][]. Dann vergleiche ich die beiden. Aber wenn ein Unterschied festgestellt wird, was muss ich dann machen?? Wenn ich dann ein pixel3[][] nehme und dort die Werte drin speichere ist schonmal das erste Problem, dass ich die Größe vorher nicht weiß...


----------



## Marco13 (17. Mai 2010)

Wie schon mehrfach gesagt: Das kann man beliebig ausgefeilt machen. Im einfachsten Fall speichert man sich für seine beiden Arrays die kleinste und größte Position, an der ein Unterschied gefunden wurde

```
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = -1;
int maxY = -1;
for (x...)
{
    for (y...)
    {
        if (differentAt(x,y))
        {
            minX = Math.min(minX, x);
            ...usw... 
        }
    }
}
```
Dann hat man am ende Das Rechteck (minX,minY) - (maxX, maxY) in dem "irgendwas" unterschiedlich ist. Das kann man dann übertragen. Natürlich ist das "schlecht", wenn sich zufällig genau der obere Linke und der Untere Rechte Pixel geändert haben: Man überträgt ALLES, obwohl nur 2 Pixel unterschiedlich sind ... da kann man sich dann was überlegen, wie man z.B. rausfinden kann, wie man die Bereiche, die aufgrund einer Änderung übertragen werden, sinnvoll wählt. Z.B. könnte man das Bild in 10x10 Kacheln einteilen, und jede Kachel übertragen, in der sich was geändert hat...


----------



## Mr.Bross (17. Mai 2010)

Ich habe den screen jetzt mal als test in 10 Scheiben geteilt. Wenn sich bei einem etwas ändert, wird eine Scheibe verschickt. Das werde ich natürlich noch verbessern, indem ich kleinere Kacheln benutze (vielleicht wirklich 10x10). 
Doch mit dieser Geschwindigkeit kann man es vergessen, da es schon alleine zum berechnen etwa 7 Sekunden benötigt. Hier ist mal der Client, bei dem ja alles aufgeteilt wird usw.


```
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.imageio.ImageIO;

public class Client {

	private BufferedImage imagebefore;
	private boolean who[];

	public static void main(String args[]) throws AWTException, IOException
	{

		new Client().los();

	}

	public void los() throws AWTException, IOException
	{

		Robot robot = new Robot();

		Socket sock;

		sock = new Socket("192.168.0.5", 5005);

		Rectangle rect = new Rectangle();
		rect.x = 0;
		rect.y = 0;
		rect.width = Toolkit.getDefaultToolkit().getScreenSize().width;
		rect.height = Toolkit.getDefaultToolkit().getScreenSize().height;

		ObjectOutputStream ob = new ObjectOutputStream(sock.getOutputStream());

		BufferedReader reader = new BufferedReader(new InputStreamReader(sock
				.getInputStream()));

		int x = 0;

		int y = 0;

		imagebefore = robot.createScreenCapture(new Rectangle(rect));

		int width = Toolkit.getDefaultToolkit().getScreenSize().width;
		int height = Toolkit.getDefaultToolkit().getScreenSize().height;

		who = new boolean[10];

		while (!Thread.interrupted())
		{

			while (!reader.ready())
			{

			}

			if (x == 20)
			{

				x = 0;

				ob.reset();

			}

			String s = reader.readLine();

			if (s.equals("-50"))
			{

				int before = (int) System.currentTimeMillis();

				int pixel2[][] = new int[width][height];

				for (int i = 0; i < 10; i++)
				{

					who[i] = false;

				}

				if (y > 0)
				{

					for (int xx = 0; xx < (width - 1); xx++)
					{
						for (int yy = 0; yy < (height - 1); yy++)
						{
							pixel2[xx][yy] = imagebefore.getRGB(xx, yy);

						}

					}

				}

				BufferedImage bi = robot.createScreenCapture(rect);

				int pixel[][] = new int[width][height];

				for (int xx = 0; xx < (width - 1); xx++)
				{
					for (int yy = 0; yy < (height - 1); yy++)
					{
						pixel[xx][yy] = bi.getRGB(xx, yy);

					}

				}

				for (int xx = 0; xx < (width - 1); xx++)
				{
					for (int yy = 0; yy < (height - 1); yy++)
					{

						pixel[xx][yy] = bi.getRGB(xx, yy);

						if (pixel[xx][yy] != pixel2[xx][yy])
						{

							if (xx < Toolkit.getDefaultToolkit()
									.getScreenSize().height / 10)
							{

								who[0] = true;

							}
							else
								if (xx < Toolkit.getDefaultToolkit()
										.getScreenSize().height / 9)
								{

									who[1] = true;

								}
								else
									if (xx < Toolkit.getDefaultToolkit()
											.getScreenSize().height / 8)
									{

										who[2] = true;

									}
									else
										if (xx < Toolkit.getDefaultToolkit()
												.getScreenSize().height / 7)
										{

											who[3] = true;

										}
										else
											if (xx < Toolkit
													.getDefaultToolkit()
													.getScreenSize().height / 6)
											{

												who[4] = true;

											}
											else
												if (xx < Toolkit
														.getDefaultToolkit()
														.getScreenSize().height / 5)
												{

													who[5] = true;

												}
												else
													if (xx < Toolkit
															.getDefaultToolkit()
															.getScreenSize().height / 4)
													{

														who[6] = true;

													}
													else
														if (xx < Toolkit
																.getDefaultToolkit()
																.getScreenSize().height / 3)
														{

															who[7] = true;

														}
														else
															if (xx < Toolkit
																	.getDefaultToolkit()
																	.getScreenSize().height / 2)
															{

																who[8] = true;

															}
															else
																if (xx < Toolkit
																		.getDefaultToolkit()
																		.getScreenSize().height)
																{

																	who[9] = true;

																}
																else
																{

																}

						}

					}

				}

				System.out.println("after");

				int is = 0;

				for (int i = 0; i < 10; i++)
				{

					if (who[i] == true)
					{

						if (i == 0)
						{
							writer.println("0");
							writer.flush();

							bi = robot.createScreenCapture(new Rectangle(0, 0,
									width / 10, height));

							is = 1;

						}
						else
							if (i == 1)
							{
								writer.println("1");
								writer.flush();

								bi = robot.createScreenCapture(new Rectangle(0,
										0, width / 9, height));

								is = 1;
							}
							else
								if (i == 2)
								{

									writer.println("2");
									writer.flush();

									bi = robot
											.createScreenCapture(new Rectangle(
													0, 0, width / 8, height));

									is = 1;

								}
								else
									if (i == 3)
									{

										writer.println("3");
										writer.flush();

										bi = robot
												.createScreenCapture(new Rectangle(
														0, 0, width / 7, height));

										is = 1;

									}
									else
										if (i == 4)
										{

											writer.println("4");
											writer.flush();

											bi = robot
													.createScreenCapture(new Rectangle(
															0, 0, width / 6,
															height));

											is = 1;

										}
										else
											if (i == 5)
											{
												writer.println("5");
												writer.flush();

												bi = robot
														.createScreenCapture(new Rectangle(
																0, 0,
																width / 5,
																height));

												is = 1;

											}
											else
												if (i == 6)
												{

													writer.println("6");
													writer.flush();

													bi = robot
															.createScreenCapture(new Rectangle(
																	0, 0,
																	width / 4,
																	height));

													is = 1;

												}
												else
													if (i == 7)
													{

														writer.println("7");
														writer.flush();

														bi = robot
																.createScreenCapture(new Rectangle(
																		0,
																		0,
																		width / 3,
																		height));

														is = 1;

													}
													else
														if (i == 8)
														{

															writer.println("8");
															writer.flush();

															bi = robot
																	.createScreenCapture(new Rectangle(
																			0,
																			0,
																			width / 2,
																			height));

															is = 1;

														}
														else
															if (i == 9)
															{

																writer
																		.println("9");
																writer.flush();

																bi = robot
																		.createScreenCapture(new Rectangle(
																				0,
																				0,
																				width,
																				height));

																is = 1;

															}
															else
															{

																System.out
																		.println("nothing (2)");

															}

					}

				}

				if (is == 0)
				{

					writer.println("10");
					writer.flush();

				}

				System.out.println("Time: "
						+ ((int) System.currentTimeMillis() - before));

				ByteArrayOutputStream baos = new ByteArrayOutputStream();

				ImageIO.write(bi, "png", baos);

				byte b[] = baos.toByteArray();

				ob.writeObject(b);
				ob.flush();

				y++;
			}

			x++;

		}

	}
}
```


----------



## Marco13 (17. Mai 2010)

Hm ... was soll das denn jetzt sein :autsch: ?

Man kann da beliebig viel Zeit reinstecken.


```
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;

import javax.imageio.ImageIO;
import javax.swing.*;
 

public class RobotServer implements Runnable
{
    public static void main(String args[])
    {
        new Thread(new RobotServer()).start();
    }
    
    private static class ImagePanel extends JPanel
    {
        private BufferedImage image;
        
        public ImagePanel(int imageSizeX, int imageSizeY)
        {
            setPreferredSize(new Dimension(imageSizeX, imageSizeY));
            image = new BufferedImage(imageSizeX, imageSizeY, BufferedImage.TYPE_INT_RGB);
        }
        
        public void addTile(Rectangle rectangle, BufferedImage tile)
        {
            Graphics g = image.createGraphics();
            g.drawImage(tile, rectangle.x, rectangle.y, this);
            g.dispose();
            
        }
        
        public void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            g.drawImage(image, 0, 0, this);
        }
    }
    

    private ImagePanel imagePanel;
    private ServerSocket serverSocket;
    private Socket socket;
    private ObjectInputStream objectInputStream;;
    private PrintWriter writer;
    
    private int width = 300;
    private int height = 300;
    
    public RobotServer()
    {
        try
        {
            SwingUtilities.invokeAndWait(new Runnable()
            {
                public void run()
                {
                    JFrame frame = new JFrame();
                    frame.setVisible(true);
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             
                    imagePanel = new ImagePanel(width, height);
                    frame.getContentPane().add(imagePanel);
                    frame.pack();
                    frame.setVisible(true);
                }
            });
        }
        catch (InterruptedException e1)
        {
            e1.printStackTrace();
        }
        catch (InvocationTargetException e1)
        {
            e1.printStackTrace();
        }
        
        
        try
        {
            serverSocket = new ServerSocket(5005);
            socket = serverSocket.accept();
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            writer = new PrintWriter(socket.getOutputStream());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    
    public void run()
    {
        try
        {
            while (true)
            {
                writer.println(RobotClient.IMAGE_REQUEST);
                writer.flush();

                while (true)
                {
                    Rectangle region = (Rectangle)objectInputStream.readObject();
                    byte pngData[] = (byte[])objectInputStream.readObject();
                    if (region.width == -1)
                    {
                        break;
                    }
                    BufferedImage image = ImageIO.read(new ByteArrayInputStream(pngData));
                    imagePanel.addTile(region, image);
                }
                imagePanel.repaint();
            }
            
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
 
    }
}
```



```
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
 
public class RobotClient implements Runnable
{
    public static final String IMAGE_REQUEST = "-50";
    
    public static void main(String args[]) throws AWTException, IOException
    {
        new Thread(new RobotClient()).start();
    }

    private Robot robot;
    private Rectangle rectangle;
    private ObjectOutputStream objectOutputStream;
    private BufferedReader reader; 
    private BufferedImage previousImage = null;
    private BufferedImage currentImage = null;
    
    public RobotClient()
    {
        try
        {
            robot = new Robot();
            Socket socket = new Socket("127.0.0.1", 5005);
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        }
        catch (AWTException e)
        {
            e.printStackTrace();
        }
        catch (UnknownHostException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        
        rectangle = new Rectangle();
        rectangle.x = 0;
        rectangle.y = 0;
        rectangle.width = 300; //Toolkit.getDefaultToolkit().getScreenSize().width;
        rectangle.height = 300; //Toolkit.getDefaultToolkit().getScreenSize().height;
        
        previousImage = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_RGB);
    }
 
    
    private List<Rectangle> computeTiles(int numTilesX, int numTilesY) 
    {
        DataBuffer currentDataBuffer = currentImage.getRaster().getDataBuffer();
        DataBufferInt currentDataBufferInt = (DataBufferInt)currentDataBuffer;
        int currentData[] = currentDataBufferInt.getData();

        DataBuffer previousDataBuffer = previousImage.getRaster().getDataBuffer();
        DataBufferInt previousDataBufferInt = (DataBufferInt)previousDataBuffer;
        int previousData[] = previousDataBufferInt.getData();

        int tileSizeX = currentImage.getWidth() / numTilesX;
        int tileSizeY = currentImage.getHeight() / numTilesY;
        
        List<Rectangle> result = new ArrayList<Rectangle>();
        for (int tx=0; tx<numTilesX; tx++)
        {
            for (int ty=0; ty<numTilesY; ty++)
            {
                if (regionsDiffer(previousData, previousImage.getWidth(), 
                    currentData, tx*tileSizeX, ty*tileSizeY, tileSizeX, tileSizeY))
                {
                    Rectangle region = new Rectangle();
                    region.x = tx * tileSizeX;
                    region.y = ty * tileSizeX;
                    region.width = tileSizeX;
                    region.height = tileSizeY;
                    result.add(region);
                }
            }
        }
        return result;
    }
    
    private boolean regionsDiffer(int a[], int stride, int b[], int x0, int y0, int w, int h)
    {
        for (int y=y0; y<y0+h; y++)
        {
            for (int x=x0; x<x0+w; x++)
            {
                int index = y*stride + x;
                if (a[index] != b[index])
                {
                    return true;
                }
            }
        }
        return false;
    }
    
    public void run()
    {
        try
        {
            while (true)
            {
                currentImage = robot.createScreenCapture(rectangle);
                String s = reader.readLine();
                if (s.equals(IMAGE_REQUEST))
                {
                    currentImage = robot.createScreenCapture(rectangle);
                    
                    List<Rectangle> regions = computeTiles(5, 5);
                    System.out.println("Client sending "+regions.size()+" regions");
                    for (int i=0; i<regions.size(); i++)
                    {
                        Rectangle r = regions.get(i);
                        BufferedImage tile = currentImage.getSubimage(r.x, r.y, r.width, r.height);
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        ImageIO.write(tile, "png", baos);
                        byte pngData[] = baos.toByteArray();
     
                        objectOutputStream.writeObject(r);
                        objectOutputStream.writeObject(pngData);
                    }
                    objectOutputStream.writeObject(new Rectangle(-1,-1,-1,-1));
                    objectOutputStream.writeObject(new byte[0]);
                    objectOutputStream.flush();
                }
                previousImage = currentImage;
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
 
    }
}
```


----------



## Mr.Bross (31. Mai 2010)

Hi,

dank dir bin ich beim letzten Schritt und es geht auch so weit. Nur leider verstehe ich deinen Quellcode nicht so ganz... Kannst du bitte ein paar Komentare dazugeben?? Wäre echt super!!!

MFG


----------



## Marco13 (31. Mai 2010)

Auch in Kommentare kann man beliebig viel Zeit reinstecken... Ganz kurz, ansonsten präziser fragen:

```
// Der Screenshot wird in numTilesX * numTilesY Tiles zerlegt,
    // diese Methode gibt eine Liste von Rectangles zurück, die
    // die Bereiche beschreiben, die sich seit dem letzten Bild 
    // verändert haben
    private List<Rectangle> computeTiles(int numTilesX, int numTilesY) 
    {
        // Magisch auf die Bilddaten zugreifen: Die Pixel werden damit
        // vom currentImage bzw. previousImage jeweils als int[]-Array abgeholt

        DataBuffer currentDataBuffer = currentImage.getRaster().getDataBuffer();
        DataBufferInt currentDataBufferInt = (DataBufferInt)currentDataBuffer;
        int currentData[] = currentDataBufferInt.getData();
 
        DataBuffer previousDataBuffer = previousImage.getRaster().getDataBuffer();
        DataBufferInt previousDataBufferInt = (DataBufferInt)previousDataBuffer;
        int previousData[] = previousDataBufferInt.getData();
 
        int tileSizeX = currentImage.getWidth() / numTilesX;
        int tileSizeY = currentImage.getHeight() / numTilesY;
        
        List<Rectangle> result = new ArrayList<Rectangle>();

        // Alle Tiles vergleichen
        for (int tx=0; tx<numTilesX; tx++)
        {
            for (int ty=0; ty<numTilesY; ty++)
            {
                // Falls die Tiles sich unterscheiden...
                if (regionsDiffer(previousData, previousImage.getWidth(), 
                    currentData, tx*tileSizeX, ty*tileSizeY, tileSizeX, tileSizeY))
                {
                    // ... ein Rectangle speichern, das den Bereich 
                    // beschreibt in dem sich etwas geändert hat
                    Rectangle region = new Rectangle();
                    region.x = tx * tileSizeX;
                    region.y = ty * tileSizeX;
                    region.width = tileSizeX;
                    region.height = tileSizeY;
                    result.add(region);
                }
            }
        }
        return result;
    }
    
    // Vergleicht zwei rechteckige Bereiche (obere Linke Ecke bei (x0,y0), größe w*h)
    // in Bildern der Breite 'stride', die als int[]-Arrays gegeben sind
    private boolean regionsDiffer(int a[], int stride, int b[], int x0, int y0, int w, int h)
    {
        for (int y=y0; y<y0+h; y++)
        {
            for (int x=x0; x<x0+w; x++)
            {
                int index = y*stride + x;
                if (a[index] != b[index])
                {
                    return true;
                }
            }
        }
        return false;
    }
    
    public void run()
    {
        try
        {
            while (true)
            {
                currentImage = robot.createScreenCapture(rectangle);
                String s = reader.readLine();

                // Warte auf IMAGE_REQUEST
                if (s.equals(IMAGE_REQUEST))
                {
                    currentImage = robot.createScreenCapture(rectangle);
                    
                    // Berechne alle Bereiche, die sich geändert haben
                    List<Rectangle> regions = computeTiles(5, 5);
                    System.out.println("Client sending "+regions.size()+" regions");
                    for (int i=0; i<regions.size(); i++)
                    {
                        Rectangle r = regions.get(i);
                        BufferedImage tile = currentImage.getSubimage(r.x, r.y, r.width, r.height);
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        ImageIO.write(tile, "png", baos);
                        byte pngData[] = baos.toByteArray();
     
                        // Schreibe das Rectangle, das den geänderten Bereich 
                        // beschreibt, und den Inhalt des Bereiches als PNG 
                        objectOutputStream.writeObject(r);
                        objectOutputStream.writeObject(pngData);
                    }

                    // Objekte, die andeuten, dass die Übertragung zuende ist...
                    objectOutputStream.writeObject(new Rectangle(-1,-1,-1,-1));
                    objectOutputStream.writeObject(new byte[0]);
                    objectOutputStream.flush();
                }
                previousImage = currentImage;
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
 
    }
}

 
 
public class RobotServer implements Runnable
{

    // Speichert ein Bild, das gemalt wird
    private static class ImagePanel extends JPanel
    {
        private BufferedImage image;
        
        public ImagePanel(int imageSizeX, int imageSizeY)
        {
            setPreferredSize(new Dimension(imageSizeX, imageSizeY));
            image = new BufferedImage(imageSizeX, imageSizeY, BufferedImage.TYPE_INT_RGB);
        }
        
        // Malt das gegebene Bild an die durch das Rectangle definierte
        // Stelle ins Haupt-Bild
        public void addTile(Rectangle rectangle, BufferedImage tile)
        {
            Graphics g = image.createGraphics();
            g.drawImage(tile, rectangle.x, rectangle.y, this);
            g.dispose();
            
        }
        
        public void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            g.drawImage(image, 0, 0, this);
        }
    }
    
 


    public void run()
    {
        try
        {
            while (true)
            {
                writer.println(RobotClient.IMAGE_REQUEST);
                writer.flush();
 
                while (true)
                {
                    // Lies das Rechteck und das PNG, das der Client geschickt hat
                    Rectangle region = (Rectangle)objectInputStream.readObject();
                    byte pngData[] = (byte[])objectInputStream.readObject();
                    if (region.width == -1)
                    {
                        break;
                    }
                    BufferedImage image = ImageIO.read(new ByteArrayInputStream(pngData));

                    // Male den Bereich ins Hauptbild
                    imagePanel.addTile(region, image);
                }
                imagePanel.repaint();
            }
```


----------



## Mr.Bross (1. Jun 2010)

Hi danke. Könntest du bitte DataBuffer genauer erklären?? (+ Methoden) Ich habe mal bei google gesucht und gefunden, dass es irgendwie einzeln die Pixel in ein int-Array speichert (glaube ich zumindest mal =) ). Wenn es so ist, macht es ja eigentlich das gleiche, wie das einzelne Auslesen eines BufferedImages mit getRGB oder??


----------



## Marco13 (1. Jun 2010)

Ja, das ist ein bißchen getrickst. Ein BufferedImage speichert je nach Typ (TYPE_INT_RGB, TYPE_WAS_WEISS_ICH...) die Daten in unterschiedlicher Form. Mal als int[], mal als byte[], mal sonstwie. Das ganze ist ziemlich "wegabstrahiert", d.h. normalerweise sieht man das nicht. Aber wenn man WEISS, dass das ein Bild vom Typ TYPE_INT_... ist, kann man sich den DataBuffer holen, den zu DataBufferInt casten, und sich dann _direkt_ den int[]-Array aus den innersten Innereien des BufferedImages holen. 

Das ganze diente nur dem Zweck, das neu-Anlegen und "aufwändige" Füllen des Arrays zu vermeiden. Das Ergebnis ist eigentlich das gleiche wie wenn man einen neuen int[w*h] erstellt, und den mit getRGB(x,y) füllt. Aber nicht ganz: So, wie das da steht, bekommt man DEN Array aus dem BufferedImage (und wenn man da drin Zahlen ändert, ändert sich das Bild!  ). Don't try this at home  (Man sollte sich schon sicher sein, dass der Typ stimmt, und niemand Mist in den Array reinschreiben kann)


----------



## Mr.Bross (2. Jun 2010)

Eigentlich springt er ja hier:


```
private boolean regionsDiffer(int a[], int stride, int b[], int x0, int y0, int w, int h)
    {
        for (int y=y0; y<y0+h; y++)
        {
            for (int x=x0; x<x0+w; x++)
            {
                int index = y*stride + x;
                if (a[index] != b[index])
                {
                    return true;
                }
            }
        }
        return false;
    }
```

das erste mal nie rein?? Denn das erste mal beim überprüfen ist y0 immer 0 wie man hier nur unschwer erkennen kann, da ja 0* egal was 0 ergibt...


```
for (int tx=0; tx<numTilesX; tx++)
        {
            for (int ty=0; ty<numTilesY; ty++)
            {
                // Falls die Tiles sich unterscheiden...
                if (regionsDiffer(previousData, previousImage.getWidth(), 
                    currentData, tx*tileSizeX, ty*tileSizeY, tileSizeX, tileSizeY))
                {
                    // ... ein Rectangle speichern, das den Bereich 
                    // beschreibt in dem sich etwas geändert hat
                    Rectangle region = new Rectangle();
                    region.x = tx * tileSizeX;
                    region.y = ty * tileSizeX;
                    region.width = tileSizeX;
                    region.height = tileSizeY;
                    result.add(region);
                }
            }
        }
```

 Das heißt eigentlich ist der erste Durchlauf unnötig... Wieso jedoch fehlt ein großes Stück Fläche, wenn ich mit tx und ty = 1 anfange??


----------



## Marco13 (3. Jun 2010)

Hm... mein Morgenkaffe wirkt wohl noch nicht... ich verstehe nicht ganz, was du meinst... bei

```
for (int tx=0; tx<numTilesX; tx++)
        {
            for (int ty=0; ty<numTilesY; ty++)
            {
                // Falls die Tiles sich unterscheiden...
                if (regionsDiffer(previousData, previousImage.getWidth(), 
                    currentData, tx*tileSizeX, ty*tileSizeY, tileSizeX, tileSizeY))
```
Läuft er für (tx,ty) die Werte durch
(0,0)
(0,1)
(0,2)
...
(1,0)
(1,1)
(1,2)
...

D.h. für eine tileSizeX/Y von 10 ruft er die regionsDiffer-Methode mit diesen Werten für (x0,y0)/(w,h) auf:

(0,0)/(10,10)
(0,10)/(10,10)
(0,20)/(10,10)
...
(10,0)/(10,10)
(10,10)/(10,10)
(10,20)/(10,10)
...

Wenn man sich das mal auf Karopapier aufmalt, sieht man, dass er da Tile-Weise durch das Rechteck läuft.... :bahnhof:


----------



## Mr.Bross (3. Jun 2010)

Erstmal Danke, habe jetzt meinen Denkfehler gefunden =)

Aber wann ist denn region.width -1?? 

(
	
	
	
	





```
if (region.width == -1)
                    {
                        break;
                    }
```
)

Ich finde keine Stelle, wo das beim Clienten verschickt wird...


----------



## Mr.Bross (3. Jun 2010)

Und was ist mit diesem hier gemeint:


```
SwingUtilities.invokeAndWait(new Runnable() {...}
```

Was ist SwingUtilities.invokeAndWait?? Ist das etwas anderes wie ein Thread??


----------



## Mr.Bross (3. Jun 2010)

Sry und noch ne letzte Frage =) Wieso kommt diese Exception


```
Exception in thread "Thread-2" java.awt.image.RasterFormatException: (y + height) is outside raster
	at sun.awt.image.IntegerInterleavedRaster.createWritableChild(Unknown Source)
	at java.awt.image.BufferedImage.getSubimage(Unknown Source)
	at de.test.byteimage.Client.run(Client.java:151)
	at java.lang.Thread.run(Unknown Source)
```

wenn ich statt den 300 die komplette Screensize nehme... Ich habe nirgends eine Stelle gefunden wo y + height gerechnet wird und auch so dürfte es überall im Bild bleiben...

PS: Die Exception kommt genau an dieser Stelle:


```
BufferedImage tile = currentImage.getSubimage(r.x, r.y,
								r.width, r.height);
```


----------



## Marco13 (3. Jun 2010)

1. Das rectangle mit Größe -1,-1 wird vom Client in Zeile 133 rausgeschickt:
objectOutputStream.writeObject(new Rectangle(-1,-1,-1,-1));
Das dient dem Zeck, dem Server klarzumachen, dass er jetzt alle Tiles erhalten hat, die sich für EINEN Frame geändert haben, und er jetzt einen neuen IMAGE_REQUEST rausschicken kann. 

2. invokeAndWait stellt sicher, dass der enthaltene Codeblock auf dem Event-Dispatch-Thread (EDT) von Swing ausgeführt wird. Das Erstellen und Verändern von JComponents muss auf dem EDT passieren. invokeAnd*Wait* wartet dabei (im Gegensatz zu invokeLater) bis das ganze auch fertig ist. Erst danach kann die Übertragung anfangen. Das ganze würde man in einer "richtigen" Anwendung natürlich anders machen (zumindest mit einem Button, der die Übertragung startet) aber .... das sollte ja keine "richtige" Anwendung sein...

3. Das mit der Exception liegt vermutlich(!) daran, dass dort implizit die Annahme drinsteckt, dass die zu übertragende Größe ohne Rest durch die Anzahl der Tiles teilbar ist (d.h. dass alle Tiles gleich groß sind). Mit "computeTiles(5, 5)" und einer Größe von 300x300 haut das hin. 

Darüber hinaus solltest du (auch wenn es dir widerstrebt, und dir unnötig anstrengend erscheint) eventuell in Erwägung ziehen, unter Umständen vielleicht ein klizekleines bißchen _selbst_ nachzudenken, WAS dort gemacht werden SOLL und WIE das gemacht werden KANN. Bringt ja nichts wenn du nur durch immer mehr nachfragen Lösungen für Teilprobleme (oder durch meinen Post auch Lösungen für ganze Probleme) zusammenpfriemelst, aber nicht bereit bist, selbst mal einen Blick in die API-Doku zu invokeAndWait zu werfen, mit Strg+F im Programmcode nach dem String "[c]-1[/c]" zu suchen und sofort im Client in Zeile 133 zu landen, oder ein bißchen Zeit zu inverstieren, nachzuvollziehen, was dort mit den Tiles eigentlich passiert...


----------

