2023-11-11 17:33:32 +01:00
|
|
|
// Copyright (C) 2023 Jakub Melka
|
|
|
|
//
|
|
|
|
// This file is part of PDF4QT.
|
|
|
|
//
|
|
|
|
// PDF4QT is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// with the written consent of the copyright owner, any later version.
|
|
|
|
//
|
|
|
|
// PDF4QT is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "pdfcreateBitonaldocumentdialog.h"
|
|
|
|
#include "ui_pdfcreateBitonaldocumentdialog.h"
|
|
|
|
|
|
|
|
#include "pdfwidgetutils.h"
|
|
|
|
#include "pdfdocumentwriter.h"
|
2023-11-16 19:40:00 +01:00
|
|
|
#include "pdfimage.h"
|
2023-11-11 17:33:32 +01:00
|
|
|
#include "pdfdbgheap.h"
|
2023-11-16 19:40:00 +01:00
|
|
|
#include "pdfexception.h"
|
2023-11-17 11:00:46 +01:00
|
|
|
#include "pdfwidgetutils.h"
|
2023-11-17 13:45:49 +01:00
|
|
|
#include "pdfimageconversion.h"
|
2023-11-11 17:33:32 +01:00
|
|
|
|
|
|
|
#include <QCheckBox>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QElapsedTimer>
|
|
|
|
#include <QtConcurrent/QtConcurrent>
|
2023-11-16 19:40:00 +01:00
|
|
|
#include <QListWidget>
|
2023-11-17 11:00:46 +01:00
|
|
|
#include <QStyledItemDelegate>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QtSvg/QSvgRenderer>
|
|
|
|
#include <QMouseEvent>
|
|
|
|
#include <QToolTip>
|
2023-11-11 17:33:32 +01:00
|
|
|
|
|
|
|
namespace pdfviewer
|
|
|
|
{
|
|
|
|
|
2023-11-17 13:45:49 +01:00
|
|
|
PDFCreateBitonalDocumentPreviewWidget::PDFCreateBitonalDocumentPreviewWidget(QWidget* parent) :
|
|
|
|
QWidget(parent)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFCreateBitonalDocumentPreviewWidget::~PDFCreateBitonalDocumentPreviewWidget()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFCreateBitonalDocumentPreviewWidget::paintEvent(QPaintEvent* event)
|
|
|
|
{
|
|
|
|
Q_UNUSED(event);
|
|
|
|
|
|
|
|
QPainter painter(this);
|
|
|
|
painter.fillRect(rect(), Qt::white);
|
|
|
|
|
|
|
|
// Caption rect
|
|
|
|
QRect captionRect = rect();
|
|
|
|
captionRect.setHeight(painter.fontMetrics().lineSpacing() * 2);
|
|
|
|
|
|
|
|
painter.fillRect(captionRect, QColor::fromRgb(0, 0, 128, 255));
|
|
|
|
|
|
|
|
if (!m_caption.isEmpty())
|
|
|
|
{
|
|
|
|
painter.setPen(Qt::white);
|
|
|
|
painter.drawText(captionRect, m_caption, QTextOption(Qt::AlignCenter));
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect imageRect = rect();
|
|
|
|
imageRect.setTop(captionRect.bottom());
|
|
|
|
imageRect = imageRect.adjusted(16, 16, -32, -32);
|
|
|
|
|
|
|
|
if (imageRect.isValid() && !m_image.isNull())
|
|
|
|
{
|
|
|
|
QRect imageDrawRect = imageRect;
|
|
|
|
imageDrawRect.setSize(m_image.size().scaled(imageRect.size(), Qt::KeepAspectRatio));
|
|
|
|
imageDrawRect.moveCenter(imageRect.center());
|
|
|
|
painter.drawImage(imageDrawRect, m_image);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFCreateBitonalDocumentPreviewWidget::setCaption(QString caption)
|
|
|
|
{
|
|
|
|
if (m_caption != caption)
|
|
|
|
{
|
|
|
|
m_caption = caption;
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFCreateBitonalDocumentPreviewWidget::setImage(QImage image)
|
|
|
|
{
|
|
|
|
m_image = std::move(image);
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
class ImagePreviewDelegate : public QStyledItemDelegate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ImagePreviewDelegate(std::vector<PDFCreateBitonalDocumentDialog::ImageConversionInfo>* imageConversionInfos, QObject* parent) :
|
|
|
|
QStyledItemDelegate(parent),
|
|
|
|
m_imageConversionInfos(imageConversionInfos)
|
|
|
|
{
|
|
|
|
m_yesRenderer.load(QString(":/resources/result-ok.svg"));
|
|
|
|
m_noRenderer.load(QString(":/resources/result-error.svg"));
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
|
|
|
{
|
|
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
|
|
|
|
|
|
QRect markRect = getMarkRect(option);
|
|
|
|
|
|
|
|
if (index.isValid())
|
|
|
|
{
|
|
|
|
const PDFCreateBitonalDocumentDialog::ImageConversionInfo& info = m_imageConversionInfos->at(index.row());
|
|
|
|
if (info.conversionEnabled)
|
|
|
|
{
|
|
|
|
m_yesRenderer.render(painter, markRect);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_noRenderer.render(painter, markRect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool editorEvent(QEvent* event,
|
|
|
|
QAbstractItemModel* model,
|
|
|
|
const QStyleOptionViewItem& option,
|
|
|
|
const QModelIndex& index)
|
|
|
|
{
|
|
|
|
Q_UNUSED(model);
|
|
|
|
Q_UNUSED(index);
|
|
|
|
|
|
|
|
if (event->type() == QEvent::MouseButtonPress && index.isValid())
|
|
|
|
{
|
|
|
|
QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event);
|
|
|
|
if (mouseEvent && mouseEvent->button() == Qt::LeftButton)
|
|
|
|
{
|
|
|
|
// Do we click on yes/no mark?
|
|
|
|
QRectF markRect = getMarkRect(option);
|
|
|
|
if (markRect.contains(mouseEvent->position()))
|
|
|
|
{
|
|
|
|
PDFCreateBitonalDocumentDialog::ImageConversionInfo& info = m_imageConversionInfos->at(index.row());
|
|
|
|
info.conversionEnabled = !info.conversionEnabled;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool helpEvent(QHelpEvent* event,
|
|
|
|
QAbstractItemView* view,
|
|
|
|
const QStyleOptionViewItem& option,
|
|
|
|
const QModelIndex& index) override
|
|
|
|
{
|
|
|
|
Q_UNUSED(index);
|
|
|
|
|
|
|
|
if (!event || !view)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event->type() == QEvent::ToolTip)
|
|
|
|
{
|
|
|
|
// Are we hovering over yes/no mark?
|
|
|
|
QRectF markRect = getMarkRect(option);
|
|
|
|
if (markRect.contains(event->pos()))
|
|
|
|
{
|
|
|
|
event->accept();
|
|
|
|
QToolTip::showText(event->globalPos(), tr("Toggle this icon to switch image conversion to bitonic format on or off."), view);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static constexpr QSize s_iconSize = QSize(24, 24);
|
|
|
|
|
|
|
|
QRect getMarkRect(const QStyleOptionViewItem& option) const
|
|
|
|
{
|
|
|
|
QSize markSize = pdf::PDFWidgetUtils::scaleDPI(option.widget, s_iconSize);
|
|
|
|
QRect markRect(option.rect.left(), option.rect.top(), markSize.width(), markSize.height());
|
|
|
|
return markRect;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<PDFCreateBitonalDocumentDialog::ImageConversionInfo>* m_imageConversionInfos;
|
|
|
|
mutable QSvgRenderer m_yesRenderer;
|
|
|
|
mutable QSvgRenderer m_noRenderer;
|
|
|
|
};
|
|
|
|
|
2023-11-16 19:40:00 +01:00
|
|
|
PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDocument* document,
|
|
|
|
const pdf::PDFCMS* cms,
|
|
|
|
QWidget* parent) :
|
2023-11-11 17:33:32 +01:00
|
|
|
QDialog(parent),
|
|
|
|
ui(new Ui::PDFCreateBitonalDocumentDialog),
|
|
|
|
m_document(document),
|
2023-11-16 19:40:00 +01:00
|
|
|
m_cms(cms),
|
2023-11-11 17:33:32 +01:00
|
|
|
m_createBitonalDocumentButton(nullptr),
|
|
|
|
m_conversionInProgress(false),
|
2023-11-17 13:45:49 +01:00
|
|
|
m_processed(false),
|
|
|
|
m_leftPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)),
|
|
|
|
m_rightPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this))
|
2023-11-11 17:33:32 +01:00
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
2023-11-17 13:45:49 +01:00
|
|
|
m_leftPreviewWidget->setCaption(tr("ORIGINAL"));
|
|
|
|
m_rightPreviewWidget->setCaption(tr("BITONIC"));
|
|
|
|
|
|
|
|
ui->mainGridLayout->addWidget(m_leftPreviewWidget, 1, 1);
|
|
|
|
ui->mainGridLayout->addWidget(m_rightPreviewWidget, 1, 2);
|
|
|
|
|
2023-11-16 19:40:00 +01:00
|
|
|
m_classifier.classify(document);
|
|
|
|
m_imageReferences = m_classifier.getObjectsByType(pdf::PDFObjectClassifier::Image);
|
|
|
|
|
2023-11-11 17:33:32 +01:00
|
|
|
m_createBitonalDocumentButton = ui->buttonBox->addButton(tr("Process"), 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);
|
2023-11-17 13:45:49 +01:00
|
|
|
connect(ui->automaticThresholdRadioButton, &QRadioButton::clicked, this, &PDFCreateBitonalDocumentDialog::updatePreview);
|
|
|
|
connect(ui->manualThresholdRadioButton, &QRadioButton::clicked, this, &PDFCreateBitonalDocumentDialog::updatePreview);
|
|
|
|
connect(ui->imageListWidget, &QListWidget::currentItemChanged, this, &PDFCreateBitonalDocumentDialog::updatePreview);
|
|
|
|
connect(ui->thresholdEditBox, &QSpinBox::editingFinished, this, &PDFCreateBitonalDocumentDialog::updatePreview);
|
2023-11-11 17:33:32 +01:00
|
|
|
|
|
|
|
pdf::PDFWidgetUtils::scaleWidget(this, QSize(640, 380));
|
|
|
|
updateUi();
|
|
|
|
pdf::PDFWidgetUtils::style(this);
|
2023-11-16 19:40:00 +01:00
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
ui->imageListWidget->setItemDelegate( new ImagePreviewDelegate(&m_imagesToBeConverted, this));
|
|
|
|
|
2023-11-16 19:40:00 +01:00
|
|
|
loadImages();
|
2023-11-17 13:45:49 +01:00
|
|
|
updatePreview();
|
2023-11-11 17:33:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
PDFCreateBitonalDocumentDialog::~PDFCreateBitonalDocumentDialog()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!m_conversionInProgress);
|
|
|
|
Q_ASSERT(!m_future.isRunning());
|
|
|
|
|
|
|
|
delete ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFCreateBitonalDocumentDialog::createBitonalDocument()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFCreateBitonalDocumentDialog::onCreateBitonalDocumentButtonClicked()
|
|
|
|
{
|
|
|
|
Q_ASSERT(!m_conversionInProgress);
|
|
|
|
Q_ASSERT(!m_future.isRunning());
|
|
|
|
|
|
|
|
m_conversionInProgress = true;
|
|
|
|
m_future = QtConcurrent::run([this]() { createBitonalDocument(); });
|
|
|
|
updateUi();
|
|
|
|
}
|
|
|
|
|
2023-11-16 19:40:00 +01:00
|
|
|
void PDFCreateBitonalDocumentDialog::loadImages()
|
|
|
|
{
|
|
|
|
QSize iconSize(QSize(256, 256));
|
|
|
|
ui->imageListWidget->setIconSize(iconSize);
|
|
|
|
QSize imageSize = iconSize * ui->imageListWidget->devicePixelRatioF();
|
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
int i = 0;
|
2023-11-16 19:40:00 +01:00
|
|
|
for (pdf::PDFObjectReference reference : m_imageReferences)
|
|
|
|
{
|
2023-11-17 11:00:46 +01:00
|
|
|
if (i++>10)
|
2023-11-16 19:40:00 +01:00
|
|
|
{
|
2023-11-17 11:00:46 +01:00
|
|
|
break;
|
2023-11-16 19:40:00 +01:00
|
|
|
}
|
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
std::optional<pdf::PDFImage> pdfImage = getImageFromReference(reference);
|
|
|
|
if (!pdfImage)
|
2023-11-16 19:40:00 +01:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
pdf::PDFCMSGeneric genericCms;
|
|
|
|
pdf::PDFRenderErrorReporterDummy errorReporter;
|
2023-11-16 19:40:00 +01:00
|
|
|
QImage image = pdfImage->getImage(&genericCms, &errorReporter, nullptr);
|
|
|
|
if (image.isNull())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QListWidgetItem* item = new QListWidgetItem(ui->imageListWidget);
|
|
|
|
image = image.scaled(imageSize.width(), imageSize.height(), Qt::KeepAspectRatio, Qt::FastTransformation);
|
|
|
|
item->setIcon(QIcon(QPixmap::fromImage(image)));
|
2023-11-17 11:00:46 +01:00
|
|
|
Qt::ItemFlags flags = item->flags();
|
|
|
|
flags.setFlag(Qt::ItemIsEditable, true);
|
|
|
|
item->setFlags(flags);
|
2023-11-16 19:40:00 +01:00
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
ImageConversionInfo imageConversionInfo;
|
|
|
|
imageConversionInfo.imageReference = reference;
|
|
|
|
imageConversionInfo.conversionEnabled = true;
|
|
|
|
m_imagesToBeConverted.push_back(imageConversionInfo);
|
2023-11-16 19:40:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 17:33:32 +01:00
|
|
|
void PDFCreateBitonalDocumentDialog::updateUi()
|
|
|
|
{
|
|
|
|
ui->thresholdEditBox->setEnabled(ui->manualThresholdRadioButton->isChecked());
|
|
|
|
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_processed && !m_conversionInProgress);
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(!m_conversionInProgress);
|
|
|
|
m_createBitonalDocumentButton->setEnabled(!m_conversionInProgress);
|
|
|
|
}
|
|
|
|
|
2023-11-17 13:45:49 +01:00
|
|
|
void PDFCreateBitonalDocumentDialog::updatePreview()
|
|
|
|
{
|
|
|
|
QModelIndex index = ui->imageListWidget->currentIndex();
|
|
|
|
|
|
|
|
m_previewImageLeft = QImage();
|
|
|
|
m_previewImageRight = QImage();
|
|
|
|
|
|
|
|
if (index.isValid())
|
|
|
|
{
|
|
|
|
const ImageConversionInfo& info = m_imagesToBeConverted.at(index.row());
|
|
|
|
|
|
|
|
std::optional<pdf::PDFImage> pdfImage = getImageFromReference(info.imageReference);
|
|
|
|
Q_ASSERT(pdfImage);
|
|
|
|
|
|
|
|
pdf::PDFCMSGeneric cmsGeneric;
|
|
|
|
pdf::PDFRenderErrorReporterDummy reporter;
|
|
|
|
QImage image = pdfImage->getImage(&cmsGeneric, &reporter, nullptr);
|
|
|
|
|
|
|
|
pdf::PDFImageConversion imageConversion;
|
|
|
|
imageConversion.setConversionMethod(ui->automaticThresholdRadioButton->isChecked() ? pdf::PDFImageConversion::ConversionMethod::Automatic : pdf::PDFImageConversion::ConversionMethod::Manual);
|
|
|
|
imageConversion.setThreshold(ui->thresholdEditBox->value());
|
|
|
|
imageConversion.setImage(image);
|
|
|
|
|
|
|
|
if (imageConversion.convert())
|
|
|
|
{
|
|
|
|
m_previewImageLeft = image;
|
|
|
|
m_previewImageRight = imageConversion.getConvertedImage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_leftPreviewWidget->setImage(m_previewImageLeft);
|
|
|
|
m_rightPreviewWidget->setImage(m_previewImageRight);
|
|
|
|
}
|
|
|
|
|
2023-11-17 11:00:46 +01:00
|
|
|
std::optional<pdf::PDFImage> PDFCreateBitonalDocumentDialog::getImageFromReference(pdf::PDFObjectReference reference) const
|
|
|
|
{
|
|
|
|
std::optional<pdf::PDFImage> pdfImage;
|
|
|
|
pdf::PDFObject imageObject = m_document->getObjectByReference(reference);
|
|
|
|
pdf::PDFRenderErrorReporterDummy errorReporter;
|
|
|
|
|
|
|
|
if (!imageObject.isStream())
|
|
|
|
{
|
|
|
|
// Image is not stream
|
|
|
|
return pdfImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pdf::PDFStream* stream = imageObject.getStream();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
pdf::PDFColorSpacePointer colorSpace;
|
|
|
|
const pdf::PDFDictionary* streamDictionary = stream->getDictionary();
|
|
|
|
if (streamDictionary->hasKey("ColorSpace"))
|
|
|
|
{
|
|
|
|
const pdf::PDFObject& colorSpaceObject = m_document->getObject(streamDictionary->get("ColorSpace"));
|
|
|
|
if (colorSpaceObject.isName() || colorSpaceObject.isArray())
|
|
|
|
{
|
|
|
|
pdf::PDFDictionary dummyDictionary;
|
|
|
|
colorSpace = pdf::PDFAbstractColorSpace::createColorSpace(&dummyDictionary, m_document, colorSpaceObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pdfImage.emplace(pdf::PDFImage::createImage(m_document,
|
|
|
|
stream,
|
|
|
|
colorSpace,
|
|
|
|
false,
|
|
|
|
pdf::RenderingIntent::Perceptual,
|
|
|
|
&errorReporter));
|
|
|
|
}
|
|
|
|
catch (pdf::PDFException)
|
|
|
|
{
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
return pdfImage;
|
|
|
|
}
|
|
|
|
|
2023-11-11 17:33:32 +01:00
|
|
|
} // namespace pdfviewer
|
2023-11-17 13:45:49 +01:00
|
|
|
|
|
|
|
|