Make coordinates transformations in wxDC really maximally precise.

Use the maximal device space extent supported by Win32 GDI and only decrease
it if the scale is so small that keeping the device space extent maximal would
result in overflowing the int range for the logical space.

This makes coordinate translations exact even for huge coordinates, while they
could be significantly wrong before due to the integer rounding errors.

Closes #13284.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@68206 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2011-07-09 23:36:19 +00:00
parent fcd9ed6c37
commit ebcdce46d2

View File

@ -83,7 +83,9 @@ IMPLEMENT_ABSTRACT_CLASS(wxMSWDCImpl, wxDCImpl)
// constants
// ---------------------------------------------------------------------------
static const int VIEWPORT_EXTENT = 1024;
// The device space in Win32 GDI measures 2^27*2^27 , so we use 2^27-1 as the
// maximal possible view port extent.
static const int VIEWPORT_EXTENT = 134217727;
// ROPs which don't have standard names (see "Ternary Raster Operations" in the
// MSDN docs for how this and other numbers in wxDC::Blit() are obtained)
@ -1960,6 +1962,31 @@ bool wxMSWDCImpl::DoGetPartialTextExtents(const wxString& text, wxArrayInt& widt
return true;
}
namespace
{
void ApplyEffectiveScale(double scale, int sign, int *device, int *logical)
{
// To reduce rounding errors as much as possible, we try to use the largest
// possible extent (2^27-1) for the device space but we must also avoid
// overflowing the int range i.e. ensure that logical extents are less than
// 2^31 in magnitude. So the minimal scale we can use is 1/16 as for
// anything smaller VIEWPORT_EXTENT/scale would overflow the int range.
static const double MIN_LOGICAL_SCALE = 1./16;
double physExtent = VIEWPORT_EXTENT;
if ( scale < MIN_LOGICAL_SCALE )
{
physExtent *= scale/MIN_LOGICAL_SCALE;
scale = MIN_LOGICAL_SCALE;
}
*device = wxRound(physExtent);
*logical = sign*wxRound(VIEWPORT_EXTENT/scale);
}
} // anonymous namespace
void wxMSWDCImpl::RealizeScaleAndOrigin()
{
// although it may seem wasteful to always use MM_ANISOTROPIC here instead
@ -1970,34 +1997,13 @@ void wxMSWDCImpl::RealizeScaleAndOrigin()
// wxWidgets API assumes that the coordinate space is "infinite" (i.e. only
// limited by 2^32 range of the integer coordinates) but in MSW API we must
// actually specify the extents that we use. So we more or less arbitrarily
// decide to use "base" VIEWPORT_EXTENT and adjust it depending on scale.
//
// To avoid rounding errors we prefer to multiply by the scale if it's > 1
// and to divide by it if it's < 1.
// actually specify the extents that we use so compute them here.
int devExtX, devExtY, // Viewport, i.e. device space, extents.
logExtX, logExtY; // Window, i.e. logical coordinate space, extents.
if ( m_scaleX >= 1 )
{
devExtX = wxRound(VIEWPORT_EXTENT*m_scaleX);
logExtX = m_signX*VIEWPORT_EXTENT;
}
else
{
devExtX = VIEWPORT_EXTENT;
logExtX = wxRound(m_signX*VIEWPORT_EXTENT/m_scaleX);
}
if ( m_scaleY >= 1 )
{
devExtY = wxRound(VIEWPORT_EXTENT*m_scaleY);
logExtY = m_signY*VIEWPORT_EXTENT;
}
else
{
devExtY = VIEWPORT_EXTENT;
logExtY = wxRound(m_signY*VIEWPORT_EXTENT/m_scaleY);
}
ApplyEffectiveScale(m_scaleX, m_signX, &devExtX, &logExtX);
ApplyEffectiveScale(m_scaleY, m_signY, &devExtY, &logExtY);
::SetViewportExtEx(GetHdc(), devExtX, devExtY, NULL);
::SetWindowExtEx(GetHdc(), logExtX, logExtY, NULL);