diff --git a/OpenBoard.pro b/OpenBoard.pro index da285a9b..f36bf9fe 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -126,7 +126,11 @@ win32 { CONFIG += axcontainer exists(console):CONFIG += console QMAKE_CXXFLAGS += /MP - QMAKE_CXXFLAGS += /MD + CONFIG( debug, debug|release ) { + QMAKE_CXXFLAGS += /MDd + } else { + QMAKE_CXXFLAGS += /MD + } QMAKE_CXXFLAGS_RELEASE += /Od /Zi QMAKE_LFLAGS += /VERBOSE:LIB UB_LIBRARY.path = $$DESTDIR diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index 06c81ef6..c85c025e 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -123,6 +123,7 @@ RefreshRateInFramePerSecond=2 Margin=20 PageFormat=A4 Resolution=300 +ZoomBehavior=1 [Podcast] AudioRecordingDevice=Default diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 80e14677..b83d5f88 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -408,6 +408,7 @@ void UBSettings::init() pdfMargin = new UBSetting(this, "PDF", "Margin", "20"); pdfPageFormat = new UBSetting(this, "PDF", "PageFormat", "A4"); pdfResolution = new UBSetting(this, "PDF", "Resolution", "300"); + pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "1"); podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index c79e4f95..5b287354 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -359,6 +359,7 @@ class UBSettings : public QObject UBSetting* pdfMargin; UBSetting* pdfPageFormat; UBSetting* pdfResolution; + UBSetting* pdfZoomBehavior; UBSetting* podcastFramesPerSecond; UBSetting* podcastVideoSize; diff --git a/src/pdf/XPDFRenderer.cpp b/src/pdf/XPDFRenderer.cpp index f7769f4b..e128b33b 100644 --- a/src/pdf/XPDFRenderer.cpp +++ b/src/pdf/XPDFRenderer.cpp @@ -37,14 +37,28 @@ #endif #include "core/memcheck.h" +#include "core/UBSettings.h" + QAtomicInt XPDFRenderer::sInstancesCount = 0; -XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) - : mDocument(0) - , mpSplashBitmap(0) - , mSplash(0) +XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) : + mpSplashBitmapHistorical(nullptr), mSplashHistorical(nullptr), mDocument(nullptr) { + 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); if (!globalParams) { @@ -67,11 +81,19 @@ XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) XPDFRenderer::~XPDFRenderer() { - if(mSplash){ - delete mSplash; - mSplash = NULL; + for(int i = 0; i < m_pdfZoomCache.size(); i++) + { + 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) { delete mDocument; @@ -175,34 +197,19 @@ int XPDFRenderer::pageRotation(int pageNumber) const 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()) { SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white - if(mSplash) - delete mSplash; - mSplash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); + if(mSplashHistorical) + delete mSplashHistorical; + mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); #ifdef USE_XPDF - mSplash->startDoc(mDocument->getXRef()); + mSplashHistorical->startDoc(mDocument->getXRef()); #else - mSplash->startDoc(mDocument); + mSplashHistorical->startDoc(mDocument); #endif int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation) bool useMediaBox = false; @@ -213,7 +220,7 @@ QImage* XPDFRenderer::createPDFImage(int pageNumber, qreal xscale, qreal yscale, 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); } else @@ -223,11 +230,100 @@ QImage* XPDFRenderer::createPDFImage(int pageNumber, qreal xscale, qreal yscale, qreal sliceW = bounds.width() * xscale; 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); } - 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; } diff --git a/src/pdf/XPDFRenderer.h b/src/pdf/XPDFRenderer.h index c668515b..4ac3f9aa 100644 --- a/src/pdf/XPDFRenderer.h +++ b/src/pdf/XPDFRenderer.h @@ -51,6 +51,15 @@ 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 { Q_OBJECT @@ -74,15 +83,49 @@ class XPDFRenderer : public PDFRenderer private: 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 m_pdfZoomCache; + + // Used when 'ZoomBehavior == 0' (no cache). + SplashBitmap* mpSplashBitmapHistorical; + // Used when 'ZoomBehavior == 0' (no cache). + SplashOutputDev* mSplashHistorical; PDFDoc *mDocument; static QAtomicInt sInstancesCount; qreal mSliceX; qreal mSliceY; - - SplashBitmap* mpSplashBitmap; - SplashOutputDev* mSplash; }; #endif // XPDFRENDERER_H