Taking Screenshots with wxWidgets under Mac OS is Really Tricky.

Taking screenshots is a very common task and it was a must for one of my current projects. What was a surprise when I understood that my favourite toolkit can’t do that in cross-platform manner.

It is official bug that wxScreenDC does not work properly under Mac OS and you can’t use Blit() message for copying screen onto wxMemoryDC.

After digging the Internet I found a kind of solution which used OpenGL and created wxWidgets-based class which takes screenshots also under Mac OS. It was really hard task for me because I haven’t used neither Carbon nor Cocoa before. However everything works now and I’m happy.

Here it is:

wxScreenshotMaker.h

#pragma once

#include 
#ifdef __WXMAC__
#include 
#endif

class wxScreenshotMaker
{
public:
	wxScreenshotMaker();
	~wxScreenshotMaker();
	wxBitmap GetScreenshot();
private:
#ifdef __WXMAC__

	int screenWidth;
	int screenHeight;

	CGLContextObj glContextObj;
	int rowSize;
	unsigned char * glBitmapData;
	unsigned char * glRowData;
		
	void InitOpenGL();
	void GrabGLScreen();
	void SwizzleBitmap();
	void FinalizeOpenGL();
#endif
};

wxScreenshotMaker.cpp

#include "wxScreenshotMaker.h"
#ifndef __WXMAC__
#include 
#else
#include 
#include 
#include 
#endif

wxScreenshotMaker::wxScreenshotMaker()
#ifdef __WXMAC__
: screenWidth(0), screenHeight(0), glBitmapData(NULL), glRowData(NULL)
#endif
{
#ifdef __WXMAC__
	wxSize size = wxGetDisplaySize();
	screenWidth = size.GetWidth();
	screenHeight = size.GetHeight();
	InitOpenGL();
#endif
}

wxScreenshotMaker::~wxScreenshotMaker()
{
#ifdef __WXMAC__
	FinalizeOpenGL();
#endif
}

wxBitmap wxScreenshotMaker::GetScreenshot()
{
#ifndef __WXMAC__
	wxScreenDC screenDC;
	wxBitmap bmp(screenDC.GetSize().GetWidth(), screenDC.GetSize().GetHeight());
	wxMemoryDC mdc(bmp);
	mdc.Blit(0, 0, bmp.GetWidth(), bmp.GetHeight(), &screenDC, 0, 0);
	mdc.SelectObject(wxNullBitmap);
	return bmp;
#else
	if(glBitmapData)
	{
		GrabGLScreen();
		wxImage img = wxImage(screenWidth, screenHeight, glBitmapData, true);
		return wxBitmap(img.Copy());
	}
#endif
}

#ifdef __WXMAC__

void wxScreenshotMaker::InitOpenGL()
{
	do
	{
		rowSize = screenWidth * 3;
		rowSize = (rowSize + 2) & ~2;

		glRowData = (unsigned char *)realloc(glRowData, rowSize);
		glBitmapData = (unsigned char *)realloc(glBitmapData, rowSize * screenHeight);

		bzero(glRowData, rowSize);
		bzero(glBitmapData, rowSize * screenHeight);

		CGDirectDisplayID display = CGMainDisplayID();
		CGLPixelFormatObj pixelFormatObj ;
		GLint numPixelFormats;
		CGLPixelFormatAttribute attribs[] =
		{
			kCGLPFAFullScreen,
			kCGLPFADisplayMask,
			(CGLPixelFormatAttribute)0,    /* Display mask bit goes here */
			(CGLPixelFormatAttribute)0
		};

		attribs[2] = (CGLPixelFormatAttribute) CGDisplayIDToOpenGLDisplayMask(display);

		/* Build a full-screen GL context */
		CGLChoosePixelFormat( attribs, &pixelFormatObj, &numPixelFormats );
		if ( pixelFormatObj == NULL ) break;
		CGLCreateContext( pixelFormatObj, NULL, &glContextObj ) ;
		CGLDestroyPixelFormat( pixelFormatObj ) ;
		if ( glContextObj == NULL ) break;
		CGLSetCurrentContext( glContextObj ) ;
		CGLSetFullScreen( glContextObj ) ;
	}
	while(false);
}

void wxScreenshotMaker::FinalizeOpenGL()
{
    CGLSetCurrentContext( NULL );
    CGLClearDrawable( glContextObj );
    CGLDestroyContext( glContextObj );
	free(glRowData);
	free(glBitmapData);
}

void wxScreenshotMaker::GrabGLScreen()
{
	glReadBuffer(GL_FRONT);
    /* Read framebuffer into our bitmap */
    glFinish();
    glPixelStorei(GL_PACK_ALIGNMENT, 3);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
    /*
      * Fetch the data in RGB format, matching the bitmap context.
      */
    glReadPixels((GLint)0, (GLint)0, (GLint)screenWidth, (GLint)screenHeight,
		GL_RGB, GL_BYTE, glBitmapData);
	
	SwizzleBitmap();
}

void wxScreenshotMaker::SwizzleBitmap()
{
	int top, bottom;
	top = 0;
    bottom = screenHeight - 1;
	void * base = glBitmapData;
	void * topP = NULL;
	void * bottomP = NULL;
	
	while(top < bottom)
	{
		topP = (void *)((top * rowSize) + (intptr_t)base);
        bottomP = (void *)((bottom * rowSize) + (intptr_t)base);
		
		bcopy( topP, glRowData, rowSize );
        bcopy( bottomP, topP, rowSize );
        bcopy( glRowData, bottomP, rowSize );

        ++top;
        --bottom;
	}
}

#endif

Sample

wxScreenshotMaker screenshot;
m_Canvas->SetBitmap(screenshot.GetScreenshot());
m_Canvas->Refresh();

Download wxScreenshotMaker and sample project.

4 comments

  1. al.zatv   •  

    Привет! Это твой пост – T-Rex, да? Просто есть чего сказать про скриншоты:)

  2. T-Rex   •     Author

    Ооо.. а поподробнее можно?

  3. al.zatv   •  

    напиши мне через email плиз
    zatv гав bk.ru
    просто делаю много скриншотов в wxWidgets:) может, мы нечто похожее делаем?

  4. T-Rex   •     Author

    Ммм? Я делаю Mac-версию какой-то тулзы для удаленного управления рабочим столом. Хз почему они решили делат скриншотами вместо того, чтобы VNC прикрутить, мне надо сделать чтобы оно работало под маокм так же как под виндой. %)
    Тут попробовали на Cocoa – отлично работает, быстро, а с glReadPixles очень медленно, всего 2 кадра в секунду. Сейчас пробуем подружить Cocoa/Objective-C библиоеку с Carbon/C++ проектом… аццко :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please leave these two fields as-is: