mirror of https://github.com/JakubMelka/PDF4QT.git
Merge remote-tracking branch 'remotes/origin/branches/issue107'
This commit is contained in:
commit
e9a87553af
|
@ -100,6 +100,8 @@ add_library(Pdf4QtLib SHARED
|
|||
sources/pdfobjecteditorwidget_impl.h
|
||||
sources/pdfdocumentsanitizer.h
|
||||
sources/pdfdocumentsanitizer.cpp
|
||||
sources/pdfimageconversion.h
|
||||
sources/pdfimageconversion.cpp
|
||||
cmaps.qrc
|
||||
)
|
||||
|
||||
|
|
|
@ -250,7 +250,7 @@ public:
|
|||
|
||||
using PDFCMSPointer = QSharedPointer<PDFCMS>;
|
||||
|
||||
class PDFCMSGeneric : public PDFCMS
|
||||
class PDF4QTLIBSHARED_EXPORT PDFCMSGeneric : public PDFCMS
|
||||
{
|
||||
public:
|
||||
explicit inline PDFCMSGeneric() = default;
|
||||
|
|
|
@ -1026,7 +1026,7 @@ void PDFPageContentStreamBuilder::replaceResources(PDFObjectReference contentStr
|
|||
QByteArray compressedData = PDFFlateDecodeFilter::compress(decodedStream);
|
||||
PDFDictionary updatedDictionary = *contentStreamObject.getStream()->getDictionary();
|
||||
updatedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(compressedData.size()));
|
||||
updatedDictionary.setEntry(PDFInplaceOrMemoryString("Filters"), PDFObject::createArray(std::make_shared<PDFArray>(qMove(array))));
|
||||
updatedDictionary.setEntry(PDFInplaceOrMemoryString("Filter"), PDFObject::createArray(std::make_shared<PDFArray>(qMove(array))));
|
||||
PDFObject newContentStream = PDFObject::createStream(std::make_shared<PDFStream>(qMove(updatedDictionary), qMove(compressedData)));
|
||||
m_documentBuilder->setObject(contentStreamReference, std::move(newContentStream));
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ private:
|
|||
class PDF4QTLIBSHARED_EXPORT PDFImage
|
||||
{
|
||||
public:
|
||||
PDFImage() = default;
|
||||
|
||||
/// Creates image from the content and the dictionary. If image can't be created, then exception is thrown.
|
||||
/// \param document Document
|
||||
|
@ -112,8 +113,6 @@ public:
|
|||
const PDFImageData& getSoftMaskData() const { return m_softMask; }
|
||||
|
||||
private:
|
||||
PDFImage() = default;
|
||||
|
||||
PDFImageData m_imageData;
|
||||
PDFImageData m_softMask;
|
||||
PDFColorSpacePointer m_colorSpace;
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
#include "pdfimageconversion.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFImageConversion::PDFImageConversion()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void PDFImageConversion::setImage(QImage image)
|
||||
{
|
||||
m_image = std::move(image);
|
||||
m_convertedImage = QImage();
|
||||
m_automaticThreshold = DEFAULT_THRESHOLD;
|
||||
}
|
||||
|
||||
void PDFImageConversion::setConversionMethod(ConversionMethod method)
|
||||
{
|
||||
m_conversionMethod = method;
|
||||
}
|
||||
|
||||
void PDFImageConversion::setThreshold(int threshold)
|
||||
{
|
||||
m_manualThreshold = threshold;
|
||||
}
|
||||
|
||||
bool PDFImageConversion::convert()
|
||||
{
|
||||
if (m_image.isNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage bitonal(m_image.width(), m_image.height(), QImage::Format_Mono);
|
||||
bitonal.fill(0);
|
||||
|
||||
// Thresholding
|
||||
int threshold = DEFAULT_THRESHOLD;
|
||||
|
||||
switch (m_conversionMethod)
|
||||
{
|
||||
case pdf::PDFImageConversion::ConversionMethod::Automatic:
|
||||
m_automaticThreshold = calculateOtsu1DThreshold();
|
||||
threshold = m_automaticThreshold;
|
||||
break;
|
||||
|
||||
case pdf::PDFImageConversion::ConversionMethod::Manual:
|
||||
threshold = m_manualThreshold;
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
for (int y = 0; y < m_image.height(); ++y)
|
||||
{
|
||||
for (int x = 0; x < m_image.width(); ++x)
|
||||
{
|
||||
QColor pixelColor = m_image.pixelColor(x, y);
|
||||
int pixelValue = pixelColor.lightness();
|
||||
bool bit = (pixelValue >= threshold);
|
||||
bitonal.setPixel(x, y, bit);
|
||||
}
|
||||
}
|
||||
|
||||
m_convertedImage = std::move(bitonal);
|
||||
return true;
|
||||
}
|
||||
|
||||
int PDFImageConversion::getThreshold() const
|
||||
{
|
||||
switch (m_conversionMethod)
|
||||
{
|
||||
case pdf::PDFImageConversion::ConversionMethod::Automatic:
|
||||
return m_automaticThreshold;
|
||||
|
||||
case pdf::PDFImageConversion::ConversionMethod::Manual:
|
||||
return m_manualThreshold;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return DEFAULT_THRESHOLD;
|
||||
}
|
||||
|
||||
QImage PDFImageConversion::getConvertedImage() const
|
||||
{
|
||||
return m_convertedImage;
|
||||
}
|
||||
|
||||
int PDFImageConversion::calculateOtsu1DThreshold() const
|
||||
{
|
||||
if (m_image.isNull())
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
// Histogram of lightness occurences
|
||||
std::array<int, 256> histogram = { };
|
||||
|
||||
for (int x = 0; x < m_image.width(); ++x)
|
||||
{
|
||||
for (int y = 0; y < m_image.height(); ++y)
|
||||
{
|
||||
int lightness = m_image.pixelColor(x, y).lightness();
|
||||
Q_ASSERT(lightness >= 0 && lightness <= 255);
|
||||
|
||||
int clampedLightness = qBound(0, lightness, 255);
|
||||
histogram[clampedLightness] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
float factor = 1.0f / float(m_image.width() * m_image.height());
|
||||
|
||||
std::array<float, 256> normalizedHistogram = { };
|
||||
std::array<float, 256> cumulativeProbabilities = { };
|
||||
std::array<float, 256> interClassVariance = { };
|
||||
|
||||
// Compute probabilities
|
||||
for (size_t i = 0; i < histogram.size(); ++i)
|
||||
{
|
||||
normalizedHistogram[i] = histogram[i] * factor;
|
||||
cumulativeProbabilities[i] = normalizedHistogram[i];
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
cumulativeProbabilities[i] += cumulativeProbabilities[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the inter-class variance for each threshold. Variables
|
||||
// with the subscript 0 denote the background, while those with
|
||||
// subscript 1 denote the foreground.
|
||||
for (size_t i = 0; i < histogram.size(); ++i)
|
||||
{
|
||||
const float w0 = cumulativeProbabilities[i] - normalizedHistogram[i];
|
||||
const float w1 = 1.0f - w0;
|
||||
|
||||
float u0 = 0.0f;
|
||||
float u1 = 0.0f;
|
||||
|
||||
// Calculate mean intensity value of the background.
|
||||
if (!qFuzzyIsNull(w0))
|
||||
{
|
||||
for (size_t j = 0; j < i; ++j)
|
||||
{
|
||||
u0 += j * normalizedHistogram[j];
|
||||
}
|
||||
|
||||
u0 /= w0;
|
||||
}
|
||||
|
||||
// Calculate mean intensity value of the foreground.
|
||||
if (!qFuzzyIsNull(w1))
|
||||
{
|
||||
for (size_t j = i; j < histogram.size(); ++j)
|
||||
{
|
||||
u1 += j * normalizedHistogram[j];
|
||||
}
|
||||
|
||||
u1 /= w1;
|
||||
}
|
||||
|
||||
const float variance = w0 * w1 * std::powf(u0 - u1, 2);
|
||||
interClassVariance[i] = variance;
|
||||
}
|
||||
|
||||
// Find maximal value of the variance
|
||||
size_t maxVarianceIndex = 0;
|
||||
float maxVarianceValue = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < interClassVariance.size(); ++i)
|
||||
{
|
||||
if (interClassVariance[i] > maxVarianceValue)
|
||||
{
|
||||
maxVarianceValue = interClassVariance[i];
|
||||
maxVarianceIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return int(maxVarianceIndex);
|
||||
}
|
||||
|
||||
} // namespace pdf
|
|
@ -0,0 +1,97 @@
|
|||
// 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/>.
|
||||
|
||||
#ifndef PDFIMAGECONVERSION_H
|
||||
#define PDFIMAGECONVERSION_H
|
||||
|
||||
#include "pdfglobal.h"
|
||||
|
||||
#include <QImage>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
/// This class facilitates various image conversions,
|
||||
/// including transforming colored images into monochromatic (or bitonal) formats.
|
||||
class PDF4QTLIBSHARED_EXPORT PDFImageConversion
|
||||
{
|
||||
public:
|
||||
PDFImageConversion();
|
||||
|
||||
enum class ConversionMethod
|
||||
{
|
||||
Automatic, ///< The threshold is determined automatically using an algorithm.
|
||||
Manual ///< The threshold is manually provided by the user.
|
||||
};
|
||||
|
||||
/// Sets the image to be converted using the specified conversion method.
|
||||
/// This operation resets any previously converted image and the automatic threshold,
|
||||
/// thereby erasing all prior image data.
|
||||
/// \param image The image to be set for conversion.
|
||||
void setImage(QImage image);
|
||||
|
||||
/// Sets the method for image conversion. Multiple methods are available
|
||||
/// for selection. If the manual method is chosen, an appropriate threshold
|
||||
/// must also be set by the user.
|
||||
/// \param method The conversion method to be used.
|
||||
void setConversionMethod(ConversionMethod method);
|
||||
|
||||
/// Sets the manual threshold value. When a non-manual (e.g., automatic) conversion
|
||||
/// method is in use, this function will retain the manual threshold settings,
|
||||
/// but the conversion will utilize an automatically calculated threshold for the image.
|
||||
/// The manually set threshold is preserved and not overwritten. Therefore, if the
|
||||
/// manual conversion method is later selected, the previously established manual
|
||||
/// threshold will be applied.
|
||||
/// \param threshold The manual threshold value to be set.
|
||||
void setThreshold(int threshold);
|
||||
|
||||
/// This method converts the image into a bitonal (monochromatic) format. If
|
||||
/// the automatic threshold calculation is enabled, it executes Otsu's 1D algorithm
|
||||
/// to determine the threshold. When the manual conversion method is selected,
|
||||
/// the automatic threshold calculation is bypassed, and the predefined manual threshold
|
||||
/// value is utilized instead. This method returns true if the conversion is
|
||||
/// successful, and false otherwise.
|
||||
bool convert();
|
||||
|
||||
/// Returns the threshold used in image conversion. If the automatic conversion method is
|
||||
/// selected, this function should be called only after executing the convert() method;
|
||||
/// otherwise, it may return invalid data. The automatic threshold calculation is
|
||||
/// performed within the convert() method.
|
||||
/// \returns The threshold value used in image conversion.
|
||||
int getThreshold() const;
|
||||
|
||||
/// Returns the converted image. This method should only be called after
|
||||
/// the convert() method has been executed, and additionally, only if the
|
||||
/// convert() method returns true. If these conditions are not met, the result
|
||||
/// is undefined.
|
||||
QImage getConvertedImage() const;
|
||||
|
||||
private:
|
||||
int calculateOtsu1DThreshold() const;
|
||||
|
||||
static constexpr int DEFAULT_THRESHOLD = 128;
|
||||
|
||||
QImage m_image;
|
||||
QImage m_convertedImage;
|
||||
ConversionMethod m_conversionMethod = ConversionMethod::Automatic;
|
||||
int m_automaticThreshold = DEFAULT_THRESHOLD;
|
||||
int m_manualThreshold = DEFAULT_THRESHOLD;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFIMAGECONVERSION_H
|
|
@ -190,7 +190,7 @@ private:
|
|||
};
|
||||
|
||||
/// Bit writer
|
||||
class PDFBitWriter
|
||||
class PDF4QTLIBSHARED_EXPORT PDFBitWriter
|
||||
{
|
||||
public:
|
||||
using Value = uint64_t;
|
||||
|
|
|
@ -47,6 +47,9 @@ add_library(Pdf4QtViewer SHARED
|
|||
pdfsanitizedocumentdialog.ui
|
||||
pdfsanitizedocumentdialog.cpp
|
||||
pdfsanitizedocumentdialog.h
|
||||
pdfcreatebitonaldocumentdialog.ui
|
||||
pdfcreatebitonaldocumentdialog.cpp
|
||||
pdfcreatebitonaldocumentdialog.h
|
||||
pdf4qtviewer.qrc
|
||||
)
|
||||
|
||||
|
@ -59,7 +62,7 @@ GENERATE_EXPORT_HEADER(Pdf4QtViewer
|
|||
PDF4QTVIEWERLIBSHARED_EXPORT
|
||||
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/pdf4qtviewer_export.h")
|
||||
|
||||
target_link_libraries(Pdf4QtViewer PRIVATE Pdf4QtLib Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport Qt6::TextToSpeech Qt6::Xml Qt6::OpenGLWidgets)
|
||||
target_link_libraries(Pdf4QtViewer PRIVATE Pdf4QtLib Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport Qt6::TextToSpeech Qt6::Xml Qt6::Svg Qt6::OpenGLWidgets)
|
||||
target_include_directories(Pdf4QtViewer INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(Pdf4QtViewer PUBLIC ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR})
|
||||
|
||||
|
|
|
@ -102,5 +102,7 @@
|
|||
<file>resources/book.svg</file>
|
||||
<file>resources/wallet.svg</file>
|
||||
<file>resources/web.svg</file>
|
||||
<file>resources/create-bitonal-document.svg</file>
|
||||
<file>resources/sanitize-document.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1,510 @@
|
|||
// 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"
|
||||
#include "pdfimage.h"
|
||||
#include "pdfdbgheap.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfwidgetutils.h"
|
||||
#include "pdfimageconversion.h"
|
||||
#include "pdfstreamfilters.h"
|
||||
#include "pdfutils.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QListWidget>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
#include <QMouseEvent>
|
||||
#include <QToolTip>
|
||||
|
||||
namespace pdfviewer
|
||||
{
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
PDFCreateBitonalDocumentDialog::PDFCreateBitonalDocumentDialog(const pdf::PDFDocument* document,
|
||||
const pdf::PDFCMS* cms,
|
||||
pdf::PDFProgress* progress,
|
||||
QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::PDFCreateBitonalDocumentDialog),
|
||||
m_document(document),
|
||||
m_cms(cms),
|
||||
m_createBitonalDocumentButton(nullptr),
|
||||
m_conversionInProgress(false),
|
||||
m_processed(false),
|
||||
m_leftPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)),
|
||||
m_rightPreviewWidget(new PDFCreateBitonalDocumentPreviewWidget(this)),
|
||||
m_progress(progress)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_leftPreviewWidget->setCaption(tr("ORIGINAL"));
|
||||
m_rightPreviewWidget->setCaption(tr("BITONIC"));
|
||||
|
||||
ui->mainGridLayout->addWidget(m_leftPreviewWidget, 1, 1);
|
||||
ui->mainGridLayout->addWidget(m_rightPreviewWidget, 1, 2);
|
||||
|
||||
m_classifier.classify(document);
|
||||
m_imageReferences = m_classifier.getObjectsByType(pdf::PDFObjectClassifier::Image);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
pdf::PDFWidgetUtils::scaleWidget(this, QSize(1024, 768));
|
||||
updateUi();
|
||||
pdf::PDFWidgetUtils::style(this);
|
||||
|
||||
ui->imageListWidget->setItemDelegate( new ImagePreviewDelegate(&m_imagesToBeConverted, this));
|
||||
|
||||
setGeometry(parent->geometry());
|
||||
|
||||
loadImages();
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
PDFCreateBitonalDocumentDialog::~PDFCreateBitonalDocumentDialog()
|
||||
{
|
||||
Q_ASSERT(!m_conversionInProgress);
|
||||
Q_ASSERT(!m_future.isRunning());
|
||||
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PDFCreateBitonalDocumentDialog::onPerformFinished()
|
||||
{
|
||||
m_future.waitForFinished();
|
||||
m_conversionInProgress = false;
|
||||
m_processed = true;
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void PDFCreateBitonalDocumentDialog::createBitonalDocument()
|
||||
{
|
||||
std::vector<ImageConversionInfo> 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<pdf::PDFImage> 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);
|
||||
|
||||
pdf::PDFBitWriter bitWriter(1);
|
||||
bitWriter.reserve(bitonicImage.sizeInBytes());
|
||||
for (int row = 0; row < bitonicImage.height(); ++row)
|
||||
{
|
||||
for (int col = 0; col < bitonicImage.width(); ++col)
|
||||
{
|
||||
QRgb pixelValue = bitonicImage.pixel(col, row);
|
||||
QRgb withoutAlphaValue = pixelValue & 0xFFFFFF;
|
||||
int value = withoutAlphaValue > 0 ? 1 : 0;
|
||||
bitWriter.write(value);
|
||||
}
|
||||
|
||||
bitWriter.finishLine();
|
||||
}
|
||||
|
||||
QByteArray imageData = bitWriter.takeByteArray();
|
||||
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("Filter"), pdf::PDFObject::createArray(std::make_shared<pdf::PDFArray>(qMove(array))));
|
||||
|
||||
pdf::PDFObject imageObject = pdf::PDFObject::createStream(std::make_shared<pdf::PDFStream>(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()
|
||||
{
|
||||
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<void>::finished, this, &PDFCreateBitonalDocumentDialog::onPerformFinished);
|
||||
m_futureWatcher->setFuture(m_future);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void PDFCreateBitonalDocumentDialog::loadImages()
|
||||
{
|
||||
QSize iconSize(QSize(256, 256));
|
||||
ui->imageListWidget->setIconSize(iconSize);
|
||||
QSize imageSize = iconSize * ui->imageListWidget->devicePixelRatioF();
|
||||
|
||||
for (pdf::PDFObjectReference reference : m_imageReferences)
|
||||
{
|
||||
std::optional<pdf::PDFImage> pdfImage = getImageFromReference(reference);
|
||||
if (!pdfImage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pdf::PDFCMSGeneric genericCms;
|
||||
pdf::PDFRenderErrorReporterDummy errorReporter;
|
||||
QImage image;
|
||||
|
||||
try
|
||||
{
|
||||
image = pdfImage->getImage(&genericCms, &errorReporter, nullptr);
|
||||
}
|
||||
catch (pdf::PDFException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
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)));
|
||||
Qt::ItemFlags flags = item->flags();
|
||||
flags.setFlag(Qt::ItemIsEditable, true);
|
||||
item->setFlags(flags);
|
||||
|
||||
ImageConversionInfo imageConversionInfo;
|
||||
imageConversionInfo.imageReference = reference;
|
||||
imageConversionInfo.conversionEnabled = true;
|
||||
m_imagesToBeConverted.push_back(imageConversionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace pdfviewer
|
||||
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// 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/>.
|
||||
|
||||
#ifndef PDFCREATEBITONALDOCUMENTDIALOG_H
|
||||
#define PDFCREATEBITONALDOCUMENTDIALOG_H
|
||||
|
||||
#include "pdfcms.h"
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfobjectutils.h"
|
||||
#include "pdfimage.h"
|
||||
#include "pdfimageconversion.h"
|
||||
#include "pdfprogress.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFuture>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class PDFCreateBitonalDocumentDialog;
|
||||
}
|
||||
|
||||
namespace pdfviewer
|
||||
{
|
||||
|
||||
class PDFCreateBitonalDocumentPreviewWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PDFCreateBitonalDocumentPreviewWidget(QWidget* parent);
|
||||
virtual ~PDFCreateBitonalDocumentPreviewWidget() override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
void setCaption(QString caption);
|
||||
void setImage(QImage image);
|
||||
|
||||
private:
|
||||
QString m_caption;
|
||||
QImage m_image;
|
||||
};
|
||||
|
||||
class PDFCreateBitonalDocumentDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
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); }
|
||||
|
||||
struct ImageConversionInfo
|
||||
{
|
||||
pdf::PDFObjectReference imageReference;
|
||||
bool conversionEnabled = true;
|
||||
};
|
||||
|
||||
private:
|
||||
void createBitonalDocument();
|
||||
void onCreateBitonalDocumentButtonClicked();
|
||||
void onPerformFinished();
|
||||
void loadImages();
|
||||
|
||||
void updateUi();
|
||||
void updatePreview();
|
||||
|
||||
std::optional<pdf::PDFImage> getImageFromReference(pdf::PDFObjectReference reference) const;
|
||||
|
||||
Ui::PDFCreateBitonalDocumentDialog* ui;
|
||||
const pdf::PDFDocument* m_document;
|
||||
const pdf::PDFCMS* m_cms;
|
||||
QPushButton* m_createBitonalDocumentButton;
|
||||
bool m_conversionInProgress;
|
||||
bool m_processed;
|
||||
QFuture<void> m_future;
|
||||
std::optional<QFutureWatcher<void>> m_futureWatcher;
|
||||
pdf::PDFDocument m_bitonalDocument;
|
||||
pdf::PDFObjectClassifier m_classifier;
|
||||
std::vector<pdf::PDFObjectReference> m_imageReferences;
|
||||
std::vector<ImageConversionInfo> m_imagesToBeConverted;
|
||||
|
||||
QImage m_previewImageLeft;
|
||||
QImage m_previewImageRight;
|
||||
|
||||
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
|
||||
|
||||
#endif // PDFCREATEBITONALDOCUMENTDIALOG_H
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PDFCreateBitonalDocumentDialog</class>
|
||||
<widget class="QDialog" name="PDFCreateBitonalDocumentDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>741</width>
|
||||
<height>530</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create Bitonal Document</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="mainGridLayout" rowstretch="0,1,0" columnstretch="2,1,1">
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="createBitonalSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Color to Bitonal Conversion Options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="optionsGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="automaticThresholdRadioButton">
|
||||
<property name="text">
|
||||
<string>Automatic (Otsu's 1D method)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="thresholdEditBox">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="manualThresholdRadioButton">
|
||||
<property name="text">
|
||||
<string>User-Defined Intensity Threshold (0-255):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QListWidget" name="imageListWidget">
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>PDFCreateBitonalDocumentDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>370</x>
|
||||
<y>508</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>370</x>
|
||||
<y>264</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>PDFCreateBitonalDocumentDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>370</x>
|
||||
<y>508</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>370</x>
|
||||
<y>264</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -33,6 +33,7 @@
|
|||
#include "pdfrendertoimagesdialog.h"
|
||||
#include "pdfoptimizedocumentdialog.h"
|
||||
#include "pdfsanitizedocumentdialog.h"
|
||||
#include "pdfcreatebitonaldocumentdialog.h"
|
||||
#include "pdfviewersettingsdialog.h"
|
||||
#include "pdfaboutdialog.h"
|
||||
#include "pdfrenderingerrorswidget.h"
|
||||
|
@ -458,6 +459,10 @@ void PDFProgramController::initialize(Features features,
|
|||
{
|
||||
connect(action, &QAction::triggered, this, &PDFProgramController::onActionSanitizeTriggered);
|
||||
}
|
||||
if (QAction* action = m_actionManager->getAction(PDFActionManager::CreateBitonalDocument))
|
||||
{
|
||||
connect(action, &QAction::triggered, this, &PDFProgramController::onActionCreateBitonalDocumentTriggered);
|
||||
}
|
||||
if (QAction* action = m_actionManager->getAction(PDFActionManager::Encryption))
|
||||
{
|
||||
connect(action, &QAction::triggered, this, &PDFProgramController::onActionEncryptionTriggered);
|
||||
|
@ -1249,6 +1254,19 @@ void PDFProgramController::onActionSanitizeTriggered()
|
|||
}
|
||||
}
|
||||
|
||||
void PDFProgramController::onActionCreateBitonalDocumentTriggered()
|
||||
{
|
||||
auto cms = m_CMSManager->getCurrentCMS();
|
||||
PDFCreateBitonalDocumentDialog dialog(m_pdfDocument.data(), cms.data(), m_progress, m_mainWindow);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
pdf::PDFDocumentPointer pointer(new pdf::PDFDocument(dialog.takeBitonaldDocument()));
|
||||
pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::ModificationFlags(pdf::PDFModifiedDocument::Reset | pdf::PDFModifiedDocument::PreserveUndoRedo));
|
||||
onDocumentModified(qMove(document));
|
||||
}
|
||||
}
|
||||
|
||||
void PDFProgramController::onActionEncryptionTriggered()
|
||||
{
|
||||
auto queryPassword = [this](bool* ok)
|
||||
|
@ -1569,6 +1587,7 @@ void PDFProgramController::updateActionsAvailability()
|
|||
m_actionManager->setEnabled(PDFActionManager::RenderToImages, hasValidDocument && canPrint);
|
||||
m_actionManager->setEnabled(PDFActionManager::Optimize, hasValidDocument);
|
||||
m_actionManager->setEnabled(PDFActionManager::Sanitize, hasValidDocument);
|
||||
m_actionManager->setEnabled(PDFActionManager::CreateBitonalDocument, hasValidDocument);
|
||||
m_actionManager->setEnabled(PDFActionManager::Encryption, hasValidDocument);
|
||||
m_actionManager->setEnabled(PDFActionManager::Save, hasValidDocument);
|
||||
m_actionManager->setEnabled(PDFActionManager::SaveAs, hasValidDocument);
|
||||
|
|
|
@ -109,6 +109,7 @@ public:
|
|||
RenderToImages,
|
||||
Optimize,
|
||||
Sanitize,
|
||||
CreateBitonalDocument,
|
||||
Encryption,
|
||||
FitPage,
|
||||
FitWidth,
|
||||
|
@ -337,6 +338,7 @@ private:
|
|||
void onActionRenderToImagesTriggered();
|
||||
void onActionOptimizeTriggered();
|
||||
void onActionSanitizeTriggered();
|
||||
void onActionCreateBitonalDocumentTriggered();
|
||||
void onActionEncryptionTriggered();
|
||||
void onActionFitPageTriggered();
|
||||
void onActionFitWidthTriggered();
|
||||
|
|
|
@ -164,6 +164,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||
m_actionManager->setAction(PDFActionManager::RenderToImages, ui->actionRender_to_Images);
|
||||
m_actionManager->setAction(PDFActionManager::Optimize, ui->actionOptimize);
|
||||
m_actionManager->setAction(PDFActionManager::Sanitize, ui->actionSanitize);
|
||||
m_actionManager->setAction(PDFActionManager::CreateBitonalDocument, ui->actionCreateBitonalDocument);
|
||||
m_actionManager->setAction(PDFActionManager::Encryption, ui->actionEncryption);
|
||||
m_actionManager->setAction(PDFActionManager::FitPage, ui->actionFitPage);
|
||||
m_actionManager->setAction(PDFActionManager::FitWidth, ui->actionFitWidth);
|
||||
|
|
|
@ -144,8 +144,9 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionEncryption"/>
|
||||
<addaction name="actionOptimize"/>
|
||||
<addaction name="actionSanitize"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSanitize"/>
|
||||
<addaction name="actionCreateBitonalDocument"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuInsert">
|
||||
<property name="title">
|
||||
|
@ -952,6 +953,10 @@
|
|||
</property>
|
||||
</action>
|
||||
<action name="actionSanitize">
|
||||
<property name="icon">
|
||||
<iconset resource="pdf4qtviewer.qrc">
|
||||
<normaloff>:/resources/sanitize-document.svg</normaloff>:/resources/sanitize-document.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sanitize...</string>
|
||||
</property>
|
||||
|
@ -979,6 +984,21 @@
|
|||
<string>Become a Sponsor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCreateBitonalDocument">
|
||||
<property name="icon">
|
||||
<iconset resource="pdf4qtviewer.qrc">
|
||||
<normaloff>:/resources/create-bitonal-document.svg</normaloff>:/resources/create-bitonal-document.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create Bitonal Document...</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Create Bitonal Document</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Convert the colored images to monochromatic to create a bitonal document.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg4518"
|
||||
width="1024"
|
||||
height="1024"
|
||||
viewBox="0 0 1024 1024"
|
||||
sodipodi:docname="create-bitonal-document.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata4524">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4522" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2035"
|
||||
id="namedview4520"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.60697936"
|
||||
inkscape:cx="-446.16507"
|
||||
inkscape:cy="382.76742"
|
||||
inkscape:window-x="-13"
|
||||
inkscape:window-y="-13"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4518" />
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="M 275,840.34984 C 239.39425,836.75303 210.93791,812.8012 200.68906,777.80196 198.55083,770.5 198.55083,770.5 198.26782,555 197.98481,339.5 197.98481,339.5 244.79539,292.5 270.54121,266.65 294.58068,242.6875 298.21644,239.25 304.82689,233 304.82689,233 439.41345,233 574,233 574,233 574,221.46788 c 0,-16.44964 3.6815,-23.54063 14.00146,-26.96836 7.72363,-2.56538 166.06628,-1.78035 170.88594,0.84721 8.98764,4.89986 12.06104,11.45552 12.09064,25.78973 0.022,10.63645 0.022,10.63645 4.27196,11.27967 22.89593,3.46521 48.18401,25.82601 56.14594,49.64663 4.76595,14.25883 4.67352,8.89106 4.38132,254.43724 -0.27726,233 -0.27726,233 -2.52142,241 -8.4023,29.95265 -31.42147,52.75955 -61.25584,60.69096 -8.5,2.25971 -8.5,2.25971 -251,2.38607 -133.375,0.0695 -244.075,-0.0327 -246,-0.22719 z m 490,-33.00821 c 17.35762,-4.79398 29.59742,-17.00765 37.37332,-37.29353 2.45292,-6.39921 3.99366,-453.89194 1.61667,-469.5481 -2.19986,-14.48955 -11.11357,-27.00001 -23.80071,-33.40445 C 770.66408,262.28724 771,261.9069 771,277.5 c 0,13.5 0,13.5 2.50822,13.5 3.73544,0 3.51498,470.65192 -0.22373,477.64255 -2.80216,5.23949 -9.12708,10.69411 -14.14293,12.19689 -7.53442,2.25737 -482.68052,1.55749 -487.64156,-0.71828 -4.8754,-2.23649 -8.98945,-6.26636 -11.85053,-11.60805 C 257.5,764.5 257.5,764.5 257,611.82303 c -0.5,-152.67696 -0.5,-152.67696 -2.60453,-155.5 -6.68903,-8.97271 -19.53324,-8.87451 -23.83093,0.1822 -2.88554,6.08083 -1.86962,307.89446 1.06214,315.54287 9.47687,24.72341 24.94287,35.64473 52.37332,36.98343 31.24799,1.525 475.00462,-0.034 481,-1.6899 z M 549.50613,723.48462 C 608.29522,716.62845 656.6189,689.18053 711.59979,631.41516 732.35953,609.60405 733.33491,607.60888 726.75,600.42502 723.5,596.8794 723.5,596.8794 689,596.29093 644.2412,595.52747 647.84972,594.56637 628.66971,612.35947 609.134,630.48256 596.22209,639.42823 578.0973,647.39723 532.63362,667.38645 474.23364,660.41077 448.62945,631.93275 443.99057,626.7732 436,615.4135 436,613.97823 c 0,-0.43662 10.76768,-0.94784 23.92819,-1.13604 26.87027,-0.38426 27.63895,-0.58383 31.40218,-8.15256 3.58151,-7.20326 4.07279,-6.58299 -40.68105,-51.36331 -45.57366,-45.60064 -44.92894,-45.0974 -53.07665,-41.42973 -2.79718,1.25915 -17.00264,14.63777 -45.35847,42.71836 C 310.92841,595.5 310.92841,595.5 310.9642,599.79966 311.06369,611.7495 314.27432,613 344.85582,613 c 24.05355,0 24.05355,0 24.68861,3.75 11.84266,69.93128 91.32462,117.07174 179.9617,106.73462 z M 646,537.43405 c 1.375,-0.70689 20.56475,-19.43124 42.64389,-41.60966 43.1112,-43.30505 42.94206,-43.09292 39.99159,-50.15438 C 726.01639,439.40165 724.11249,439 697.01828,439 c -18.40336,0 -24.16717,-0.29472 -24.44633,-1.25 -9.26076,-31.69033 -18.68029,-48.38717 -36.78802,-65.2096 C 592.55321,332.3782 529.03396,319.07899 464.12597,336.59985 411.6213,350.77263 366.32483,384.72873 314.85436,448.5 300.0098,466.89222 299,468.53606 299,474.30906 c 0,11.09459 3.46247,11.98038 45,11.51218 36.66276,-0.41325 30.61063,2.50744 59.45867,-28.69406 C 432.87399,425.31213 454.76264,409.35438 481,400.59627 521.997,386.91134 567.04829,394.54425 592.5745,419.5 598.55894,425.3507 607,436.44042 607,438.45196 607,438.75338 596.17765,439 582.95034,439 c -27.05976,0 -28.96721,0.4028 -31.58582,6.67001 -3.18128,7.61388 -6.48857,3.63459 54.90397,66.05974 29.31079,29.80378 30.39419,30.50468 39.73151,25.7043 z M 315.53668,388.3328 c 7.07205,-2.40969 13.91868,-9.53856 15.27685,-15.90662 0.57488,-2.69541 0.9427,-5.00328 0.81738,-5.1286 -0.17754,-0.17754 -93.38778,-0.48534 -100.88091,-0.33313 -1.56316,0.0317 -1.75,1.26338 -1.75,11.53555 0,11.5 0,11.5 40.82186,11.5 35.48238,0 41.46186,-0.21807 45.71482,-1.6672 z M 722.42901,343.20507 C 740,324.91015 740,324.91015 740,274.95507 740,225 740,225 704.5,225 669,225 669,225 669,275.50741 c 0,50.50741 0,50.50741 16.75,68.49574 9.2125,9.89358 17.28055,17.87774 17.92901,17.74259 0.64845,-0.13516 9.08595,-8.47846 18.75,-18.54067 z M 303.08795,311.75 c 0.0484,-12.7875 0.28877,-25.3875 0.53421,-28 0.28494,-3.03296 0.0525,-4.75 -0.64315,-4.75 -0.59917,0 -13.77706,12.58712 -29.2842,27.97138 C 245.5,334.94277 245.5,334.94277 274.25,334.97138 303,335 303,335 303.08795,311.75 Z m 334.9119,-51.91821 c 8e-5,-17.41752 0.27329,-32.38002 0.60714,-33.25 C 639.14591,225.17739 637.29548,225 622.10699,225 605,225 605,225 605,232.95078 c 0,15.23051 -3.66246,23.69246 -12.2366,28.27206 C 588.5,263.5 588.5,263.5 461.89019,263.76495 c -126.60982,0.26496 -126.60982,0.26496 -127.31515,13.24008 -0.38794,7.13632 -0.39208,13.48198 -0.009,14.10148 0.5128,0.82972 40.55932,1.02984 152.065,0.75992 C 637.9997,291.5 637.9997,291.5 637.99985,259.83179 Z"
|
||||
id="path4528"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg815"
|
||||
width="1365.3333"
|
||||
height="1365.3333"
|
||||
viewBox="0 0 1365.3333 1365.3333"
|
||||
sodipodi:docname="sanitize-document.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata821">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs819" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2035"
|
||||
id="namedview817"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.2875977"
|
||||
inkscape:cx="599.95446"
|
||||
inkscape:cy="638.7656"
|
||||
inkscape:window-x="-13"
|
||||
inkscape:window-y="-13"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg815" />
|
||||
<path
|
||||
style="fill:#000000;stroke-width:2.26666641"
|
||||
d="m 387.36867,1277.0958 c -134.74112,-6.3339 -223.92939,-127.5348 -186.88279,-253.9613 2.61829,-8.9352 81.10843,-168.81275 189.74083,-386.48532 L 575.50199,265.40319 v -25.42066 c 0,-30.86119 -3.26752,-38.59602 -25.22362,-59.70922 l -14.44304,-13.88857 v -40.68299 c 0,-39.5947 0.14025,-40.81479 5.24608,-45.611425 l 5.2461,-4.928436 h 137.65694 137.65693 l 5.56363,5.563641 c 5.47354,5.473524 5.56364,6.191706 5.56364,44.349 0,43.44888 0.17136,42.84788 -16.79031,58.8895 -22.31805,21.10751 -29.43769,53.99586 -18.64558,86.13118 3.78004,11.25575 128.14738,260.13291 302.99304,606.33332 77.9276,154.29927 77.828,154.03857 77.9911,203.99997 0.2108,64.6021 -25.4795,117.0637 -75.9494,155.094 -42.3468,31.9091 -67.2713,39.1595 -138.13219,40.181 -65.07981,0.9382 -561.06001,2.1354 -576.86664,1.3923 z m 631.26663,-64.6954 c 73.7251,-22.3868 116.4342,-93.2836 98.4209,-163.378 -2.1609,-8.4086 -24.0529,-54.34464 -55.2482,-115.92721 -28.418,-56.1 -80.99471,-160.13998 -116.83706,-231.19998 -177.29757,-351.505 -180.34082,-357.35442 -191.97081,-368.9844 -37.54993,-37.54993 -98.8371,-36.4393 -137.77309,2.49668 -12.81461,12.81462 -17.65225,22.08365 -158.23695,303.18655 -79.76,159.48231 -157.26426,313.99023 -172.23171,343.35093 -20.88167,40.96233 -28.13608,57.52993 -31.17868,71.20573 -17.32063,77.853 39.11789,153.964 120.18897,162.0827 39.28608,3.9342 631.14343,1.334 644.86663,-2.833 z m -693.59997,-94.0183 c 0,-1.3567 358.6382,-718.07058 360.02855,-719.49231 1.06676,-1.09085 357.52872,705.58291 362.47012,718.58461 1.217,3.2023 -219.68984,0.9452 -222.72121,-2.2756 -1.57205,-1.6703 -21.7094,-33.1269 -44.74965,-69.9036 -57.03289,-91.03543 -87.59202,-138.81969 -91.05936,-142.38643 -3.1057,-3.19471 -7.52053,3.30381 -101.62185,149.58433 l -41.81274,64.9978 -110.26692,0.7488 c -60.64682,0.4119 -110.26694,0.476 -110.26694,0.1425 z"
|
||||
id="path825"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
Loading…
Reference in New Issue