Page 1 of 2 1 2 LastLast
Results 1 to 15 of 26

Thread: Bitmap 90 graden roteren

  1. #1

    Bitmap 90 graden roteren

    Graag zou ik een snel algorithme willen dat een TImage.Picture.Bitmap 90 graden links/rechts kan roteren.

    Momenteel heb ik dit:
    Code:
    type TRichting = (riLinks, riRechts); // rotatie richting
    var Image_: TImage;
    
    procedure TForm1.Roteer(Richting: TRichting);
    type TRgbTripleArray = array[word] of TRGBTriple;
      PRGBTripleArray = ^TRGBTripleArray;
    var w, h, r, c, x, y: word;
      BM: TBitMap;
      P: PRGBTripleArray;
      Ti: TImage;
    begin
      Screen.Cursor := CrHourGlass;
    
      BM := TBitMap.Create;
    
    {$D-}
    {$O+}
    {$Q-}
    {$R-}
    {$C-}
    {$T-}
    
      with Image_ do
      begin
        Jpeg.LoadFromFile(Hint);
        Picture.Bitmap.Assign(Jpeg);
    
        w := Picture.Bitmap.Width;
        h := Picture.BitMap.Height;
    
        BM.SetSize(H, W);
    
        for r := 0 to h - 1 do
        begin
          P := Picture.Bitmap.ScanLine[r];
          for c := 0 to w - 1 do
          begin
            if Richting = riRechts then
            begin
              x := (h - 1) - r; // 90 degrees right rotation
              y := c; // 90 degrees right rotation
            end else
            begin
              x := r; //90 degrees left rotation
              y := (w - 1) - c; //90 degrees left rotation
            end;
            BM.Canvas.Pixels[x, y] := (P[c].rgbtBlue shl 16) + (P[c].rgbtGreen shl 8) + P[c].rgbtRed;
          end;
        end;
    
        Image_.Picture.Bitmap.Assign(BM); // show picture
    
        Jpeg.Assign(BM); // and save it
        Jpeg.SaveToFile(Hint);
    
      end;
    
    {$D+}
    {$O-}
    {$Q+}
    {$R+}
    {$C+}
    {$T+}
    
      Bm.Free;
      Screen.Cursor := CrDefault;
    end;
    Bovenstaande code werkt goed, maar is nogal (tergend) traag. Ik zou graan weten of er snellere methoden bestaan (bv routines in windows zelf) om dit te doen?

    Alvast bedankt!

    p.s. Er is ook een methode die gebruikt maakt van canvas.copyrect, maar die kan alleen links/rechts en/of boven/onder verwisselen. Wel bliksemsnel...
    Vriendelijke groeten,
    Dany

  2. #2
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Wil je een beetje sneller, of heel veel sneller? 64-bit only of algemeen ?

    De eerste link is zwart wit. Ik heb een variant met generics die ook voor 24 en 32-bits bitmaps werkt, maar die vereist (vanwege generics bugs) XE7+ of FPC3, zie benenden

    De assembler versie is 4x zo snel, maar altijd zwart wit (maar bij erg grote beelden is het verschil kleiner, omdat dat het geheugen de beperking is)

    Om je een idee te geven (rowpitch is overigens intptr(scanline[1])-intptr(scanline[0]) )

    Delphi Code:
    1. const stepsize =64;
    2.  
    3. procedure tbwimagegen<T>.rotatealign(Target:TLocalType);
    4. // warning: this method has some fixes that hardcode it to RGB24 to avoid delphi XE bugs.
    5. // (multiplications by 3)
    6. var stepsx,stepsy,restx,resty : Integer;
    7.    RowPitchSource, RowPitchTarget : Integer;
    8.    pSource, pTarget,ps1,ps2 : RefT;
    9.    x,y,i,j: integer;
    10.    rpstep : integer;
    11. begin
    12.   RowPitchSource := RowPitch;                 // bytes to jump to next line. Can be negative (includes alignment)
    13.   RowPitchTarget := target.RowPitch;        rpstep:=RowPitchTarget*stepsize;
    14.   stepsx:=ImageWidth div stepsize;
    15.   stepsy:=ImageHeight div stepsize;
    16.   // check if mod 16=0 here for both dimensions, if so -> SSE2?
    17.   for y :=    0 to stepsy- 1 do
    18.     begin
    19.       psource:=GetImagePointer(0,y*stepsize);          // gets pointer to pixel x,y
    20.       ptarget:=Target.GetImagePointer(target.imagewidth-(y+1)*stepsize,0);
    21.       for x :=  0 to stepsx - 1 do
    22.         begin
    23.           for i := 0 to stepsize - 1 do
    24.             begin
    25.               ps1:=reft(@pbbyte(psource)[rowpitchsource*i]);   // ( 0,i)
    26.               ps2:=reft(@ptarget[(stepsize-1-i)]);       //  (maxx-i,0);
    27.               for j := 0 to stepsize - 1 do
    28.                begin
    29.                  ps2[0]:=ps1[workaround*j];
    30.                  ps2:=reft(@pbyte(ps2)[RowPitchTarget]);
    31. //                 inc(pbyte(ps2),RowPitchTarget);
    32.                end;
    33.             end;
    34.           inc(psource,stepsize);
    35. //          inc(pbyte(ptarget),rpstep);
    36.           ptarget:=@pbyte(ptarget)[rpstep];
    37.         end;
    38.     end;
    39.   // 3 more areas to do, with dimensions
    40.   // - stepsy*stepsize * restx        // right most column of restx width
    41.   // - stepsx*stepsize * resty        // bottom row with resty height
    42.   // - restx*resty                    // bottom-right rectangle.
    43.   restx:=ImageWidth mod stepsize;   // typically zero because width is typically 1024 or 2048
    44.   resty:=Imageheight mod stepsize;
    45.   if restx>0 then
    46.     begin
    47.       // one loop less, since we know this fits in one line of  "blocks"
    48.       psource:=GetImagePointer(ImageWidth-restx,0);    // gets pointer to pixel x,y
    49.       ptarget:=Target.GetImagePointer(Target.imagewidth-stepsize,Target.imageheight-restx);
    50.       for y := 0 to stepsy - 1 do
    51.         begin
    52.           for i := 0 to stepsize - 1 do
    53.             begin
    54.               ps1:=@pbyte(psource)[rowpitchsource*i];   // ( 0,i)
    55.               ps2:=@ptarget[stepsize-1-i];       //  (maxx-i,0);
    56.               for j := 0 to restx - 1 do
    57.                begin
    58.                  ps2[0]:=ps1[j];
    59.                  inc(pbyte(ps2),RowPitchTarget);
    60.                end;
    61.             end;
    62.          inc(pbyte(psource),stepsize*RowPitchSource);
    63.          dec(ptarget,stepsize);
    64.        end;
    65.     end;
    66.   if resty>0 then
    67.     begin
    68.       // one loop less, since we know this fits in one line of  "blocks"
    69.       psource:=GetImagePointer(0,ImageHeight-resty);    // gets pointer to pixel x,y
    70.       ptarget:=Target.GetImagePointer(0,0);
    71.       for x := 0 to stepsx - 1 do
    72.         begin
    73.           for i := 0 to resty- 1 do
    74.             begin
    75.               ps1:=@pbyte(psource)[rowpitchsource*i];   // ( 0,i)
    76.               ps2:=@ptarget[resty-1-i];       //  (maxx-i,0);
    77.               for j := 0 to stepsize - 1 do
    78.                begin
    79.                  ps2[0]:=ps1[j];
    80.                  inc(pbyte(ps2),RowPitchTarget);
    81.                end;
    82.             end;
    83.          inc(psource,stepsize);
    84.          inc(pbyte(ptarget),rpstep);
    85.        end;
    86.     end;
    87.  if (resty>0) and (restx>0) then
    88.     begin
    89.       // one loop less, since we know this fits in one line of  "blocks"
    90.       psource:=GetImagePointer(ImageWidth-restx,ImageHeight-resty);    // gets pointer to pixel x,y
    91.       ptarget:=Target.GetImagePointer(0,target.ImageHeight-restx);
    92.       for i := 0 to resty- 1 do
    93.         begin
    94.           ps1:=@pbyte(psource)[rowpitchsource*i];   // ( 0,i)
    95.           ps2:=@ptarget[resty-1-i];       //  (maxx-i,0);
    96.           for j := 0 to restx - 1 do
    97.             begin
    98.               ps2[0]:=ps1[j];
    99.               inc(pbyte(ps2),RowPitchTarget);
    100.             end;
    101.        end;
    102.     end;
    103. end;
    Last edited by marcov; 21-Jan-18 at 22:21.

  3. #3
    Ik dacht dat dat wel simpel zou zijn, maar de meeste voorbeelden zijn verrassend ingewikkeld. Uiteindelijk kwam ik uit op de Gdi+ ImageRotateFlip functie, die je natuurlijk weer in kan pakken in een functie die een TBitmap slikt:

    Delphi Code:
    1. uses
    2.   GDIPAPI, GDIPOBJ;
    3.  
    4. procedure RotateFlipBitmap(Bitmap: TBitmap; RotateFlipType: TRotateFlipType);
    5. var
    6.   GdipBitmap: Pointer;
    7.   NewBitmap: HBITMAP;
    8. begin
    9.   GdipCreateBitmapFromHBITMAP(Bitmap.Handle, 0, GdipBitmap);
    10.   GdipImageRotateFlip(GdipBitmap, RotateFlipType);
    11.   GdipCreateHBITMAPFromBitmap(GdipBitmap, NewBitmap, 0);
    12.   Bitmap.Handle := NewBitmap;
    13. end;

    En dan aanroepen met een bitmap naar keuze:

    Delphi Code:
    1. RotateFlipBitmap(Image1.Picture.Bitmap, Rotate90FlipNone);

    Bovenstaande regel, met een image van ca 2000x1500x24bit draait in ca 33ms. Ongetwijfeld veel trager dan de functies van Marco, maar vast sneller dan via de Pixels property van een canvas.
    Last edited by GolezTrol; 21-Jan-18 at 22:35.
    1+1=b

  4. #4
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Dat is lastig te zeggen zonder machine info. Met name de generatie en cache size is heel belangrijk, want je image is 2k*1.5k*3=9mbyte.

    Zelf doe ik het daar ook niet goed. Ik zit op een oude i7-3770 in dezelfde regionen (24ms voor 4x4k zw=16 MByte , maar een copy is maar 4ms, dus er is nog een factor 6 aan verbetering mogelijk, zoals "zwaardere" loop tiling. Kleur is natuurlijk al een factor 3 meer looptiling dan zw)

    Het handwerk is natuurlijk wel meer portable, en bruikbaar in threads (GDI+ ?)

    We roteren alleen in de kleinere beelden (vereenvoudigt de beeld verwerking), voor de echt gigantische beelden (denk 75-132MByte) roteren we alleen voor visualisatie in de shader, en passen de metingen aan qua coordinaten systeem. Is anders geen doen.
    Last edited by marcov; 21-Jan-18 at 23:00.

  5. #5
    Stijn Sanders develyoy's Avatar
    Join Date
    Jun 2008
    Location
    GentBrugge, Belgi?½
    Posts
    1,046
    Mocht je nog opties zoeken: ikzelf zou bij kleine of gewone beelden PlgBlt gebruiken, of bij grotere beelden GraphicsMagick

  6. #6
    Senior Member Wok's Avatar
    Join Date
    Dec 2002
    Location
    Alkmaar
    Posts
    2,085
    Ik ben altijd heel tevreden geweest met KDImage editor, maar of deze nog voldoende ondersteund wordt weet ik niet.
    10.4.2, Delphi2010, of Lazarus 2.2.0

  7. #7
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Kan Graphics32 dit eigenlijk? Zo ja, dan zal dat waarschijnlijk ook wel snel zijn.

  8. #8
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Quote Originally Posted by develyoy View Post
    Mocht je nog opties zoeken: ikzelf zou bij kleine of gewone beelden PlgBlt gebruiken ...

    Is de opmerking

    Scaling, translation, and reflection transformations are allowed in the source device context; however, rotation and shear transformations are not.
    in die URL dan verouderd? Of is dat een ander soort rotatie?

  9. #9
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Quote Originally Posted by EricLang View Post
    Kan Graphics32 dit eigenlijk? Zo ja, dan zal dat waarschijnlijk ook wel snel zijn.
    Niet echt lijkt het. gr32.rotate90 is de meest basic implementatie die er is, tien regels die elke beginnende programmeur kan bedenken. De bovenstaande looptiling code is 2-8 keer sneller afhankelijk van bitmap grootte en processor

  10. #10
    Silly member NGLN's Avatar
    Join Date
    Aug 2004
    Location
    Werkendam
    Posts
    5,133
    Quote Originally Posted by marcov View Post
    Is die opmerking in die URL dan verouderd? Of is dat een ander soort rotatie?
    Marco, die opmerking betreft transformaties die in de Device Context zijn ingesteld met bijvoorbeeld SetWorldTransform.

    Hier nog een aantal voorbeelden van rotatie met SetWorldTransform, PlgBlt, Graphics32, en GDI+. Hoewel allemaal uitgewerkt voor willekeurige rotatiehoeken zal het voor 90° ook wel werken.
    (Sender as TNLDUser).Signature := 'Groeten van Albert';

  11. #11
    Ja, ik had PlgBlt ook gevonden, maar de voorbeelden leken voor deze situatie nodeloos complex, en diverse opmerkingen suggereerden dat het voor roteren niet vlekkeloos werkt. Daarbij kan ik me voorstellen dat je door de willekeurige rotatiehoeken ook wat bijwerkingen krijgt, wellicht ook als je wel precies 90 graden roteert.

    Maar als het wel goed werkt en je het op een soortgelijke manier wrapt, dan heb je natuurlijk het voordeel dat je een generiekere, krachtigere functie hebt, die in gebruik net zo eenvoudig is.
    1+1=b

  12. #12
    Quote Originally Posted by GolezTrol View Post
    Ik dacht dat dat wel simpel zou zijn, maar de meeste voorbeelden zijn verrassend ingewikkeld. Uiteindelijk kwam ik uit op de Gdi+ ImageRotateFlip functie, die je natuurlijk weer in kan pakken in een functie die een TBitmap slikt:

    Delphi Code:
    1. uses
    2.   GDIPAPI, GDIPOBJ;
    3.  
    4. procedure RotateFlipBitmap(Bitmap: TBitmap; RotateFlipType: TRotateFlipType);
    5. var
    6.   GdipBitmap: Pointer;
    7.   NewBitmap: HBITMAP;
    8. begin
    9.   GdipCreateBitmapFromHBITMAP(Bitmap.Handle, 0, GdipBitmap);
    10.   GdipImageRotateFlip(GdipBitmap, RotateFlipType);
    11.   GdipCreateHBITMAPFromBitmap(GdipBitmap, NewBitmap, 0);
    12.   Bitmap.Handle := NewBitmap;
    13. end;

    En dan aanroepen met een bitmap naar keuze:

    Delphi Code:
    1. RotateFlipBitmap(Image1.Picture.Bitmap, Rotate90FlipNone);

    Bovenstaande regel, met een image van ca 2000x1500x24bit draait in ca 33ms. Ongetwijfeld veel trager dan de functies van Marco, maar vast sneller dan via de Pixels property van een canvas.
    Hoi GolezTrol, je code werkt prachting, veel sneller (30x sneller) dan mijn oorspronkelijke code. Ik meet een rotatietijd in de buurt van 0,11 seconden (geen milliseconden). Ik heb die tijd gemeten met "QueryPerformanceFrequency" en "QueryPerformanceCounter". Ik werk met windows7, 12 Gb ram, AMD Phenom II op 3,5 Ghz.
    Al met al is 0,11 seconden per bitmap een meer dan acceptabel resultaat voor 2500 x 1600 x 24 bit beeldjes.

    Mijn code ziet er nu zo uit:
    Code:
    uses ...,...,Jpeg;
    
    type TRichting = (rilinks, rirechts);
    
    var Jpeg: TJpegImage;
        Image_: TImage;
    
    procedure RotateFlipBitmap(Bitmap: TBitmap; RotateFlipType: TRotateFlipType);
    var
      GdipBitmap: Pointer;
      NewBitmap: HBITMAP;
    begin
      GdipCreateBitmapFromHBITMAP(Bitmap.Handle, 0, GdipBitmap);
      GdipImageRotateFlip(GdipBitmap, RotateFlipType);
      GdipCreateHBITMAPFromBitmap(GdipBitmap, NewBitmap, 0);
      Bitmap.Handle := NewBitmap;
    end;
    
    procedure TForm1.Roteer(Richting: TRichting);
    var BM : TBitmap;
    begin
      Screen.Cursor := CrHourGlass;
    
    {$D-}
    {$O+}
    {$Q-}
    {$R-}
    {$C-}
    {$T-}
      with Image_ do
      begin
    
        Jpeg.LoadFromFile(Hint); // Hint bevat de Jpeg filenaam
    
        Picture.Bitmap.Assign(Jpeg); // make sure the image is in a reachable bitmap
       
        if Richting = riLinks
        then RotateFlipBitmap(Picture.Bitmap, Rotate270FlipNone) // 270 gegrees right rotation
        else RotateFlipBitmap(Picture.Bitmap, Rotate90FlipNone); // 90 degrees right rotation
       
        Jpeg.Assign(Picture.Bitmap); // get the processed bitmap
    
        // save as Jpeg file
        Jpeg.SaveToFile(Hint);
    
      end;
    
    {$D+}
    {$O-}
    {$Q+}
    {$R+}
    {$C+}
    {$T+}
    
      Screen.Cursor := CrDefault;
    end
    Ik zie nu wel dat ik erg veel tijd verlies met
    - het copieren van de JpegImage inhoud naar de bitmap die ik later bewerk: Picture.Bitmap.Assign(Jpeg); kost 0,45 seconden!
    Raar maar waar: het terugkopiëren van de Picture bitmap naar de JpegImage kost veel minder tijd: Jpeg.Assign(Picture.Bitmap); kost 1 milliseconde!
    - het saven van de Jpeg image kost 0,6 seconden.

    Zoals je ziet gaat er veel meer tijd verloren vóór en ná de roteerbewerking.

    De reden van het gebruik van een JpegImage voor het ophalen en het saven is:
    - een TImage laadt wel de Jpeg file goed in, maar zet de inhoud niet in zijn bitmap (die ik wil bewerken)
    - de bitmap van een TJpegImage is protected, dus daarom kopieer in de Jpeg inhoud naar Image_.Picture.Bitmap (die ik wel kan bewerken)
    - Een TImage.Picture.SaveToFile doet dat waarschijnlijk altijd in .bmp formaat, het is in elk geval geen jpeg, ondanks dat ik die fileextentie gebruik. Dus daarom kopieer ik de bewerkte bitmap terug naar het JpegImage en doe daar de SaveTofile.

    Zou ik van deze nieuwe punten een aparte thread maken, tenslotte hebben ze niks te maken met het roteren van een plaatje maar met inlezen en saven van Jpeg plaatjes?

    Welbedankt!
    Last edited by Dany; 24-Jan-18 at 21:12.
    Vriendelijke groeten,
    Dany

  13. #13
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Kijk eens of je de compressie van de jpeg kan configureren. Het op de lichtste compressie stand zetten kan veel schelen. (dat is waarschijnlijk waarom de assign vrijwel gratis is (alleen kopieer naar interne bitmap) en de save duur (de compressie))

  14. #14
    0.11 seconden is 110ms. Dat is iets trager dan wat ik had gemeten, maar vergelijkbaar genoeg om het te kunnen verklaren aan de hand van de hardware en de afmetingen van de afbeelding. Het verbaast me niet zo dat het converteren van en naar jpg relatief veel tijd kost. Dat is tenslotte een ingewikkeldere operatie dan het verplaatsen van pixels. Bestand opslaan is logischerwijs trager vanwege de Disk IO.

    Wat je zegt over de omzettingen klopt, in ieder geval deels. TPicture is een abstractie die een graphic van een bepaald type (bijvoorbeeld TBitmap of TJpegImage) kan bevatten. Als je er en jpg in stopt, dan krijg je er inderdaad niet automatisch een bitmap uit. Het zou wel kunnen zijn dat als je TPicture.Bitmap opvraagt, dat hij dan de graphic in kwestie omzet naar een bitmap, maar als dat al zo is, zal dat denk ik niet wezenlijk anders of sneller zijn dan wat je nu doet.

    Als je veel afbeeldingen wilt converten, dan is het inderdaad misschien handig om aparte threads te maken. Je kan dan de hele riedel (laden, converteren, roteren, opslaan) als een geheel uitvoeren, maar voor meerdere afbeeldingen tegelijk. Het nadeel is dat je dan mogelijk met zoveel threads tegelijk plaatjes gaat laden en opslaan, wat niet echt bevordelijk is voor de lees- en schrijfsnelheid, zeker als je een spinning disc hebt.

    Het is dan wellicht beter om een aparte thread te hebben voor de disk IO, die direct het volgende plaatje gaat lezen, terwijl een andere thread verder gaat met het converteren van de afbeelding.
    1+1=b

  15. #15
    Quote Originally Posted by marcov View Post
    Kijk eens of je de compressie van de jpeg kan configureren. Het op de lichtste compressie stand zetten kan veel schelen. (dat is waarschijnlijk waarom de assign vrijwel gratis is (alleen kopieer naar interne bitmap) en de save duur (de compressie))
    Hoi Marcov, ik heb inderdaad een paar tijden iets naar beneden kunnen halen door het volgende te doen:
    Code:
    Jpeg.GrayScale := true;
    Jpeg.Performance := jpBestSpeed;
    - het copieren van de JpegImage inhoud naar de bitmap die ik later bewerk: Picture.Bitmap.Assign(Jpeg); kost 0,45 seconden! -> nog minder dan 100ms na de wijziging (hoofdzakelijk door de performance parameter).
    - het saven van de Jpeg image kost 0,6 seconden. -> nog 0,4 seconden na de wijziging (hoofdzakelijk door de GrayScale parameter).

    Ik zit nu op een totaaltijd van 0,5s per plaatje, wat voor mij meer dan acceptabel is!

    Welbedankt!
    Last edited by Dany; 25-Jan-18 at 19:35.
    Vriendelijke groeten,
    Dany

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •