Added 'ZoomBehavior=3' (compromise loss vs speed) and 'ZoomBehavior=4' (multithreaded). Made sure pdf image cache is skipped in non GUI cases (ex. exporting pdf).

preferencesAboutTextFull
John Papale 4 years ago
parent d5988f979c
commit a1074cc7d6
  1. 4
      src/adaptors/UBExportPDF.cpp
  2. 4
      src/adaptors/UBThumbnailAdaptor.cpp
  3. 4
      src/board/UBBoardController.cpp
  4. 17
      src/domain/UBGraphicsPDFItem.cpp
  5. 7
      src/domain/UBGraphicsPDFItem.h
  6. 3
      src/domain/UBGraphicsScene.cpp
  7. 2
      src/domain/UBGraphicsScene.h
  8. 13
      src/domain/UBItem.h
  9. 14
      src/pdf/GraphicsPDFItem.cpp
  10. 7
      src/pdf/GraphicsPDFItem.h
  11. 3
      src/pdf/PDFRenderer.h
  12. 4
      src/pdf/UBWebPluginPDFWidget.cpp
  13. 213
      src/pdf/XPDFRenderer.cpp
  14. 79
      src/pdf/XPDFRenderer.h

@ -113,7 +113,7 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin
QSize pageSize = scene->sceneSize(); QSize pageSize = scene->sceneSize();
// set high res rendering // set high res rendering
scene->setRenderingQuality(UBItem::RenderingQualityHigh); scene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed);
scene->setRenderingContext(UBGraphicsScene::NonScreen); scene->setRenderingContext(UBGraphicsScene::NonScreen);
// Setting output page size // Setting output page size
@ -132,7 +132,7 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin
// Restore screen rendering quality // Restore screen rendering quality
scene->setRenderingContext(UBGraphicsScene::Screen); scene->setRenderingContext(UBGraphicsScene::Screen);
scene->setRenderingQuality(UBItem::RenderingQualityNormal); scene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed);
// Restore background state // Restore background state
scene->setBackground(isDark, pageBackground); scene->setBackground(isDark, pageBackground);

@ -153,12 +153,12 @@ void UBThumbnailAdaptor::persistScene(UBDocumentProxy* proxy, UBGraphicsScene* p
} }
pScene->setRenderingContext(UBGraphicsScene::NonScreen); pScene->setRenderingContext(UBGraphicsScene::NonScreen);
pScene->setRenderingQuality(UBItem::RenderingQualityHigh); pScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed);
pScene->render(&painter, imageRect, sceneRect, Qt::KeepAspectRatio); pScene->render(&painter, imageRect, sceneRect, Qt::KeepAspectRatio);
pScene->setRenderingContext(UBGraphicsScene::Screen); pScene->setRenderingContext(UBGraphicsScene::Screen);
pScene->setRenderingQuality(UBItem::RenderingQualityNormal); pScene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed);
thumb.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(fileName, "JPG"); thumb.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(fileName, "JPG");
} }

@ -2179,13 +2179,13 @@ void UBBoardController::grabScene(const QRectF& pSceneRect)
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
mActiveScene->setRenderingContext(UBGraphicsScene::NonScreen); mActiveScene->setRenderingContext(UBGraphicsScene::NonScreen);
mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh); mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed);
mActiveScene->render(&painter, targetRect, pSceneRect); mActiveScene->render(&painter, targetRect, pSceneRect);
mActiveScene->setRenderingContext(UBGraphicsScene::Screen); mActiveScene->setRenderingContext(UBGraphicsScene::Screen);
// mActiveScene->setRenderingQuality(UBItem::RenderingQualityNormal); // mActiveScene->setRenderingQuality(UBItem::RenderingQualityNormal);
mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh); mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheAllowed);
mPaletteManager->addItem(QPixmap::fromImage(image)); mPaletteManager->addItem(QPixmap::fromImage(image));

@ -58,6 +58,16 @@ QVariant UBGraphicsPDFItem::itemChange(GraphicsItemChange change, const QVariant
return GraphicsPDFItem::itemChange(change, newValue); return GraphicsPDFItem::itemChange(change, newValue);
} }
void UBGraphicsPDFItem::updateChild()
{
CacheMode prevCacheMode = cacheMode();
GraphicsPDFItem::update();
// Workaround: Necessary, otherwise only the control scene is updated, the display scene refresh is ignored for an unknown reason.
setCacheMode(prevCacheMode);
}
void UBGraphicsPDFItem::setUuid(const QUuid &pUuid) void UBGraphicsPDFItem::setUuid(const QUuid &pUuid)
{ {
UBItem::setUuid(pUuid); UBItem::setUuid(pUuid);
@ -142,6 +152,11 @@ void UBGraphicsPDFItem::setRenderingQuality(RenderingQuality pRenderingQuality)
} }
} }
void UBGraphicsPDFItem::setCacheBehavior(UBItem::CacheBehavior cacheBehavior)
{
UBItem::setCacheBehavior(cacheBehavior);
GraphicsPDFItem::setCacheAllowed(cacheBehavior == UBItem::CacheAllowed);
}
UBGraphicsScene* UBGraphicsPDFItem::scene() UBGraphicsScene* UBGraphicsPDFItem::scene()
{ {
@ -153,7 +168,7 @@ UBGraphicsPixmapItem* UBGraphicsPDFItem::toPixmapItem() const
{ {
QPixmap pixmap(mRenderer->pageSizeF(mPageNumber).toSize()); QPixmap pixmap(mRenderer->pageSizeF(mPageNumber).toSize());
QPainter painter(&pixmap); QPainter painter(&pixmap);
mRenderer->render(&painter, mPageNumber); mRenderer->render(&painter, mPageNumber, false /* Cache allowed */);
UBGraphicsPixmapItem *pixmapItem = new UBGraphicsPixmapItem(); UBGraphicsPixmapItem *pixmapItem = new UBGraphicsPixmapItem();
pixmapItem->setPixmap(pixmap); pixmapItem->setPixmap(pixmap);

@ -59,13 +59,14 @@ class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphic
virtual void setRenderingQuality(RenderingQuality pRenderingQuality); virtual void setRenderingQuality(RenderingQuality pRenderingQuality);
virtual void setCacheBehavior(CacheBehavior cacheBehavior);
virtual UBGraphicsScene* scene(); virtual UBGraphicsScene* scene();
virtual UBGraphicsPixmapItem* toPixmapItem() const; virtual UBGraphicsPixmapItem* toPixmapItem() const;
virtual void clearSource(){;} virtual void clearSource(){;}
virtual void setUuid(const QUuid &pUuid); virtual void setUuid(const QUuid &pUuid);
protected: protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
@ -74,7 +75,9 @@ class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphic
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
virtual void updateChild();
private slots:
void OnRequireUpdate();
}; };
#endif /* UBGRAPHICSPDFITEM_H_ */ #endif /* UBGRAPHICSPDFITEM_H_ */

@ -2396,7 +2396,7 @@ void UBGraphicsScene::addMask(const QPointF &center)
curtain->setSelected(true); curtain->setSelected(true);
} }
void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQuality) void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior)
{ {
QListIterator<QGraphicsItem*> itItems(mFastAccessItems); QListIterator<QGraphicsItem*> itItems(mFastAccessItems);
@ -2409,6 +2409,7 @@ void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQua
if (ubItem) if (ubItem)
{ {
ubItem->setRenderingQuality(pRenderingQuality); ubItem->setRenderingQuality(pRenderingQuality);
ubItem->setCacheBehavior(cacheBehavior);
} }
} }
} }

@ -295,7 +295,7 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem
mViewState = pViewState; mViewState = pViewState;
} }
virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality); virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior);
QList<QUrl> relativeDependencies() const; QList<QUrl> relativeDependencies() const;

@ -51,6 +51,13 @@ class UBItem
RenderingQualityNormal = 0, RenderingQualityHigh RenderingQualityNormal = 0, RenderingQualityHigh
}; };
enum CacheBehavior
{
CacheNotAllowed,
CacheAllowed,
nbrCacheBehavior
};
virtual QUuid uuid() const virtual QUuid uuid() const
{ {
return mUuid; return mUuid;
@ -71,6 +78,11 @@ class UBItem
mRenderingQuality = pRenderingQuality; mRenderingQuality = pRenderingQuality;
} }
virtual void setCacheBehavior(CacheBehavior cacheBehavior)
{
mCacheBehavior = cacheBehavior;
}
virtual UBItem* deepCopy() const = 0; virtual UBItem* deepCopy() const = 0;
virtual void copyItemParameters(UBItem *copy) const = 0; virtual void copyItemParameters(UBItem *copy) const = 0;
@ -98,6 +110,7 @@ class UBItem
QUrl mSourceUrl; QUrl mSourceUrl;
CacheBehavior mCacheBehavior;
}; };
class UBGraphicsItem class UBGraphicsItem

@ -39,13 +39,16 @@ GraphicsPDFItem::GraphicsPDFItem(PDFRenderer *renderer, int pageNumber, QGraphic
: QObject(0), QGraphicsItem(parentItem) : QObject(0), QGraphicsItem(parentItem)
, mRenderer(renderer) , mRenderer(renderer)
, mPageNumber(pageNumber) , mPageNumber(pageNumber)
, mIsCacheAllowed(true)
{ {
setCacheMode(QGraphicsItem::DeviceCoordinateCache); setCacheMode(QGraphicsItem::DeviceCoordinateCache);
mRenderer->attach(); mRenderer->attach();
connect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate()));
} }
GraphicsPDFItem::~GraphicsPDFItem() GraphicsPDFItem::~GraphicsPDFItem()
{ {
disconnect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate()));
mRenderer->detach(); mRenderer->detach();
} }
@ -70,8 +73,15 @@ void GraphicsPDFItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o
} }
if (option) if (option)
mRenderer->render(painter, mPageNumber, option->exposedRect); {
else mRenderer->render(painter, mPageNumber, mIsCacheAllowed, option->exposedRect);
} else
qWarning("GraphicsPDFItem::paint: option is null, ignoring painting"); qWarning("GraphicsPDFItem::paint: option is null, ignoring painting");
} }
void GraphicsPDFItem::OnRequireUpdate()
{
updateChild();
}

@ -51,10 +51,15 @@ class GraphicsPDFItem : public QObject, public QGraphicsItem
int pageNumber() const { return mPageNumber; } int pageNumber() const { return mPageNumber; }
QUuid fileUuid() const { return mRenderer->fileUuid(); } QUuid fileUuid() const { return mRenderer->fileUuid(); }
QByteArray fileData() const { return mRenderer->fileData(); } QByteArray fileData() const { return mRenderer->fileData(); }
void setCacheAllowed(bool const value) { mIsCacheAllowed = value; }
virtual void updateChild() = 0;
protected: protected:
PDFRenderer *mRenderer; PDFRenderer *mRenderer;
int mPageNumber; int mPageNumber;
bool mIsCacheAllowed;
private slots:
void OnRequireUpdate();
}; };
#endif // GRAPHICSPDFITEM_H #endif // GRAPHICSPDFITEM_H

@ -66,8 +66,7 @@ class PDFRenderer : public QObject
void setDPI(int desiredDPI) { this->dpiForRendering = desiredDPI; } void setDPI(int desiredDPI) { this->dpiForRendering = desiredDPI; }
public slots: virtual void render(QPainter *p, int pageNumber, bool const cacheAllowed, const QRectF &bounds = QRectF()) = 0;
virtual void render(QPainter *p, int pageNumber, const QRectF &bounds = QRectF()) = 0;
private: private:
QAtomicInt mRefCount; QAtomicInt mRefCount;

@ -152,7 +152,9 @@ void UBWebPluginPDFWidget::paintEvent(QPaintEvent *event)
painter.translate((geometry().width() - (pageSize.width() * mScale)) / 2, 0); painter.translate((geometry().width() - (pageSize.width() * mScale)) / 2, 0);
painter.scale(mScale, mScale); painter.scale(mScale, mScale);
mRenderer->render(&painter, mPageNumber, event->rect()); // Note: If you ever want to use the cache, you need to make sure the object 'update' is called
// when the processing is completed. See 'signalUpdateParent'.
mRenderer->render(&painter, mPageNumber, false /* Cache allowed */, event->rect());
painter.setPen(QPen(Qt::gray, 1)); painter.setPen(QPen(Qt::gray, 1));
painter.drawRect(0, 0, pageSize.width(), pageSize.height()); painter.drawRect(0, 0, pageSize.width(), pageSize.height());
} }

@ -47,23 +47,47 @@ namespace constants{
const double mode2_zoomFactorStage1 = 2.5; const double mode2_zoomFactorStage1 = 2.5;
const double mode2_zoomFactorStage2 = 5.0; const double mode2_zoomFactorStage2 = 5.0;
const double mode2_zoomFactorStage3 = 10.0; const double mode2_zoomFactorStage3 = 10.0;
const double mode3_zoomFactorStage1 = 1.0;
const double mode3_zoomFactorStage2 = 3.0;
const double mode4_zoomFactorStart = .25;
const double mode4_zoomFactorStepSquare = .25;
const double mode4_zoomFactorIterations = 7;
SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white
} }
XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) : XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile)
mpSplashBitmapHistorical(nullptr), mSplashHistorical(nullptr), mDocument(nullptr) : m_pdfZoomMode(UBSettings::settings()->pdfZoomBehavior->get().toUInt())
, mpSplashBitmapHistorical(nullptr)
, mSplashHistorical(nullptr)
, mDocument(nullptr)
{ {
switch (UBSettings::settings()->pdfZoomBehavior->get().toUInt()) { switch (m_pdfZoomMode) {
case 0: // Render each time (historical initial implementation). case 0: // Render each time (historical initial implementation).
break; break;
case 1: // Render a single image, degradated quality when zoomed big. case 1: // Render a single image, degradated quality when zoomed big.
default: default:
m_pdfZoomCache.push_back(constants::mode1_zoomFactor); m_pdfZoomCache.push_back(constants::mode1_zoomFactor);
break; break;
case 2: // Render three images, optimal quality all the time. case 2: // Render three images, use downsampling, optimal quality all the time, slower.
m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage1); m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage1);
m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage2); m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage2);
m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage3); m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage3);
break; break;
case 3: // Do not downsample, minimal loss, faster. Not necessarily the expected result,
// because a 'zoom factor 1' here does not correspond to a user choice 'zoom factor 1'.
// The zoom requested is dependent on many factors, including the input pdf, the output screen resolution
// and the zoom user choice. Thus, the 'mode3_zoomFactorStage1' might be fine on one screen, but
// fuzzy on another one.
m_pdfZoomCache.push_back(constants::mode3_zoomFactorStage1);
m_pdfZoomCache.push_back(constants::mode3_zoomFactorStage2);
break;
case 4: // Multithreaded, several steps, downsampled.
for (int i = 0; i < constants::mode4_zoomFactorIterations; i++ )
{
double const zoomValue = constants::mode4_zoomFactorStart+constants::mode4_zoomFactorStepSquare*static_cast<double>(i*i);
m_pdfZoomCache.push_back(zoomValue);
}
break;
} }
Q_UNUSED(importingFile); Q_UNUSED(importingFile);
@ -84,18 +108,19 @@ XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) :
mDocument = new PDFDoc(new GooString(filename.toLocal8Bit()), 0, 0, 0); // the filename GString is deleted on PDFDoc desctruction mDocument = new PDFDoc(new GooString(filename.toLocal8Bit()), 0, 0, 0); // the filename GString is deleted on PDFDoc desctruction
#endif #endif
sInstancesCount.ref(); sInstancesCount.ref();
connect(&m_cacheThread, SIGNAL(finished()), this, SLOT(OnThreadFinished()));
} }
XPDFRenderer::~XPDFRenderer() XPDFRenderer::~XPDFRenderer()
{ {
disconnect(&m_cacheThread, SIGNAL(finished()), this, SLOT(OnThreadFinished()));
m_cacheThread.cancelPending();
m_cacheThread.wait(); // Would crash if data deleted during processing.
for(int i = 0; i < m_pdfZoomCache.size(); i++) for(int i = 0; i < m_pdfZoomCache.size(); i++)
{ {
PdfZoomCacheData &cacheData = m_pdfZoomCache[i]; PdfZoomCacheData &cacheData = m_pdfZoomCache[i];
if(cacheData.splash != nullptr){ cacheData.cleanup();
cacheData.cachedImage = QImage(); // The 'cachedImage' uses a buffer from 'splash'.
delete cacheData.splash;
cacheData.splash = nullptr;
}
} }
if(mSplashHistorical) if(mSplashHistorical)
@ -209,10 +234,10 @@ QImage* XPDFRenderer::createPDFImageHistorical(int pageNumber, qreal xscale, qre
{ {
if (isValid()) if (isValid())
{ {
SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white
if(mSplashHistorical) if(mSplashHistorical)
delete mSplashHistorical; delete mSplashHistorical;
mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, paperColor);
mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, constants::paperColor);
#ifdef USE_XPDF #ifdef USE_XPDF
mSplashHistorical->startDoc(mDocument->getXRef()); mSplashHistorical->startDoc(mDocument->getXRef());
#else #else
@ -246,23 +271,55 @@ QImage* XPDFRenderer::createPDFImageHistorical(int pageNumber, qreal xscale, qre
return new QImage(mpSplashBitmapHistorical->getDataPtr(), mpSplashBitmapHistorical->getWidth(), mpSplashBitmapHistorical->getHeight(), mpSplashBitmapHistorical->getWidth() * 3, QImage::Format_RGB888); 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) void XPDFRenderer::OnThreadFinished()
{
emit signalUpdateParent();
if (m_cacheThread.isJobPending())
m_cacheThread.start();
}
void XPDFRenderer::render(QPainter *p, int pageNumber, bool const cacheAllowed, const QRectF &bounds)
{ {
//qDebug() << "render enter";
Q_UNUSED(bounds); Q_UNUSED(bounds);
if (isValid()) if (isValid())
{ {
if (m_pdfZoomCache.size() > 0) if (m_pdfZoomCache.size() > 0 && cacheAllowed)
{ {
qreal xscale = p->worldTransform().m11(); qreal xscale = p->worldTransform().m11();
qreal yscale = p->worldTransform().m22(); qreal yscale = p->worldTransform().m22();
Q_ASSERT(qFuzzyCompare(xscale, yscale)); // Zoom equal in all axes expected. Q_ASSERT(qFuzzyCompare(xscale, yscale)); // Zoom equal in all axes expected.
Q_ASSERT(xscale > 0.0); // Potential Div0 later if this assert fail. Q_ASSERT(xscale > 0.0); // Potential Div0 later if this assert fail.
qreal zoomRequested = xscale;
int zoomIndex = 0; int zoomIndex = 0;
if (m_pdfZoomMode == 3)
{
// Choose a zoom which is inferior or equivalent than the user choice (= minor loss, downscaling).
bool foundIndex = false;
for (zoomIndex = m_pdfZoomCache.size()-1; zoomIndex >= 0 && !foundIndex;)
{
if (zoomRequested >= m_pdfZoomCache[zoomIndex].ratio) {
foundIndex = true;
} else {
zoomIndex--;
}
}
if (!foundIndex) // Use the smallest one.
zoomIndex = 0;
if (zoomIndex == 0 && m_pdfZoomCache[zoomIndex].ratio != zoomRequested)
{
m_pdfZoomCache[zoomIndex].cleanup();
m_pdfZoomCache[zoomIndex] = PdfZoomCacheData(zoomRequested);
}
} else {
// Choose a zoom which is superior or equivalent than the user choice (= no loss, upscaling).
bool foundIndex = false; bool foundIndex = false;
for (; zoomIndex < m_pdfZoomCache.size() && !foundIndex;) for (; zoomIndex < m_pdfZoomCache.size() && !foundIndex;)
{ {
if (xscale <= m_pdfZoomCache[zoomIndex].ratio) { if (zoomRequested <= (m_pdfZoomCache[zoomIndex].ratio+0.1)) {
foundIndex = true; foundIndex = true;
} else { } else {
zoomIndex++; zoomIndex++;
@ -271,16 +328,63 @@ void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds)
if (!foundIndex) // Use the previous one. if (!foundIndex) // Use the previous one.
zoomIndex--; zoomIndex--;
}
QImage pdfImage = createPDFImageCached(pageNumber, m_pdfZoomCache[zoomIndex]);
qreal ratioExpected = m_pdfZoomCache[zoomIndex].ratio;
qreal ratioObtained = ratioExpected;
int const initialZoomIndex = zoomIndex;
if (pdfImage == QImage() && m_pdfZoomCache[zoomIndex].hasToBeProcessed)
{
// Try to temporarily fallback on a valid image, for a fuzzy or downsampled preview.
// The actual result will be updated after the processing.
bool isCurrent = true;
while (zoomIndex < m_pdfZoomCache.size()-1 && (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || (m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber && !isCurrent)))
{
zoomIndex = zoomIndex+1;
isCurrent = false;
}
while (zoomIndex > 0 && (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber))
zoomIndex = zoomIndex-1;
ratioObtained = m_pdfZoomCache[zoomIndex].ratio;
}
if (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber)
{
// No alternate image found. Build an alternate image in order to display some progress.
// Also make sure we fallback to the initial ratio request.
zoomIndex = initialZoomIndex;
qreal ratioDiff = m_pdfZoomCache[zoomIndex].ratio;
pdfImage = QImage(bounds.width()*ratioDiff, bounds.height()*ratioDiff, QImage::Format_RGB888);
pdfImage.fill("white");
QPainter painter(&pdfImage);
QString const text = tr("Processing...");
QFont font = painter.font();
if (font.pixelSize() != -1)
font.setPixelSize(ratioDiff*font.pixelSize());
else
font.setPointSizeF(ratioDiff*font.pointSizeF());
painter.setFont(font);
QFontMetrics textMetric(font, &pdfImage);
QSize textSize = textMetric.size(0, text);
painter.drawText((bounds.width()*ratioDiff-textSize.width())/2, (bounds.height()*ratioDiff-textSize.height())/2, text);
} else {
pdfImage = m_pdfZoomCache[zoomIndex].cachedImage;
}
QImage const &pdfImage = createPDFImageCached(pageNumber, m_pdfZoomCache[zoomIndex]);
QTransform savedTransform = p->worldTransform(); QTransform savedTransform = p->worldTransform();
double const ratioDifferenceBetweenWorldAndImage = 1.0/m_pdfZoomCache[zoomIndex].ratio; 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 // The 'pdfImage' is maybe rendered with a different quality than requested. We adjust the 'transform' to zoom it
// out the required ratio. // in or out of the required ratio.
QTransform newTransform = savedTransform.scale(ratioDifferenceBetweenWorldAndImage, ratioDifferenceBetweenWorldAndImage); QTransform newTransform = savedTransform.scale(ratioDifferenceBetweenWorldAndImage, ratioDifferenceBetweenWorldAndImage);
p->setWorldTransform(newTransform); p->setWorldTransform(newTransform);
/* qDebug() << "drawImage size=" << p->viewport() << "bounds" << bounds <<
"pdfImage" << pdfImage.size() << "savedTransform" << savedTransform.m11() <<
"ratioDiff" << ratioDifferenceBetweenWorldAndImage << "zoomRequested" << zoomRequested <<
"zoomIndex" << zoomIndex; */
p->drawImage(QPointF( mSliceX, mSliceY), pdfImage); p->drawImage(QPointF( mSliceX, mSliceY), pdfImage);
p->setWorldTransform(savedTransform); p->setWorldTransform(savedTransform);
@ -291,46 +395,89 @@ void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds)
QImage *pdfImage = createPDFImageHistorical(pageNumber, xscale, yscale, bounds); QImage *pdfImage = createPDFImageHistorical(pageNumber, xscale, yscale, bounds);
QTransform savedTransform = p->worldTransform(); QTransform savedTransform = p->worldTransform();
p->resetTransform(); p->resetTransform();
//qDebug() << "drawImage size=" << p->viewport() << "bounds" << bounds << "pdfImage" << pdfImage->size() << "savedTransform" << savedTransform.m11();
p->drawImage(QPointF(savedTransform.dx() + mSliceX, savedTransform.dy() + mSliceY), *pdfImage); p->drawImage(QPointF(savedTransform.dx() + mSliceX, savedTransform.dy() + mSliceY), *pdfImage);
p->setWorldTransform(savedTransform); p->setWorldTransform(savedTransform);
delete pdfImage; delete pdfImage;
} }
} }
//qDebug() << "render leave";
} }
QImage& XPDFRenderer::createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData) QImage& XPDFRenderer::createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData)
{ {
if (isValid()) if (isValid())
{ {
SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white if (cacheData.requireUpdateImage(pageNumber) && !cacheData.hasToBeProcessed)
if (cacheData.requireUpdateImage(pageNumber)) {
mSliceX = 0.;
mSliceY = 0.;
CacheThread::JobData jobData;
jobData.cacheData = &cacheData;
jobData.document = mDocument;
jobData.dpiForRendering = this->dpiForRendering;
jobData.pageNumber = pageNumber;
jobData.cacheData->hasToBeProcessed = true;
// Make sure we reset that image, because the data uses 'splash' buffer, which will be deallocated and
// reallocated when the job is started.
jobData.cacheData->cachedImage = QImage();
m_cacheThread.pushJob(jobData);
if (m_pdfZoomMode == 4)
{
// Start the job multithreaded. The item will be refreshed when the signal 'finished' is emitted.
m_cacheThread.start();
} else {
// Perform the job now. Note this will lock the GUI until the job is done.
m_cacheThread.run();
}
}
} else {
cacheData.cachedImage = QImage();
}
return cacheData.cachedImage;
}
void XPDFRenderer::CacheThread::run()
{ {
cacheData.prepareNewSplash(pageNumber, paperColor); m_jobMutex.lock();
CacheThread::JobData jobData = m_nextJob.first();
m_nextJob.pop_front();
/* qDebug() << "XPDFRenderer::CacheThread starting page" << jobData.pageNumber
<< "ratio" << jobData.cacheData->ratio; */
jobData.cacheData->prepareNewSplash(jobData.pageNumber, constants::paperColor);
#ifdef USE_XPDF #ifdef USE_XPDF
cacheData.splash->startDoc(mDocument->getXRef()); jobData.cacheData->splash->startDoc(jobData.document->getXRef());
#else #else
cacheData.splash->startDoc(mDocument); jobData.cacheData->splash->startDoc(jobData.document);
#endif #endif
m_jobMutex.unlock();
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;
bool crop = true; bool crop = true;
bool printing = false; bool printing = false;
mSliceX = 0.;
mSliceY = 0.;
mDocument->displayPage(cacheData.splash, pageNumber, this->dpiForRendering * cacheData.ratio, this->dpiForRendering * cacheData.ratio, jobData.document->displayPage(jobData.cacheData->splash, jobData.pageNumber, jobData.dpiForRendering * jobData.cacheData->ratio,
jobData.dpiForRendering * jobData.cacheData->ratio,
rotation, useMediaBox, crop, printing); rotation, useMediaBox, crop, printing);
cacheData.splashBitmap = cacheData.splash->getBitmap();
}
m_jobMutex.lock();
jobData.cacheData->splashBitmap = jobData.cacheData->splash->getBitmap();
// Note this uses the 'cacheData.splash->getBitmap()->getDataPtr()' as data buffer. // Note this uses the 'cacheData.splash->getBitmap()->getDataPtr()' as data buffer.
cacheData.cachedImage = QImage(cacheData.splashBitmap->getDataPtr(), cacheData.splashBitmap->getWidth(), cacheData.splashBitmap->getHeight(), jobData.cacheData->cachedImage = QImage(jobData.cacheData->splashBitmap->getDataPtr(), jobData.cacheData->splashBitmap->getWidth(), jobData.cacheData->splashBitmap->getHeight(),
cacheData.splashBitmap->getWidth() * 3 /* bytesPerLine, 24 bits for RGB888, = 3 bytes */, jobData.cacheData->splashBitmap->getWidth() * 3 /* bytesPerLine, 24 bits for RGB888, = 3 bytes */,
QImage::Format_RGB888); QImage::Format_RGB888);
} else {
cacheData.cachedImage = QImage();
}
return cacheData.cachedImage; /* qDebug() << "XPDFRenderer::CacheThread completed page" << jobData.pageNumber
<< "ratio" << jobData.cacheData->ratio << "final size is" << jobData.cacheData->cachedImage.size(); */
jobData.cacheData->hasToBeProcessed = false;
m_jobMutex.unlock();
} }

@ -30,6 +30,8 @@
#ifndef XPDFRENDERER_H #ifndef XPDFRENDERER_H
#define XPDFRENDERER_H #define XPDFRENDERER_H
#include <QImage> #include <QImage>
#include <QThread>
#include <QMutexLocker>
#include "PDFRenderer.h" #include "PDFRenderer.h"
#include <splash/SplashBitmap.h> #include <splash/SplashBitmap.h>
@ -59,30 +61,30 @@ class XPDFRenderer : public PDFRenderer
XPDFRenderer(const QString &filename, bool importingFile = false); XPDFRenderer(const QString &filename, bool importingFile = false);
virtual ~XPDFRenderer(); virtual ~XPDFRenderer();
bool isValid() const; virtual bool isValid() const override;
virtual int pageCount() const override;
virtual QSizeF pageSizeF(int pageNumber) const override;
virtual int pageRotation(int pageNumber) const override;
virtual QString title() const override;
virtual void render(QPainter *p, int pageNumber, const bool cacheAllowed, const QRectF &bounds = QRectF()) override;
virtual int pageCount() const; signals:
void signalUpdateParent();
virtual QSizeF pageSizeF(int pageNumber) const;
virtual int pageRotation(int pageNumber) const;
virtual QString title() const;
public slots:
void render(QPainter *p, int pageNumber, const QRectF &bounds = QRectF());
private: private:
void init(); void init();
struct PdfZoomCacheData { struct PdfZoomCacheData {
PdfZoomCacheData(double const a_ratio) : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(a_ratio) {}; PdfZoomCacheData(double const a_ratio) : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(a_ratio), hasToBeProcessed(false) {};
~PdfZoomCacheData() {}; ~PdfZoomCacheData() {};
SplashBitmap* splashBitmap; SplashBitmap* splashBitmap;
//! Note: The 'cachedImage' uses a buffer from 'splash'. Make sure it is invalidated BEFORE 'splash' deallocation.
QImage cachedImage; QImage cachedImage;
int cachedPageNumber; int cachedPageNumber;
SplashOutputDev* splash; SplashOutputDev* splash;
double const ratio; double ratio;
bool hasToBeProcessed;
QList<QObject *> updateListAfterProcessing;
bool requireUpdateImage(int const pageNumber) const { bool requireUpdateImage(int const pageNumber) const {
return (pageNumber != cachedPageNumber) || (splash == nullptr); return (pageNumber != cachedPageNumber) || (splash == nullptr);
@ -98,15 +100,61 @@ class XPDFRenderer : public PDFRenderer
splash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); splash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor);
cachedPageNumber = pageNumber; cachedPageNumber = pageNumber;
} }
void cleanup()
{
if(splash != nullptr){
cachedImage = QImage();
delete splash;
splash = nullptr;
}
}
PdfZoomCacheData& operator=(PdfZoomCacheData &rhs) {
Q_ASSERT(splash == nullptr); // Assigning non null rhs not managed.
ratio = rhs.ratio;
return *this;
}
}; };
//! Spawned when a pdf processing is required, when no matching image is found in cache.
class CacheThread : public QThread
{
public:
struct JobData {
PdfZoomCacheData* cacheData;
PDFDoc *document;
int pageNumber;
double dpiForRendering;
};
CacheThread() {}
~CacheThread() {}
void pushJob(JobData &jobData) {
QMutexLocker lock(&m_jobMutex);
m_nextJob.push_back(jobData);
}
virtual void run() override;
bool isJobPending() { QMutexLocker lock(&m_jobMutex); return m_nextJob.size() > 0; }
void cancelPending() { QMutexLocker lock(&m_jobMutex); m_nextJob.clear(); }
private:
QList<JobData> m_nextJob;
QMutex m_jobMutex;
};
CacheThread m_cacheThread;
QImage &createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData); QImage &createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData);
QImage* createPDFImageHistorical(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds); QImage* createPDFImageHistorical(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds);
// Used when 'ZoomBehavior == 1 or 2'. // Used when 'ZoomBehavior == 1, 2, 3 or 4'.
// =1 has only x3 zoom in cache (= loss if user zoom > 3.0). // =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). // =2, has 2.5, 5 and 10 (= no loss, but a bit slower).
// =3, has 1.0, 2.5, 5 and 10, but downsampled instead of upsampled (= minor quality loss, a bit faster).
// =4, multithreaded, multiple level of zoom.
QVector<PdfZoomCacheData> m_pdfZoomCache; QVector<PdfZoomCacheData> m_pdfZoomCache;
int const m_pdfZoomMode;
// Used when 'ZoomBehavior == 0' (no cache). // Used when 'ZoomBehavior == 0' (no cache).
SplashBitmap* mpSplashBitmapHistorical; SplashBitmap* mpSplashBitmapHistorical;
@ -117,6 +165,9 @@ class XPDFRenderer : public PDFRenderer
static QAtomicInt sInstancesCount; static QAtomicInt sInstancesCount;
qreal mSliceX; qreal mSliceX;
qreal mSliceY; qreal mSliceY;
private slots:
void OnThreadFinished();
}; };
#endif // XPDFRENDERER_H #endif // XPDFRENDERER_H

Loading…
Cancel
Save