Categories: ComponentsLibraries

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 <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();

Download wxScreenshotMaker and sample project.

T-Rex

View Comments

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

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

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

Share
Published by
T-Rex

Recent Posts

Разработка кроссплатформенных модульных приложений на C++ с библиотекой wxWidgets

Введение Уже долгое время не пишу статьи о разработке, хотя сам процесс написания мне очень…

10 years ago

wxWidgets App With Plugins (Windows/Linux/Mac) – Sample Source Code

I can see that there is still a lot of topics at wxWidgets forums related…

11 years ago

wxToolBox is Now Open-Source!

I've just published the source code of wxToolBox component and a couple of sample apps at…

11 years ago

Microsoft Kinect Helper Library and Sample for wxWidgets

Microsoft released their Kinect SDK several days ago. So, for those wxWidgets developers who are…

13 years ago

wxJSON 1.1.0 Released

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to…

14 years ago

wxRuby. Оно даже работает!

Вдохновленнный читаемой нынче книгой My Job Went to India: 52 Ways to Save Your Job…

15 years ago