Screenshot: Unterschied zwischen den Versionen
Yogu (Diskussion | Beiträge) K (→Speichern im TGA-Format bei Nutzung von SDL) |
DGLBot (Diskussion | Beiträge) K (Der Ausdruck ''<pascal>(.*?)</pascal>'' wurde ersetzt mit ''<source lang="pascal">$1</source>''.) |
||
Zeile 9: | Zeile 9: | ||
Der [[glReadPixels]] Befehl für neue Grafikkarten könnte in etwa so aussehen: | Der [[glReadPixels]] Befehl für neue Grafikkarten könnte in etwa so aussehen: | ||
− | <pascal> | + | <source lang="pascal"> |
// ungünstige Reihenfolge Rot Grün Blau | // ungünstige Reihenfolge Rot Grün Blau | ||
glReadPixels( 0, 0, Breite, Hoehe, GL_RGB, GL_UNSIGNED_BYTE, Daten ); | glReadPixels( 0, 0, Breite, Hoehe, GL_RGB, GL_UNSIGNED_BYTE, Daten ); | ||
// auf neueren Grafikkarten: | // auf neueren Grafikkarten: | ||
glReadPixels( 0, 0, Breite, Hoehe, GL_BGR, GL_UNSIGNED_BYTE, Daten ); | glReadPixels( 0, 0, Breite, Hoehe, GL_BGR, GL_UNSIGNED_BYTE, Daten ); | ||
− | </ | + | </source> |
Was die einzelnen Parameter bedeuten, könnt ihr im [[glReadPixels]] Artikel nachlesen. Es sei hier nur gesagt das das letzte Argument der Speicherplatz ist, an dem OpenGL die Daten abspeichern soll (meist ein Pointer z.B. auf ein Array). | Was die einzelnen Parameter bedeuten, könnt ihr im [[glReadPixels]] Artikel nachlesen. Es sei hier nur gesagt das das letzte Argument der Speicherplatz ist, an dem OpenGL die Daten abspeichern soll (meist ein Pointer z.B. auf ein Array). | ||
Zeile 29: | Zeile 29: | ||
Der Header sieht so aus: | Der Header sieht so aus: | ||
− | <pascal> BITMAPFILEHEADER = packed record | + | <source lang="pascal"> BITMAPFILEHEADER = packed record |
bfType: Word; | bfType: Word; | ||
bfSize: DWORD; | bfSize: DWORD; | ||
Zeile 49: | Zeile 49: | ||
biClrUsed: DWORD; | biClrUsed: DWORD; | ||
biClrImportant: DWORD; | biClrImportant: DWORD; | ||
− | end;</ | + | end;</source> |
Nun zur Speicherfunktion für einen Screenshot: | Nun zur Speicherfunktion für einen Screenshot: | ||
− | <pascal>procedure ScreenShot(const Name : string); | + | <source lang="pascal">procedure ScreenShot(const Name : string); |
var F : file; | var F : file; | ||
FileInfo: BITMAPINFOHEADER; | FileInfo: BITMAPINFOHEADER; | ||
Zeile 102: | Zeile 102: | ||
FreeMem(pPicData, FileInfo.biSizeImage); | FreeMem(pPicData, FileInfo.biSizeImage); | ||
end; | end; | ||
− | end;</ | + | end;</source> |
=== Speichern im TGA Format === | === Speichern im TGA Format === | ||
Zeile 109: | Zeile 109: | ||
Der Header sieht diesmal so aus: | Der Header sieht diesmal so aus: | ||
− | <pascal>type | + | <source lang="pascal">type |
TTGAHEADER = packed record | TTGAHEADER = packed record | ||
tfType : Byte; | tfType : Byte; | ||
Zeile 122: | Zeile 122: | ||
tfImageDes : Byte; | tfImageDes : Byte; | ||
end; | end; | ||
− | </ | + | </source> |
Nun zur Speicherfunktion für ein Screenshot ohne Alpha Wert | Nun zur Speicherfunktion für ein Screenshot ohne Alpha Wert | ||
− | <pascal> | + | <source lang="pascal"> |
procedure ScreenShot(const Name : string); | procedure ScreenShot(const Name : string); | ||
var | var | ||
Zeile 168: | Zeile 168: | ||
end; | end; | ||
end; | end; | ||
− | </ | + | </source> |
=== Speichern im TGA-Format bei Nutzung von SDL === | === Speichern im TGA-Format bei Nutzung von SDL === | ||
Zeile 174: | Zeile 174: | ||
Das Speichern eines Screenshots im TGA-Format sieht auch bei Nutzung von SDL nicht wesentlich anders aus. Als erstes müssen die Units sdl und sdl_utils eingebunden sein, was aber sicherlich schon der Fall ist. Ansonsten einfach kurz | Das Speichern eines Screenshots im TGA-Format sieht auch bei Nutzung von SDL nicht wesentlich anders aus. Als erstes müssen die Units sdl und sdl_utils eingebunden sein, was aber sicherlich schon der Fall ist. Ansonsten einfach kurz | ||
− | <pascal>uses | + | <source lang="pascal">uses |
− | sdl, sdl_utils;</ | + | sdl, sdl_utils;</source> |
und wir hätten auch dies erledigt. | und wir hätten auch dies erledigt. | ||
Zeile 181: | Zeile 181: | ||
Was sich allerdings etwas unterscheided, ist, dass man gegebenenfalls einige Vorkehrungen treffen muss, um die richtige Reihenfolge der einzelnen Farbkanäle zu gewährleisten, was mit folgender Routine ein leichtes ist: | Was sich allerdings etwas unterscheided, ist, dass man gegebenenfalls einige Vorkehrungen treffen muss, um die richtige Reihenfolge der einzelnen Farbkanäle zu gewährleisten, was mit folgender Routine ein leichtes ist: | ||
− | <pascal>procedure SwapBGR(Surface: PSDL_Surface); | + | <source lang="pascal">procedure SwapBGR(Surface: PSDL_Surface); |
var | var | ||
x,y: Integer; | x,y: Integer; | ||
Zeile 205: | Zeile 205: | ||
SDL_PutPixel(Surface, x, y, pixel); | SDL_PutPixel(Surface, x, y, pixel); | ||
end; | end; | ||
− | end;</ | + | end;</source> |
Auch hier benötigen wir wieder den TGA-Header als Record: | Auch hier benötigen wir wieder den TGA-Header als Record: | ||
− | <pascal>type | + | <source lang="pascal">type |
TTGAHEADER = packed record | TTGAHEADER = packed record | ||
tfType : Byte; | tfType : Byte; | ||
Zeile 222: | Zeile 222: | ||
tfImageDes : Byte; | tfImageDes : Byte; | ||
end; | end; | ||
− | </ | + | </source> |
Nach diesen kleinen Vorbereitungen folgt eigentlich auch, wie gewohnt, der normale Code zum Speichern, nur eben mit SDL: | Nach diesen kleinen Vorbereitungen folgt eigentlich auch, wie gewohnt, der normale Code zum Speichern, nur eben mit SDL: | ||
− | <pascal>//Speichert eine Surface in eine TGA-Datei. | + | <source lang="pascal">//Speichert eine Surface in eine TGA-Datei. |
//Surface wird am Ende freigegeben. Die Videosurface sollte also vorher geblittet werden | //Surface wird am Ende freigegeben. Die Videosurface sollte also vorher geblittet werden | ||
//und diese dann weitergereicht werden. | //und diese dann weitergereicht werden. | ||
Zeile 309: | Zeile 309: | ||
SDL_FreeSurface(surface); | SDL_FreeSurface(surface); | ||
end; | end; | ||
− | end;</ | + | end;</source> |
That's it. Have a nice day! | That's it. Have a nice day! |
Version vom 10. März 2009, 19:10 Uhr
Inhaltsverzeichnis
Allgemeines zum Screenshot erstellen
Einleitung
Ein Screenshot ist ein Abbild des aktuellen Bildschirm Inhaltes.
Im Falle von OpenGL greifen wir nicht auf das Fenster zurück sondern auf den Framebuffer. Neben dem eigentlichen Vorgang des Bilddatenbesorgens ist es meistens auch noch sinnvoll die Daten dem Benutzer verfügbar zu machen. Dies geschieht meistens in Form einer Bild-Datei oder eines Eintrages in der Zwischenablage.
Bilddaten besorgen
Mit Hilfe des glReadPixels Befehls kann der Framebuffer ausgelesen werden. Für einen Screenshot sind meistens die RGB-Werte interessant. Bei diesen Werten ergibt sich auch schon das erste Problem: OpenGL speichert die Daten in der Reihenfolge Rot Grün Blau, während es in einigen Bildformaten (nämlich denen die auf Bitmaps aufbauen) üblich ist, die Daten in der Reihenfolge Blau Grün Rot zu speichern. Glücklicherweise kann man in aktuellen OpenGL Versionen glReadPixels anweisen die Daten auch in dieser Reihenfolge zu liefern.
Der glReadPixels Befehl für neue Grafikkarten könnte in etwa so aussehen:
// ungünstige Reihenfolge Rot Grün Blau
glReadPixels( 0, 0, Breite, Hoehe, GL_RGB, GL_UNSIGNED_BYTE, Daten );
// auf neueren Grafikkarten:
glReadPixels( 0, 0, Breite, Hoehe, GL_BGR, GL_UNSIGNED_BYTE, Daten );
Was die einzelnen Parameter bedeuten, könnt ihr im glReadPixels Artikel nachlesen. Es sei hier nur gesagt das das letzte Argument der Speicherplatz ist, an dem OpenGL die Daten abspeichern soll (meist ein Pointer z.B. auf ein Array).
Hinweis
Unter SDL finden sich die Bilddaten schon in der Displaysurface unter pixels.
Speichern des Screenshots
Die Speicherung der Screenshots ist, sofern man erst einmal die Rohdaten der einzelnen Pixel hat, nicht mehr allzu kompliziert. Da der Computer aber ohne zusätzliche Informationen zu diesen Rohdaten später nichts mehr mit ihnen anfangen könnte, muss man einige zusätzliche Informationen mit in seine Zieldatei schreiben, die oftmals als "Header" bezeichnet werden und Informationen zu Breite, Höhe und "Pixelformat" beinhalten.
Speichern im BMP Format
Eine BMP-Datei hat einen relativ einfachen Aufbau. Als erstes kommen Header-Informationen (das Zeugs, was die Daten als Bitmap ausweist ;-)), dann FileInfo-Informationen (das, was angibt, wie groß das Bitmap ist) und schließlich die eigentlichen Bilddaten.
Der Header sieht so aus:
BITMAPFILEHEADER = packed record
bfType: Word;
bfSize: DWORD;
bfReserved1: Word;
bfReserved2: Word;
bfOffBits: DWORD;
end;
BITMAPINFOHEADER = packed record
biSize: DWORD;
biWidth: Longint;
biHeight: Longint;
biPlanes: Word;
biBitCount: Word;
biCompression: DWORD;
biSizeImage: DWORD;
biXPelsPerMeter: Longint;
biYPelsPerMeter: Longint;
biClrUsed: DWORD;
biClrImportant: DWORD;
end;
Nun zur Speicherfunktion für einen Screenshot:
procedure ScreenShot(const Name : string);
var F : file;
FileInfo: BITMAPINFOHEADER;
FileHeader : BITMAPFILEHEADER;
pPicData:Pointer;
Viewport : array[0..3] of integer;
begin
//Speicher für die Speicherung der Header-Informationen vorbereiten
ZeroMemory(@FileHeader, SizeOf(BITMAPFILEHEADER));
ZeroMemory(@FileInfo, SizeOf(BITMAPINFOHEADER));
//Größe des Viewports abfragen --> Spätere Bildgrößenangaben
glGetIntegerv(GL_VIEWPORT, @Viewport);
//Initialisieren der Daten des Headers
FileHeader.bfType := 19778; //$4D42 = 'BM'
FileHeader.bfOffBits := SizeOf(BITMAPINFOHEADER)+SizeOf(BITMAPFILEHEADER);
//Schreiben der Bitmap-Informationen
FileInfo.biSize := SizeOf(BITMAPINFOHEADER);
FileInfo.biWidth := Viewport[2];
FileInfo.biHeight := Viewport[3];
FileInfo.biPlanes := 1;
FileInfo.biBitCount := 32;
FileInfo.biSizeImage := FileInfo.biWidth*FileInfo.biHeight*(FileInfo.biBitCount div 8);
//Größenangabe auch in den Header übernehmen
FileHeader.bfSize := FileHeader.bfOffBits + FileInfo.biSizeImage;
//Speicher für die Bilddaten reservieren
GetMem(pPicData, FileInfo.biSizeImage);
try
//Bilddaten von OpenGL anfordern (siehe oben)
glReadPixels(0, 0, Viewport[2], Viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, pPicData);
//Und den ganzen Müll in die Datei schieben ;-)
//Moderne Leute nehmen dafür auch Streams ...
AssignFile(f, name);
Rewrite( f,1 );
try
BlockWrite(F, FileHeader, SizeOf(BITMAPFILEHEADER));
BlockWrite(F, FileInfo, SizeOf(BITMAPINFOHEADER));
BlockWrite(F, pPicData^, FileInfo.biSizeImage );
finally
CloseFile(f);
end;
finally
//Und den angeforderten Speicher wieder freigeben ...
FreeMem(pPicData, FileInfo.biSizeImage);
end;
end;
Speichern im TGA Format
Eine TGA-Datei hat einen ähnlich einfachen Aufbau. Als erstes kommen auch hier Header-Informationen und dannach die eigentlichen Daten.
Der Header sieht diesmal so aus:
type
TTGAHEADER = packed record
tfType : Byte;
tfColorMapType : Byte;
tfImageType : Byte;
tfColorMapSpec : Array[0..4] of Byte;
tfOrigX : Word; //Array [0..1] of Byte;
tfOrigY : Word;
tfWidth : Word;
tfHeight : Word;
tfBpp : Byte;
tfImageDes : Byte;
end;
Nun zur Speicherfunktion für ein Screenshot ohne Alpha Wert
procedure ScreenShot(const Name : string);
var
DataBuffer : array of Byte;
f : file;
tgaHeader : TTGAHEADER;
width, height : integer;
DataSize:Integer;
viewport : Array[0..3] of integer;
begin
//Viewport-Größe lesen
glGetIntegerv(GL_VIEWPORT, @viewport);
width := viewport[2];
height := viewport[3];
//Größe der Daten berechnen
DataSize := Width * Height * 3;
//Größe des Puffers festlegen (Speicher reservieren)
SetLength(DataBuffer,DataSize);
// TGA Kopf mit Daten füllen
ZeroMemory(@tgaHeader, SizeOf(tgaHeader));
tgaHeader.tfImageType := 2; // TGA_RGB = 2
tgaHeader.tfWidth := Width;
tgaHeader.tfHeight := Height;
tgaHeader.tfBpp := 24;
//Daten von OpenGL anfordern
glReadPixels(0,0,Width, Height, GL_BGR, GL_UNSIGNED_BYTE, @DataBuffer[0]);
//Datei erstellen
AssignFile(f, Name);
Rewrite( f,1 );
try
// TGA Kopf in die Datei reinschreiben
BlockWrite(F, tgaHeader, SizeOf(tgaHeader));
// Die eigentlichen Bilddaten in die Datei schreiben
BlockWrite(f, DataBuffer[0], DataSize );
finally
CloseFile(f);
end;
end;
Speichern im TGA-Format bei Nutzung von SDL
Das Speichern eines Screenshots im TGA-Format sieht auch bei Nutzung von SDL nicht wesentlich anders aus. Als erstes müssen die Units sdl und sdl_utils eingebunden sein, was aber sicherlich schon der Fall ist. Ansonsten einfach kurz
uses
sdl, sdl_utils;
und wir hätten auch dies erledigt.
Was sich allerdings etwas unterscheided, ist, dass man gegebenenfalls einige Vorkehrungen treffen muss, um die richtige Reihenfolge der einzelnen Farbkanäle zu gewährleisten, was mit folgender Routine ein leichtes ist:
procedure SwapBGR(Surface: PSDL_Surface);
var
x,y: Integer;
pixel: UInt32;
pred, pgreen, pblue, palpha: PUInt8;
red, green, blue, alpha: UInt8;
begin
for y:=0 to (Surface.h-1) do
for x:=0 to (Surface.w-1) do
begin
//reads the pixels them
pixel:=SDL_GetPixel(Surface, x, y);
pred:=@red;
pgreen:=@green;
pblue:=@blue;
palpha:=@alpha;
//read and swap color
SDL_GetRGBA(pixel,Surface.format, pred, pgreen, pblue, palpha);
pixel:=SDL_MapRGBA(Surface.format, blue, green, red, alpha);
SDL_PutPixel(Surface, x, y, pixel);
end;
end;
Auch hier benötigen wir wieder den TGA-Header als Record:
type
TTGAHEADER = packed record
tfType : Byte;
tfColorMapType : Byte;
tfImageType : Byte;
tfColorMapSpec : Array[0..4] of Byte;
tfOrigX : Word;
tfOrigY : Word;
tfWidth : Word;
tfHeight : Word;
tfBpp : Byte;
tfImageDes : Byte;
end;
Nach diesen kleinen Vorbereitungen folgt eigentlich auch, wie gewohnt, der normale Code zum Speichern, nur eben mit SDL:
//Speichert eine Surface in eine TGA-Datei.
//Surface wird am Ende freigegeben. Die Videosurface sollte also vorher geblittet werden
//und diese dann weitergereicht werden.
function SaveSurfacetoTGA(filename: String; surface: PSDL_Surface): Boolean;
var
TGAHEADER : TTGAHEADER;
rwop: PSDL_RWops;
ImageSize: Integer;
i : Integer;
begin
Result:=False;
//Prüfen, ob's was zu Speichern gibt ...
if not Assigned(surface) then
begin
writeln('Keine Surface übergeben');
exit;
end;
//Haben wir ein Pixelformat mit mindestens 24 Bit?
if surface.format.BytesPerPixel<3 then
begin
writeln('Farbtiefe nicht unterstützt');
exit;
end;
//Müssen wir die Reihenfolge der Farbkanäle anpassen?
if SDL_BYTEORDER<>SDL_BIG_ENDIAN then
begin
SwapBGR(surface);
end;
//Na gut, füllen wir den Header aus ...
with TGAHEADER do
begin
tfType:=0;
tfColorMapType:=0;
tfImageType:=2;
for i:=0 to 4 do
tfColorMapSpec[i]:=0;
tfOrigX:=0;
tfOrigY:=0;
tfWidth:=surface.w;
tfHeight:=surface.h;
tfBpp:=surface.format.BitsPerPixel;
tfImageDes:=0;
end;
//Ermitteln des benötigten Speicherplatze...
ImageSize:=surface.w*surface.h*surface.format.BytesPerPixel;
//Datei zum Schreiben öffnen.
rwop:=SDL_RWfromFile(PChar(filename),'w+b');
if rwop=nil then
begin
Writeln('Fehler beim Erstellen des Rwops');
exit;
end;
try
//Versuchen, den Header in die Datei zu schreiben
if SDL_RWWrite(rwop,@TGAHEADER,SizeOf(TGAHEADER),1)<>1 then
begin
writeln('Fehler beim Schreiben des Headers');
exit;
end;
//Versuchen, die Bilddaten in die Datei zu schreibenSchreiben der Bilddaten
if SDL_RWWrite(rwop,surface.pixels,ImageSize,1)<>1 then
begin
writeln('Fehler beim Schreiben der Daten');
exit;
end;
//Scheinbar waren wir erfolgreich
Result:=True;
finally
//Datei schließen ...
SDL_RWClose(rwop);
//Und das Surface freigeben
SDL_FreeSurface(surface);
end;
end;
That's it. Have a nice day!
Siehe Auch
Tutorial Renderpass (Dieses Tutorial erklärt wie man das gerenderte zu anderen Zwecken verwenden kann.)