Merge branch 'dev-qt5.1x-pdf_optimize' into dev-qt5.1x

preferencesAboutTextFull
Clément Fauconnier 4 years ago
commit 0345a8575e
  1. 6
      OpenBoard.pro
  2. 1
      resources/etc/OpenBoard.config
  3. 1
      src/core/UBSettings.cpp
  4. 1
      src/core/UBSettings.h
  5. 160
      src/pdf/XPDFRenderer.cpp
  6. 51
      src/pdf/XPDFRenderer.h

@ -126,7 +126,11 @@ win32 {
CONFIG += axcontainer CONFIG += axcontainer
exists(console):CONFIG += console exists(console):CONFIG += console
QMAKE_CXXFLAGS += /MP QMAKE_CXXFLAGS += /MP
QMAKE_CXXFLAGS += /MD CONFIG( debug, debug|release ) {
QMAKE_CXXFLAGS += /MDd
} else {
QMAKE_CXXFLAGS += /MD
}
QMAKE_CXXFLAGS_RELEASE += /Od /Zi QMAKE_CXXFLAGS_RELEASE += /Od /Zi
QMAKE_LFLAGS += /VERBOSE:LIB QMAKE_LFLAGS += /VERBOSE:LIB
UB_LIBRARY.path = $$DESTDIR UB_LIBRARY.path = $$DESTDIR

@ -123,6 +123,7 @@ RefreshRateInFramePerSecond=2
Margin=20 Margin=20
PageFormat=A4 PageFormat=A4
Resolution=300 Resolution=300
ZoomBehavior=1
[Podcast] [Podcast]
AudioRecordingDevice=Default AudioRecordingDevice=Default

@ -408,6 +408,7 @@ void UBSettings::init()
pdfMargin = new UBSetting(this, "PDF", "Margin", "20"); pdfMargin = new UBSetting(this, "PDF", "Margin", "20");
pdfPageFormat = new UBSetting(this, "PDF", "PageFormat", "A4"); pdfPageFormat = new UBSetting(this, "PDF", "PageFormat", "A4");
pdfResolution = new UBSetting(this, "PDF", "Resolution", "300"); pdfResolution = new UBSetting(this, "PDF", "Resolution", "300");
pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "1");
podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10);
podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium");

@ -359,6 +359,7 @@ class UBSettings : public QObject
UBSetting* pdfMargin; UBSetting* pdfMargin;
UBSetting* pdfPageFormat; UBSetting* pdfPageFormat;
UBSetting* pdfResolution; UBSetting* pdfResolution;
UBSetting* pdfZoomBehavior;
UBSetting* podcastFramesPerSecond; UBSetting* podcastFramesPerSecond;
UBSetting* podcastVideoSize; UBSetting* podcastVideoSize;

@ -37,14 +37,28 @@
#endif #endif
#include "core/memcheck.h" #include "core/memcheck.h"
#include "core/UBSettings.h"
QAtomicInt XPDFRenderer::sInstancesCount = 0; QAtomicInt XPDFRenderer::sInstancesCount = 0;
XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) :
: mDocument(0) mpSplashBitmapHistorical(nullptr), mSplashHistorical(nullptr), mDocument(nullptr)
, mpSplashBitmap(0)
, mSplash(0)
{ {
switch (UBSettings::settings()->pdfZoomBehavior->get().toUInt()) {
case 0: // Render each time (historical initial implementation).
break;
case 1: // Render a single image, degradated quality when zoomed big.
default:
m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode1_zoomFactor);
break;
case 2: // Render three images, optimal quality all the time.
m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage1);
m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage2);
m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage3);
break;
}
Q_UNUSED(importingFile); Q_UNUSED(importingFile);
if (!globalParams) if (!globalParams)
{ {
@ -67,11 +81,19 @@ XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile)
XPDFRenderer::~XPDFRenderer() XPDFRenderer::~XPDFRenderer()
{ {
if(mSplash){ for(int i = 0; i < m_pdfZoomCache.size(); i++)
delete mSplash; {
mSplash = NULL; PdfZoomCacheData &cacheData = m_pdfZoomCache[i];
if(cacheData.splash != nullptr){
cacheData.cachedImage = QImage(); // The 'cachedImage' uses a buffer from 'splash'.
delete cacheData.splash;
cacheData.splash = nullptr;
}
} }
if(mSplashHistorical)
delete mSplashHistorical;
if (mDocument) if (mDocument)
{ {
delete mDocument; delete mDocument;
@ -175,34 +197,19 @@ int XPDFRenderer::pageRotation(int pageNumber) const
return 0; return 0;
} }
void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds)
{
if (isValid())
{
qreal xscale = p->worldTransform().m11();
qreal yscale = p->worldTransform().m22();
QImage *pdfImage = createPDFImage(pageNumber, xscale, yscale, bounds);
QTransform savedTransform = p->worldTransform();
p->resetTransform();
p->drawImage(QPointF(savedTransform.dx() + mSliceX, savedTransform.dy() + mSliceY), *pdfImage);
p->setWorldTransform(savedTransform);
delete pdfImage;
}
}
QImage* XPDFRenderer::createPDFImage(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds) QImage* XPDFRenderer::createPDFImageHistorical(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds)
{ {
if (isValid()) if (isValid())
{ {
SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white
if(mSplash) if(mSplashHistorical)
delete mSplash; delete mSplashHistorical;
mSplash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, paperColor);
#ifdef USE_XPDF #ifdef USE_XPDF
mSplash->startDoc(mDocument->getXRef()); mSplashHistorical->startDoc(mDocument->getXRef());
#else #else
mSplash->startDoc(mDocument); mSplashHistorical->startDoc(mDocument);
#endif #endif
int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation) int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation)
bool useMediaBox = false; bool useMediaBox = false;
@ -213,7 +220,7 @@ QImage* XPDFRenderer::createPDFImage(int pageNumber, qreal xscale, qreal yscale,
if (bounds.isNull()) if (bounds.isNull())
{ {
mDocument->displayPage(mSplash, pageNumber, this->dpiForRendering * xscale, this->dpiForRendering *yscale, mDocument->displayPage(mSplashHistorical, pageNumber, this->dpiForRendering * xscale, this->dpiForRendering *yscale,
rotation, useMediaBox, crop, printing); rotation, useMediaBox, crop, printing);
} }
else else
@ -223,11 +230,100 @@ QImage* XPDFRenderer::createPDFImage(int pageNumber, qreal xscale, qreal yscale,
qreal sliceW = bounds.width() * xscale; qreal sliceW = bounds.width() * xscale;
qreal sliceH = bounds.height() * yscale; qreal sliceH = bounds.height() * yscale;
mDocument->displayPageSlice(mSplash, pageNumber, this->dpiForRendering * xscale, this->dpiForRendering * yscale, mDocument->displayPageSlice(mSplashHistorical, pageNumber, this->dpiForRendering * xscale, this->dpiForRendering * yscale,
rotation, useMediaBox, crop, printing, mSliceX, mSliceY, sliceW, sliceH); rotation, useMediaBox, crop, printing, mSliceX, mSliceY, sliceW, sliceH);
} }
mpSplashBitmap = mSplash->getBitmap(); mpSplashBitmapHistorical = mSplashHistorical->getBitmap();
}
return new QImage(mpSplashBitmapHistorical->getDataPtr(), mpSplashBitmapHistorical->getWidth(), mpSplashBitmapHistorical->getHeight(), mpSplashBitmapHistorical->getWidth() * 3, QImage::Format_RGB888);
}
void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds)
{
Q_UNUSED(bounds);
if (isValid())
{
if (m_pdfZoomCache.size() > 0)
{
qreal xscale = p->worldTransform().m11();
qreal yscale = p->worldTransform().m22();
Q_ASSERT(qFuzzyCompare(xscale, yscale)); // Zoom equal in all axes expected.
Q_ASSERT(xscale > 0.0); // Potential Div0 later if this assert fail.
int zoomIndex = 0;
bool foundIndex = false;
for (; zoomIndex < m_pdfZoomCache.size() && !foundIndex;)
{
if (xscale <= m_pdfZoomCache[zoomIndex].ratio) {
foundIndex = true;
} else {
zoomIndex++;
}
}
if (!foundIndex) // Use the previous one.
zoomIndex--;
QImage const &pdfImage = createPDFImageCached(pageNumber, m_pdfZoomCache[zoomIndex]);
QTransform savedTransform = p->worldTransform();
double const ratioDifferenceBetweenWorldAndImage = 1.0/m_pdfZoomCache[zoomIndex].ratio;
// The 'pdfImage' is rendered with a quality equal or superior. We adjust the 'transform' to zoom it
// out the required ratio.
QTransform newTransform = savedTransform.scale(ratioDifferenceBetweenWorldAndImage, ratioDifferenceBetweenWorldAndImage);
p->setWorldTransform(newTransform);
p->drawImage(QPointF( mSliceX, mSliceY), pdfImage);
p->setWorldTransform(savedTransform);
} else {
qreal xscale = p->worldTransform().m11();
qreal yscale = p->worldTransform().m22();
QImage *pdfImage = createPDFImageHistorical(pageNumber, xscale, yscale, bounds);
QTransform savedTransform = p->worldTransform();
p->resetTransform();
p->drawImage(QPointF(savedTransform.dx() + mSliceX, savedTransform.dy() + mSliceY), *pdfImage);
p->setWorldTransform(savedTransform);
delete pdfImage;
}
}
}
QImage& XPDFRenderer::createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData)
{
if (isValid())
{
SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white
if (cacheData.requireUpdateImage(pageNumber))
{
cacheData.prepareNewSplash(pageNumber, paperColor);
#ifdef USE_XPDF
cacheData.splash->startDoc(mDocument->getXRef());
#else
cacheData.splash->startDoc(mDocument);
#endif
int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation)
bool useMediaBox = false;
bool crop = true;
bool printing = false;
mSliceX = 0.;
mSliceY = 0.;
mDocument->displayPage(cacheData.splash, pageNumber, this->dpiForRendering * cacheData.ratio, this->dpiForRendering * cacheData.ratio,
rotation, useMediaBox, crop, printing);
cacheData.splashBitmap = cacheData.splash->getBitmap();
}
// Note this uses the 'cacheData.splash->getBitmap()->getDataPtr()' as data buffer.
cacheData.cachedImage = QImage(cacheData.splashBitmap->getDataPtr(), cacheData.splashBitmap->getWidth(), cacheData.splashBitmap->getHeight(),
cacheData.splashBitmap->getWidth() * 3 /* bytesPerLine, 24 bits for RGB888, = 3 bytes */,
QImage::Format_RGB888);
} else {
cacheData.cachedImage = QImage();
} }
return new QImage(mpSplashBitmap->getDataPtr(), mpSplashBitmap->getWidth(), mpSplashBitmap->getHeight(), mpSplashBitmap->getWidth() * 3, QImage::Format_RGB888);
return cacheData.cachedImage;
} }

@ -51,6 +51,15 @@
class PDFDoc; class PDFDoc;
namespace XPDFRendererZoomFactor
{
const double mode1_zoomFactor = 3.0;
const double mode2_zoomFactorStage1 = 2.5;
const double mode2_zoomFactorStage2 = 5.0;
const double mode2_zoomFactorStage3 = 10.0;
}
class XPDFRenderer : public PDFRenderer class XPDFRenderer : public PDFRenderer
{ {
Q_OBJECT Q_OBJECT
@ -74,15 +83,49 @@ class XPDFRenderer : public PDFRenderer
private: private:
void init(); void init();
QImage* createPDFImage(int pageNumber, qreal xscale = 0.5, qreal yscale = 0.5, const QRectF &bounds = QRectF());
struct PdfZoomCacheData {
PdfZoomCacheData(double const a_ratio) : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(a_ratio) {};
~PdfZoomCacheData() {};
SplashBitmap* splashBitmap;
QImage cachedImage;
int cachedPageNumber;
SplashOutputDev* splash;
double const ratio;
bool requireUpdateImage(int const pageNumber) const {
return (pageNumber != cachedPageNumber) || (splash == nullptr);
}
void prepareNewSplash(int const pageNumber, SplashColor &paperColor)
{
if(splash != nullptr)
{
cachedImage = QImage();
delete splash;
}
splash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor);
cachedPageNumber = pageNumber;
}
};
QImage &createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData);
QImage* createPDFImageHistorical(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds);
// Used when 'ZoomBehavior == 1 or 2'.
// =1 has only x3 zoom in cache (= loss if user zoom > 3.0).
// =2, has 2.5, 5 and 10 (= no loss, but a bit slower).
QVector<PdfZoomCacheData> m_pdfZoomCache;
// Used when 'ZoomBehavior == 0' (no cache).
SplashBitmap* mpSplashBitmapHistorical;
// Used when 'ZoomBehavior == 0' (no cache).
SplashOutputDev* mSplashHistorical;
PDFDoc *mDocument; PDFDoc *mDocument;
static QAtomicInt sInstancesCount; static QAtomicInt sInstancesCount;
qreal mSliceX; qreal mSliceX;
qreal mSliceY; qreal mSliceY;
SplashBitmap* mpSplashBitmap;
SplashOutputDev* mSplash;
}; };
#endif // XPDFRENDERER_H #endif // XPDFRENDERER_H

Loading…
Cancel
Save