2D-Grafik Bild ohne AWT, Swing und JavaFX rotieren

RalleYTN

Bekanntes Mitglied
Hallo Leute!
Wie der Titel schon sagt möchte ich ein Bild rotieren. Das Problem ist jetzt jedoch, dass mir nicht die AWT, Swing und JavaFX Klassen zur Verfügung stehen und ich an Java 5 gefesselt bin.
Das heisst ich muss mir meine eigene Bild-Klasse schreiben und die ganzen Algorithmen die ich brauche ebenfalls selber schreiben.

Momentan speichere ich Bilder in einem Zweidimensionalen Integer Array. In diesem Raster werden die Farben im Format ARGB gehalten.

Bis jetzt habe ich den folgenden Algorithmus:
Java:
    public SimpleImage rotate(int width, int height, int rotationCenterX, int rotationCenterY, double degrees) {
       
       int imgWidth = this.data.length;
       int imgHeight = this.data[0].length;
       double radians = Math.toRadians(degrees);
       int[][] data = new int[width][height];
       
       for(int x = 0; x < imgWidth; x++) {
           
           for(int y = 0; y < imgHeight; y++) {
               
               int newX = (int)(Math.cos(radians) * (x - rotationCenterX) - Math.sin(radians) * (y - rotationCenterY) + rotationCenterX);
               int newY = (int)(Math.sin(radians) * (x - rotationCenterX) + Math.cos(radians) * (y - rotationCenterY) + rotationCenterY);
           
               try {
                   
                   data[newX][newY] = this.data[x][y];
                   
               } catch(ArrayIndexOutOfBoundsException exception) {}
           }
       }
       
       return new SimpleImage(data);
   }

Das Funktioniert auch schon ganz gut.... Allerdings nur wenn ich das Bild um 90, 180 oder 270 Grad drehen möchte. Wenn ich das Bild nun um 45° drehen möchte kommt folgendes dabei raus: {siehe Anhang}

Es befinden sich schwarze Punkte überall im Bild. Ich habe jetzt schon mindestens 'ne Stunde gesucht und konnte nicht herausfinden wie ich das Verhindern kann.

Hat jemand eine Idee wie ich den Algorithmus abändern/erweitern muss damit ich kein Problem mit diesen toten Punkten bekomme?
 

Anhänge

  • Ergebnis45.jpg
    Ergebnis45.jpg
    22,6 KB · Aufrufe: 77
das funktioniert halt nicht so einfach sondern ist wesentlich komplexer als du denkst. Wenn du nicht um gerade Winkel drehst, dann bekommst du ja bei Ganzen Pixel Koordinaten zu mehreren Quellpunkten den gleichen Zeilpunkt und wiederum bestimmt Zielpunkte werden gar nicht getroffen.

Um um jeden beliebigen Winkel drehen zu können musst du mit Subpixeln rechnen. Das bedeutet du Must Deine Ergebnis Koordinaten in float behandeln und dann mehrere zielpixel gleichzeitig setzen und nicht nur setzen sondern auch noch zumischen.

Um mal ein einfaches Beispiel mit total fiktiven werten zu geben

Nehmen wir an du hast den Quellpixel 100, 100 und berechnest nun die neue Position. Sagen wir das Ergebnis wäre 50.5, 50,5. dann Must du die Pixel 50,50 und 50,51 und 51,50 und 51,51 jeweils auf diese Farbe setzen.

Würde nun ein weiters quellpixel auf so ein bereits gesetztes Pixel treffen. Dann Must du es im Verhältnis zumischen. Sagen wir du hast ein Ergebnis Pvon 49.25, 49.25 dann würde dem Pixel 50,50 die neue Farbe mit 25% zugemischt.

Gruß

Claus
 
Das Ergebnis ist zwar immernoch noch nicht perfekt, jedoch gibt es keine Lücken mehr 🙂
Java:
    public SimpleImage rotate(double degrees, int width, int height, int rotationCenterX, int rotationCenterY) {
     
       int imgWidth = this.data.length;
       int imgHeight = this.data[0].length;
       double radians = Math.toRadians(degrees);
       int[][] data = new int[width][height];
       Integer[][] buffer = new Integer[width][height];
     
       for(int x = 0; x < imgWidth; x++) {
         
           for(int y = 0; y < imgHeight; y++) {
             
               double newX = Math.cos(radians) * (x - rotationCenterX) - Math.sin(radians) * (y - rotationCenterY) + rotationCenterX;
               double newY = Math.sin(radians) * (x - rotationCenterX) + Math.cos(radians) * (y - rotationCenterY) + rotationCenterY;
               int pixel = this.data[x][y];
               float ratioX = (float)((int)newX - newX);
               float ratioY = (float)((int)newY - newY);
             
               try {
                 
                   buffer[(int)newX][(int)newY] = pixel;
                 
               } catch(ArrayIndexOutOfBoundsException exception) {}

               try {
                 
                   if(ratioX != 0.0F) {
                     
                       Integer bufferPixel = buffer[(int)newX + 1][(int)newY];
                       buffer[(int)newX + 1][(int)newY] = bufferPixel != null ? ColorUtils.mix(pixel, bufferPixel, ratioX) : pixel;
                   }
                 
               } catch(ArrayIndexOutOfBoundsException exception) {}
             
               try {
                 
                   if(ratioY != 0.0F) {
                     
                       Integer bufferPixel = buffer[(int)newX][(int)newY + 1];
                       buffer[(int)newX][(int)newY + 1] = bufferPixel != null ? ColorUtils.mix(pixel, bufferPixel, ratioY) : pixel;
                   }
                 
               } catch(ArrayIndexOutOfBoundsException exception) {}
             
               try {
                 
                   if(ratioX != 0.0F && ratioY != 0.0F) {
                     
                       float ratio = (ratioX + ratioY) / 2;
                       Integer bufferPixel = buffer[(int)newX + 1][(int)newY + 1];
                       buffer[(int)newX + 1][(int)newY + 1] = bufferPixel != null ? ColorUtils.mix(pixel, bufferPixel, ratio) : pixel;
                   }
                 
               } catch(ArrayIndexOutOfBoundsException exception) {}
           }
       }
     
       for(int x = 0; x < width; x++) {
         
           for(int y = 0; y < height; y++) {
             
               Integer bufferPixel = buffer[x][y];
               data[x][y] = bufferPixel != null ? bufferPixel : 0x00000000;
           }
       }
     
       return new SimpleImage(data);
   }

upload_2017-5-11_23-25-57.jpeg
latest
 
Mach das Bild mal grün und mische den rest dann darauf. Ich denke die scharzen ausfransungen sind immer noch Lücken. Aber sieht ja schon deutlich besser aus.

Um die Lücken gänzlich zu vermeiden solltest du anders herum gehen. Du solltest durch die Zielpixel mit ganzzahlen iterieren und zum Zielpixel dann den oder die Quellpixel errechnen. Dann hast du für jedes Zielpixel einen garantierten Farbwert.

Gruß

Claus
 
Ich habe das Bild einmal über einen grünen Hintergrund gezeichnet. Es sind jetzt schon keine Lücken mehr drin. Die Qualität des Bildes steigt auch, wenn ich das Farbenmischen komplett weg lasse(ist dann aber auch noch nicht ganz perfekt). Das mit dem anders herum rechnen klingt aber nach einer guten idee. Ich werde es mal ausprobieren und später das Ergebnis posten. 🙂
 
Auf jeden Fall sieht man oben am Bild, das das Mischen irgendwie nicht stimmen kann. Denn sonnst müßtest du einen geraden Rand haben der durch ein Graustufen Antialising geglättert ist. Du hast am oberen Rand nur schwarze und weisse Pixel. Es müssten sich aber auch graue Pixel ergeben wie am unteren Rand

Gruß

Claus
 
Ich bin anscheinend zu doof den Quellpixel zu berechnen.
Java:
int sourceX = (int)(rotationCenterX - ((y + rotationCenterY) / Math.sin(radians)) + ((x + rotationCenterX) / Math.cos(radians)));
int sourceY = (int)(rotationCenterY - ((y + rotationCenterY) / Math.cos(radians)) - ((x + rotationCenterX) / Math.sin(radians)));
Ich habe jetzt einfach mal die Rechnung umgedreht bekomme jedoch nicht den Quellpixel. :/
 
Leider bin ich auch nicht der große Mathematiker. Ich habe das irgendwann vor gut 30 Jahren mal in C auf dem Amiga gemacht als es noch gar keine Libs für sowas gab. Deswegen erinnere ich mich auch nur noch waage 🙂
 
Ok habs doch noch hinbekommen. Jetzt komme ich mit weniger Code aus, bin schneller und habe eine bessere Qualität. Das Bild wird jetzt jedoch gegen den Uhrzeigersinn gedreht.

Java:
    public SimpleImage rotate(double degrees, int width, int height, int rotationCenterX, int rotationCenterY) {
       
       int imgWidth = this.data.length;
       int imgHeight = this.data[0].length;
       double radians = Math.toRadians(degrees);
       int[][] data = new int[width][height];
       
       for(int x = 0; x < width; x++) {
           
           for(int y = 0; y < height; y++) {
               
               int sourceX = (int)(rotationCenterX - ((rotationCenterY - y) * Math.cos(radians)) + ((rotationCenterX - x) * Math.sin(radians)));
               int sourceY = (int)(rotationCenterY - ((rotationCenterY - y) * Math.sin(radians)) - ((rotationCenterX - x) * Math.cos(radians)));

               if(sourceX >= 0 && sourceX < imgWidth && sourceY >= 0 && sourceY < imgHeight) {

                   data[x][y] = this.data[sourceX][sourceY];
               }
           }
       }
       
       return new SimpleImage(data);
    }



upload_2017-5-13_14-2-8.jpeg
 
Super das schaut doch ziemlich nett aus. Ich verstehe aber immer noch nicht warum es da so Pixel "Aussreisser" an den Übergängen von Schwarz zu weiß im Bild gibt. Der Rahmen ist ja jetzt auch perfekt. Allerdings machst du ja jetzt auch überhaupt keine Subpixel mehr. Das wird es wohl sein
 
Es ist auf jeden Fall ein annehmbares Ergebnis. Um die Sache mit der Drehrichtung zu lösen habe ich ganz einfach die Variable 'degrees' um 180 erhöht und das Ergebnis negiert. Jetzt dreht sich das Bild wieder im Uhrzeigersinn. Es gibt anscheinend noch andere Rotationsalgorithmen mit Bilinearer Interpolation die Ergebnisse mit besserer Qualität liefern. Jedoch reicht mein Mathematik wissen dafür nicht aus.

Danke für deine Hilfe @Thallius
 
Ist im Prinzip das Gleiche wie vorher. Du hast ja in diesem Fall auch eine float quell Koordinate. Denentsprechend holst du dir die vier Farben aus den benachbarten Pixeln und mischt sie im richtigen Verhältnis zusammen bevor du sie ins ERgebnis pixel setzt.
 

Neue Themen


Zurück
Oben