diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp index bc532b0..609d4a6 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp @@ -74,6 +74,22 @@ PDFColorBuffer PDFFloatBitmap::getPixels() return PDFColorBuffer(m_data.data(), m_data.size()); } +PDFColorComponent PDFFloatBitmap::getPixelInkCoverage(size_t x, size_t y) const +{ + PDFConstColorBuffer buffer = getPixel(x, y); + + const uint8_t colorChannelIndexStart = m_format.getColorChannelIndexStart(); + const uint8_t colorChannelIndexEnd = m_format.getColorChannelIndexEnd(); + + PDFColorComponent inkCoverage = 0.0; + for (uint8_t i = colorChannelIndexStart; i < colorChannelIndexEnd; ++i) + { + inkCoverage += buffer[i]; + } + + return inkCoverage; +} + const PDFColorComponent* PDFFloatBitmap::begin() const { return m_data.data(); diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.h b/Pdf4QtLib/sources/pdftransparencyrenderer.h index 856b01f..aa8d4e9 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.h +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.h @@ -157,6 +157,9 @@ public: /// Returns buffer with all pixels PDFColorBuffer getPixels(); + /// Returns ink coverage + PDFColorComponent getPixelInkCoverage(size_t x, size_t y) const; + const PDFColorComponent* begin() const; const PDFColorComponent* end() const; diff --git a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp index f7467af..a463915 100644 --- a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp +++ b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.cpp @@ -69,11 +69,17 @@ OutputPreviewDialog::OutputPreviewDialog(const pdf::PDFDocument* document, pdf:: connect(ui->displayVectorGraphicsCheckBox, &QCheckBox::clicked, this, &OutputPreviewDialog::updatePageImage); connect(ui->inksTreeWidget->model(), &QAbstractItemModel::dataChanged, this, &OutputPreviewDialog::onInksChanged); connect(ui->alarmColorButton, &QPushButton::clicked, this, &OutputPreviewDialog::onAlarmColorButtonClicked); + connect(ui->displayModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &OutputPreviewDialog::onDisplayModeChanged); + connect(ui->inkCoverageLimitEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &OutputPreviewDialog::onInkCoverageLimitChanged); + connect(ui->richBlackLimitEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &OutputPreviewDialog::onRichBlackLimtiChanged); updatePageImage(); updateInks(); updatePaperColorWidgets(); updateAlarmColorButtonIcon(); + onDisplayModeChanged(); + onInkCoverageLimitChanged(ui->inkCoverageLimitEdit->value()); + onRichBlackLimtiChanged(ui->richBlackLimitEdit->value()); } OutputPreviewDialog::~OutputPreviewDialog() @@ -220,6 +226,11 @@ void OutputPreviewDialog::onSimulatePaperColorChecked(bool checked) updatePageImage(); } +void OutputPreviewDialog::onDisplayModeChanged() +{ + ui->imageWidget->setDisplayMode(OutputPreviewWidget::DisplayMode(ui->displayModeComboBox->currentData().toInt())); +} + void OutputPreviewDialog::onInksChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { Q_UNUSED(topLeft); @@ -231,6 +242,16 @@ void OutputPreviewDialog::onInksChanged(const QModelIndex& topLeft, const QModel } } +void OutputPreviewDialog::onInkCoverageLimitChanged(double value) +{ + ui->imageWidget->setInkCoverageLimit(value / 100.0); +} + +void OutputPreviewDialog::onRichBlackLimtiChanged(double value) +{ + ui->imageWidget->setRichBlackLimit(value / 100.0); +} + void OutputPreviewDialog::updatePageImage() { if (!isRenderingDone()) diff --git a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.h b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.h index 7110d8a..5e7ce55 100644 --- a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.h +++ b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewdialog.h @@ -59,7 +59,10 @@ private: void onAlarmColorButtonClicked(); void onSimulateSeparationsChecked(bool checked); void onSimulatePaperColorChecked(bool checked); + void onDisplayModeChanged(); void onInksChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles); + void onInkCoverageLimitChanged(double value); + void onRichBlackLimtiChanged(double value); struct RenderedImage { diff --git a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.cpp b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.cpp index b87a72a..ca78590 100644 --- a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.cpp +++ b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.cpp @@ -30,7 +30,9 @@ OutputPreviewWidget::OutputPreviewWidget(QWidget* parent) : BaseClass(parent), m_inkMapper(nullptr), m_displayMode(Separations), - m_alarmColor(Qt::red) + m_alarmColor(Qt::red), + m_inkCoverageLimit(3.0), + m_richBlackLimit(1.0) { setMouseTracking(true); } @@ -53,6 +55,8 @@ void OutputPreviewWidget::clear() m_infoBoxItems.clear(); m_imagePointUnderCursor = std::nullopt; m_inkCoverageMM.dirty(); + m_alarmCoverageImage.dirty(); + m_alarmRichBlackImage.dirty(); update(); } @@ -72,6 +76,8 @@ void OutputPreviewWidget::setPageImage(QImage image, pdf::PDFFloatBitmapWithColo } m_inkCoverageMM.dirty(); + m_alarmCoverageImage.dirty(); + m_alarmRichBlackImage.dirty(); buildInfoBoxItems(); update(); @@ -89,12 +95,52 @@ void OutputPreviewWidget::paintEvent(QPaintEvent* event) QRect contentRect = getContentRect(); QRect pageImageRect = getPageImageRect(contentRect); - if (pageImageRect.isValid() && !m_pageImage.isNull()) + if (pageImageRect.isValid()) { painter.save(); painter.setClipRect(pageImageRect, Qt::IntersectClip); - painter.translate(0, (pageImageRect.height() - m_pageImage.height()) / 2); - painter.drawImage(pageImageRect.topLeft(), m_pageImage); + + switch (m_displayMode) + { + case Separations: + { + if (!m_pageImage.isNull()) + { + painter.translate(0, (pageImageRect.height() - m_pageImage.height()) / 2); + painter.drawImage(pageImageRect.topLeft(), m_pageImage); + } + break; + } + + case ColorWarningInkCoverage: + { + const QImage& image = getAlarmCoverageImage(); + if (!image.isNull()) + { + painter.translate(0, (pageImageRect.height() - image.height()) / 2); + painter.drawImage(pageImageRect.topLeft(), image); + } + break; + } + + case ColorWarningRichBlack: + { + const QImage& image = getAlarmRichBlackImage(); + if (!image.isNull()) + { + painter.translate(0, (pageImageRect.height() - image.height()) / 2); + painter.drawImage(pageImageRect.topLeft(), image); + } + break; + } + + case InkCoverage: + break; + + default: + Q_ASSERT(false); + } + painter.restore(); } @@ -411,6 +457,16 @@ const std::vector& OutputPreviewWidget::getInkCoverage() return m_inkCoverageMM.get(this, &OutputPreviewWidget::getInkCoverageImpl); } +const QImage& OutputPreviewWidget::getAlarmCoverageImage() const +{ + return m_alarmCoverageImage.get(this, &OutputPreviewWidget::getAlarmCoverageImageImpl); +} + +const QImage& OutputPreviewWidget::getAlarmRichBlackImage() const +{ + return m_alarmRichBlackImage.get(this, &OutputPreviewWidget::getAlarmRichBlackImageImpl); +} + std::vector OutputPreviewWidget::getInkCoverageImpl() const { std::vector result; @@ -447,6 +503,111 @@ std::vector OutputPreviewWidget::getInkCoverageImpl() co return result; } +QImage OutputPreviewWidget::getAlarmCoverageImageImpl() const +{ + QImage alarmImage = m_pageImage; + + const int width = alarmImage.width(); + const int height = alarmImage.height(); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + pdf::PDFColorComponent inkCoverage = m_originalProcessBitmap.getPixelInkCoverage(x, y); + if (inkCoverage > m_inkCoverageLimit) + { + alarmImage.setPixelColor(x, y, m_alarmColor); + } + } + } + + return alarmImage; +} + +QImage OutputPreviewWidget::getAlarmRichBlackImageImpl() const +{ + QImage alarmImage = m_pageImage; + + const pdf::PDFPixelFormat pixelFormat = m_originalProcessBitmap.getPixelFormat(); + if (pixelFormat.getProcessColorChannelCount() == 4) + { + const int width = alarmImage.width(); + const int height = alarmImage.height(); + + const uint8_t blackChannelIndex = pixelFormat.getProcessColorChannelIndexStart() + 3; + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + pdf::PDFConstColorBuffer buffer = m_originalProcessBitmap.getPixel(x, y); + pdf::PDFColorComponent blackInk = buffer[blackChannelIndex]; + + if (blackInk > m_richBlackLimit) + { + pdf::PDFColorComponent inkCoverage = m_originalProcessBitmap.getPixelInkCoverage(x, y); + pdf::PDFColorComponent inkCoverageWithoutBlack = inkCoverage - blackInk; + + if (!qFuzzyIsNull(inkCoverageWithoutBlack)) + { + alarmImage.setPixelColor(x, y, m_alarmColor); + } + } + } + } + } + + return alarmImage; +} + +pdf::PDFColorComponent OutputPreviewWidget::getRichBlackLimit() const +{ + return m_richBlackLimit; +} + +void OutputPreviewWidget::setRichBlackLimit(pdf::PDFColorComponent richBlackLimit) +{ + if (m_richBlackLimit != richBlackLimit) + { + m_richBlackLimit = richBlackLimit; + m_alarmRichBlackImage.dirty(); + buildInfoBoxItems(); + update(); + } +} + +pdf::PDFColorComponent OutputPreviewWidget::getInkCoverageLimit() const +{ + return m_inkCoverageLimit; +} + +void OutputPreviewWidget::setInkCoverageLimit(pdf::PDFColorComponent inkCoverageLimit) +{ + if (m_inkCoverageLimit != inkCoverageLimit) + { + m_inkCoverageLimit = inkCoverageLimit; + m_alarmCoverageImage.dirty(); + buildInfoBoxItems(); + update(); + } +} + +OutputPreviewWidget::DisplayMode OutputPreviewWidget::getDisplayMode() const +{ + return m_displayMode; +} + +void OutputPreviewWidget::setDisplayMode(const DisplayMode& displayMode) +{ + if (m_displayMode != displayMode) + { + m_displayMode = displayMode; + buildInfoBoxItems(); + update(); + } +} + QColor OutputPreviewWidget::getAlarmColor() const { return m_alarmColor; @@ -454,7 +615,13 @@ QColor OutputPreviewWidget::getAlarmColor() const void OutputPreviewWidget::setAlarmColor(const QColor& alarmColor) { - m_alarmColor = alarmColor; + if (m_alarmColor != alarmColor) + { + m_alarmColor = alarmColor; + m_alarmCoverageImage.dirty(); + m_alarmRichBlackImage.dirty(); + update(); + } } const pdf::PDFInkMapper* OutputPreviewWidget::getInkMapper() const diff --git a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.h b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.h index 90d2353..6edbba5 100644 --- a/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.h +++ b/Pdf4QtViewerPlugins/OutputPreviewPlugin/outputpreviewwidget.h @@ -61,6 +61,15 @@ public: QColor getAlarmColor() const; void setAlarmColor(const QColor& alarmColor); + DisplayMode getDisplayMode() const; + void setDisplayMode(const DisplayMode& displayMode); + + pdf::PDFColorComponent getInkCoverageLimit() const; + void setInkCoverageLimit(pdf::PDFColorComponent inkCoverageLimit); + + pdf::PDFColorComponent getRichBlackLimit() const; + void setRichBlackLimit(pdf::PDFColorComponent richBlackLimit); + protected: virtual void paintEvent(QPaintEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; @@ -80,8 +89,12 @@ private: void addInfoBoxColoredRect(QColor color); const std::vector& getInkCoverage() const; + const QImage& getAlarmCoverageImage() const; + const QImage& getAlarmRichBlackImage() const; std::vector getInkCoverageImpl() const; + QImage getAlarmCoverageImageImpl() const; + QImage getAlarmRichBlackImageImpl() const; enum InfoBoxStyle { @@ -113,8 +126,12 @@ private: std::vector m_infoBoxItems; QColor m_alarmColor; std::optional m_imagePointUnderCursor; + pdf::PDFColorComponent m_inkCoverageLimit; + pdf::PDFColorComponent m_richBlackLimit; mutable pdf::PDFCachedItem> m_inkCoverageMM; + mutable pdf::PDFCachedItem m_alarmCoverageImage; + mutable pdf::PDFCachedItem m_alarmRichBlackImage; QImage m_pageImage; pdf::PDFFloatBitmapWithColorSpace m_originalProcessBitmap;