Screenshot: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(Bilddaten besorgen)
 
(13 dazwischenliegende Versionen von 7 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
= Allgemeines zum Screenshot erstellen =
+
== Allgemeines zum Screenshot erstellen ==
== Einleitung ==
+
=== Einleitung ===
Ein '''Screenshot''' ist ein Abbild des aktuellen Bildschrim Inhaltes. In dem Falle von OpenGL greifen wir aber nicht auf das Fenster zurück sondern auf den [[Framebuffer]]. Neben dem eigentlichen Vorgang, nämlich der Besorgung der Bilddaten, ist es meißtens 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.
+
Ein '''Screenshot''' ist ein Abbild des aktuellen Bildschirm Inhaltes.  
  
== Bilddaten besorgen ==
+
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.
Mit Hilfe des [[glReadPixels]] Befehl kann der [[Framebuffer]] ausgelesen werden. Für einen Screenshot sind meißtens die [[RGB]]-Werte interessant. Bei diesen Werten ergibt sich auch schon das erste Problemm OpenGL speichert die Daten in der Reinfolge Rot Grün Blau, wärend es in einigen Bildformaten (nämlich denen die auf Bitmaps aufbauen) üblich ist die Daten in der Reinfolge Blau Grün Rot zu speichern. Glücklicherweise kann man in aktuellen OpenGL Versionen [[glReadPixels]] anweisen die Daten auch in dieser Reinfolge zu liefern.
 
  
Der [[glReadPixels]] Befehl für neue Grafikarten könnte in etwa so aussehen:
+
=== Bilddaten besorgen ===
<pascal>
+
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.
   // ungünstige Reinfolge Rot Grün Blau  
+
 
 +
Der [[glReadPixels]] Befehl für neue Grafikkarten könnte in etwa so aussehen:
 +
<source lang="pascal">
 +
   // 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 neuren Grafikarten:
+
   // 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 );
</pascal>
+
</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).
 +
{{Hinweis|Möchte man, dass die Pixeldaten hintereinanderweg im Speicher erscheinen, so sollte man glPixelStoref(GL_PACK_ALIGNMENT,1) setzen, ansonten werden die Daten je nach Format durch Pixelstore weiter formatiert.}}
 +
 
 +
=== Hinweis ===
 +
Unter [[SDL]] finden sich die Bilddaten schon in der [[SDL_Surface|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.
  
Was die einzelen Parameter bedeuten, könnt ihr im [[glReadPixels]] Artikel nachlesen. Es sei hier nur gesagt das das letzte Argument der Speicherplatz ist, an den OpenGL die Daten abspeichern soll(meißt ein Pointer z.B. auf ein Array).
+
=== Speichern im BMP Format ===
  
[[Kategorie:Anleitung]]
+
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.
  
= Speichern im TGA Format =
+
Der Header sieht so aus:
 +
<source lang="pascal"> BITMAPFILEHEADER = packed record
 +
    bfType: Word;
 +
    bfSize: DWORD;
 +
    bfReserved1: Word;
 +
    bfReserved2: Word;
 +
    bfOffBits: DWORD;
 +
  end;
  
Eine TGA-Datei hat einen relativ einfachen Aufbau. Als erstes kommen Header-Informationen und dannach die Daten. Der Header sieht so aus:
+
BITMAPINFOHEADER = packed record
<pascal>type  
+
    biSize: DWORD;
 +
    biWidth: Longint;
 +
    biHeight: Longint;
 +
    biPlanes: Word;
 +
    biBitCount: Word;
 +
    biCompression: DWORD;
 +
    biSizeImage: DWORD;
 +
    biXPelsPerMeter: Longint;
 +
    biYPelsPerMeter: Longint;
 +
    biClrUsed: DWORD;
 +
    biClrImportant: DWORD;
 +
  end;</source>
 +
 
 +
Nun zur Speicherfunktion für einen Screenshot:
 +
<source lang="pascal">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;</source>
 +
 
 +
=== 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:
 +
<source lang="pascal">type  
 
   TTGAHEADER = packed record
 
   TTGAHEADER = packed record
 
     tfType : Byte;
 
     tfType : Byte;
Zeile 34: Zeile 123:
 
     tfImageDes : Byte;
 
     tfImageDes : Byte;
 
   end;
 
   end;
</pascal>
+
</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 46: Zeile 135:
 
   viewport : Array[0..3] of integer;
 
   viewport : Array[0..3] of integer;
 
begin
 
begin
 
+
  //Viewport-Größe lesen
 
   glGetIntegerv(GL_VIEWPORT, @viewport);
 
   glGetIntegerv(GL_VIEWPORT, @viewport);
 
   width := viewport[2];
 
   width := viewport[2];
 
   height := viewport[3];
 
   height := viewport[3];
  
   DataSize := ffWidth * ffHeight * 3;
+
  //Größe der Daten berechnen
 +
   DataSize := Width * Height * 3;
  
 +
  //Größe des Puffers festlegen (Speicher reservieren)
 
   SetLength(DataBuffer,DataSize);
 
   SetLength(DataBuffer,DataSize);
  
Zeile 58: Zeile 149:
 
   ZeroMemory(@tgaHeader, SizeOf(tgaHeader));
 
   ZeroMemory(@tgaHeader, SizeOf(tgaHeader));
 
   tgaHeader.tfImageType := 2; // TGA_RGB = 2
 
   tgaHeader.tfImageType := 2; // TGA_RGB = 2
   tgaHeader.tfWidth := Width  
+
   tgaHeader.tfWidth := Width;
 
   tgaHeader.tfHeight := Height;
 
   tgaHeader.tfHeight := Height;
 
   tgaHeader.tfBpp := 24;
 
   tgaHeader.tfBpp := 24;
  
 +
  //Daten von OpenGL anfordern
 
   glReadPixels(0,0,Width, Height, GL_BGR, GL_UNSIGNED_BYTE, @DataBuffer[0]);
 
   glReadPixels(0,0,Width, Height, GL_BGR, GL_UNSIGNED_BYTE, @DataBuffer[0]);
 
    
 
    
Zeile 77: Zeile 169:
 
   end;
 
   end;
 
end;
 
end;
</pascal>
+
</source>
 +
 
 +
=== 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
 +
 
 +
<source lang="pascal">uses
 +
    sdl, sdl_utils;</source>
 +
 
 +
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:
 +
 
 +
<source lang="pascal">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);
  
[[Kategorie:Anleitung]]
+
      SDL_PutPixel(Surface, x, y, pixel);
 +
    end;
 +
end;</source>
  
= Speichern im BMP Format =
+
Auch hier benötigen wir wieder den TGA-Header als Record:
  
Auch eine BMP-Datei hat einen relativ einfachen Aufbau. Als erstes kommen Header-Informationen, dann FileInfo-Informationen und dannach die Daten. Der Header sieht so aus:
+
<source lang="pascal">type
<pascal> BITMAPINFOHEADER = packed record
+
  TTGAHEADER = packed record
     biSize: DWORD;
+
     tfType  : Byte;
     biWidth: Longint;
+
     tfColorMapType : Byte;
     biHeight: Longint;
+
     tfImageType    : Byte;
     biPlanes: Word;
+
     tfColorMapSpec : Array[0..4] of Byte;
     biBitCount: Word;
+
     tfOrigX    : Word;
     biCompression: DWORD;
+
     tfOrigY    : Word;
     biSizeImage: DWORD;
+
     tfWidth    : Word;
     biXPelsPerMeter: Longint;
+
     tfHeight  : Word;
    biYPelsPerMeter: Longint;
+
     tfBpp      : Byte;
     biClrUsed: DWORD;
+
     tfImageDes : Byte;
     biClrImportant: DWORD;
 
 
   end;
 
   end;
 +
</source>
 +
 +
Nach diesen kleinen Vorbereitungen folgt eigentlich auch, wie gewohnt, der normale Code zum Speichern, nur eben mit SDL:
 +
 +
<source lang="pascal">//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;
  
BITMAPFILEHEADER = packed record
+
  rwop: PSDL_RWops;
    bfType: Word;
+
  ImageSize: Integer;
    bfSize: DWORD;
+
   i : Integer;
    bfReserved1: Word;
 
    bfReserved2: Word;
 
    bfOffBits: DWORD;
 
   end;</pascal>
 
Nun zur Speicherfunktion für ein Screenshot :
 
<pascal>procedure ScreenShot(const Name : string);
 
var F : file;
 
    FileInfo: BITMAPINFOHEADER;
 
    FileHeader : BITMAPFILEHEADER;
 
    pPicData:Pointer;
 
    Viewport : array[0..3] of integer;
 
 
begin
 
begin
  ZeroMemory(@FileHeader, SizeOf(BITMAPFILEHEADER));
+
  Result:=False;
ZeroMemory(@FileInfo, SizeOf(BITMAPINFOHEADER));
+
 
 +
  //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;
  
glGetIntegerv(GL_VIEWPORT, @Viewport);
+
  //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;
  
FileInfo.biSize := SizeOf(BITMAPINFOHEADER);
+
    //Versuchen, die Bilddaten in die Datei zu schreibenSchreiben der Bilddaten
FileInfo.biWidth := Viewport[2];
+
    if SDL_RWWrite(rwop,surface.pixels,ImageSize,1)<>1 then
FileInfo.biHeight := Viewport[3];
+
    begin
FileInfo.biPlanes := 1;
+
      writeln('Fehler beim Schreiben der Daten');
FileInfo.biBitCount := 32;
+
      exit;
FileInfo.biSizeImage := FileInfo.biWidth*FileInfo.biHeight*(FileInfo.biBitCount div 8);
+
    end;
  
FileHeader.bfType := 19778;
+
    //Scheinbar waren wir erfolgreich
FileHeader.bfOffBits := SizeOf(BITMAPINFOHEADER)+SizeOf(BITMAPFILEHEADER);
+
    Result:=True;
FileHeader.bfSize := FileHeader.bfOffBits + FileInfo.biSizeImage;
+
  finally
 +
    //Datei schließen ...
 +
    SDL_RWClose(rwop);
  
GetMem(pPicData, FileInfo.biSizeImage);
+
    //Und das Surface freigeben
 +
    SDL_FreeSurface(surface);
 +
  end;
 +
end;</source>
  
// get image as 32-bit format
+
That's it. Have a nice day!
glReadPixels(0, 0, Viewport[2], Viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, pPicData);
 
// glReadPixels(0, 0, FileInfo.biWidth, FileInfo.biHeight, GL_BGRA, GL_UNSIGNED_BYTE, pPicData);
 
  
  AssignFile(f, name);
+
== Siehe Auch ==
  Rewrite( f,1 );
+
[[Tutorial Renderpass]] (Dieses Tutorial erklärt wie man das gerenderte zu anderen Zwecken verwenden kann.)
  BlockWrite(F, FileHeader, SizeOf(BITMAPFILEHEADER));
 
  BlockWrite(F, FileInfo, SizeOf(BITMAPINFOHEADER));
 
  BlockWrite(F, pPicData^, FileInfo.biSizeImage );
 
  CloseFile(f);
 
  
FreeMem(pPicData, FileInfo.biSizeImage);
+
[[Kategorie:Anleitung]] [[Kategorie:Technik_oder_Algorithmus]]
end;</pascal>
 

Aktuelle Version vom 4. September 2010, 04:14 Uhr

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).

Info DGL.png Möchte man, dass die Pixeldaten hintereinanderweg im Speicher erscheinen, so sollte man glPixelStoref(GL_PACK_ALIGNMENT,1) setzen, ansonten werden die Daten je nach Format durch Pixelstore weiter formatiert.

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.)