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 <wx/wx.h>
#ifdef __WXMAC__
#include <OpenGL/OpenGL.h>
#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 <wx/dcscreen.h>
#else
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <OpenGL/gl.h>
#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();
4 Comments
al.zatv
Привет! Это твой пост – T-Rex, да? Просто есть чего сказать про скриншоты:)
T-Rex
Ооо.. а поподробнее можно?
al.zatv
напиши мне через email плиз
zatv гав bk.ru
просто делаю много скриншотов в wxWidgets:) может, мы нечто похожее делаем?
T-Rex
Ммм? Я делаю Mac-версию какой-то тулзы для удаленного управления рабочим столом. Хз почему они решили делат скриншотами вместо того, чтобы VNC прикрутить, мне надо сделать чтобы оно работало под маокм так же как под виндой. %)
Тут попробовали на Cocoa – отлично работает, быстро, а с glReadPixles очень медленно, всего 2 кадра в секунду. Сейчас пробуем подружить Cocoa/Objective-C библиоеку с Carbon/C++ проектом… аццко 🙂