From 0ac0f4b259b6be781f870602939b11d48b6e84ec Mon Sep 17 00:00:00 2001 From: Artur Wieczorek Date: Fri, 14 Apr 2017 09:33:20 +0200 Subject: [PATCH] Fix wxMemoryDC::Blit() with itself as source in wxGTK3 Drawing on the Cairo surface itself can give invalid results when source and destination regions overlap. To avoid this problem we have to copy actual source surface to the temporary one and use this copy in drawing operations. Closes #17666. --- src/gtk/dc.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/src/gtk/dc.cpp b/src/gtk/dc.cpp index 942035d114..d2d5f19cda 100644 --- a/src/gtk/dc.cpp +++ b/src/gtk/dc.cpp @@ -142,15 +142,79 @@ bool wxGTKCairoDCImpl::DoStretchBlit(int xdest, int ydest, int dstWidth, int dst const int xsrc_dev = source->LogicalToDeviceX(xsrc); const int ysrc_dev = source->LogicalToDeviceY(ysrc); - cairo_surface_t* surface = cairo_get_target(cr_src); - cairo_surface_flush(surface); + cairo_surface_t* surfaceSrc = cairo_get_target(cr_src); + cairo_surface_flush(surfaceSrc); + + cairo_surface_t* surfaceTmp = NULL; + // If destination (this) and source wxDC refer to the same Cairo context + // it means that we operate on one surface and results of drawing + // can be invalid if destination and source regions overlap. + // In such situation we have to copy source surface to the temporary + // surface and use this copy in the drawing operations. + if ( cr == cr_src ) + { + // Check if destination and source regions overlap. + bool regOverlap; +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0) + if ( cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0) ) + { + cairo_rectangle_int_t rdst; + rdst.x = xdest; + rdst.y = ydest; + rdst.width = dstWidth; + rdst.height = dstHeight; + cairo_region_t* regdst = cairo_region_create_rectangle(&rdst); + + cairo_rectangle_int_t rsrc; + rsrc.x = xsrc; + rsrc.y = ysrc; + rsrc.width = srcWidth; + rsrc.height = srcHeight; + cairo_region_overlap_t ov = cairo_region_contains_rectangle(regdst, &rsrc); + cairo_region_destroy(regdst); + regOverlap = (ov != CAIRO_REGION_OVERLAP_OUT); + } + else +#endif // Cairo 1.10 + { + wxRect rdst(xdest, ydest, dstWidth, dstHeight); + wxRect rsrc(xsrc, ysrc, srcWidth, srcHeight); + regOverlap = rdst.Intersects(rsrc); + } + // If necessary, copy source surface to the temporary one. + if ( regOverlap ) + { + const int w = cairo_image_surface_get_width(surfaceSrc); + const int h = cairo_image_surface_get_height(surfaceSrc); +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) + if ( cairo_version() >= CAIRO_VERSION_ENCODE(1, 12, 0) ) + { + surfaceTmp = cairo_surface_create_similar_image(surfaceSrc, + cairo_image_surface_get_format(surfaceSrc), + w, h); + } + else +#endif // Cairo 1.12 + { + surfaceTmp = cairo_surface_create_similar(surfaceSrc, + CAIRO_CONTENT_COLOR_ALPHA, + w, h); + } + cairo_t* crTmp = cairo_create(surfaceTmp); + cairo_set_source_surface(crTmp, surfaceSrc, 0, 0); + cairo_rectangle(crTmp, 0.0, 0.0, w, h); + cairo_set_operator(crTmp, CAIRO_OPERATOR_SOURCE); + cairo_fill(crTmp); + cairo_destroy(crTmp); + } + } cairo_save(cr); cairo_translate(cr, xdest, ydest); cairo_rectangle(cr, 0, 0, dstWidth, dstHeight); double sx, sy; source->GetUserScale(&sx, &sy); cairo_scale(cr, dstWidth / (sx * srcWidth), dstHeight / (sy * srcHeight)); - cairo_set_source_surface(cr, surface, -xsrc_dev, -ysrc_dev); + cairo_set_source_surface(cr, surfaceTmp ? surfaceTmp : surfaceSrc, -xsrc_dev, -ysrc_dev); const wxRasterOperationMode rop_save = m_logicalFunction; SetLogicalFunction(rop); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); @@ -181,6 +245,10 @@ bool wxGTKCairoDCImpl::DoStretchBlit(int xdest, int ydest, int dstWidth, int dst cairo_fill(cr); } cairo_restore(cr); + if ( surfaceTmp ) + { + cairo_surface_destroy(surfaceTmp); + } m_logicalFunction = rop_save; return true; }