From dcc41a9c88d8805d7626e87836ba05437cb44348 Mon Sep 17 00:00:00 2001 From: Artur Wieczorek <7330332+a-wi@users.noreply.github.com> Date: Thu, 24 Nov 2022 22:33:45 +0200 Subject: [PATCH] Fix creating wxCairoContext from CGContext (wxOSX) Cairo surface works properly if origin of the clipping region of CGContext is (0,0) so if it's not the case we need to apply a transformation to move it to (0,0). This shift needs to be applied as an offset added to the device coordinates when drawing to surface. Closes #22990. --- src/generic/graphicc.cpp | 59 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/generic/graphicc.cpp b/src/generic/graphicc.cpp index 554ed8c369..a93b53a05f 100644 --- a/src/generic/graphicc.cpp +++ b/src/generic/graphicc.cpp @@ -530,7 +530,10 @@ protected: #ifdef __WXMSW__ static wxPoint MSWAdjustHdcOrigin(HDC hdc); #endif // __WXMSW__ - +#ifdef __WXMAC__ + static wxPoint MacAdjustCgContextOrigin(CGContextRef cg); +#endif // __WXMAC__ + #ifdef __WXQT__ QPainter* m_qtPainter; QImage* m_qtImage; @@ -581,6 +584,10 @@ protected: #endif // __WXGTK3__ #endif // __WXGTK__ +#ifdef __WXMAC__ + CGContextRef m_cgContext; +#endif // __WXMAC__ + private: cairo_t* m_context; cairo_matrix_t m_internalTransform; @@ -2056,8 +2063,13 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxWindowDC& if ( cr ) Init(cairo_reference(cr), true); #elif defined(__WXMAC__) - CGContextRef cgcontext = (CGContextRef)dc.GetWindow()->MacGetCGContextRef(); - cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(cgcontext, width, height); + m_cgContext = (CGContextRef)dc.GetWindow()->MacGetCGContextRef(); + CGContextSaveGState(m_cgContext); + // Cairo surface works properly if origin of the clipping region of CGContext is (0,0) + // so if it's not the cae we need to move it to (0,0) and apply corrective offset to the device coordinates. + wxPoint surfOffs = MacAdjustCgContextOrigin(m_cgContext); + cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(m_cgContext, width, height); + cairo_surface_set_device_offset(surface, surfOffs.x, surfOffs.y); Init( cairo_create( surface ) ); cairo_surface_destroy( surface ); #endif @@ -2258,8 +2270,13 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxMemoryDC& #endif #ifdef __WXMAC__ - CGContextRef cgcontext = (CGContextRef)dc.GetGraphicsContext()->GetNativeContext(); - cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(cgcontext, width, height); + m_cgContext = (CGContextRef)dc.GetGraphicsContext()->GetNativeContext(); + CGContextSaveGState(m_cgContext); + // Cairo surface works properly if origin of the clipping region of CGContext is (0,0) + // so if it's not the cae we need to move it to (0,0) and apply corrective offset to the device coordinates. + wxPoint surfOffs = MacAdjustCgContextOrigin(m_cgContext); + cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(m_cgContext, width, height); + cairo_surface_set_device_offset(surface, surfOffs.x, surfOffs.y); Init( cairo_create( surface ) ); cairo_surface_destroy( surface ); #endif @@ -2383,6 +2400,10 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, cairo_t *context ) m_mswSurface = nullptr; m_mswStateSavedDC = 0; #endif // __WXMSW__ +#ifdef __WXMAC__ + m_cgContext = nullptr; +#endif // __WXMAC__ + Init( cairo_reference(context), true ); m_width = 0; m_height = 0; @@ -2425,6 +2446,10 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, wxWindow *window) m_height = sz.y; #endif // __WXMSW__ +#ifdef __WXMAC__ + m_cgContext = nullptr; +#endif // __WXMAC__ + #ifdef __WXQT__ // direct m_qtSurface is not being used yet (this needs cairo qt surface) #endif @@ -2442,6 +2467,9 @@ wxCairoContext::wxCairoContext(wxGraphicsRenderer* renderer) : m_mswSurface = nullptr; m_mswStateSavedDC = 0; #endif // __WXMSW__ +#ifdef __WXMAC__ + m_cgContext = nullptr; +#endif // __WXMAC__ Init(nullptr); m_width = 0; m_height = 0; @@ -2466,6 +2494,12 @@ wxCairoContext::~wxCairoContext() ::RestoreDC(hdc, m_mswStateSavedDC); } #endif +#ifdef __WXMAC__ + if ( m_cgContext != nullptr ) + { + CGContextRestoreGState(m_cgContext); + } +#endif // __WXMAC__ #ifdef __WXQT__ if ( m_qtPainter != nullptr ) { @@ -2592,6 +2626,21 @@ wxPoint wxCairoContext::MSWAdjustHdcOrigin(HDC hdc) } #endif // __WXMSW__ +#ifdef __WXMAC__ +/* static */ +wxPoint wxCairoContext::MacAdjustCgContextOrigin(CGContextRef cg) +{ + // Cairo surface works properly if origin of the clipping region of CGContext is (0,0) + // so if it's not the case we need to apply a transformation to move it to (0,0). + // This shift needs to be applied as an offset added to the device coordinates when drawing to surface. + + CGRect clip = CGContextGetClipBoundingBox(cg); + CGAffineTransform cgTransform = CGAffineTransformMake(1, 0, 0, 1, clip.origin.x, clip.origin.y); + CGContextConcatCTM(cg, cgTransform); + return wxPoint(-clip.origin.x, -clip.origin.y); +} +#endif // __WXMAC__ + void wxCairoContext::Clip( const wxRegion& region ) { // Create a path with all the rectangles in the region