Einzelnen Beitrag anzeigen

berens

Registriert seit: 3. Sep 2004
431 Beiträge
 
Delphi 2010 Professional
 
#1

PNG Vertikal spiegeln mit ScanLine

  Alt 31. Mai 2021, 15:02
Hallo zusammen,
ich muss ein PNG auf dem Kopf stellen.

Wen es interessiert: Ich zeichne mit OpenGL "hüsch" eine Grafik in einen Framebuffer (32-Bit RGBA) und möchte sie als PNG abspeichert. Aufgrund der Natur von OpenGL steht leider der Framebuffer immer auf dem Kopf (dieses Problem ist bekannt und lässt sich NICHT lösen - man kann nur mit Workarounds damit leben). Ich muss also vor dem Speichern die Grafik vertikal spiegeln.

Da ich unbedingt den Alpha-Kanal benötige, und Aufgrund vieler verschiedener Probleme viele verschiedene andere Lösungansätze ausscheiden (Umweg über TBitmap oder TBitmap32 u.ä.), scheint es am schlausten, die Daten erst im fertigen TPNGImage zu flippen.

Das ursprüngliche Beispiel war mit ".Pixels", was ja bekanntermaßen sehr langsam ist.
Da "ScanLine" mir bei TPNGImage leider auch nur einen nicht näher definierten Pointer zurückgibt, bin ich etwas vorsichtig damit, da ich mit dem Pointer-Gedöns auch nach all den Jahren nicht klarkomme.

Generell: Meine Lösung funktioniert. Aber ich habe fast das Gefühl, als könnte ich aus/in Arbeitsspeicher lesen/schreiben, der nicht dafür vorgesehen ist. Deshalb meine Bitten:

1) Kontrolle lesen, speziell FlipPngVertical. Ist das fachlich richtig?

2) Das "BitsForPixel"-Beispiel habe ich umgewandelt in mein selbstgestricktes GetPixelSizeForPNG. Die Benennung ist jetzt vielleicht nicht ganz so glücklich, letztendlich soll es mir die Anzahl der Bytes zurückgeben, die ein Pixel hat. Dementsprechend ergibt sich bei meinen Grafiken das Result = 4, da die Grafik COLOR_RGBALPHA ist. Entsprechend wird in FlipPngVertical bei CopyMemory mit dem Faktor 4 gearbeitet.

3) Mit dem falschen(?) Faktor 3 komme ich trotzdem auf das gleiche Ergebnis, die Grafik wird korrekt abgespeichert. Das dürfte wohl daran liegen, dass das 4te Byte pro Pixel in Wirklichkeit in ".AlphaScanline[]" abgelegt ist, und nicht als 4tes Byte in dem eigentlich ScanLine-Array? Hier würde ich also mit dem Faktor 4 immer *mehr* rauskopieren, als in Wirklichkeit in der Scanline steht (133% des Arbeitsspeicherbereichs, der eigentlich für die Scanline vorgesehen ist?). Gerade beim Schreiben der letzten Scanline kopiere ich vielleicht damit einige Bytes in den RAM aushalb des TPNGImage-Objekts? Ja/Nein, wie geht's richtig?

Delphi-Quellcode:
// Thx to https://stackoverflow.com/questions/9929387/how-to-get-the-pixelformat-or-bitdepth-of-tpngimage
function BitsForPixel(const AColorType, ABitDepth: Byte): Integer;
begin
  case AColorType of
    COLOR_GRAYSCALEALPHA: Result := (ABitDepth * 2);
    COLOR_RGB: Result := (ABitDepth * 3);
    COLOR_RGBALPHA: Result := (ABitDepth * 4);
    COLOR_GRAYSCALE, COLOR_PALETTE: Result := ABitDepth;
  else
      Result := 0;
  end;
end;

function GetPixelSizeForPNG(const AColorType: Byte): Byte;
begin
  Result := 0;
  case AColorType of
    COLOR_GRAYSCALE, COLOR_PALETTE: Result := 1;
    COLOR_GRAYSCALEALPHA: Result := 2;
    COLOR_RGB: Result := 3;
    COLOR_RGBALPHA: Result := 4;
  end;
end;

// Basierend auf einem Code-Beispiel von Gustavo Daud (Submited on 17 Apr 2006 )
procedure FlipPngVertical(_Source, _Target: TPngImage);
var
  X, Y: Integer;
  IsAlpha: Boolean;
  MemSource, MemTarget: pByteArray;
  PixelSize: Byte;

begin
  _Target.Assign(_Source);

  IsAlpha := _Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA];
  PixelSize := GetPixelSizeForPNG(_Source.Header.ColorType);
  
  for Y := 0 to _Target.Height - 1 do begin
    if IsAlpha then begin
      CopyMemory(_Target.AlphaScanline[_Target.Height - Y - 1],
        _Source.AlphaScanline[Y], _Target.Width);
    end;

    MemSource := _Source.Scanline[Y];
    MemTarget := _Target.Scanline[_Target.Height - Y - 1];

    // Nein: Es wird nur 1/3 des Bildes kopiert!
    // CopyMemory(MemTarget, MemSource, _Target.Width);

    // Ja! Wahrscheinlich weil das Bild 32-Bit ist?
    CopyMemory(MemTarget, MemSource, _Target.Width * PixelSize);

// for X := 0 to _Target.Width - 1 do begin
// _Target.Pixels[X, Y] := _Source.Pixels[X, _Target.Height - Y - 1];
// end;
  end;
end;
Danke im Voraus!
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat