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++ проектом… аццко 🙂