# 2D Pixelbild rotieren



## DylanP1234 (2. Jan 2016)

Guten Abend Leute!

Bei meinem Problem handelt es sich um das drehen eines Bildes. Ich habe mehrere Stunden versucht dies selbst zu lösen und habe auch im Internet / YT-Videos zwar vieles zum Thema gefunden, konnte es aber einfach nicht auf mein Programm anwenden.

Im Grunde genommen soll ein Bild um einen gewissen Winkel um die Mitte des Bildes gedreht werden. Ein "Bild" beschreibt bei mir ein als 1-Dimensionales, in meiner Klasse PixelImage gespeichertes, Array. Dazu meine Klasse PixelImage:

```
public class PixelImage
{
   public static final int undefinedColorValue = 0x010000;
   private final int width;
   private final int height;
   private int[] pixels;

   public PixelImage(int width, int height)
   {
     this.width = width;
     this.height = height;
     pixels = new int[width * height];
     for(int i = 0; i < pixels.length; i++)
     {
       pixels[i] = undefinedColorValue;
     }
    }
    public PixelImage rotateImage(PixelImage pixIm, double angle)
    {
       // hier soll ein neues um "angle" rotiertes Bild zurückgegeben werden.
    }

    // ... es folgen einige get- und set-Methoden
}
```
Beim erzeugen eines neuen PixelImages wird ein Breite * Höhe-großes Array mit meiner "Undefinierten Farbe" (#010000) angelegt. Die Farben der einzelnen Pixel können dann per set Methoden einfach verändert werden.

In einer anderen Klasse innerhalb der render-Methode wird vor dem "kopieren" des Bildes auf den Bildschirm dann noch die Rotations-Methode rotateImage() aufgerufen. Das zurückgegebene, rotierte Bild soll dann auf dem Bildschirm bzw. in mein "Hauptbild" geschrieben werden. Die Methode, die dies übernimmt sieht so aus:

```
public boolean putPixelImageOnScreen(int x, int y, double angle, PixelImage pixIm)
   {
     pixIm = pixIm.rotateImage(pixIm, angle);
 
     for(int i = 0; i < pixIm.getHeight(); i++)
     {
       for(int j = 0; j < pixIm.getWidth(); j++)
       {
         if(pixIm.getPixelValueAt(j, i) == PixelImage.undefinedColorValue) continue;
         setPixelValueAt(x + j, y + i, pixIm.getPixelValueAt(j, i));
       }
     }
     return true;
   }
```
Zuerst wird das Bild rotiert. Dann werden alle Pixel von a nach b verfrachtet solange sie nicht der Undefinierten Farbe entsprechen. In dem Falle wird der einzelne Pixel ignoriert. Die Methode setPixelValueAt stammt in diesem Falle von meinem "Bildschirm" und dient daher nur dem Übertragen der Werte von meinem PixelImage auf den Bildschirm / Screen.

Zurück zur Rotation:

Mein Problem ist nun, dass ich einfach zu blöd dazu bin, diese rotation zu berechnen!
Ich möchte die Rotation NICHT mit Graphics2D ... rotate() machen, da ich dann mein ganzes System umstellen müsste. Ich möchte die Kontrolle über einzelne Pixel behalten. Dadurch gibt es ja eigentlich nur die Möglichkeit as Bild zu drehen, indem ich das ganze Mathematisch mit sin(a), cos(a) usw. angehe. Der Einfachkeit halber hat mein Bild ein quadratisches Format (erstmal). Außerdem möchte ich um den Mittelpunkt P meines Bildes drehen also P((w-1)/2 | (h-1)/2) wobei w der Breite und h der Höhe entspricht. Damit ich nicht mit halben Pixeln arbeite habe ich auch bisher nur ungerade Höhen und Breiten benutzt.

Ein großes Problem, dass ich nicht bewältigen kann ist für mich, dass beim Drehen eines Quadrats sich die Höhe und Länge der "Außenlinien" (sagt man das so?) ja logischerweise vergrößert. (Ich kann ja kein gedrehtes Quadrat abspeichern, also muss ich ein größeres erstellen, in welches das gedrehte "hineinpasst".) Nun habe nach zu vielen Versuchen leider keine Lösung um zu Berechnen, wie breit und hoch dieses neue Quadrat sein muss. Der Ansatz lag dabei beim drehen der Ecken meines Bildes, um dann zu schauen, was die Längen dann sind. Und dann habe ich es auch immernoch nicht geschafft, die neuen, gedrehten Positionen meiner Pixel zu berechnen.

Also als Zusammenfassung noch einmal:
Was ich gerne wissen würde wäre..
- eine Erklärung zum "Drehen" der Pixel um den Mittelpunkt
- eine Erklärung zum berechnen dieses "äußeren" Quadrates
- und zuguterletzt wäre eine funktionierende rotateImage() Methode die mir ein neues, um "angle" gedrehtes, Bild zurückgibt, sehr schön!



Ich hoffe ihr habt mein Code und mein Problem verstanden und könnt mir helfen, worüber ich mich schoneinmal im Voraus bedanken möchte.

Mit freundlich Grüßen,
DylanP 

P.S.: Bitte um move in Java AWT, SWING etc. Forum, da ich dort aus unerklärlichen Gründen vorher nicht posten konnte.


----------



## DylanP1234 (3. Jan 2016)

EDIT:

Einen Fortschritt konnte ich bisher erzielen.
Ich kann nun meine Bilder rotieren lassen, jedoch mit einem unschönen Nebeneffekt, den ich ja vorher schon angesprochen hatte.
Dazu ein Screenshot.

Im ersten Screenshot sieht man ein rot gestreiftes und um 0 Grad rotiertes Quadrat.
Dann im zweiten Screenshot dasselbe Quadrat um 45 Grad rotiert.
Im zweiten Screenshot sieht man das rotierte Quadrat (die größe der Bilder sind nicht gleich, daher der Unterschied), welches ja eigentlich wie eine Raute aussehen sollte. Jedoch wird das Bild wieder an den Seiten "abgeschnitten" (wie ich versucht habe, mit rot einzuzeichnen).

Ich schätze mal, die Höhe und Breite des rotierten Bildes sind zu klein bzw. immernoch so groß wie vor der Rotation.

Zum Verständnis erst einmal mein Code zum rotieren.

```
public PixelImage rotateImage(PixelImage pixIm, double angle)
   {     
     int width = pixIm.getWidth();
     int height = pixIm.getHeight();
     int[] pixels = pixIm.getPixels();
     
     BufferedImage unrotatedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
     for(int y = 0; y < height; y++)
     {
       for(int x = 0; x < width; x++)
       {
         unrotatedImage.setRGB(x, y, pixels[x + y * width]);
       }
     }
     
     BufferedImage rotatedImage = new BufferedImage(unrotatedImage.getWidth(), unrotatedImage.getHeight(), unrotatedImage.getType());
     AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(angle), (unrotatedImage.getWidth() - 1) / 2, (unrotatedImage.getHeight() - 1) / 2);
     Graphics2D g = (Graphics2D) rotatedImage.getGraphics();
     g.setTransform(at);
     g.drawImage(unrotatedImage, 0, 0, null);
     g.dispose();
     PixelImage resultImage = new PixelImage(rotatedImage.getWidth(), rotatedImage.getHeight());
     
     resultImage.setPixels(((DataBufferInt) rotatedImage.getRaster().getDataBuffer()).getData());
     
     return resultImage;
   }
```
Da man durch ein BufferedImage auf die Graphics2D-Komponente kommt, mit der man Bilder dann leicht rotieren lassen kann, und da ich weiß, wie ich ein Pixel-Array in ein BufferedImage und umgekehrt verwandeln kann, war dies dann doch eine relativ gute Lösung (anders als ich zuerst gedacht hatte).

Durch diese Methode bekomme ich ein rotiertes PixelImage zurück. Leider ist, wie man oben sehen kann, dieses Bild dann abgeschnitten. Und wie schon gesagt sind Breite und Höhe gleichgeblieben.

Der Fehler liegt wahrscheinlich darin, dass ich die Breite und Höhe des neuen PixelImages der Breite und Höhe des rotierten BufferedImages setze. Dies ist im Grunde genommen ja richtig, aber anscheinend wird die Breite und Höhe des rotierten BufferedImages immernoch vom UNrotierten genommen, was dann dazu führt, dass das Bild abgeschnitten ist.

Ich hoffe ihr habt diesen Ansatz verstanden.

Was ich nun eigentlich nur bräuchte wäre, wie vorher auch schon, einfach die neue Breite und Höhe des rotierten Bildes, welche ich dann also neue Breite und Höhe meines rotierten, neuen PixelImages eingeben kann.


----------



## DylanP1234 (3. Jan 2016)

Okay ich habe mein Problem jetzt halbwegs gelöst.

Wie man im Screenshot sehen kann, habe ich mein eigentliches Bild (das rote Quadrat) ersteinmal in eine Art größeres Quadrat gepackt, was doppelte Breite und doppelte Höhe hat.


```
public PixelImage rotateImage(PixelImage pixIm, double angle)
   {     
     int width = pixIm.getWidth();
     int height = pixIm.getHeight();
     int[] pixels = pixIm.getPixels();
     
     BufferedImage unrotatedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
     for(int y = 0; y < height; y++)
     {
       for(int x = 0; x < width; x++)
       {
         unrotatedImage.setRGB(x, y, pixels[x + y * width]);
       }
     }
     
     BufferedImage rotatedImage = new BufferedImage((int) (unrotatedImage.getWidth() * 2), (int) (unrotatedImage.getHeight() * 2), unrotatedImage.getType());
     AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(angle), rotatedImage.getWidth() / 2, rotatedImage.getHeight() / 2);
     Graphics2D g = (Graphics2D) rotatedImage.getGraphics();
     g.setTransform(at);
     g.drawImage(unrotatedImage, unrotatedImage.getWidth() / 2, unrotatedImage.getHeight() / 2, null);
     g.dispose();
     PixelImage resultImage = new PixelImage(rotatedImage.getWidth(), rotatedImage.getHeight());
     
     resultImage.setPixels(((DataBufferInt) rotatedImage.getRaster().getDataBuffer()).getData());
     
     return resultImage;
   }
```
Der Mittelpunkt, um den das Bild gedreht wurde, wird nun durch Breite / 2 und Höhe / 2 dieses größeren Quadrats bestimmt.

Diese Lösung ist nicht perfekt, aber sollte fürs erste so funktionieren. Auch wenn es einen kleinen Knackpunkt gibt: Die x / y Koordinate meines roten Quadrat-Objekts ist nun der Punkt oben links des großen Quadrats. Das sollte aber kein allzu schwieriges Hindernis sein, denn da zum Beispiel Kollisionerkennung sowieso Pixelgenau ist und daher nicht auf diesen Punkt direkt zurückgreift.

Eigentlich hätte ich es ja gerne so gehabt, dass das große Quadrat genau das rote Quadrat umschließt ohne einen Zwischenraum, aber so ist es auch in Ordnung.

Also bis dann!


----------



## Androbin (18. Jan 2016)

Aus der offiziellen JavaDoc zu BufferedImage:
"setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
Sets an array of integer pixels in the default RGB color model (TYPE_INT_ARGB) and default sRGB color space, into a portion of the image data."

Es funktioniert wie System.arraycopy und verschafft dir einen Performance-Einschub gegenüber deiner doppelten for-Schleife.


----------

