From f06bfe37aefa9c0ae7af74112de97ae7ade0f3cb Mon Sep 17 00:00:00 2001 From: Artur Wieczorek Date: Sat, 11 Jun 2016 22:35:33 +0200 Subject: [PATCH] Fixed creating wxGraphicContext from native DC with Cairo renderer (MSW). When x- or y-coordinate of DC origin > 0 then surface created with cairo_win32_surface_create() is not fully operational (for some Cairo operations memory access violation errors occur - see Cairo bug 96482) so in this case we would need to apply a workaround and pass non-transformed DC to Cairo and next apply original DC transformation to the Cairo context operations on our own. Closes #17564 --- src/common/cairo.cpp | 2 + src/generic/graphicc.cpp | 138 +++++++++++++++++++++++++++++++-------- 2 files changed, 114 insertions(+), 26 deletions(-) diff --git a/src/common/cairo.cpp b/src/common/cairo.cpp index 7eb422d12c..3832448724 100644 --- a/src/common/cairo.cpp +++ b/src/common/cairo.cpp @@ -238,6 +238,8 @@ (), () , NULL ) \ m( cairo_surface_t*, cairo_surface_create_similar_image, \ (cairo_surface_t *other, cairo_format_t format, int width, int height), (other, format, width, height), NULL) \ + m( cairo_status_t, cairo_surface_status, \ + (cairo_surface_t *surface), (surface), CAIRO_STATUS_SUCCESS) \ wxCAIRO_PLATFORM_METHODS(m) #define wxCAIRO_DECLARE_TYPE(rettype, name, args, argnames, defret) \ diff --git a/src/generic/graphicc.cpp b/src/generic/graphicc.cpp index a2b2dd3f7b..7f8fb5ae8f 100644 --- a/src/generic/graphicc.cpp +++ b/src/generic/graphicc.cpp @@ -1869,15 +1869,15 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxMemoryDC& HDC hdc = (HDC)dc.GetHDC(); m_mswStateSavedDC = ::SaveDC(hdc); - bool hasBitmap = false; + bool hasBitmap = false; // To signal that Cairo context is created + // from raw bitmap and not from DC. + bool adjustTransformFromDC = false; // To signal that we have to transfer + // transformation settings from source + // wxDC to Cairo context on our own. // cairo_win32_surface_create creates a 24-bit bitmap, // so if we 32bpp bitmap, we need to create a 32-bit surface instead. - if (bmp.GetDepth() < 32) - { - m_mswSurface = cairo_win32_surface_create(hdc); - } - else + if ( bmp.GetDepth() == 32 ) { #if wxUSE_WXDIB // We need to convert the currently selected bitmap to a DIB @@ -1892,7 +1892,7 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxMemoryDC& bmp.ConvertToDIB(); // Does nothing if already a DIB. - if (!bmp.HasAlpha()) + if ( !bmp.HasAlpha() ) { // Initialize alpha channel, even if we don't have any alpha yet, // we should have correct (opaque) alpha values in it for Cairo @@ -1940,8 +1940,20 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxMemoryDC& info.bmWidth, info.bmHeight, info.bmWidthBytes); - - hasBitmap = true; + if ( cairo_surface_status(m_mswSurface) == CAIRO_STATUS_SUCCESS ) + { + hasBitmap = true; + // We will have to transfer transformation settings + // from source wxDC to Cairo context on our own + // because in this case they are not inherited + // implicitly from underlying DC. + adjustTransformFromDC = true; + } + else + { + cairo_surface_destroy(m_mswSurface); + m_mswSurface = NULL; + } } else { @@ -1952,29 +1964,52 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, const wxMemoryDC& { wxLogLastError( wxS("wxCairoContext ctor - GetObject") ); } + } - // Fallback if we failed to create Cairo surface from 32bpp bitmap. - if( !hasBitmap ) + // Fallback if Cairo surface hasn't been created from 32bpp bitmap. + if( !hasBitmap ) + { + // When x- or y-coordinate of DC origin > 0 then surface + // created from DC is not fully operational (for some Cairo + // operations memory access violation errors occur - see Cairo + // bug 96482) so in this case we would need to pass non-transformed + // DC to Cairo and to apply original DC transformation to the Cairo + // context operations on our own. + // We believe this bug will be fixed in the next Cairo version. +#if CAIRO_VERSION <= CAIRO_VERSION_ENCODE(1, 15, 2) + if ( cairo_version() <= CAIRO_VERSION_ENCODE(1, 15, 2) ) { - m_mswSurface = cairo_win32_surface_create(hdc); + wxCoord orgX, orgY; + dc.GetDeviceOrigin(&orgX, &orgY); + if ( orgX > 0 || orgY > 0 ) + { + ::SetViewportOrgEx(hdc, 0, 0, NULL); + ::SetViewportExtEx(hdc, 1, 1, NULL); + ::SetWindowOrgEx(hdc, 0, 0, NULL); + ::SetWindowExtEx(hdc, 1, 1, NULL); + adjustTransformFromDC = true; + } } +#endif // Cairo <= 1.15.2 + + m_mswSurface = cairo_win32_surface_create(hdc); + wxASSERT_MSG( cairo_surface_status(m_mswSurface) == CAIRO_STATUS_SUCCESS, + wxS("wxCairoContext ctor - Error creating Cairo surface") ); } Init( cairo_create(m_mswSurface) ); - // If we've created a image surface, we need: - // 1. To flip the Y axis so that all drawing will appear right side up. - // 2. To explicitly transfer transformations settings from source DC - // to Cairo context because they are not inherited implicitly. - // We have to remember these operations as an internal transformation - // which is not going to be exposed through e.g. GetTransform(). if (hasBitmap) { - // Flip the Y axis. + // If we've created an image surface we need to flip the Y axis + // so that all drawing will appear right side up. + // We have to remember these operations as an internal transformation + // which is not going to be exposed through e.g. GetTransform(). cairo_matrix_init(&m_internalTransform, 1.0, 0.0, 0.0, -1.0, 0.0, height); - - // Transfer transformation settings from source DC to Cairo context on our own. - ApplyTransformFromDC(dc); } + // Transfer transformation settings from source wxDC + // to Cairo context on our own, if required. + if ( adjustTransformFromDC ) + ApplyTransformFromDC(dc); #endif // __WXMSW__ #ifdef __WXGTK3__ @@ -2062,8 +2097,58 @@ wxCairoContext::wxCairoContext( wxGraphicsRenderer* renderer, HDC handle ) : wxGraphicsContext(renderer) { m_mswStateSavedDC = ::SaveDC(handle); + + bool adjustTransformFromDC = false; // To signal that we have to transfer + // transformation settings from source + // wxDC to Cairo context on our own. + cairo_matrix_t dcTransform; + cairo_matrix_init_identity(&dcTransform); + // When x- or y-coordinate of DC origin > 0 then surface + // created from DC is not fully operational (for some Cairo + // operations memory access violation errors occur - see Cairo + // bug 96482) so in this case we would need to pass non-transformed + // DC to Cairo and to apply original DC transformation to the Cairo + // context operations on our own. + // We believe this bug will be fixed in the next Cairo version. +#if CAIRO_VERSION <= CAIRO_VERSION_ENCODE(1, 15, 2) + if ( cairo_version() <= CAIRO_VERSION_ENCODE(1, 15, 2) ) + { + POINT devOrg; + ::GetViewportOrgEx(handle, &devOrg); + if ( devOrg.x > 0 || devOrg.y > 0 ) + { + SIZE devExt; + ::GetViewportExtEx(handle, &devExt); + POINT logOrg; + ::GetWindowOrgEx(handle, &logOrg); + SIZE logExt; + ::GetWindowExtEx(handle, &logExt); + + double sx = (double)devExt.cx / logExt.cx; + double sy = (double)devExt.cy / logExt.cy; + + cairo_matrix_translate(&dcTransform, devOrg.x, devOrg.y); + cairo_matrix_scale(&dcTransform, sx, sy); + cairo_matrix_translate(&dcTransform, -logOrg.x, -logOrg.y); + + ::SetViewportOrgEx(handle, 0, 0, NULL); + ::SetViewportExtEx(handle, 1, 1, NULL); + ::SetWindowOrgEx(handle, 0, 0, NULL); + ::SetWindowExtEx(handle, 1, 1, NULL); + + adjustTransformFromDC = true; + } + } +#endif // Cairo <= 1.15.2 m_mswSurface = cairo_win32_surface_create(handle); Init( cairo_create(m_mswSurface) ); + if ( adjustTransformFromDC ) + { + cairo_matrix_multiply(&m_internalTransform, + &dcTransform, &m_internalTransform); + cairo_set_matrix(m_context, &m_internalTransform); + } + m_width = 0; m_height = 0; // Try to determine DC size. @@ -2088,6 +2173,7 @@ wxCairoContext::wxCairoContext(wxGraphicsRenderer* renderer, HWND hWnd) m_mswStateSavedDC = 0; m_mswSurface = cairo_win32_surface_create((HDC)m_mswWindowHDC); + Init(cairo_create(m_mswSurface)); m_width = 0; m_height = 0; @@ -2178,7 +2264,8 @@ wxCairoContext::~wxCairoContext() #ifdef __WXMSW__ if ( m_mswSurface ) { - HDC hdc = cairo_win32_surface_get_dc(m_mswSurface); +// HDC hdc = cairo_win32_surface_get_dc(m_mswSurface); + HDC hdc = 0; cairo_surface_destroy(m_mswSurface); @@ -2219,8 +2306,8 @@ void wxCairoContext::Init(cairo_t *context) void wxCairoContext::ApplyTransformFromDC(const wxDC& dc, ApplyTransformMode mode) { - // Transfer transformation settings from source DC to Cairo context - // and store it as an internal transformation + // Transfer transformation settings from source wxDC + // to Cairo context and store them as an internal transformation // (which is not going to be exposed). double sx, sy; dc.GetUserScale(&sx, &sy); @@ -2242,7 +2329,6 @@ void wxCairoContext::ApplyTransformFromDC(const wxDC& dc, ApplyTransformMode mod org = dc.GetLogicalOrigin(); cairo_matrix_translate(&m_internalTransform, -org.x, -org.y); - // Apply all above transformations. cairo_set_matrix(m_context, &m_internalTransform); }