Backported Vaclav Slavik's speeded up accurate print preview code from trunk

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@60850 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Julian Smart
2009-06-01 10:16:13 +00:00
parent 20e227dfd0
commit 5fefca5fb1
4 changed files with 123 additions and 261 deletions

View File

@@ -121,6 +121,10 @@ wxMSW:
- Fixed generation of wxEVT_CHAR_HOOK events.
- Implement wxFileName::SetTimes() for directories (Steve Lamerton).
- Backported speed-up of Vaclav Slavik's high quality print preview speed-up.
You now need to set wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW in
src/common/prntbase.cpp and wxUSE_ENH_METAFILE_FROM_DC in
include/wx/msw/enhmeta.h.
wxMac:

View File

@@ -18,6 +18,9 @@
#include "wx/dataobj.h"
#endif
// Change this to 1 if you set wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW to 1 in prntbase.cpp
#define wxUSE_ENH_METAFILE_FROM_DC 0
// ----------------------------------------------------------------------------
// wxEnhMetaFile: encapsulation of Win32 HENHMETAFILE
// ----------------------------------------------------------------------------
@@ -82,6 +85,15 @@ public:
int width = 0, int height = 0,
const wxString& description = wxEmptyString);
#if wxUSE_ENH_METAFILE_FROM_DC
// as above, but takes reference DC as first argument to take resolution,
// size, font metrics etc. from
wxEnhMetaFileDC(const wxDC& referenceDC,
const wxString& filename = wxEmptyString,
int width = 0, int height = 0,
const wxString& description = wxEmptyString);
#endif
virtual ~wxEnhMetaFileDC();
// obtain a pointer to the new metafile (caller should delete it)

View File

@@ -29,6 +29,14 @@
#endif
#endif
#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
#include "wx/msw/enhmeta.h"
#endif
#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW && !wxUSE_ENH_METAFILE_FROM_DC
#error Please set wxUSE_ENH_METAFILE_FROM_DC to 1 in include/wx/msw/enhmeta.h.
#endif
#include "wx/dcprint.h"
#ifndef WX_PRECOMP
@@ -84,9 +92,7 @@
typedef bool (wxPrintPreviewBase::*RenderPageIntoDCFunc)(wxDC&, int);
static bool RenderPageIntoBitmapHQ(wxPrintPreviewBase *preview,
RenderPageIntoDCFunc RenderPageIntoDC,
wxPrinterDC& printerDC,
wxBitmap& bmp, int pageNum,
int pageWidth, int pageHeight);
wxBitmap& bmp, int pageNum);
#endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
//----------------------------------------------------------------------------
@@ -1529,25 +1535,13 @@ bool wxPrintPreviewBase::RenderPageIntoDC(wxDC& dc, int pageNum)
bool wxPrintPreviewBase::RenderPageIntoBitmap(wxBitmap& bmp, int pageNum)
{
#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
// try high quality rendering first:
static bool s_hqPreviewFailed = false;
if ( !s_hqPreviewFailed )
{
wxPrinterDC printerDC(m_printDialogData.GetPrintData());
if ( RenderPageIntoBitmapHQ(this,
&wxPrintPreviewBase::RenderPageIntoDC,
printerDC,
bmp, pageNum,
m_pageWidth, m_pageHeight) )
bmp, pageNum) )
{
return true;
}
else
{
wxLogTrace(_T("printing"),
_T("high-quality preview failed, falling back to normal"));
s_hqPreviewFailed = true; // don't bother re-trying
}
}
#endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
@@ -1823,268 +1817,53 @@ void wxPrintPreview::DetermineScaling()
//----------------------------------------------------------------------------
#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
// The preview, as implemented in wxPrintPreviewBase (and as used prior to wx3)
// is inexact: it uses screen DC, which has much lower resolution and has
// other properties different from printer DC, so the preview is not quite
// right.
//
// To make matters worse, if the application depends heavily on GetTextExtent()
// or does text layout itself, the output in preview and on paper can be very
// different. In particular, wxHtmlEasyPrinting is affected and the preview
// can be easily off by several pages.
//
// To fix this, we attempt to render the preview into high-resolution bitmap
// using DC with same resolution etc. as the printer DC. This takes lot of
// memory, so the code is more complicated than it could be, but the results
// are much better.
//
// Finally, this code is specific to wxMSW, because it doesn't make sense to
// bother with it on other platforms. Both OSX and modern GNOME/GTK+
// environments have builtin accurate preview (that applications should use
// instead) and the differences between screen and printer DC in wxGTK are so
// large than this trick doesn't help at all.
namespace
{
// If there's not enough memory, we need to render the preview in parts.
// Unfortunately we cannot simply use wxMemoryDC, because it reports its size
// as bitmap's size, and we need to use smaller bitmap while still reporting
// original ("correct") DC size, because printing code frequently uses
// GetSize() to determine scaling factor. This DC class handles this.
class PageFragmentDC : public wxMemoryDC
{
public:
PageFragmentDC(wxDC *printer, wxBitmap& bmp,
const wxPoint& offset,
const wxSize& fullSize)
: wxMemoryDC(printer),
m_offset(offset),
m_fullSize(fullSize)
{
SetDeviceOrigin(0, 0);
SelectObject(bmp);
}
virtual void SetDeviceOrigin(wxCoord x, wxCoord y)
{
wxMemoryDC::SetDeviceOrigin(x - m_offset.x, y - m_offset.y);
}
virtual void DoGetDeviceOrigin(wxCoord *x, wxCoord *y) const
{
wxMemoryDC::DoGetDeviceOrigin(x, y);
if ( x ) *x += m_offset.x;
if ( x ) *y += m_offset.y;
}
virtual void DoGetSize(int *width, int *height) const
{
if ( width )
*width = m_fullSize.x;
if ( height )
*height = m_fullSize.y;
}
private:
wxPoint m_offset;
wxSize m_fullSize;
};
// estimate how big chunks we can render, given available RAM
long ComputeFragmentSize(long printerDepth,
long width,
long height)
{
// Compute the amount of memory needed to generate the preview.
// Memory requirements of RenderPageFragment() are as follows:
//
// (memory DC - always)
// width * height * printerDepth/8
// (wxImage + wxDIB instance)
// width * height * (3 + 4)
// (this could be reduced to *3 if using wxGraphicsContext)
//
// So, given amount of memory M, we can render at most
//
// height = M / (width * (printerDepth/8 + F))
//
// where F is 3 or 7 depending on whether wxGraphicsContext is used or not
wxMemorySize memAvail = wxGetFreeMemory();
if ( memAvail == -1 )
{
// we don't know; 10meg shouldn't be a problem hopefully
memAvail = 10000000;
}
else
{
// limit ourselves to half of available RAM to have a margin for other
// apps, for our rendering code, and for miscalculations
memAvail /= 2;
}
const float perPixel = float(printerDepth)/8 + (3 + 4);
const long perLine = long(width * perPixel);
const long maxstep = (memAvail / perLine).GetValue();
const long step = wxMin(height, maxstep);
wxLogTrace(_T("printing"),
_T("using %liMB of RAM (%li lines) for preview, %li %lipx fragments"),
long((memAvail >> 20).GetValue()),
maxstep,
(height+step-1) / step,
step);
return step;
}
} // anonymous namespace
static bool RenderPageFragment(wxPrintPreviewBase *preview,
RenderPageIntoDCFunc RenderPageIntoDC,
float scaleX, float scaleY,
int *nextFinalLine,
wxPrinterDC& printer,
wxMemoryDC& finalDC,
const wxRect& rect,
int pageWidth, int pageHeight,
int pageNum)
{
// compute 'rect' equivalent in the small final bitmap:
const wxRect smallRect(wxPoint(0, *nextFinalLine),
wxPoint(int(rect.GetRight() * scaleX),
int(rect.GetBottom() * scaleY)));
wxLogTrace(_T("printing"),
_T("rendering fragment of page %i: [%i,%i,%i,%i] scaled down to [%i,%i,%i,%i]"),
pageNum,
rect.x, rect.y, rect.GetRight(), rect.GetBottom(),
smallRect.x, smallRect.y, smallRect.GetRight(), smallRect.GetBottom()
);
// create DC and bitmap compatible with printer DC:
wxBitmap large(rect.width, rect.height, printer);
if ( !large.IsOk() )
return false;
// render part of the page into it:
{
PageFragmentDC memoryDC(&printer, large,
rect.GetPosition(),
wxSize(pageWidth, pageHeight));
if ( !memoryDC.IsOk() )
return false;
memoryDC.Clear();
if ( !(preview->*RenderPageIntoDC)(memoryDC, pageNum) )
return false;
} // release bitmap from memoryDC
// now scale the rendered part down and blit it into final output:
wxImage img;
{
wxDIB dib(large);
if ( !dib.IsOk() )
return false;
large = wxNullBitmap; // free memory a.s.a.p.
img = dib.ConvertToImage();
} // free the DIB now that it's no longer needed, too
if ( !img.IsOk() )
return false;
img.Rescale(smallRect.width, smallRect.height, wxIMAGE_QUALITY_HIGH);
if ( !img.IsOk() )
return false;
wxBitmap bmp(img);
if ( !bmp.IsOk() )
return false;
img = wxNullImage;
finalDC.DrawBitmap(bmp, smallRect.x, smallRect.y);
if ( bmp.IsOk() )
{
*nextFinalLine += smallRect.height;
return true;
}
return false;
}
static bool RenderPageIntoBitmapHQ(wxPrintPreviewBase *preview,
RenderPageIntoDCFunc RenderPageIntoDC,
wxPrinterDC& printerDC,
wxBitmap& bmp, int pageNum,
int pageWidth, int pageHeight)
wxBitmap& bmp, int pageNum)
{
wxLogTrace(_T("printing"), _T("rendering HQ preview of page %i"), pageNum);
// The preview, as implemented in wxPrintPreviewBase (and as used prior to
// wx3) is inexact: it uses screen DC, which has much lower resolution and
// has other properties different from printer DC, so the preview is not
// quite right.
//
// To make matters worse, if the application depends heavily on
// GetTextExtent() or does text layout itself, the output in preview and on
// paper can be very different. In particular, wxHtmlEasyPrinting is
// affected and the preview can be easily off by several pages.
//
// To fix this, we render the preview into high-resolution enhanced
// metafile with properties identical to the printer DC. This guarantees
// metrics correctness while still being fast.
if ( !printerDC.IsOk() )
// print the preview into a metafile:
wxPrinterDC printerDC(preview->GetPrintDialogData().GetPrintData());
wxEnhMetaFileDC metaDC(printerDC,
wxEmptyString,
printerDC.GetSize().x, printerDC.GetSize().y);
if ( !(preview->*RenderPageIntoDC)(metaDC, pageNum) )
return false;
// compute scale factor
const float scaleX = float(bmp.GetWidth()) / float(pageWidth);
const float scaleY = float(bmp.GetHeight()) / float(pageHeight);
wxEnhMetaFile *metafile = metaDC.Close();
if ( !metafile )
return false;
// now render the metafile:
wxMemoryDC bmpDC;
bmpDC.SelectObject(bmp);
bmpDC.Clear();
const int initialStep = ComputeFragmentSize(printerDC.GetDepth(),
pageWidth, pageHeight);
wxRect outRect(0, 0, bmp.GetWidth(), bmp.GetHeight());
metafile->Play(&bmpDC, &outRect);
wxRect todo(0, 0, pageWidth, initialStep); // rect to render
int nextFinalLine = 0; // first not-yet-rendered output line
while ( todo.y < pageHeight )
{
todo.SetBottom(wxMin(todo.GetBottom(), pageHeight - 1));
delete metafile;
if ( !RenderPageFragment(preview, RenderPageIntoDC,
scaleX, scaleY,
&nextFinalLine,
printerDC,
bmpDC,
todo,
pageWidth, pageHeight,
pageNum) )
{
if ( todo.height < 20 )
{
// something is very wrong if we can't render even at this
// slow space, let's bail out and fall back to low quality
// preview
wxLogTrace(_T("printing"),
_T("it seems that HQ preview doesn't work at all"));
return false;
}
// it's possible our memory calculation was off, or conditions
// changed, or there's not enough _bitmap_ resources; try if using
// smaller bitmap would help:
todo.height /= 2;
wxLogTrace(_T("printing"),
_T("preview of fragment failed, reducing height to %ipx"),
todo.height);
continue; // retry at the same position again
}
// move to the next segment
todo.Offset(0, todo.height);
}
// TODO: we should keep the metafile and reuse it when changing zoom level
return true;
}
#endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW
#endif // wxUSE_PRINTING_ARCHITECTURE

View File

@@ -226,6 +226,73 @@ wxEnhMetaFileDC::wxEnhMetaFileDC(const wxString& filename,
}
}
#if wxUSE_ENH_METAFILE_FROM_DC
void PixelToHIMETRIC(LONG *x, LONG *y, HDC hdcRef)
{
int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE),
iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE),
iWidthPels = GetDeviceCaps(hdcRef, HORZRES),
iHeightPels = GetDeviceCaps(hdcRef, VERTRES);
*x *= (iWidthMM * 100);
*x /= iWidthPels;
*y *= (iHeightMM * 100);
*y /= iHeightPels;
}
void HIMETRICToPixel(LONG *x, LONG *y, HDC hdcRef)
{
int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE),
iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE),
iWidthPels = GetDeviceCaps(hdcRef, HORZRES),
iHeightPels = GetDeviceCaps(hdcRef, VERTRES);
*x *= iWidthPels;
*x /= (iWidthMM * 100);
*y *= iHeightPels;
*y /= (iHeightMM * 100);
}
wxEnhMetaFileDC::wxEnhMetaFileDC(const wxDC& referenceDC,
const wxString& filename,
int width, int height,
const wxString& description)
{
m_width = width;
m_height = height;
HDC hdcRef = GetHdcOf(referenceDC);
RECT rect;
RECT *pRect;
if ( width && height )
{
rect.top =
rect.left = 0;
rect.right = width;
rect.bottom = height;
// CreateEnhMetaFile() wants them in HIMETRIC
PixelToHIMETRIC(&rect.right, &rect.bottom, hdcRef);
pRect = &rect;
}
else
{
// GDI will try to find out the size for us (not recommended)
pRect = (LPRECT)NULL;
}
m_hDC = (WXHDC)::CreateEnhMetaFile(hdcRef, GetMetaFileName(filename),
pRect, description.wx_str());
if ( !m_hDC )
{
wxLogLastError(_T("CreateEnhMetaFile"));
}
}
#endif
void wxEnhMetaFileDC::DoGetSize(int *width, int *height) const
{
if ( width )