Make wxGC offsetting consistent across scales

In order to avoid drawing anomalies with wxGCDC on HiDPI displays, the shift
must be half a logical unit rather than half a device unit, and it needs to be
applied regardless of the scale factor. An exception is made for the zero-width
(one logical pixel) pen, which uses a half logical pixel shift. This reverts
d43b2862c3 (Fix drawing rectangle with width 1 pen using wxGCDC and HiDPI, 2023-04-25)
See #23485
This commit is contained in:
Paul Cornett 2023-11-19 17:37:22 -08:00
parent dede4b9326
commit 6857dc8e63
5 changed files with 149 additions and 153 deletions

View file

@ -70,27 +70,6 @@ static wxCompositionMode TranslateRasterOp(wxRasterOperationMode function)
return wxCOMPOSITION_INVALID;
}
namespace {
class OffsetDisabler
{
wxGraphicsContext* const m_gc;
const bool m_enable;
public:
explicit OffsetDisabler(wxGraphicsContext* gc)
: m_gc(gc)
, m_enable(gc->OffsetEnabled())
{
gc->EnableOffset(false);
}
~OffsetDisabler()
{
m_gc->EnableOffset(m_enable);
}
};
} // anonymous namespace
//-----------------------------------------------------------------------------
// wxDC bridge class
//-----------------------------------------------------------------------------
@ -991,17 +970,14 @@ void wxGCDCImpl::DoDrawRectangle(wxCoord x, wxCoord y, wxCoord w, wxCoord h)
CalcBoundingBox(wxPoint(x, y), wxSize(w, h));
if (m_pen.IsNonTransparent() && m_pen.GetWidth() == 1)
if (m_pen.IsNonTransparent())
{
// Match raster-based wxDC implementations, which draw the line
// along the inside edge of the solid rectangle
OffsetDisabler offsetDisabler(m_graphicContext);
if (w < 0) { w = -w; x -= w; }
if (h < 0) { h = -h; y -= h; }
m_graphicContext->DrawRectangle(x + 0.5, y + 0.5, w - 1, h - 1);
w--;
h--;
}
else
m_graphicContext->DrawRectangle(x, y, w, h);
m_graphicContext->DrawRectangle(x, y, w, h);
}
void wxGCDCImpl::DoDrawRoundedRectangle(wxCoord x, wxCoord y,
@ -1022,15 +998,14 @@ void wxGCDCImpl::DoDrawRoundedRectangle(wxCoord x, wxCoord y,
CalcBoundingBox(wxPoint(x, y), wxSize(w, h));
if (m_pen.IsNonTransparent() && m_pen.GetWidth() == 1)
if (m_pen.IsNonTransparent())
{
OffsetDisabler offsetDisabler(m_graphicContext);
if (w < 0) { w = -w; x -= w; }
if (h < 0) { h = -h; y -= h; }
m_graphicContext->DrawRoundedRectangle(x + 0.5, y + 0.5, w - 1, h - 1, radius);
w--;
h--;
}
else
m_graphicContext->DrawRoundedRectangle(x, y, w, h, radius);
m_graphicContext->DrawRoundedRectangle(x, y, w, h, radius);
}
void wxGCDCImpl::DoDrawEllipse(wxCoord x, wxCoord y, wxCoord w, wxCoord h)

View file

@ -450,14 +450,9 @@ public:
if (width <= 0)
return true;
// no offset if overall scale is not odd integer
double x = GetContentScaleFactor(), y = x;
cairo_user_to_device_distance(m_context, &x, &y);
if (!wxIsSameDouble(fmod(wxMin(fabs(x), fabs(y)), 2.0), 1.0))
return false;
// offset if pen width is odd integer
return wxIsSameDouble(fmod(width, 2.0), 1.0);
const int w = int(width);
return (w & 1) && wxIsSameDouble(width, w);
}
virtual void Clip( const wxRegion &region ) override;
@ -586,6 +581,8 @@ protected:
CGContextRef m_cgContext;
#endif // __WXMAC__
class OffsetHelper;
private:
cairo_t* m_context;
cairo_matrix_t m_internalTransform;
@ -1044,7 +1041,11 @@ void wxCairoPenData::Apply( wxGraphicsContext* context )
double width = m_width;
if (width <= 0)
{
double x = context->GetContentScaleFactor(), y = x;
double x = 1, y = x;
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,14,0)
if (cairo_version() >= CAIRO_VERSION_ENCODE(1,14,0))
cairo_surface_get_device_scale(cairo_get_target(ctext), &x, &y);
#endif
cairo_user_to_device_distance(ctext, &x, &y);
width = 1 / wxMin(fabs(x), fabs(y));
}
@ -1954,29 +1955,45 @@ wxCairoBitmapData::~wxCairoBitmapData()
// wxCairoContext implementation
//-----------------------------------------------------------------------------
class wxCairoOffsetHelper
class wxCairoContext::OffsetHelper
{
public :
wxCairoOffsetHelper(cairo_t* ctx, double scaleFactor, bool offset)
OffsetHelper(bool shouldOffset, cairo_t* cr, const wxGraphicsPen& pen)
{
m_ctx = ctx;
m_offset = 0;
if (offset)
m_shouldOffset = shouldOffset;
if (!shouldOffset)
return;
m_cr = cr;
m_offsetX = m_offsetY = 0.5;
const double width = static_cast<wxCairoPenData*>(pen.GetRefData())->GetWidth();
if (width <= 0)
{
double x = scaleFactor, y = x;
cairo_user_to_device_distance(ctx, &x, &y);
m_offset = 0.5 / wxMin(fabs(x), fabs(y));
cairo_translate(m_ctx, m_offset, m_offset);
// For 1-pixel pen width, offset by half a device pixel
double sx = 1, sy = sx;
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,14,0)
if (cairo_version() >= CAIRO_VERSION_ENCODE(1,14,0))
cairo_surface_get_device_scale(cairo_get_target(cr), &sx, &sy);
#endif
cairo_user_to_device_distance(cr, &sx, &sy);
m_offsetX /= sx;
m_offsetY /= sy;
}
cairo_translate(cr, m_offsetX, m_offsetY);
}
~wxCairoOffsetHelper( )
~OffsetHelper()
{
if (m_offset > 0)
cairo_translate(m_ctx, -m_offset, -m_offset);
if (m_shouldOffset)
cairo_translate(m_cr, -m_offsetX, -m_offsetY);
}
private:
cairo_t* m_ctx;
double m_offset;
cairo_t* m_cr;
double m_offsetX, m_offsetY;
bool m_shouldOffset;
} ;
#if wxUSE_PRINTING_ARCHITECTURE
@ -2741,7 +2758,7 @@ void wxCairoContext::StrokePath( const wxGraphicsPath& path )
{
if ( !m_pen.IsNull() )
{
wxCairoOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(ShouldOffset(), m_context, m_pen);
cairo_path_t* cp = (cairo_path_t*) path.GetNativePath() ;
cairo_append_path(m_context,cp);
((wxCairoPenData*)m_pen.GetRefData())->Apply(this);
@ -2754,7 +2771,7 @@ void wxCairoContext::FillPath( const wxGraphicsPath& path , wxPolygonFillMode fi
{
if ( !m_brush.IsNull() )
{
wxCairoOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(ShouldOffset(), m_context, m_pen);
cairo_path_t* cp = (cairo_path_t*) path.GetNativePath() ;
cairo_append_path(m_context,cp);
((wxCairoBrushData*)m_brush.GetRefData())->Apply(this);
@ -2783,7 +2800,7 @@ void wxCairoContext::DrawRectangle( wxDouble x, wxDouble y, wxDouble w, wxDouble
}
if ( !m_pen.IsNull() )
{
wxCairoOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(ShouldOffset(), m_context, m_pen);
((wxCairoPenData*)m_pen.GetRefData())->Apply(this);
cairo_rectangle(m_context, x, y, w, h);
cairo_stroke(m_context);

View file

@ -485,6 +485,8 @@ public:
virtual WXHDC GetNativeHDC() override;
virtual void ReleaseNativeHDC(WXHDC hdc) override;
class OffsetHelper;
protected:
// Used from ctors (including those in the derived classes) and takes
@ -1827,32 +1829,42 @@ void * wxGDIPlusMatrixData::GetNativeMatrix() const
// wxGDIPlusContext implementation
//-----------------------------------------------------------------------------
class wxGDIPlusOffsetHelper
class wxGDIPlusContext::OffsetHelper
{
public :
wxGDIPlusOffsetHelper(Graphics* gr, double scaleFactor, bool offset)
OffsetHelper(wxGDIPlusContext* gc, Graphics* gr, const wxGraphicsPen& pen)
{
m_shouldOffset = gc->ShouldOffset();
if (!m_shouldOffset)
return;
m_gr = gr;
m_offset = 0;
if (offset)
m_offsetX = m_offsetY = 0.5f;
const double width = static_cast<wxGDIPlusPenData*>(pen.GetRefData())->GetWidth();
if (width <= 0)
{
// For 1-pixel pen width, offset by half a device pixel
Matrix matrix;
gr->GetTransform(&matrix);
const float f = float(scaleFactor);
const float f = float(gc->GetContentScaleFactor());
PointF pt(f, f);
matrix.TransformVectors(&pt);
m_offset = 0.5f / wxMin(std::abs(pt.X), std::abs(pt.Y));
m_gr->TranslateTransform(m_offset, m_offset);
m_offsetX /= pt.X;
m_offsetY /= pt.Y;
}
gr->TranslateTransform(m_offsetX, m_offsetY);
}
~wxGDIPlusOffsetHelper( )
~OffsetHelper()
{
if (m_offset > 0)
m_gr->TranslateTransform(-m_offset, -m_offset);
if (m_shouldOffset)
m_gr->TranslateTransform(-m_offsetX, -m_offsetY);
}
public :
Graphics* m_gr;
float m_offset;
float m_offsetX, m_offsetY;
bool m_shouldOffset;
} ;
wxGDIPlusContext::wxGDIPlusContext( wxGraphicsRenderer* renderer, HDC hdc, wxDouble width, wxDouble height )
@ -1991,7 +2003,7 @@ void wxGDIPlusContext::DrawRectangle( wxDouble x, wxDouble y, wxDouble w, wxDoub
if (m_composition == wxCOMPOSITION_DEST)
return;
wxGDIPlusOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(this, m_context, m_pen);
Brush *brush = m_brush.IsNull() ? nullptr : ((wxGDIPlusBrushData*)m_brush.GetRefData())->GetGDIPlusBrush();
Pen *pen = m_pen.IsNull() ? nullptr : ((wxGDIPlusPenData*)m_pen.GetGraphicsData())->GetGDIPlusPen();
@ -2030,7 +2042,7 @@ void wxGDIPlusContext::StrokeLines( size_t n, const wxPoint2DDouble *points)
if ( !m_pen.IsNull() )
{
wxGDIPlusOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(this, m_context, m_pen);
PointF *cpoints = new PointF[n];
for (size_t i = 0; i < n; i++)
{
@ -2048,7 +2060,7 @@ void wxGDIPlusContext::DrawLines( size_t n, const wxPoint2DDouble *points, wxPol
if (m_composition == wxCOMPOSITION_DEST)
return;
wxGDIPlusOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(this, m_context, m_pen);
PointF *cpoints = new PointF[n];
for (size_t i = 0; i < n; i++)
{
@ -2071,7 +2083,7 @@ void wxGDIPlusContext::StrokePath( const wxGraphicsPath& path )
if ( !m_pen.IsNull() )
{
wxGDIPlusOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(this, m_context, m_pen);
m_context->DrawPath( ((wxGDIPlusPenData*)m_pen.GetGraphicsData())->GetGDIPlusPen() , (GraphicsPath*) path.GetNativePath() );
}
}
@ -2083,7 +2095,7 @@ void wxGDIPlusContext::FillPath( const wxGraphicsPath& path , wxPolygonFillMode
if ( !m_brush.IsNull() )
{
wxGDIPlusOffsetHelper helper(m_context, GetContentScaleFactor(), ShouldOffset());
OffsetHelper helper(this, m_context, m_pen);
((GraphicsPath*) path.GetNativePath())->SetFillMode( fillStyle == wxODDEVEN_RULE ? FillModeAlternate : FillModeWinding);
m_context->FillPath( ((wxGDIPlusBrushData*)m_brush.GetRefData())->GetGDIPlusBrush() ,
(GraphicsPath*) path.GetNativePath());
@ -2477,15 +2489,9 @@ bool wxGDIPlusContext::ShouldOffset() const
if (width <= 0)
return true;
// no offset if overall scale is not odd integer
const wxGraphicsMatrix matrix(GetTransform());
double x = GetContentScaleFactor(), y = x;
matrix.TransformDistance(&x, &y);
if (!wxIsSameDouble(fmod(wxMin(fabs(x), fabs(y)), 2.0), 1.0))
return false;
// offset if pen width is odd integer
return wxIsSameDouble(fmod(width, 2.0), 1.0);
const int w = int(width);
return (w & 1) && wxIsSameDouble(width, w);
}
void* wxGDIPlusContext::GetNativeContext()

View file

@ -1101,36 +1101,6 @@ wxCOMPtr<ID2D1Geometry> wxD2DConvertRegionToGeometry(ID2D1Factory* direct2dFacto
return wxCOMPtr<ID2D1Geometry>(resultGeometry);
}
class wxD2DOffsetHelper
{
public:
explicit wxD2DOffsetHelper(wxGraphicsContext* g)
: m_context(g)
{
m_offset = 0;
if (m_context->ShouldOffset())
{
const wxGraphicsMatrix matrix(m_context->GetTransform());
double x = m_context->GetContentScaleFactor(), y = x;
matrix.TransformDistance(&x, &y);
m_offset = 0.5 / wxMin(fabs(x), fabs(y));
m_context->Translate(m_offset, m_offset);
}
}
~wxD2DOffsetHelper()
{
if (m_offset > 0)
{
m_context->Translate(-m_offset, -m_offset);
}
}
private:
wxGraphicsContext* m_context;
double m_offset;
};
bool operator==(const D2D1::Matrix3x2F& lhs, const D2D1::Matrix3x2F& rhs)
{
return
@ -4004,6 +3974,8 @@ public:
WXHDC GetNativeHDC() override;
void ReleaseNativeHDC(WXHDC hdc) override;
class OffsetHelper;
private:
void Init();
@ -4065,6 +4037,41 @@ private:
wxDECLARE_NO_COPY_CLASS(wxD2DContext);
};
class wxD2DContext::OffsetHelper
{
public:
OffsetHelper(wxD2DContext* gc, const wxGraphicsPen& pen)
{
m_shouldOffset = gc->ShouldOffset();
if (!m_shouldOffset)
return;
m_gc = gc;
m_offsetX = m_offsetY = 0.5;
const float width = wxGetD2DPenData(pen)->GetWidth();
if (width <= 0)
{
// For 1-pixel pen width, offset by half a device pixel
double x = gc->GetContentScaleFactor(), y = x;
gc->GetTransform().TransformDistance(&x, &y);
m_offsetX /= x;
m_offsetY /= y;
}
gc->Translate(m_offsetX, m_offsetY);
}
~OffsetHelper()
{
if (m_shouldOffset)
m_gc->Translate(-m_offsetX, -m_offsetY);
}
private:
wxD2DContext* m_gc;
double m_offsetX, m_offsetY;
bool m_shouldOffset;
};
//-----------------------------------------------------------------------------
// wxD2DContext implementation
//-----------------------------------------------------------------------------
@ -4391,7 +4398,7 @@ void wxD2DContext::StrokePath(const wxGraphicsPath& p)
if (m_composition == wxCOMPOSITION_DEST)
return;
wxD2DOffsetHelper helper(this);
OffsetHelper helper(this, m_pen);
EnsureInitialized();
AdjustRenderTargetSize();
@ -4762,21 +4769,15 @@ bool wxD2DContext::ShouldOffset() const
if (!m_enableOffset || m_pen.IsNull())
return false;
wxD2DPenData* const penData = wxGetD2DPenData(m_pen);
const float width = wxGetD2DPenData(m_pen)->GetWidth();
// always offset for 1-pixel width
if (penData->IsZeroWidth())
if (width <= 0)
return true;
// no offset if overall scale is not odd integer
const wxGraphicsMatrix matrix(GetTransform());
double x = GetContentScaleFactor(), y = x;
matrix.TransformDistance(&x, &y);
if (!wxIsSameDouble(fmod(wxMin(fabs(x), fabs(y)), 2.0), 1.0))
return false;
// offset if pen width is odd integer
return wxIsSameDouble(fmod(double(penData->GetWidth()), 2.0), 1.0);
const int w = int(width);
return (w & 1) && width == float(w);
}
void wxD2DContext::DoDrawText(const wxString& str, wxDouble x, wxDouble y)
@ -4861,7 +4862,7 @@ void wxD2DContext::DrawRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
if (m_composition == wxCOMPOSITION_DEST)
return;
wxD2DOffsetHelper helper(this);
OffsetHelper helper(this, m_pen);
EnsureInitialized();
AdjustRenderTargetSize();
@ -4890,7 +4891,7 @@ void wxD2DContext::DrawRoundedRectangle(wxDouble x, wxDouble y, wxDouble w, wxDo
if (m_composition == wxCOMPOSITION_DEST)
return;
wxD2DOffsetHelper helper(this);
OffsetHelper helper(this, m_pen);
EnsureInitialized();
AdjustRenderTargetSize();
@ -4920,7 +4921,7 @@ void wxD2DContext::DrawEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
if (m_composition == wxCOMPOSITION_DEST)
return;
wxD2DOffsetHelper helper(this);
OffsetHelper helper(this, m_pen);
EnsureInitialized();
AdjustRenderTargetSize();

View file

@ -1481,15 +1481,9 @@ public:
if (width <= 0)
return true;
// no offset if overall scale is not odd integer
const wxGraphicsMatrix matrix(GetTransform());
double x = GetContentScaleFactor(), y = x;
matrix.TransformDistance(&x, &y);
if (!wxIsSameDouble(fmod(wxMin(fabs(x), fabs(y)), 2.0), 1.0))
return false;
// offset if pen width is odd integer
return wxIsSameDouble(fmod(width, 2.0), 1.0);
const int w = int(width);
return (w & 1) && wxIsSameDouble(width, w);
}
//
// text
@ -1517,6 +1511,8 @@ public:
void SetNativeContext( CGContextRef cg );
class OffsetHelper;
wxDECLARE_NO_COPY_CLASS(wxMacCoreGraphicsContext);
private:
@ -1560,34 +1556,35 @@ private:
// wxMacCoreGraphicsContext implementation
//-----------------------------------------------------------------------------
class wxQuartzOffsetHelper
class wxMacCoreGraphicsContext::OffsetHelper
{
public :
wxQuartzOffsetHelper( CGContextRef cg, double scaleFactor, bool offset )
OffsetHelper(bool shouldOffset, CGContextRef cg, const wxGraphicsPen& pen)
{
m_shouldOffset = shouldOffset;
if (!shouldOffset)
return;
m_offset = CGSizeMake(0.5, 0.5);
m_cg = cg;
m_offset = offset;
if ( m_offset )
double width = static_cast<wxMacCoreGraphicsPenData*>(pen.GetRefData())->GetWidth();
if (width <= 0)
{
const double f = 0.5 / scaleFactor;
m_userOffset = CGSizeMake(f, f);
CGContextTranslateCTM( m_cg, m_userOffset.width , m_userOffset.height );
}
else
{
m_userOffset = CGSizeMake(0.0, 0.0);
// For 1-pixel pen width, offset by half a device pixel
m_offset = CGContextConvertSizeToUserSpace(cg, m_offset);
}
CGContextTranslateCTM(cg, m_offset.width, m_offset.height);
}
~wxQuartzOffsetHelper( )
~OffsetHelper()
{
if ( m_offset )
CGContextTranslateCTM( m_cg, -m_userOffset.width , -m_userOffset.height );
if (m_shouldOffset)
CGContextTranslateCTM(m_cg, -m_offset.width, -m_offset.height);
}
public :
CGSize m_userOffset;
CGSize m_offset;
CGContextRef m_cg;
bool m_offset;
bool m_shouldOffset;
} ;
void wxMacCoreGraphicsContext::Init()
@ -2253,7 +2250,7 @@ void wxMacCoreGraphicsContext::StrokePath( const wxGraphicsPath &path )
if (m_composition == wxCOMPOSITION_DEST)
return;
wxQuartzOffsetHelper helper( m_cgContext, GetContentScaleFactor(), ShouldOffset() );
OffsetHelper helper(ShouldOffset(), m_cgContext, m_pen);
wxMacCoreGraphicsPenData* penData = (wxMacCoreGraphicsPenData*)m_pen.GetRefData();
penData->Apply(this);
@ -2333,7 +2330,7 @@ void wxMacCoreGraphicsContext::DrawPath( const wxGraphicsPath &path , wxPolygonF
if ( !m_pen.IsNull() )
((wxMacCoreGraphicsPenData*)m_pen.GetRefData())->Apply(this);
wxQuartzOffsetHelper helper( m_cgContext, GetContentScaleFactor(), ShouldOffset() );
OffsetHelper helper(ShouldOffset(), m_cgContext, m_pen);
CGContextAddPath( m_cgContext , (CGPathRef) path.GetNativePath() );
CGContextDrawPath( m_cgContext , mode );
@ -2747,7 +2744,7 @@ void wxMacCoreGraphicsContext::DrawRectangle( wxDouble x, wxDouble y, wxDouble w
if ( !m_pen.IsNull() )
{
wxQuartzOffsetHelper helper( m_cgContext, GetContentScaleFactor(), ShouldOffset() );
OffsetHelper helper(ShouldOffset(), m_cgContext, m_pen);
((wxMacCoreGraphicsPenData*)m_pen.GetRefData())->Apply(this);
CGContextStrokeRect(m_cgContext, rect);
}