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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | #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
1 2 3 | 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++ проектом… аццко