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.
This commit is contained in:
Artur Wieczorek 2017-04-14 09:33:20 +02:00
parent f171d48be4
commit 0ac0f4b259

View File

@ -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;
}