![]() |
PNG Vertikal spiegeln mit ScanLine
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:
Danke im Voraus!
// 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; |
AW: PNG Vertikal spiegeln mit ScanLine
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 ![]() 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. ![]() |
AW: PNG Vertikal spiegeln mit ScanLine
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. :roll: 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:
daraus habe ich mir gebastelt:
{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};
Delphi-Quellcode:
Ergibt bei RGBA also 24 --> 3 Bytes.
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; Die andere Stelle steht in Zeile 1073:
Delphi-Quellcode:
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.
{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; Somit ergibt sich für mich der folgende QuellCode:
Delphi-Quellcode:
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...
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; 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... |
AW: PNG Vertikal spiegeln mit ScanLine
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. |
AW: PNG Vertikal spiegeln mit ScanLine
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"] |
AW: PNG Vertikal spiegeln mit ScanLine
Zitat:
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); |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:35 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz