From 805c967b70105300c9f480d20cfbe79045e2b784 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 17 Nov 2023 15:25:25 +0100 Subject: [PATCH] Issue #107: Create document with converted bitonic images --- .../pdfcreatebitonaldocumentdialog.cpp | 113 +++++++++++++++++- Pdf4QtViewer/pdfcreatebitonaldocumentdialog.h | 14 ++- .../pdfcreatebitonaldocumentdialog.ui | 35 +++++- Pdf4QtViewer/pdfprogramcontroller.cpp | 2 +- 4 files changed, 156 insertions(+), 8 deletions(-) diff --git a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.cpp b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.cpp index 8cac3d3..e8de277 100644 --- a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.cpp +++ b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.cpp @@ -25,6 +25,7 @@ #include "pdfexception.h" #include "pdfwidgetutils.h" #include "pdfimageconversion.h" +#include "pdfstreamfilters.h" #include #include @@ -200,6 +201,7 @@ private: PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDocument* document, const pdf::PDFCMS* cms, + pdf::PDFProgress* progress, QWidget* parent) : QDialog(parent), ui(new Ui::PDFCreateBitonalDocumentDialog), @@ -209,7 +211,8 @@ PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDoc m_conversionInProgress(false), m_processed(false), m_leftPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)), - m_rightPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)) + m_rightPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)), + m_progress(progress) { ui->setupUi(this); @@ -222,7 +225,7 @@ PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDoc m_classifier.classify(document); m_imageReferences = m_classifier.getObjectsByType(pdf::PDFObjectClassifier::Image); - m_createBitonalDocumentButton = ui->buttonBox->addButton(tr("Process"), QDialogButtonBox::ActionRole); + m_createBitonalDocumentButton = ui->buttonBox->addButton(tr("Perform"), QDialogButtonBox::ActionRole); connect(m_createBitonalDocumentButton, &QPushButton::clicked, this, &PDFCreateBitonalDocumentDialog::onCreateBitonalDocumentButtonClicked); connect(ui->automaticThresholdRadioButton, &QRadioButton::clicked, this, &PDFCreateBitonalDocumentDialog::updateUi); connect(ui->manualThresholdRadioButton, &QRadioButton::clicked, this, &PDFCreateBitonalDocumentDialog::updateUi); @@ -231,7 +234,7 @@ PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDoc connect(ui->imageListWidget, &QListWidget::currentItemChanged, this, &PDFCreateBitonalDocumentDialog::updatePreview); connect(ui->thresholdEditBox, &QSpinBox::editingFinished, this, &PDFCreateBitonalDocumentDialog::updatePreview); - pdf::PDFWidgetUtils::scaleWidget(this, QSize(640, 380)); + pdf::PDFWidgetUtils::scaleWidget(this, QSize(1024, 768)); updateUi(); pdf::PDFWidgetUtils::style(this); @@ -249,9 +252,93 @@ PDFCreateBitonalDocumentDialog::~PDFCreateBitonalDocumentDialog() delete ui; } +void PDFCreateBitonalDocumentDialog::onPerformFinished() +{ + m_future.waitForFinished(); + m_conversionInProgress = false; + m_processed = true; + updateUi(); +} + void PDFCreateBitonalDocumentDialog::createBitonalDocument() { + std::vector imagesToBeConverted; + std::copy_if(m_imagesToBeConverted.begin(), m_imagesToBeConverted.end(), std::back_inserter(imagesToBeConverted), [](const auto& item) { return item.conversionEnabled; }); + // Do we have something to be converted? + if (imagesToBeConverted.empty()) + { + return; + } + + pdf::ProgressStartupInfo info; + info.showDialog = true; + info.text = tr("Converting images..."); + m_progress->start(imagesToBeConverted.size(), std::move(info)); + + pdf::PDFObjectStorage storage = m_document->getStorage(); + + pdf::PDFCMSGeneric genericCms; + pdf::PDFRenderErrorReporterDummy errorReporter; + + for (int i = 0; i < imagesToBeConverted.size(); ++i) + { + pdf::PDFObjectReference reference = imagesToBeConverted[i].imageReference; + std::optional pdfImage = getImageFromReference(reference); + + QImage image; + try + { + image = pdfImage->getImage(&genericCms, &errorReporter, nullptr); + } + catch (pdf::PDFException) + { + // Do nothing + } + + // Just for code safety - this should never occur in here. + if (image.isNull()) + { + continue; + } + + pdf::PDFImageConversion imageConversion; + imageConversion.setConversionMethod(m_conversionMethod); + imageConversion.setThreshold(m_manualThreshold); + imageConversion.setImage(image); + + if (imageConversion.convert()) + { + QImage bitonicImage = imageConversion.getConvertedImage(); + Q_ASSERT(bitonicImage.format() == QImage::Format_Mono); + + QByteArray imageData((const char*)bitonicImage.constBits(), bitonicImage.sizeInBytes()); + QByteArray compressedData = pdf::PDFFlateDecodeFilter::compress(imageData); + + pdf::PDFArray array; + array.appendItem(pdf::PDFObject::createName("FlateDecode")); + + pdf::PDFDictionary dictionary; + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("Type"), pdf::PDFObject::createName("XObject")); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("Subtype"), pdf::PDFObject::createName("Image")); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("Width"), pdf::PDFObject::createInteger(image.width())); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("Height"), pdf::PDFObject::createInteger(image.height())); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("ColorSpace"), pdf::PDFObject::createName("DeviceGray")); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("BitsPerComponent"), pdf::PDFObject::createInteger(1)); + dictionary.addEntry(pdf::PDFInplaceOrMemoryString("Predictor"), pdf::PDFObject::createInteger(1)); + dictionary.setEntry(pdf::PDFInplaceOrMemoryString("Length"), pdf::PDFObject::createInteger(compressedData.size())); + dictionary.setEntry(pdf::PDFInplaceOrMemoryString("Filters"), pdf::PDFObject::createArray(std::make_shared(qMove(array)))); + + pdf::PDFObject imageObject = pdf::PDFObject::createStream(std::make_shared(qMove(dictionary), qMove(compressedData))); + storage.setObject(reference, std::move(imageObject)); + } + + m_progress->step(); + } + + m_bitonalDocument = pdf::PDFDocument(std::move(storage), m_document->getInfo()->version, QByteArray()); + + m_progress->finish(); } void PDFCreateBitonalDocumentDialog::onCreateBitonalDocumentButtonClicked() @@ -259,8 +346,14 @@ void PDFCreateBitonalDocumentDialog::onCreateBitonalDocumentButtonClicked() Q_ASSERT(!m_conversionInProgress); Q_ASSERT(!m_future.isRunning()); + m_conversionMethod = ui->automaticThresholdRadioButton->isChecked() ? pdf::PDFImageConversion::ConversionMethod::Automatic : pdf::PDFImageConversion::ConversionMethod::Manual; + m_manualThreshold = ui->thresholdEditBox->value(); + m_conversionInProgress = true; m_future = QtConcurrent::run([this]() { createBitonalDocument(); }); + m_futureWatcher.emplace(); + connect(&m_futureWatcher.value(), &QFutureWatcher::finished, this, &PDFCreateBitonalDocumentDialog::onPerformFinished); + m_futureWatcher->setFuture(m_future); updateUi(); } @@ -273,7 +366,7 @@ void PDFCreateBitonalDocumentDialog::loadImages() int i = 0; for (pdf::PDFObjectReference reference : m_imageReferences) { - if (i++>10) + if (i++>1) { break; } @@ -286,7 +379,17 @@ void PDFCreateBitonalDocumentDialog::loadImages() pdf::PDFCMSGeneric genericCms; pdf::PDFRenderErrorReporterDummy errorReporter; - QImage image = pdfImage->getImage(&genericCms, &errorReporter, nullptr); + QImage image; + + try + { + image = pdfImage->getImage(&genericCms, &errorReporter, nullptr); + } + catch (pdf::PDFException) + { + // Do nothing + } + if (image.isNull()) { continue; diff --git a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.h b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.h index a634721..73e468a 100644 --- a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.h +++ b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.h @@ -22,6 +22,8 @@ #include "pdfdocument.h" #include "pdfobjectutils.h" #include "pdfimage.h" +#include "pdfimageconversion.h" +#include "pdfprogress.h" #include #include @@ -57,7 +59,10 @@ class PDFCreateBitonalDocumentDialog : public QDialog Q_OBJECT public: - explicit PDFCreateBitonalDocumentDialog(const pdf::PDFDocument* document, const pdf::PDFCMS* cms, QWidget* parent); + explicit PDFCreateBitonalDocumentDialog(const pdf::PDFDocument* document, + const pdf::PDFCMS* cms, + pdf::PDFProgress* progress, + QWidget* parent); virtual ~PDFCreateBitonalDocumentDialog() override; pdf::PDFDocument takeBitonaldDocument() { return qMove(m_bitonalDocument); } @@ -71,6 +76,7 @@ public: private: void createBitonalDocument(); void onCreateBitonalDocumentButtonClicked(); + void onPerformFinished(); void loadImages(); void updateUi(); @@ -85,6 +91,7 @@ private: bool m_conversionInProgress; bool m_processed; QFuture m_future; + std::optional> m_futureWatcher; pdf::PDFDocument m_bitonalDocument; pdf::PDFObjectClassifier m_classifier; std::vector m_imageReferences; @@ -95,6 +102,11 @@ private: PDFCreateBitonalDocumentPreviewWidget* m_leftPreviewWidget; PDFCreateBitonalDocumentPreviewWidget* m_rightPreviewWidget; + + pdf::PDFProgress* m_progress; + + pdf::PDFImageConversion::ConversionMethod m_conversionMethod = pdf::PDFImageConversion::ConversionMethod::Automatic; + int m_manualThreshold = 128; }; } // namespace pdfviewer diff --git a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.ui b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.ui index a772509..b2b27d8 100644 --- a/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.ui +++ b/Pdf4QtViewer/pdfcreatebitonaldocumentdialog.ui @@ -73,5 +73,38 @@ - + + + buttonBox + accepted() + PDFCreateBitonalDocumentDialog + accept() + + + 370 + 508 + + + 370 + 264 + + + + + buttonBox + rejected() + PDFCreateBitonalDocumentDialog + reject() + + + 370 + 508 + + + 370 + 264 + + + + diff --git a/Pdf4QtViewer/pdfprogramcontroller.cpp b/Pdf4QtViewer/pdfprogramcontroller.cpp index 0a3ea9b..b8dd48a 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.cpp +++ b/Pdf4QtViewer/pdfprogramcontroller.cpp @@ -1257,7 +1257,7 @@ void PDFProgramController::onActionSanitizeTriggered() void PDFProgramController::onActionCreateBitonalDocumentTriggered() { auto cms = m_CMSManager->getCurrentCMS(); - PDFCreateBitonalDocumentDialog dialog(m_pdfDocument.data(), cms.data(), m_mainWindow); + PDFCreateBitonalDocumentDialog dialog(m_pdfDocument.data(), cms.data(), m_progress, m_mainWindow); if (dialog.exec() == QDialog::Accepted) {