AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

PNG Vertikal spiegeln mit ScanLine

Ein Thema von berens · begonnen am 31. Mai 2021 · letzter Beitrag vom 8. Jun 2021
Antwort Antwort
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
Redeemer

Registriert seit: 19. Jan 2009
Ort: Kirchlinteln (LK Verden)
1.017 Beiträge
 
Delphi 2009 Professional
 
#2

AW: PNG Vertikal spiegeln mit ScanLine

  Alt 31. Mai 2021, 22:14
Prinzipiell richtig.
Das mit dem Alpha in ScanLine oder nicht ist mir auch schon mal aufgefallen. Ich möchte nicht ausschließen, dass das von der Version von PNGDelphi (inzwischen somit Delphi) abhängt. Ich hatte mal sehr großen Spaß (nicht!) mit Palettenbildern, wo in machen Delphi-Versionen 2BPP-Bilder plötzlich Scanlines mit 4BPP hatten, aber BitDepth 2 war. Darstellen von Palettenbildern hat aber mit beiden nicht funktioniert.

Absurde Idee:
Man könnte jetzt einen Bitmap-Header nehmen und hinter diesen einfach den Kram aus dem Grafikspeicher kippen. 32-Bit-Bitmaps sind standardmäßig ARGB (glaube ich) - könnte blöd sein. Mit V3INFOHEADER oder neuer und "Kompression"-Typ Bitfields kann man das umdefinieren. Das Beispiel hier, aber Cardinals an 0x36 und 0x3E tauschen, dann hat man RGBA. Wenn die Höhe positiv ist, ist das Bitmap falschherum. 32-Bit-Bitmaps haben keine Füllbytes, das ist auch gut. Targa ist übrigens fix BGRA.
Janni
2005 PE, 2009 PA, XE2 PA
  Mit Zitat antworten Zitat
berens

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

AW: PNG Vertikal spiegeln mit ScanLine

  Alt 1. Jun 2021, 13:15
Hallo nochmal alle, und Danke an Redeemer für Deinen Beitrag.
Also ich bin mir immer noch unsicher, was das mit den 3 bzw. 4 Bytes pro Pixel angeht.

Ich habe jetzt mal in den QuellCode der pngimage geschaut, und da gibt es zwei verschiedene Größenberechnungen für Bits/Bytes pro Pixel, und *natürlich* sind die mal wieder alle "Private", so dass ich mir die Prozeduren kopieren muss, wenn ich nicht in jedem Modul TPNGImage durch was abgeleitetes ersetzen möchte.

Jetzt kommt zu meiner Freude bei beiden Verfahren wieder zwei verschiedene Zahlen raus: Ein Mal 3 Bytes pro Pixel, ein Mal 4 Bytes pro Pixel, so dass ich nun genauso schlau bin wie vorher auch. Ich nehme an, eine Angabe bezieht sich tatsächlich auf die ByteProPixel für die ScanLine, die Andere auf die ByteProPixel beim Arbeiten mit einem Stream (lesen/schreiben, bzw. Laden/Speichern einer .png-Datei).

Bitte hasst mich nicht dafür, und es soll auch keine "Faulheit" sein, aber ich habe keine Zeit und Nerv, die *komplette* PngImage Unit jetzt solange durchzuarbeiten, bis ich sie ausreichend verstehe. Da mein Programm mit 3 Bytes / Pixel die richtige Ausgabe erzeugt, verwende ich diesen Wert, und hoffe, es wird nichts schlimmes passieren (mit den 4 Bytes / Pixel könnte imo sehr wohl etwas Schlimmes passieren!).
Falls jemand zufälligerweise das notwendige Know-How hat um die Frage zu Beantworten, bin ich natürlich sehr dankbar dafür!

---

Die eine Angabe findet sich in den pngimage Unit in Zeile 2274:
Delphi-Quellcode:
  {Obtain number of bits for each pixel}
  case ColorType of
    COLOR_GRAYSCALE, COLOR_PALETTE, COLOR_GRAYSCALEALPHA:
      case BitDepth of
        {These are supported by windows}
        1, 4, 8: SetInfo(BitDepth, TRUE);
        {2 bits for each pixel is not supported by windows bitmap}
        2 : SetInfo(4, TRUE);
        {Also 16 bits (2 bytes) for each pixel is not supported}
        {and should be transormed into a 8 bit grayscale}
        16 : SetInfo(8, TRUE);
      end;
    {Only 1 byte (8 bits) is supported}
    COLOR_RGB, COLOR_RGBALPHA: SetInfo(24, FALSE);
  end {case ColorType};
daraus habe ich mir gebastelt:
Delphi-Quellcode:
function BitsPerPixel(const ColorType, BitDepth: Byte): Integer;
begin
  Result := 0;

  case ColorType of
    COLOR_GRAYSCALE, COLOR_PALETTE, COLOR_GRAYSCALEALPHA:
      case BitDepth of
        {These are supported by windows}
        1, 4, 8: Result := BitDepth;
        {2 bits for each pixel is not supported by windows bitmap}
        2 : Result := 4;
        {Also 16 bits (2 bytes) for each pixel is not supported}
        {and should be transormed into a 8 bit grayscale}
        16 : Result := 8;
      end;
    {Only 1 byte (8 bits) is supported}
    COLOR_RGB, COLOR_RGBALPHA: Result := 24;
  end;
end;
Ergibt bei RGBA also 24 --> 3 Bytes.

Die andere Stelle steht in Zeile 1073:
Delphi-Quellcode:
{Calculates number of bytes for the number of pixels using the}
{color mode in the paramenter}
function BytesForPixels(const Pixels: Integer; const ColorType,
  BitDepth: Byte): Integer;
begin
  case ColorType of
    {Palette and grayscale contains a single value, for palette}
    {an value of size 2^bitdepth pointing to the palette index}
    {and grayscale the value from 0 to 2^bitdepth with color intesity}
    COLOR_GRAYSCALE, COLOR_PALETTE:
      Result := (Pixels * BitDepth + 7) div 8;
    {RGB contains 3 values R, G, B with size 2^bitdepth each}
    COLOR_RGB:
      Result := (Pixels * BitDepth * 3) div 8;
    {Contains one value followed by alpha value booth size 2^bitdepth}
    COLOR_GRAYSCALEALPHA:
      Result := (Pixels * BitDepth * 2) div 8;
    {Contains four values size 2^bitdepth, Red, Green, Blue and alpha}
    COLOR_RGBALPHA:
      Result := (Pixels * BitDepth * 4) div 8;
    else
      Result := 0;
  end {case ColorType}
end;
Diese gibt bei RGBA für einen Pixel den Wert 4 zurück, was -denke ich- in meinem Fall (für die Scanline ohne Alpha) falsch wäre.

Somit ergibt sich für mich der folgende QuellCode:
Delphi-Quellcode:
function BitsPerPixel(const ColorType, BitDepth: Byte): Integer;
begin
  Result := 0;

  case ColorType of
    COLOR_GRAYSCALE, COLOR_PALETTE, COLOR_GRAYSCALEALPHA:
      case BitDepth of
        {These are supported by windows}
        1, 4, 8: Result := BitDepth;
        {2 bits for each pixel is not supported by windows bitmap}
        2 : Result := 4;
        {Also 16 bits (2 bytes) for each pixel is not supported}
        {and should be transormed into a 8 bit grayscale}
        16 : Result := 8;
      end;
    {Only 1 byte (8 bits) is supported}
    COLOR_RGB, COLOR_RGBALPHA: Result := 24;
  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;
  BytesPerPixel: integer;
begin
  _Target.Assign(_Source);

  IsAlpha := _Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA];
  
  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?
    BytesPerPixel := BitsPerPixel(_Source.Header.ColorType, _Source.Header.BitDepth) div 8;
    CopyMemory(MemTarget, MemSource, _Source.Width * BytesPerPixel);;

// for X := 0 to _Target.Width - 1 do begin
// _Target.Pixels[X, Y] := _Source.Pixels[X, _Target.Height - Y - 1];
// end;
  end;
end;
Jetzt muss ich mich schon direkt ducken, denn der Nächste wird zurecht anmerken "Was, wenn das PNG in Graustufen ist und du weniger als 8 Bit pro Pixel hast, dann schlägt das Kopieren fehl!". Aber vielleicht hat derjenige ja eine bessere Idee für mich...

Gibt's denn nicht irgendwie SizeOf() oder Length() für die _Source.ScanLine, was ich anwenden kann, um die exakte Größe heraus zu bekommen? Das kann doch eigentlich kein Hexenwerk sein...
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
Redeemer

Registriert seit: 19. Jan 2009
Ort: Kirchlinteln (LK Verden)
1.017 Beiträge
 
Delphi 2009 Professional
 
#4

AW: PNG Vertikal spiegeln mit ScanLine

  Alt 1. Jun 2021, 16:45
Ich hab jetzt mal in meinen Code geschaut und 3 sollte richtig sein, da PNG Chroma und Alpha trennt. Das macht auch ein nachträgliches CreateAlpha einfacher, denn PngDelphi muss nicht jeden Pixel vergrößern.
Letzteres kannst du ausnutzen, wenn du ganz sicher gehen willst: Du erstellst ein RGB-Bild, kopierst seine verdrehten Scanlines - denn die Pixelgröße ist zu diesem Zeitpunkt garantiert nicht 4 Byte - und sagst dann CreateAlpha. Danach kopierst du die verdrehten Alpha-Scanlines.
Janni
2005 PE, 2009 PA, XE2 PA
  Mit Zitat antworten Zitat
berens

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

AW: PNG Vertikal spiegeln mit ScanLine

  Alt 7. Jun 2021, 13:29
Danke für die Antwort.

Mit blanko PNGs erzeugen habe ich ziemliche Probleme, das aber das ist ein anderes Thema. Der Code mit 24 pro Pixel passt perfekt, das löst mein Problem abschließend.

[Post zum entfernen der Markierung "Offene Frage"]
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
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.429 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: PNG Vertikal spiegeln mit ScanLine

  Alt 8. Jun 2021, 08:24
Gibt's denn nicht irgendwie SizeOf() oder Length() für die _Source.ScanLine, was ich anwenden kann, um die exakte Größe heraus zu bekommen? Das kann doch eigentlich kein Hexenwerk sein...
BytesForPixels macht das im Prinzip schon richtig.
Aber Alpha-Kanal und Farben werden in zwei separaten Images abgelegt.

COLOR_RGBALPHA:

Image für Farben (Scanline):
BytesForPixels(...,COLOR_RGB, 8);

Image für Alpha (AlphaScanline):
BytesForPixels(...,COLOR_GRAYSCALE, 8);
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:11 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