W
nach dem oben beschriebenen Weg bin ich zwar fehlerlos durch das Programm gekommen, konnte dann aber am Ende nicht die Pixeldaten lesen, die ich erwartet hätte. Darauf bin ich dann nochmal in mich gegangen, habe einiges nachgelesen, und habe dann mit diesen Erfahrungen mir meinen ersten Versuch mit dem Memory Device Context wieder angeschaut.
Und schlußendlich hat es dann auch geklappt. Wichtige Infos dabei waren: GetDC(nullptr) liefert den DC (Device Context) des Bildschirms. Und bei CreateCompatibleBitmap darf nicht der HDC aus CreateDC sondern muss der aus GetDC benutzt werden! Aus diesem Missverständnis heraus resultierte auch meine Frage nach dem SelectObject (s.o.).
Weiter war wichtig, dass man zwar beim Pixelformat eine 4-Byte-Farbtiefe angegeben muss (=32Bit), wenn man den DC des Screens verwendet, aber beim Auslesen mit glReadPixels komme ich nur an die 3-Byte-Farbinformation (RGB) ran. Letzteres ist für mich aber auch völlig ausreichend.
Hier mein lauffähiger Stand:
#include "Deleter.h" // make_unique_ptr<>
#include <Windows.h>
#include <gl/GL.h>
#include <algorithm> // -> ReadImage
#include <map> // -> ReadImage
#include <vector> // -> ReadImage
#include <iostream> // std:::cerr u.a.
#include <string>
#include <system_error>
void glCheck()
{
auto const err = glGetError();
if( err != GL_NO_ERROR )
{
assert(( "glGetError() != GL_NO_ERROR", 0));
throw std::runtime_error( "glGetError=" + std::to_string(err) );
}
}
void display()
{ // simple sample
glClear(GL_COLOR_BUFFER_BIT);
glCheck();
glBegin(GL_TRIANGLES);
glColor3f( 1.0f, 0.5f, 0.2f );
glVertex2i(0, 1);
glColor3f( .8f, 0.0f, 0.0f );
glVertex2i(-1, -1);
glColor3f( 1.0f, 0.0f, 0.0f );
glVertex2i(1, -1);
glEnd();
glCheck();
glFlush();
glCheck();
}
void ReadImage( int WIDTH, int HEIGHT )
{
auto const bytesPerPixel = 3; // RGB sind 3 Byte
std::vector< unsigned char > pixels( WIDTH * HEIGHT * bytesPerPixel );
GLenum format = GL_RGB; // oder GL_COLOR_INDEX?;
glReadPixels( 0, 0, WIDTH, HEIGHT, format, GL_UNSIGNED_BYTE, &pixels[0] );
glCheck();
std::map< unsigned char, char > chars;
const char* CHARS = ".+*#O/!abcdefghijklm"; // Zeichen, die die verschiedene Farben darstellen
auto pCh = CHARS;
for( auto line=0; line < HEIGHT; ++line, std::cout << std::endl )
{
auto px = &pixels[0] + 0 + line * WIDTH * bytesPerPixel; // 0:=rot; 1:=grün; 2:=blau
for( auto column = 0; column < std::min(78, WIDTH); ++column, px += bytesPerPixel )
{
auto c = chars.find( *px );
if( c != end(chars) )
std::cout << c->second;
else
{
std::cout << *pCh;
chars.emplace( *px, *pCh++ );
if( pCh >= CHARS + 20 )
pCh = CHARS;
}
}
}
}
template< typename P >
void setPixelFormat( const P& hdc, BYTE type, DWORD flags, BYTE cColorBits )
{
using namespace std;
PIXELFORMATDESCRIPTOR pfd;
pfd = {0};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.iPixelType = type;
pfd.dwFlags = flags;
pfd.cColorBits = cColorBits;
auto const pf = ::ChoosePixelFormat( hdc.get(), &pfd );
if( pf == 0 ) {
auto const ec = ::GetLastError();
throw std::system_error( ec, std::system_category(), "ChoosePixelFormat failed" );
}
if( ::SetPixelFormat( hdc.get(), pf, &pfd ) == FALSE ) {
auto const ec = ::GetLastError();
throw std::system_error( ec, std::system_category(), "SetPixelFormat failed" );
}
}
class GlContext
{
public:
template< typename P >
explicit GlContext( const P& hDc )
: m_hDc( hDc.get() )
, m_hGlRc( ::wglCreateContext( m_hDc ) )
{
if( !m_hGlRc )
{
auto const ec = ::GetLastError();
throw std::system_error( ec, std::system_category(), "wglCreateContext failed" );
}
if( !::wglMakeCurrent( m_hDc, m_hGlRc ) )
{
auto const ec = ::GetLastError();
throw std::system_error( ec, std::system_category(), "wglMakeCurrent failed" );
}
}
~GlContext()
{
::wglMakeCurrent( nullptr, nullptr );
::wglDeleteContext( m_hGlRc );
}
GlContext( const GlContext& ) = delete;
GlContext& operator=( const GlContext& ) = delete;
private:
HDC m_hDc;
HGLRC m_hGlRc;
};
int do_main()
{
using namespace std;
auto const WIDTH = 20;
auto const HEIGHT = 20;
auto hdcSreen = GetDC( nullptr ); // If this parameter is nullptr, GetDC retrieves the DC for the entire screen.
{
auto hdcMem = make_unique_ptr< HDC, ::DeleteDC >( ::CreateCompatibleDC( hdcSreen ) );
auto hBM = make_unique_ptr< HGDIOBJ, ::DeleteObject >( ::CreateCompatibleBitmap( hdcSreen, WIDTH, HEIGHT ) ); // hier NICHT den HDC 'hdcMem' mitgeben!
::SelectObject( hdcMem.get(), hBM.get() );
setPixelFormat( hdcMem, PFD_TYPE_RGBA, PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL, 32 ); // ColorBits MUSS ==32 sein
GlContext glContext( hdcMem ); // wglCreateContext und wglMakeCurrent
display(); // OpenGL calls
ReadImage( WIDTH, HEIGHT ); // .. pixel access with glReadPixels
}
return 0;
}
int main() {
try { return do_main(); }
catch( const std::exception& ex ) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
}
und der Vollständigkeit halber auch noch der Inhalt von "Deleter.h" - hat aber nichts mit dem eigentlichen Problem zu tun:
#pragma once
#include <Windows.h>
#include <memory>
#include <system_error>
#include <type_traits>
template< typename Hnd, BOOL( __stdcall *DelType )( Hnd ) >
struct Deleter
{
void operator()( Hnd hnd ) const
{
if( hnd )
DelType( hnd );
}
};
template< typename Hnd, BOOL( __stdcall *DelType )( Hnd ) >
std::unique_ptr< typename std::remove_pointer< Hnd >::type, Deleter< Hnd, DelType > > make_unique_ptr( Hnd hnd )
{
if( !hnd )
{
auto ec = ::GetLastError();
throw std::system_error( ec, std::system_category(), "kreieren eines Objekts schlug fehl" );
}
return std::unique_ptr< typename std::remove_pointer< Hnd >::type, Deleter< Hnd, DelType > >( hnd );
}
Gruß
Werner