Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   GDI+ Bilddrehung mit Transparenz (https://www.delphipraxis.net/199843-gdi-bilddrehung-mit-transparenz.html)

Duck-of-Devastation 25. Feb 2019 08:44

GDI+ Bilddrehung mit Transparenz
 
Moin!


ich versuche mittels GDI+ ein TBitmap (pf32Bit, bmDIP, afDefined) zu rotieren. Dieses klappt auch mit u.a. Procedure, leider geht dabei die Transparenz flöten und ich kenne mich mit GDI+ leider nicht ausreichend aus (Delphi 10.1 Berlin):


Code:
uses gdipapi, gdipobj; ...

procedure RotateBitmapGDI(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  Tmp: TGPBitmap;
  Matrix: TGPMatrix;
  C: Single;
  S: Single;
  NewSize: TSize;
  Graphs, G2: TGPGraphics;
  P: TGPPointF;
begin


  Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
 
  if tmp.getpixelformat=PixelFormatAlpha then showmessage('alpha');
  if tmp.getpixelformat=PixelFormatPAlpha then showmessage('Prealpha');
  if isalphapixelformat(tmp.GetPixelFormat) then ShowMessage('ALPHA');
  if iscanonicalpixelformat(tmp.GetPixelFormat) then ShowMessage('Canonical');
  if tmp.GetPixelFormat=PixelFormat32bppRGB then ShowMessage('32RGB');
  if isindexedpixelformat(tmp.GetPixelFormat) then ShowMessage('Indexed');
  showmessage(inttostr(getpixelformatsize(tmp.GetPixelFormat)));


  Matrix := TGPMatrix.Create;
  try
    Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));
    if AdjustSize then
    begin
      C := Cos(DegToRad(Degs));
      S := Sin(DegToRad(Degs));
      NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      Bmp.Width := NewSize.cx;
      Bmp.Height := NewSize.cy;
    end;
    Graphs := TGPGraphics.Create(Bmp.Canvas.Handle);
    try
      Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
      Graphs.SetTransform(Matrix);
      Graphs.DrawImage(tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2, (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);
    finally
      Graphs.Free;
    end;
  finally
    Matrix.Free;
    Tmp.Free;
  end;
end;

Das Problem ist offenbar, dass bei
Code:
Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);
das Pixelformat nicht übernommen wird. Ich habe alternativ ausprobiert, die TGPBitmap aus einem Stream zu laden, auch das entfernt aber leider die Transparenz. Wenn ich die tmp-Bitmap selbst mit pixelFormat32bppARGB erzeuge, bin ich leider zu doof, meine TBitmap dort reinzukopieren (ich habe versucht, zu tmp ein TGPGraphics zu erzeugen und mit DrawImage reinzumalen, aber da bin ich offenbar zu blöd zu).


Kann mir da jemand auf die Sprünge helfen?


VG

Sven

Duck-of-Devastation 27. Feb 2019 08:21

AW: GDI+ Bilddrehung mit Transparenz
 
So... ich beantworte dann mal selbst.

Vorweg: ich habe es bislang nicht hinbekommen, eine TBitmap mit 32bit und Alpha in eine TGPBitmap oder ein TGPImage zu kopieren, ohne dass der Alphakanal verschwindet. Die TGPBitmap wird immer als 32bppRGB, nicht als 32bppARGB erzeugt.
Mein Umweg läuft darüber, erst die TBitmap in ein TPNGImage zu wandeln, dieses in einen Stream zu schreiben und aus dem Stream ein TGPImage zu erzeugen:

Code:


procedure RotateBitmapGDI(Bmp: TBitmap; Degs: Integer; AdjustSize: Boolean;
  BkColor: TColor = clNone);
var
  Tmp: TGPImage;
  Matrix: TGPMatrix;
  C: Single;
  S: Single;
  NewSize: TSize;
  Graphs: TGPGraphics;
  fstream: TStream;
  tmpi: TGPIMage;
  png: TPNGImage;
begin

  //TBitmap (pf32, Alphatransparenz) nach PNG
  png:=TPngImage.Create;
  png:=Png4TransparentBitmap(bmp);

  //PNG in Stream und nach TGPImage
  fstream:=TMemoryStream.Create;
  try
   png.SaveToStream(fstream);
   fstream.Position:=0;
   tmp:=TGPImage.Create(TStreamAdapter.Create(fstream));
  finally
   fstream.Free;
   png.Free;
  end;

  //die TBitmap löschen:
  bmp.SetSize(0,0);
  bmp.SetSize(120,120);

  Matrix := TGPMatrix.Create;
  try
    Matrix.RotateAt(Degs, MakePoint(0.5 * Bmp.Width, 0.5 * Bmp.Height));   //Rotation
    if AdjustSize then
    begin
      C := Cos(DegToRad(Degs));
      S := Sin(DegToRad(Degs));
      NewSize.cx := Round(Bmp.Width * Abs(C) + Bmp.Height * Abs(S));
      NewSize.cy := Round(Bmp.Width * Abs(S) + Bmp.Height * Abs(C));
      Bmp.Width := NewSize.cx;
      Bmp.Height := NewSize.cy;
    end;
    Graphs := TGPGraphics.Create(Bmp.Canvas.Handle); //den Canvas der ursprünglichen TBitmap auswählen
    try
      //Graphs.Clear(ColorRefToARGB(ColorToRGB(BkColor)));
      Graphs.SetTransform(Matrix);
      Graphs.DrawImage(tmp, (Cardinal(Bmp.Width) - Tmp.GetWidth) div 2, (Cardinal(Bmp.Height) - Tmp.GetHeight) div 2);  //zurückschreiben
    finally
      Graphs.Free;
    end;
  finally
    Matrix.Free;
    Tmp.Free;
  end;
end;

Das ganze benutzt noch diese Hilfsprozedur:

Code:
function PNG4TransparentBitMap(bmp:TBitmap):TPNGImage;
//201011 Thomas Wassermann
var
  x, y:Integer;
  vBmpRGBA: ^TRGBAArray;
  vPngRGB: ^TRGB;
begin
  Result := TPNGImage.CreateBlank(COLOR_RGBALPHA, 8, bmp.Width , bmp.Height);
  Result.CreateAlpha;
  Result.Canvas.CopyMode:= cmSrcCopy;
  Result.Canvas.Draw(0,0,bmp);

  for y := 0 to pred(bmp.Height) do begin
    vBmpRGBA := bmp.ScanLine[y];
    vPngRGB:= Result.Scanline[y];

    for x := 0 to pred(bmp.width) do begin
      Result.AlphaScanline[y][x] := vBmpRGBA[x].A;
      if bmp.AlphaFormat in [afDefined,afPremultiplied] then begin
        if vBmpRGBA[x].A <> 0 then begin
          vPngRGB^.b:= round(vBmpRGBA[x].b/vBmpRGBA[x].A*255);
          vPngRGB^.r:= round(vBmpRGBA[x].r/vBmpRGBA[x].A*255);
          vPngRGB^.g:= round(vBmpRGBA[x].g/vBmpRGBA[x].A*255);
        end else begin
          vPngRGB^.b:= round(vBmpRGBA[x].b*255);
          vPngRGB^.r:= round(vBmpRGBA[x].r*255);
          vPngRGB^.g:= round(vBmpRGBA[x].g*255);
        end;
      end;
      inc(vPngRGB);
    end;
  end;
end;
Das ist natürlich irgendwie von-hinten-durch-die-Brust-ins-Auge und nicht gerade schnell (was in meinem Fall nichts ausmacht), aber alle anderen Varianten, die ich direkt mit der Ursprungsbitmap ausprobiert habe - ohne den Umweg über PNG - funktionierten nicht.

Ich kann aber nicht ganz glauben, dass das "normale" GDI (mittlerweile) ohne Probleme mit 32Bit-TBitmaps mit Alphatransparenz arbeitet, aber die TGPBitmaps von GDI+ die Alphainformation nicht aus einer TBitmap (direkt) übernehmen können.

Kennt jemand eine schnellere Lösung?

VG
Sven

stahli 27. Feb 2019 09:39

AW: GDI+ Bilddrehung mit Transparenz
 
Es kann völlig falsch sein, aber mal eine Überlegung:

Ist es so, dass die Transparenzfarbe in dem Pixel links oben (oder so) festgelegt ist?
Dann könntest Du möglicherweise alle Pixel drehen - außer die in den 4 Ecken.

Wie gesagt, nur eine Überlegung - die vielleicht auch totaler Quatsch ist.

Sherlock 27. Feb 2019 11:21

AW: GDI+ Bilddrehung mit Transparenz
 
Hier mal eine Alternative zu GDI+, die ich persönlich übersichtlicher finde: WIC.

Delphi-Quellcode:
uses ...., Vcl.Graphics, Winapi.Wincodec

procedure TForm24.Button1Click(Sender: TObject);
var
  myOriginal  : TWICImage;
  myRotate    : IWICBitmap;
  iDecoder    : IWICBitmapDecoder;
  iDecoderFrame: IWICBitmapFrameDecode;
  iFlipRotator : IWICBitmapFlipRotator;
  hres        : HRESULT;
  myFile      : string;
begin
  myFile := '..\..\testpilot-header.png';
  myOriginal := TWICImage.Create;
  try
    myOriginal.LoadFromFile(myFile);
    hres := myOriginal.ImagingFactory.CreateDecoderFromFilename(PWideChar(myFile), GUID_NULL, GENERIC_READ,
      WICDecodeMetadataCacheOnDemand, iDecoder);
    if Succeeded(hres) then
    begin
      hres := iDecoder.GetFrame(0, iDecoderFrame);
      if Succeeded(hres) then
      begin
        hres := myOriginal.ImagingFactory.CreateBitmapFlipRotator(iFlipRotator);
        if Succeeded(hres) then
        begin
          hres := iFlipRotator.Initialize(iDecoderFrame, WICBitmapTransformRotate90);
          if Succeeded(hres) then
          begin
            myOriginal.ImagingFactory.CreateBitmapFromSource(iFlipRotator, WICBitmapCacheOnDemand, myRotate);
            if Assigned(myRotate) then
            begin
              myOriginal.Handle := myRotate;
              myOriginal.SaveToFile('../../rotated.png');
            end;
          end;
        end;
      end;
    end;
  finally
    myOriginal.Free;
  end;
end;
Da muss sicherlich noch irgendwie etwas aufgeräumt werden. Aber es läuft.

Sherlock

TiGü 27. Feb 2019 15:49

AW: GDI+ Bilddrehung mit Transparenz
 
Zitat:

Zitat von Sherlock (Beitrag 1426521)
Geändert von Sherlock (Heute um 13:29 Uhr) Grund: eine Variable war überflüssig

Wenn du jetzt
Delphi-Quellcode:
myRotate
rausgekürzt hast, dann war das zuviel. :wink:

Duck-of-Devastation 27. Feb 2019 16:41

AW: GDI+ Bilddrehung mit Transparenz
 
Moin!

Vielen Dank für Eure Vorschläge!

Zitat:

Zitat von stahli (Beitrag 1426508)
Ist es so, dass die Transparenzfarbe in dem Pixel links oben (oder so) festgelegt ist?
Dann könntest Du möglicherweise alle Pixel drehen - außer die in den 4 Ecken.

Das ist es leider nicht, die Ausgangsbitmap hat einen "echten" Alphakanal (mit semitransparenten Pixeln).

Zitat:

Zitat von Sherlock (Beitrag 1426521)
Hier mal eine Alternative zu GDI+, die ich persönlich übersichtlicher finde: WIC.
Sherlock

Das probier ich mal aus, vielen Dank! Wobei ich mich ursprünglich für GDI+ entschieden hatte, weil das Antialiasing der rotierten Bitmap dort zu schön aussieht. Ich hatte zuerst die Rotation mit PlgBlt umgesetzt, aber das sind nach der Rotation so hässliche "Kanten" im rotierten Bild zu sehen. WIC hatte ich bislang allerdings noch gar nicht auf der Rechnung...

VG
Sven

EWeiss 27. Feb 2019 18:26

AW: GDI+ Bilddrehung mit Transparenz
 
Zitat:

Das ist es leider nicht, die Ausgangsbitmap hat einen "echten" Alphakanal (mit semitransparenten Pixeln).
Verstehe das Problem nicht.

Du lädst das Bitmap mit GDI+ wenn ich das richtig verstehe.
Danach hast du ein Graphics Image <> TBitmap.

Was hält dich nun davon ab das Image zu HBitmap zu konvertieren und dieses dann zur Rotation zu verwenden.
Weder die Farben noch der Alpha Channel wird dadurch berührt da ändert sich nichts.

GDI++ ..

Nachtrag:
Mein Ansatz.

Delphi-Quellcode:
  if img <> 0 then
  begin
  //  GetImageSize(Img, ImgW, ImgH);
    GdipGetImagePixelFormat(img, format);
    if format = PixelFormat32bppARGB then  // Accept files with alpha channel
    begin
       GdipCreateHBITMAPFromBitmap(pointer(img), hbmReturn, $00ff0000);
       if hbmReturn <> 0 then
          Result := hbmReturn;
    end;

    GdipDisposeImage(img);
  end;

gruss

Duck-of-Devastation 27. Feb 2019 18:41

AW: GDI+ Bilddrehung mit Transparenz
 
Zitat:

Zitat von EWeiss (Beitrag 1426558)
Verstehe das Problem nicht.

Du lädst das Bitmap mit GDI+ wenn ich das richtig verstehe.
Danach hast du ein Graphics Image <> TBitmap.

Was hält dich nun davon ab das Image zu HBitmap zu konvertieren und dieses dann zur Rotation zu verwenden.
Weder die Farben noch der Alpha Channel wird dadurch berührt da ändert sich nichts.

gruss

Hi...

nein, ich habe ursprünglich ein TBitmap "bmp" (aus einer ImageList, ich lade es also nicht direkt mit GDI+), das ich drehen möchte. Dazu möchte ich es mit Transparenz in eine TGPBitmap "tmp" wandeln/ kopieren, dann drehen und wieder zurückgeben.

Mit

Delphi-Quellcode:
Tmp := TGPBitmap.Create(Bmp.Handle, Bmp.Palette);

hat die TGPBitmap Tmp nachher aber das Pixelformat 32bppRGB statt 32bppARGB, der Alphakanal verschwindet bei der Umwandlung. Mein Problem ist also der Schritt von TBitmap zu TGPBitmap…


VG

EWeiss 27. Feb 2019 18:47

AW: GDI+ Bilddrehung mit Transparenz
 
Wie ich schon sagte..
TBitmap zu TGPBitmap := Graphics Image.
Es spielt keine rolle welches Format du verwendest am ende ist es immer ein Graphics Image Object.

PNG, JPG, BMP und Konsorte.

Zitat:

aus einer ImageList, ich lade es also nicht direkt mit GDI+
Und das Bitmap wenn du es schon hast hat kein Handle?
Dann reicht ein GdipCreateBitmapFromHBITMAP..

Zitat:

hat die TGPBitmap Tmp nachher aber das Pixelformat 32bppRGB
Nicht schon vorher, also in der ImageList?
Prüfe das doch mal vorher mit GdipGetImagePixelFormat.
Du musst also sicherstellen das dein Bitmap in der ImgageList auch schon im 32Bit ARGB Format vorliegt.

Teste das hier.
Du must die Bitmaps vorher in ein GDI+ fähigen Bitmap Format (Graphics Image Object) konvertieren sonst wird das nichts!
Delphi-Quellcode:
function ImageListToGPBitmap(SourceImageList: TImageList): TGPBitmap;
var
    bmp: TGPBitmap;
    g: TGPGraphics;
    dc: HDC;
    i: Integer;
    x: Integer;

    procedure GdipCheck(Status: Winapi.GDIPAPI.TStatus);
    begin
        if Status <> Ok then
            raise Exception.CreateFmt('%s', [GetStatus(Status)]);
    end;
begin
    //Note: Code is public domain. No attribution required.
    bmp := TGPBitmap.Create(SourceImageList.Width*SourceImageList.Count, SourceImageList.Height);
    GdipCheck(bmp.GetLastStatus);

    g := TGPGraphics.Create(bmp);
    GdipCheck(g.GetLastStatus);

    g.Clear($00000000);
    GdipCheck(g.GetLastStatus);

    dc := g.GetHDC;

    for i := 0 to dmGlobal.imgImages.Count-1 do
    begin
        x := i * dmGlobal.imgImages.Width;

        ImageList_DrawEx(dmGlobal.imgImages.Handle, i, dc,
                        x, 0, dmGlobal.imgImages.Width, dmGlobal.imgImages.Height,
                        CLR_NONE, CLR_DEFAULT,
                        ILD_TRANSPARENT);
    end;
    g.ReleaseHDC(dc);
    g.Free;

    Result := bmp;
end;
gruss

Sherlock 28. Feb 2019 06:48

AW: GDI+ Bilddrehung mit Transparenz
 
Zitat:

Zitat von TiGü (Beitrag 1426543)
Zitat:

Zitat von Sherlock (Beitrag 1426521)
Geändert von Sherlock (Heute um 13:29 Uhr) Grund: eine Variable war überflüssig

Wenn du jetzt
Delphi-Quellcode:
myRotate
rausgekürzt hast, dann war das zuviel. :wink:

Gnarf!
In der Tat. Ich hab die im Endergebnis 10 Minuten später nicht mehr gesehen und dachte "weg damit!"

Sherlock


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:25 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz