From 638d138729f779fec0697f0ac049a2074c02d59e Mon Sep 17 00:00:00 2001 From: Kevin Ollivier Date: Thu, 29 May 2008 17:04:18 +0000 Subject: [PATCH] Implementing wxScreenDC::GetAsBitmap on Mac. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@53822 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- build/bakefiles/files.bkl | 2 + configure.in | 4 +- include/wx/mac/carbon/dcscreen.h | 3 + include/wx/mac/common/glgrab.h | 22 ++++ src/mac/carbon/dcscreen.cpp | 39 +++++- src/mac/common/glgrab.c | 213 +++++++++++++++++++++++++++++++ 6 files changed, 277 insertions(+), 6 deletions(-) create mode 100644 include/wx/mac/common/glgrab.h create mode 100644 src/mac/common/glgrab.c diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl index cfef7d3bcb..780ae525b2 100644 --- a/build/bakefiles/files.bkl +++ b/build/bakefiles/files.bkl @@ -2053,12 +2053,14 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file! + src/mac/common/glgrab.c src/mac/corefoundation/gsockosx.cpp src/mac/corefoundation/hid.cpp src/mac/corefoundation/utilsexc_cf.cpp + wx/mac/common/glgrab.h diff --git a/configure.in b/configure.in index 8682b37266..1fd15cfaad 100644 --- a/configure.in +++ b/configure.in @@ -4185,7 +4185,7 @@ if test "$wxUSE_OPENGL" = "yes"; then if test "$wxUSE_MGL" = 1 -o "$wxUSE_DFB" = "1"; then AC_MSG_WARN([wxGLCanvas not implemented for this port, library will be compiled without it.]) wxUSE_OPENGL="no" - elif test "$wxUSE_MAC" = 1 -o "$wxUSE_COCOA" = "1"; then + elif test "$wxUSE_COCOA" = "1"; then OPENGL_LIBS="-framework OpenGL -framework AGL" elif test "$wxUSE_MSW" = 1; then OPENGL_LIBS="-lopengl32 -lglu32" @@ -7779,7 +7779,7 @@ if test "$wxUSE_MAC" = 1 ; then fi fi if test "$USE_DARWIN" = 1; then - LDFLAGS="$LDFLAGS -framework IOKit -framework Carbon -framework Cocoa -framework System -framework QuickTime" + LDFLAGS="$LDFLAGS -framework IOKit -framework Carbon -framework Cocoa -framework System -framework QuickTime -framework OpenGL -framework AGL" else LDFLAGS="$LDFLAGS -lCarbonLib" fi diff --git a/include/wx/mac/carbon/dcscreen.h b/include/wx/mac/carbon/dcscreen.h index 052f5fb7b8..34105cc128 100644 --- a/include/wx/mac/carbon/dcscreen.h +++ b/include/wx/mac/carbon/dcscreen.h @@ -23,11 +23,14 @@ class WXDLLEXPORT wxScreenDC: public wxWindowDC wxScreenDC(); virtual ~wxScreenDC(); + wxBitmap DoGetAsBitmap(const wxRect*) const; + // Compatibility with X's requirements for // drawing on top of all windows static bool StartDrawingOnTop(wxWindow* WXUNUSED(window)) { return TRUE; } static bool StartDrawingOnTop(wxRect* WXUNUSED(rect) = NULL) { return TRUE; } static bool EndDrawingOnTop() { return TRUE; } + private: #if wxMAC_USE_CORE_GRAPHICS void* m_overlayWindow; diff --git a/include/wx/mac/common/glgrab.h b/include/wx/mac/common/glgrab.h new file mode 100644 index 0000000000..b021b04b57 --- /dev/null +++ b/include/wx/mac/common/glgrab.h @@ -0,0 +1,22 @@ +#import + +#if defined __cplusplus + extern "C" { +#endif + + CGImageRef grabViaOpenGL(CGDirectDisplayID display, CGRect srcRect); + +#if defined __cplusplus + } +#endif +#import + +#if defined __cplusplus + extern "C" { +#endif + + CGImageRef grabViaOpenGL(CGDirectDisplayID display, CGRect srcRect); + +#if defined __cplusplus + } +#endif diff --git a/src/mac/carbon/dcscreen.cpp b/src/mac/carbon/dcscreen.cpp index 74af9accb5..6dc8b4d83f 100644 --- a/src/mac/carbon/dcscreen.cpp +++ b/src/mac/carbon/dcscreen.cpp @@ -14,14 +14,11 @@ #include "wx/dcscreen.h" #include "wx/mac/uma.h" +#include "wx/mac/common/glgrab.h" #include "wx/graphics.h" IMPLEMENT_DYNAMIC_CLASS(wxScreenDC, wxWindowDC) -// TODO : for the Screenshot use case, which doesn't work in Quartz -// we should do a GetAsBitmap using something like -// http://www.cocoabuilder.com/archive/message/cocoa/2005/8/13/144256 - // Create a DC representing the whole screen wxScreenDC::wxScreenDC() { @@ -79,3 +76,37 @@ wxScreenDC::~wxScreenDC() DisposePort( (CGrafPtr) m_macPort ) ; #endif } + +wxBitmap wxScreenDC::DoGetAsBitmap(const wxRect *subrect) const +{ + CGRect srcRect = CGRectMake(0, 0, m_width, m_height); + if (subrect) + { + srcRect.origin.x = subrect->GetX(); + srcRect.origin.y = subrect->GetY(); + srcRect.size.width = subrect->GetWidth(); + srcRect.size.height = subrect->GetHeight(); + } + + wxBitmap bmp = wxBitmap(srcRect.size.width, srcRect.size.height, 32); + + CGContextRef context = (CGContextRef)bmp.GetHBITMAP(); + + CGContextSaveGState(context); + + CGContextTranslateCTM( context, 0, m_height ); + CGContextScaleCTM( context, 1, -1 ); + + if ( subrect ) + srcRect = CGRectOffset( srcRect, -subrect->x, -subrect->y ) ; + + CGImageRef image = grabViaOpenGL(kCGNullDirectDisplay, srcRect); + + wxASSERT_MSG(image, wxT("wxScreenDC::GetAsBitmap - unable to get screenshot.")); + + CGContextDrawImage(context, srcRect, image); + + CGContextRestoreGState(context); + + return bmp; +} diff --git a/src/mac/common/glgrab.c b/src/mac/common/glgrab.c new file mode 100644 index 0000000000..d97a62a168 --- /dev/null +++ b/src/mac/common/glgrab.c @@ -0,0 +1,213 @@ +/* + IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in + consideration of your agreement to the following terms, and your use, installation or modification + of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install or modify this Apple software. + + In consideration of your agreement to abide by the following terms, and subject to these + terms, Apple grants you a personal, non-exclusive license, under Apple\xd5s copyrights in + this original Apple software (the "Apple Software"), to use and modify the Apple Software, + with or without modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain this notice and + the following text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used + to endorse or promote products derived from the Apple Software without specific prior + written permission from Apple. Except as expressly tated in this notice, no other rights or + licenses, express or implied, are granted by Apple herein, including but not limited + to any patent rights that may be infringed by your derivative works or by other works + in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, + EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS + USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND + WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR + OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import +#import +#import + +#include "wx/mac/common/glgrab.h" + + +/* + * perform an in-place swap from Quadrant 1 to Quadrant III format + * (upside-down PostScript/GL to right side up QD/CG raster format) + * We do this in-place, which requires more copying, but will touch + * only half the pages. (Display grabs are BIG!) + * + * Pixel reformatting may optionally be done here if needed. + */ +static void swizzleBitmap(void * data, int rowBytes, int height) +{ + int top, bottom; + void * buffer; + void * topP; + void * bottomP; + void * base; + + + top = 0; + bottom = height - 1; + base = data; + buffer = malloc(rowBytes); + + + while ( top < bottom ) + { + topP = (void *)((top * rowBytes) + (intptr_t)base); + bottomP = (void *)((bottom * rowBytes) + (intptr_t)base); + + + /* + * Save and swap scanlines. + * + * This code does a simple in-place exchange with a temp buffer. + * If you need to reformat the pixels, replace the first two bcopy() + * calls with your own custom pixel reformatter. + */ + bcopy( topP, buffer, rowBytes ); + bcopy( bottomP, topP, rowBytes ); + bcopy( buffer, bottomP, rowBytes ); + + ++top; + --bottom; + } + free( buffer ); +} + + +/* + * Given a display ID and a rectangle on that display, generate a CGImageRef + * containing the display contents. + * + * srcRect is display-origin relative. + * + * This function uses a full screen OpenGL read-only context. + * By using OpenGL, we can read the screen using a DMA transfer + * when it's in millions of colors mode, and we can correctly read + * a microtiled full screen OpenGL context, such as a game or full + * screen video display. + * + * Returns a CGImageRef. When you are done with the CGImageRef, release it + * using CFRelease(). + * Returns NULL on an error. + */ +CGImageRef grabViaOpenGL(CGDirectDisplayID display, CGRect srcRect) +{ + CGContextRef bitmap; + CGImageRef image; + void * data; + long bytewidth; + GLint width, height; + long bytes; + CGColorSpaceRef cSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB); + + CGLContextObj glContextObj; + CGLPixelFormatObj pixelFormatObj ; + long numPixelFormats ; + CGLPixelFormatAttribute attribs[] = + { + kCGLPFAFullScreen, + kCGLPFADisplayMask, + 0, /* Display mask bit goes here */ + 0 + } ; + + + if ( display == kCGNullDirectDisplay ) + display = CGMainDisplayID(); + attribs[2] = CGDisplayIDToOpenGLDisplayMask(display); + + + /* Build a full-screen GL context */ + CGLChoosePixelFormat( attribs, &pixelFormatObj, &numPixelFormats ); + if ( pixelFormatObj == NULL ) // No full screen context support + return NULL; + CGLCreateContext( pixelFormatObj, NULL, &glContextObj ) ; + CGLDestroyPixelFormat( pixelFormatObj ) ; + if ( glContextObj == NULL ) + return NULL; + + + CGLSetCurrentContext( glContextObj ) ; + CGLSetFullScreen( glContextObj ) ; + + + glReadBuffer(GL_FRONT); + + + width = srcRect.size.width; + height = srcRect.size.height; + + + bytewidth = width * 4; // Assume 4 bytes/pixel for now + bytewidth = (bytewidth + 3) & ~3; // Align to 4 bytes + bytes = bytewidth * height; // width * height + + /* Build bitmap context */ + data = malloc(height * bytewidth); + if ( data == NULL ) + { + CGLSetCurrentContext( NULL ); + CGLClearDrawable( glContextObj ); // disassociate from full screen + CGLDestroyContext( glContextObj ); // and destroy the context + return NULL; + } + bitmap = CGBitmapContextCreate(data, width, height, 8, bytewidth, + cSpace, kCGImageAlphaNoneSkipFirst /* XRGB */); + CFRelease(cSpace); + + + /* Read framebuffer into our bitmap */ + glFinish(); /* Finish all OpenGL commands */ + glPixelStorei(GL_PACK_ALIGNMENT, 4); /* Force 4-byte alignment */ + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + + /* + * Fetch the data in XRGB format, matching the bitmap context. + */ + glReadPixels((GLint)srcRect.origin.x, (GLint)srcRect.origin.y, width, height, + GL_BGRA, +#ifdef __BIG_ENDIAN__ + GL_UNSIGNED_INT_8_8_8_8_REV, // for PPC +#else + GL_UNSIGNED_INT_8_8_8_8, // for Intel! http://lists.apple.com/archives/quartz-dev/2006/May/msg00100.html +#endif + data); + /* + * glReadPixels generates a quadrant I raster, with origin in the lower left + * This isn't a problem for signal processing routines such as compressors, + * as they can simply use a negative 'advance' to move between scanlines. + * CGImageRef and CGBitmapContext assume a quadrant III raster, though, so we need to + * invert it. Pixel reformatting can also be done here. + */ + swizzleBitmap(data, bytewidth, height); + + + /* Make an image out of our bitmap; does a cheap vm_copy of the bitmap */ + image = CGBitmapContextCreateImage(bitmap); + + /* Get rid of bitmap */ + CFRelease(bitmap); + free(data); + + + /* Get rid of GL context */ + CGLSetCurrentContext( NULL ); + CGLClearDrawable( glContextObj ); // disassociate from full screen + CGLDestroyContext( glContextObj ); // and destroy the context + + /* Returned image has a reference count of 1 */ + return image; +}