From 59da885d746b9f39e449485a185733c5337c94a0 Mon Sep 17 00:00:00 2001 From: zvezdochiot Date: Thu, 20 Jul 2023 16:27:05 +0300 Subject: [PATCH] 1.0.19: feature #50: foreground and background zones --- src/core/filters/output/OutputGenerator.cpp | 73 ++++++++++++++++++- .../filters/output/PictureLayerProperty.cpp | 10 +++ .../filters/output/PictureLayerProperty.h | 2 +- src/core/filters/output/PictureZoneEditor.cpp | 18 +++++ .../filters/output/PictureZonePropDialog.cpp | 14 +++- .../filters/output/PictureZonePropDialog.ui | 14 ++++ 6 files changed, 125 insertions(+), 6 deletions(-) diff --git a/src/core/filters/output/OutputGenerator.cpp b/src/core/filters/output/OutputGenerator.cpp index 8f52adf7f..2b63cf51f 100644 --- a/src/core/filters/output/OutputGenerator.cpp +++ b/src/core/filters/output/OutputGenerator.cpp @@ -166,7 +166,7 @@ class OutputGenerator::Processor { const QRect& sourceRect, const QRect& sourceSubRect) const; - void modifyBinarizationMask(BinaryImage& bwMask, const QRect& maskRect, const ZoneSet& zones) const; + void modifyBinarizationMask(BinaryImage& bwMask, const BinaryImage& bwContent, const QRect& maskRect, const ZoneSet& zones) const; BinaryThreshold adjustThreshold(BinaryThreshold threshold) const; @@ -520,6 +520,42 @@ void fillExcept(BinaryImage& image, const BinaryImage& bwMask, const BWColor col } } +void BinaryImageXOR(BinaryImage& image, const BinaryImage& bwMask, const BWColor color) { + uint32_t* imageLine = image.data(); + const int imageStride = image.wordsPerLine(); + const uint32_t* bwMaskLine = bwMask.data(); + const int bwMaskStride = bwMask.wordsPerLine(); + const int width = image.width(); + const int height = image.height(); + const uint32_t msb = uint32_t(1) << 31; + + if (color == BLACK) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if ((imageLine[x >> 5] & (msb >> (x & 31))) != (bwMaskLine[x >> 5] & (msb >> (x & 31)))) { + imageLine[x >> 5] |= (msb >> (x & 31)); + } else { + imageLine[x >> 5] &= ~(msb >> (x & 31)); + } + } + imageLine += imageStride; + bwMaskLine += bwMaskStride; + } + } else { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if ((imageLine[x >> 5] & (msb >> (x & 31))) != (bwMaskLine[x >> 5] & (msb >> (x & 31)))) { + imageLine[x >> 5] &= ~(msb >> (x & 31)); + } else { + imageLine[x >> 5] |= (msb >> (x & 31)); + } + } + imageLine += imageStride; + bwMaskLine += bwMaskStride; + } + } +} + void fillMarginsInPlace(QImage& image, const QPolygonF& contentPoly, const QColor& color, @@ -1320,6 +1356,7 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping if (m_renderParams.mixedOutput()) { BinaryImage bwMask(m_workingBoundingRect.size(), BLACK); processPictureZones(bwMask, pictureZones, GrayImage(maybeNormalized)); + BinaryImage bwMaskOut = BinaryImage(GrayImage(maybeNormalized)); if (m_dbg) { m_dbg->add(bwMask, "bwMask"); } @@ -1334,7 +1371,10 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping } m_status.throwIfCancelled(); - modifyBinarizationMask(bwMask, m_workingBoundingRect, pictureZones); + if (m_renderParams.needMorphologicalSmoothing()) morphologicalSmoothInPlace(bwMaskOut); + maybeDespeckleInPlace(bwMaskOut, m_workingBoundingRect, m_croppedContentRect, + m_despeckleLevel, specklesImage, m_dpi); + modifyBinarizationMask(bwMask, bwMaskOut, m_workingBoundingRect, pictureZones); fillMarginsInPlace(bwMask, m_contentAreaInWorkingCs, BLACK); if (m_dbg) { m_dbg->add(bwMask, "bwMask with zones"); @@ -1540,6 +1580,7 @@ std::unique_ptr OutputGenerator::Processor::processWithDewarping(Zo if (m_renderParams.mixedOutput()) { warpedBwMask = BinaryImage(m_workingBoundingRect.size(), BLACK); processPictureZones(warpedBwMask, pictureZones, warpedGrayOutput); + BinaryImage bwMaskOut = BinaryImage(warpedGrayOutput); if (m_dbg) { m_dbg->add(warpedBwMask, "warpedBwMask"); } @@ -1554,7 +1595,10 @@ std::unique_ptr OutputGenerator::Processor::processWithDewarping(Zo } m_status.throwIfCancelled(); - modifyBinarizationMask(warpedBwMask, m_workingBoundingRect, pictureZones); + if (m_renderParams.needMorphologicalSmoothing()) morphologicalSmoothInPlace(bwMaskOut); + maybeDespeckleInPlace(bwMaskOut, m_workingBoundingRect, m_croppedContentRect, + m_despeckleLevel, specklesImage, m_dpi); + modifyBinarizationMask(warpedBwMask, bwMaskOut, m_workingBoundingRect, pictureZones); if (m_dbg) { m_dbg->add(warpedBwMask, "warpedBwMask with zones"); } @@ -1898,6 +1942,7 @@ BinaryImage OutputGenerator::Processor::estimateBinarizationMask(const GrayImage } void OutputGenerator::Processor::modifyBinarizationMask(BinaryImage& bwMask, + const BinaryImage& bwContent, const QRect& maskRect, const ZoneSet& zones) const { QTransform xform = m_xform.transform(); @@ -1921,7 +1966,27 @@ void OutputGenerator::Processor::modifyBinarizationMask(BinaryImage& bwMask, } } - // Pass 1: ZONEERASER3 + // Pass 3: ZONEBG + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEBG) { + const QPolygonF poly(zone.spline().toPolygon()); + BinaryImageXOR(bwMask, bwContent, BLACK); + PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); + BinaryImageXOR(bwMask, bwContent, BLACK); + } + } + + // Pass 4: ZONEFG + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEFG) { + const QPolygonF poly(zone.spline().toPolygon()); + BinaryImageXOR(bwMask, bwContent, WHITE); + PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); + BinaryImageXOR(bwMask, bwContent, WHITE); + } + } + + // Pass 5: ZONEERASER3 for (const Zone& zone : zones) { if (zone.properties().locateOrDefault()->layer() == PLP::ZONEERASER3) { const QPolygonF poly(zone.spline().toPolygon()); diff --git a/src/core/filters/output/PictureLayerProperty.cpp b/src/core/filters/output/PictureLayerProperty.cpp index a3bf71879..91ba553d1 100644 --- a/src/core/filters/output/PictureLayerProperty.cpp +++ b/src/core/filters/output/PictureLayerProperty.cpp @@ -38,6 +38,10 @@ PictureLayerProperty::Layer PictureLayerProperty::layerFromString(const QString& return ZONEPAINTER2; } else if (str == "eraser3") { return ZONEERASER3; + } else if (str == "foreground") { + return ZONEFG; + } else if (str == "background") { + return ZONEBG; } else { return ZONENOOP; } @@ -56,6 +60,12 @@ QString PictureLayerProperty::layerToString(Layer layer) { case ZONEERASER3: str = "eraser3"; break; + case ZONEFG: + str = "foreground"; + break; + case ZONEBG: + str = "background"; + break; default: str = ""; break; diff --git a/src/core/filters/output/PictureLayerProperty.h b/src/core/filters/output/PictureLayerProperty.h index 074dd5ab5..c6ce23bdf 100644 --- a/src/core/filters/output/PictureLayerProperty.h +++ b/src/core/filters/output/PictureLayerProperty.h @@ -16,7 +16,7 @@ class QString; namespace output { class PictureLayerProperty : public Property { public: - enum Layer { ZONENOOP, ZONEERASER1, ZONEPAINTER2, ZONEERASER3 }; + enum Layer { ZONENOOP, ZONEERASER1, ZONEPAINTER2, ZONEERASER3, ZONEFG, ZONEBG }; explicit PictureLayerProperty(Layer layer = ZONENOOP); diff --git a/src/core/filters/output/PictureZoneEditor.cpp b/src/core/filters/output/PictureZoneEditor.cpp index fb49ce61a..28e7513f1 100644 --- a/src/core/filters/output/PictureZoneEditor.cpp +++ b/src/core/filters/output/PictureZoneEditor.cpp @@ -239,6 +239,24 @@ void PictureZoneEditor::paintOverPictureMask(QPainter& painter) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } + + painter.setCompositionMode(QPainter::CompositionMode_Clear); + + // Third pass: ZONEFG + for (const EditableZoneSet::Zone& zone : zones()) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEFG) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); + } + } + + painter.setCompositionMode(QPainter::CompositionMode_Clear); + + // Third pass: ZONEBG + for (const EditableZoneSet::Zone& zone : zones()) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEBG) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); + } + } } // PictureZoneEditor::paintOverPictureMask void PictureZoneEditor::showPropertiesDialog(const EditableZoneSet::Zone& zone) { diff --git a/src/core/filters/output/PictureZonePropDialog.cpp b/src/core/filters/output/PictureZonePropDialog.cpp index fd20472a2..26b3f7e45 100644 --- a/src/core/filters/output/PictureZonePropDialog.cpp +++ b/src/core/filters/output/PictureZonePropDialog.cpp @@ -24,11 +24,19 @@ PictureZonePropDialog::PictureZonePropDialog(std::shared_ptr props, case PictureLayerProperty::ZONEERASER3: ui.zoneeraser3->setChecked(true); break; + case PictureLayerProperty::ZONEFG: + ui.zoneforeground->setChecked(true); + break; + case PictureLayerProperty::ZONEBG: + ui.zonebackground->setChecked(true); + break; } connect(ui.zoneeraser1, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); connect(ui.zonepainter2, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); connect(ui.zoneeraser3, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + connect(ui.zoneforeground, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + connect(ui.zonebackground, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); } void PictureZonePropDialog::itemToggled(bool selected) { @@ -41,10 +49,14 @@ void PictureZonePropDialog::itemToggled(bool selected) { layer = PictureLayerProperty::ZONEPAINTER2; } else if (obj == ui.zoneeraser3) { layer = PictureLayerProperty::ZONEERASER3; + } else if (obj == ui.zoneforeground) { + layer = PictureLayerProperty::ZONEFG; + } else if (obj == ui.zonebackground) { + layer = PictureLayerProperty::ZONEBG; } m_props->locateOrCreate()->setLayer(layer); emit updated(); } -} // namespace output \ No newline at end of file +} // namespace output diff --git a/src/core/filters/output/PictureZonePropDialog.ui b/src/core/filters/output/PictureZonePropDialog.ui index cc2e164ba..9b479dc17 100644 --- a/src/core/filters/output/PictureZonePropDialog.ui +++ b/src/core/filters/output/PictureZonePropDialog.ui @@ -35,6 +35,20 @@ + + + + Add to foreground + + + + + + + Add to background + + +