Rotate Bitmap um 90°


  • Gesperrt

    In Java würde man beide Bilder erst in Speicher laden, um dann wahlfreien Zugriff zu haben.



  • Das hat doch damit nichts zu tun? Die Bilddaten sind doch auch hier im Speicher. So "wahlfrei" ist der Zugriff nicht, wenn man Cache mit einbezieht.



  • @Ronco sagte in Rotate Bitmap um 90°:

    @CyborgBeta : Ja, über direkt den Pixel ändern habe ich auch schon nachgedacht, habe aber nichts für bitmap gefunden, sowas wie pixel[x2][y2] = pixel[x1][y1]. Komme dann nicht dran vorbei immer die gesamte Zeile mit ScanLine zu lesen und nur einen Pixel zu verändern.

    Vielleicht habe ich etwas nicht richtig verstanden, aber direkter Pixelzugriff sähe für mich so aus:

    TRGBTriple& access_pixel(Bitmap* bitmap, int x, int y)
    {
        return *(reinterpret_cast<TRGBTriple*>(bitmap->ScanLine[y]) + x);
    }
    

    (Ohne Range Check natürlich)

    Das würde solche Funktionen ermöglichen:

    TRGBTriple getPixel(Bitmap* bitmap, int x, int y)
    {
        return access_pixel(bitmap, x, y);
    }
    
    void setPixel(Bitmap* bitmap, int x, int y, TRGBTriple color)
    {
        access_pixel(bitmap, x, y) = color;
    }
    

    Das sind für mich einzelne Pixelzugriffe, ich sehe nicht warum man da eine ganze Scanline verarbeiten müsste, wenn man mit nur einem Pixel hanterien will. Sind nicht alle Scanlines eh bereits im Speicher und über das bitmap->ScanLine-Array erreichbar?

    Eine Rechts-Rotation sähe dann z.B. so aus (wenn ich mich da gedanklich nicht verhaspelt hab 🙂 ):

    for (int x = 0; x < iWidth; x++)
    {
        for (int y = 0; y < iHeight; y++)
            setPixel(bitmap2, iHeight - y, iWidth - x, getPixel(bitmap, x, y));
    }
    

    Das sollte eigentlich trotz etwas ungünstigen Cache-Effekten schnell genug sein - da würde ich eigentlich aus dem Bauch heraus in etwa so ne Laufzeit-Grössenordnung von vielleicht 10ms für ein 1080p-Bild erwarten. Optimierungen wie von @wob vorgeschlagen sind zwar für maximale Performance sinnvoll, aber vermutlich hier nicht das, was deinen Code langsam macht.

    @CyborgBeta Die Laufzeit ist hier auch bei zwei geschachtelten Schleifen linear zur Anzahl der Pixel (n = width * height), nicht quadratisch.


  • Gesperrt

    @Finnegan sagte in Rotate Bitmap um 90°:

    @CyborgBeta Die Laufzeit ist hier auch bei zwei geschachtelten Schleifen linear zur Anzahl der Pixel (n = width * height), nicht quadratisch.

    Ich ging von einem "quadratischen" Bild aus und n == width == height ... n war bei mir also nur eine Seitenlänge, nicht die komplette Anzahl Pixel ...

    Ich denke auch, dass das so wie von dir beschrieben schnell genug sein sollte ...

    Aber sein "Fehler" war halt, jedes Mal erst die komplette "ScanLine" in ein anderes Array zu kopieren, denke ich.


    Mit 8x8 oder 16x16 ... zu hantieren, wäre eine Mikrooptimierung.


  • Gesperrt

    @Finnegan sagte in Rotate Bitmap um 90°:

    Eine Rechts-Rotation sähe dann z.B. so aus (wenn ich mich da gedanklich nicht verhaspelt hab

    Das ist richtig. Normalerweise ist eine +90°-Drehung ccw und eine -90°-Drehung cw (im Uhrzeigersinn/"rechtsherum").

    Wenn du h1-h2 und w1-w2 rechnest, hast du eine cw-Drehung. (Ich hoffe aber... ich hab mich jetzt nicht auch verhaspelt 😅)



  • Probier mal Folgendes aus (Annahme: TBitmap hält die Pixeldaten in einem zusammen hängenden Speicherbereich):

    • Bestimme die Speicheradresse von ScanLine[0] (erster Pixel, erste Zeile)
    • Bestimme die Speicheradresse von ScanLine[1] (erster Pixel, zweite Zeile)

    Aus der Differenz kannst du die Länge einer Zeile berechnen und damit die Speicheradresse jedes einzelnen Pixels. Ich vermute mal, dass du TBitmap aus der VCL benutzt, der Zugriff per ScanLine ist langsam und sollte so selten wie möglich benutzt werden.



  • @DocShoe sagte in Rotate Bitmap um 90°:

    Probier mal Folgendes aus (Annahme: TBitmap hält die Pixeldaten in einem zusammen hängenden Speicherbereich):

    • Bestimme die Speicheradresse von ScanLine[0] (erster Pixel, erste Zeile)
    • Bestimme die Speicheradresse von ScanLine[1] (erster Pixel, zweite Zeile)

    Aus der Differenz kannst du die Länge einer Zeile berechnen und damit die Speicheradresse jedes einzelnen Pixels. Ich vermute mal, dass du TBitmap aus der VCL benutzt, der Zugriff per ScanLine ist langsam und sollte so selten wie möglich benutzt werden.

    Interessant. Hat ScanLine den operator[] überladen oder was geht da? Kenne VCL nicht und hab gerade erst gesehen, dass der Thread sich im entsprechenden Unterforum bedindet. Ich ging davon aus, dass ScanLine ein Pointer-Array auf die Bitmap-Zeilen ist. Sowas wäre extrem langsam, wenn auch nicht sehr effizient.

    Sorry mit dem VCL-Bezug, wenn man auf "ungelesene" Beiträge klickt, ist das Unterforum nicht immer so klar erkennbar 🙂



  • @DocShoe sagte in Rotate Bitmap um 90°:

    Aus der Differenz kannst du die Länge einer Zeile berechnen und damit die Speicheradresse jedes einzelnen Pixels. Ich vermute mal, dass du TBitmap aus der VCL benutzt, der Zugriff per ScanLine ist langsam und sollte so selten wie möglich benutzt werden.

    Gibr es für TBitmap keine direkten Zeiger auf die Rohdaten ?

    In Lazarus, welches verwandt mit den Ex-Borlandprodukten ist, sieht dies in etwas so aus.

    var
      bit :TBitmap;
    begin
      bit.RawImage.Data[12] := 123;  
    

  • Gesperrt

    @Ronco Wenn der Zugriff auf dieses ScanLine-Dings langsam ist, dann leg doch ein (bzw. zwei) zweidimensionale int-Arrays mit den Rohdaten/argb (die ich nicht kenne) der Pixel an ... Dann kannst du translatieren/rotieren und das zweite Array zurück in das originale ScanLine-Dings bringen.

    Hm, ist wie beim Kochen. Wenn du jede Zutat erst on-the-fly aus dem Schrank holen musst, dann dauert alles länger, als wenn du alle Zutaten im Vorhinein bereitgestellt hast. (Physikalisch gesehen, verringert sich die Strecke bzw. Arbeit).



  • @Finnegan
    In Delphi gibt es Properties, hinter denen sich Funktionsaufrufe verbergen können. Man merkt Delphi und C# an, dass sie den gleichen Designer haben (Anders Hejlsberg). Hinter ScanLine steckt ein Funktionsaufruf, in dem diverse Sachen geprüft werden und zum Schluss über Zeigerarithmetik eine Adresse berechnet wird. Wenn man das für jeden Pixel immer wieder machen muss dauert's halt ewig.

    @Mathuas
    Ich wüsste nicht, dass TBitmap sowas hat.

    PS:
    Es gibt auch bottom-up Bitmaps, da haben die einzelnen Scanlines absteigende Speicheradressen. Muss man bei meinem vorherigen Vorschlag berücksichtigen. Der Vorschlag dürfte funktionieren, da die Bitmap-Daten tatsächlich zusammenhängend im Speicher liegen.



  • @DocShoe : Deine Idee war in die richtige Richtung. Damit ist der Zeitverlust weg, allerdings hab ich es bisher nicht hinbekommen, dass auch die Farben kommen bzw. die richtige Stelle der Pixel zu finden. Es kommt nur eine weiße Fläche. Evtl. geht es auch gar nicht, kann ich aber nicht sagen. Sieht so aus als ob ich da nicht an der richtigen Stelle bin. Ich habe nochmal die ganze Routine hier, vielleicht hat ja jemand noch eine Idee. Ich denke das Problem liegt in den Zeilen 90-100 oder 111-112 ?

    //---------------------------------------------------------------------------
    
    #include <vcl.h>
    #include <memory>
    #include <jpeg.hpp>
    #include <sstream>
    #pragma hdrstop
    
    #include "Unit1.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    
    std::unique_ptr<Graphics::TBitmap> DstBitmap(new Graphics::TBitmap);
    
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    
    __fastcall TForm1::TForm1(TComponent* Owner)
    	: TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
     String FN = "C:\\Temp\\Test\\20230727_093931 UR.jpg";
    
     //rotateBitmap( FN, 1 );
     //rotateBitmap( FN, 3 );
     rotateBitmap( FN, 6 );
    }
    
    //---------------------------------------------------------------------------
    // JPEG Exif Orientation Tag
    //
    //												Value	0th Row		0th Column
    //												------------------------------
    //	1 = Horizontal (normal)						1		top			left side
    //	2 = Mirror horizontal 						2		top			right side
    //	3 = Rotate 180°								3		bottom		right side
    //	4 = Mirror vertical 						4		bottom		left side
    //	5 = Mirror horizontal and rotate 270° CW 	5		left side		top
    //	6 = Rotate 90° CW							6		right side		top
    //	7 = Mirror horizontal and rotate 90° CW  	7		right side		bottom
    //	8 = Rotate 270° CW							8		left side		bottom
    //
    void __fastcall TForm1::rotateBitmap( String FileName, int Orientation )
    {
     std::unique_ptr<TJPEGImage> JImage(new TJPEGImage());
     std::unique_ptr<Graphics::TBitmap> bitmap1(new Graphics::TBitmap);
     std::unique_ptr<Graphics::TBitmap> bitmap2(new Graphics::TBitmap);
     TRGBTriple *ptr1, *ptr2;
    
     JImage->LoadFromFile( FileName );
     bitmap1->Assign( JImage.get() );
    
     dtStart = Now();
    
     bitmap1->PixelFormat   = pf24bit;
     bitmap2->PixelFormat   = pf24bit;
     DstBitmap->PixelFormat = pf24bit;
    
     int iHeight, iWidth;
    
     if ( Orientation == 3 )       // lower-right, rotate 180°  right
       {
    	bitmap2->Width  = bitmap1->Width;
    	bitmap2->Height = bitmap1->Height;
    	iWidth  = bitmap2->Width;
    	iHeight = bitmap2->Height;
    
    	for ( int y2 = 0, y1 = iHeight-1; y2 < iHeight; y2++, y1-- )
    	   {
    		ptr1 = reinterpret_cast<TRGBTriple *>(bitmap1->ScanLine[y1]);
    		ptr2 = reinterpret_cast<TRGBTriple *>(bitmap2->ScanLine[y2]);
    		for ( int x2 = 0, x1 = iWidth-1; x2 < iWidth; x2++, x1-- )
    		   {
    			ptr2[x2] = ptr1[x1];
    		   }
    	   }
       }
     else if ( Orientation == 6 )  // upper-right, rotate 90°  right
       {
    	bitmap2->Width  = bitmap1->Height;
    	bitmap2->Height = bitmap1->Width;
    	iWidth  = bitmap2->Width;
    	iHeight = bitmap2->Height;
    
    	std::ostringstream os1, os2;
    	ptr1 = reinterpret_cast<TRGBTriple *>(bitmap1->ScanLine[0]);
    	ptr2 = reinterpret_cast<TRGBTriple *>(bitmap1->ScanLine[1]);
    	os1 << reinterpret_cast<TRGBTriple *>( ptr1 );
    	os2 << reinterpret_cast<TRGBTriple *>( ptr2 );
    	String Str1 = os1.str().c_str();
    	String Str2 = os2.str().c_str();
    	int adr1, adr2, diff;
    	TryStrToInt( "0x"+Str1, adr1 );
    	TryStrToInt( "0x"+Str2, adr2 );
    	diff = adr2 - adr1;
    
    	for ( int y2 = 0, col = 0; y2 < iHeight && y2 < 4624; y2++, col++ )
    	   {
    		for ( int x2 = 0, y1 = iWidth-1; y1 >= 0; x2++, y1-- )
    		   {
    			if ( x2 == 0 )
    			  {
    			   ptr2 = reinterpret_cast<TRGBTriple *>(bitmap2->ScanLine[x2]);
    			   ptr1 = reinterpret_cast<TRGBTriple *>(bitmap1->ScanLine[x2]);
                  }
    			ptr2 = ptr1;
    			ptr1 += diff;
    		   }
    	   }
       }
     else       // No rotation
       {
    	bitmap2->Width  = bitmap1->Width;
    	bitmap2->Height = bitmap1->Height;
    
    	bitmap2->Assign( bitmap1.get() );
       }
    
    	dtEnd = Now();
    	dtDiff = dtEnd - dtStart;
    	Label1->Caption = dtDiff.TimeString();
    
    	DstBitmap->Width  = bitmap2->Width;
    	DstBitmap->Height = bitmap2->Height;
    
    	 int ret = SetStretchBltMode( DstBitmap->Canvas->Handle, HALFTONE );
    
    	 StretchBlt( DstBitmap->Canvas->Handle, 0, 0, PaintBox1->Width, PaintBox1->Height,
    				 bitmap2->Canvas->Handle, 0, 0, bitmap2->Width, bitmap2->Height, SRCCOPY );
    
    	 HDC hdc = PaintBox1->Canvas->Handle;
    
    	 BitBlt( hdc, 0, 0, PaintBox1->Width, PaintBox1->Height,
    			 DstBitmap->Canvas->Handle, 0, 0, SRCCOPY );
    
    	 bitmap2->SaveToFile( "C:\\Temp\\Test\\SBMP.bmp" );
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
    {
      PaintBox1->Canvas->Draw( 0, 0, DstBitmap.get() );
    }
    //---------------------------------------------------------------------------
    
    //---------------------------------------------------------------------------
    
    #ifndef Unit1H
    #define Unit1H
    //---------------------------------------------------------------------------
    #include <System.Classes.hpp>
    #include <Vcl.Controls.hpp>
    #include <Vcl.StdCtrls.hpp>
    #include <Vcl.Forms.hpp>
    #include <Vcl.ExtCtrls.hpp>
    //---------------------------------------------------------------------------
    class TForm1 : public TForm
    {
    __published:	// Von der IDE verwaltete Komponenten
    	TButton *Button1;
    	TPaintBox *PaintBox1;
    	TLabel *Label1;
    	void __fastcall Button1Click(TObject *Sender);
    	void __fastcall PaintBox1Paint(TObject *Sender);
    private:	// Benutzer-Deklarationen
        TDateTime dtStart, dtEnd, dtDiff;
    	void __fastcall rotateBitmap( String, int );
    public:		// Benutzer-Deklarationen
    	__fastcall TForm1(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TForm1 *Form1;
    //---------------------------------------------------------------------------
    #endif
    
    


  • @Ronco sagte in Rotate Bitmap um 90°:

    StretchBlt( DstBitmap->Canvas->Handle, ... );
    

    Nur ne schnelle Antwort, da ich grad kurz angebunden bin: Das sieht ja aus, als seien die Bitmaps unter der Haube Win32-API-Bitmaps. Ich hab hier mal vor ner Weile einen kurzen Demo-Code gepostet, wie man mit der Windows-API Direktzugriff auf die Pixel hinbekommt und das Bitmap dann anzeigt. Das ist zwar ein reines Win32-Programm, aber vielleicht hilft dir das ja weiter um dir den einen oder anderen Kniff abzugucken:

    https://www.c-plusplus.net/forum/topic/353154/endlich-pixelkontrolle-visual-studio-22/11

    Eventuell kannst du auch über das Bitmap-Handle (HDC/HBITMAP) Zugriff auf die Pixeldaten bekommen ohne den Umweg über ScanLine. Das dürfte deutlich schneller sein.



  • @Finnegan : Werde mich mit deinem Programm mal beschäftigen. Danke!



  • @Ronco: In deiner Schleife in den Zeilen 102-114 kopiert dein Code ja auch gar nichts.

    Wenn du schon WinAPI-Funktionen wie BitBlt oder StretchBlt benutzt, dann kannst du auch PlgBlt zum Rotieren benutzen, s. z.B. den Delphi-Code Rotate TImage in Delphi dafür.

    Oder so per ScanLine: Delphi/Lazarus - Rotate TBitmap 90 degrees



  • @Ronco Da du eh die Win32 API nutzt (Und davon die GDI API) zum zeichnen der bilder, könntest du auch die GDI API für Transform nutzen um die Bilder vor dem zeichnen zu rotieren.

    https://learn.microsoft.com/en-us/windows/win32/gdi/coordinate-spaces-and-transformations

    Falls man von TBitmap nach GDI+ klassen kommen kann, wäre auch die GDI+ API (C++) eine alternative
    https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-rotating-reflecting-and-skewing-images-use



  • @firefly : Vielen Dank für die Tipps, werde das mal prüfen.
    @Th69 : Vielen Dank für den Hinweis mit PlgBlt, damit funktioniert es, und auch schnell ! So werde ich es jetzt lassen, obwohl mich das ärgert, dass ich den Tipp von @DocShoe nicht umsetzen kann. Das ganze Pointer Geraffel fällt mir nicht leicht, aber ich probier mal weiter, nur dem Interesse wegen ☺


Anmelden zum Antworten