diff --git a/src/core/filters/output/OutputGenerator.cpp b/src/core/filters/output/OutputGenerator.cpp index 423027e77..eaf300930 100644 --- a/src/core/filters/output/OutputGenerator.cpp +++ b/src/core/filters/output/OutputGenerator.cpp @@ -166,7 +166,10 @@ 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 +523,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, @@ -609,7 +648,7 @@ void removeAutoPictureZones(ZoneSet& pictureZones) { Zone createPictureZoneFromPoly(const QPolygonF& polygon) { PropertySet propertySet; - propertySet.locateOrCreate()->setLayer(output::PictureLayerProperty::PAINTER2); + propertySet.locateOrCreate()->setLayer(output::PictureLayerProperty::ZONEPAINTER2); propertySet.locateOrCreate()->setZoneCategory(ZoneCategoryProperty::AUTO); return Zone(SerializableSpline(polygon), propertySet); } @@ -1334,7 +1373,13 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping } m_status.throwIfCancelled(); - modifyBinarizationMask(bwMask, m_workingBoundingRect, pictureZones); + BinaryImage bwMaskOut = BinaryImage(maybeNormalized); + if (m_renderParams.needMorphologicalSmoothing()) + morphologicalSmoothInPlace(bwMaskOut); + maybeDespeckleInPlace(bwMaskOut, m_workingBoundingRect, m_croppedContentRect, m_despeckleLevel, specklesImage, + m_dpi); + modifyBinarizationMask(bwMask, bwMaskOut, m_workingBoundingRect, pictureZones); + bwMaskOut.release(); // Save memory. fillMarginsInPlace(bwMask, m_contentAreaInWorkingCs, BLACK); if (m_dbg) { m_dbg->add(bwMask, "bwMask with zones"); @@ -1540,6 +1585,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 +1600,12 @@ 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); + bwMaskOut.release(); // Save memory. if (m_dbg) { m_dbg->add(warpedBwMask, "warpedBwMask with zones"); } @@ -1898,6 +1949,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(); @@ -1905,25 +1957,45 @@ void OutputGenerator::Processor::modifyBinarizationMask(BinaryImage& bwMask, using PLP = PictureLayerProperty; - // Pass 1: ERASER1 + // Pass 1: ZONEERASER1 for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::ERASER1) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEERASER1) { const QPolygonF poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bwMask, BLACK, xform.map(poly), Qt::WindingFill); } } - // Pass 2: PAINTER2 + // Pass 2: ZONEPAINTER2 + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEPAINTER2) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); + } + } + + // 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::PAINTER2) { + 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 1: ERASER3 + // Pass 5: ZONEERASER3 for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::ERASER3) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEERASER3) { const QPolygonF poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bwMask, BLACK, xform.map(poly), Qt::WindingFill); } diff --git a/src/core/filters/output/PictureLayerProperty.cpp b/src/core/filters/output/PictureLayerProperty.cpp index 3b29f659a..91ba553d1 100644 --- a/src/core/filters/output/PictureLayerProperty.cpp +++ b/src/core/filters/output/PictureLayerProperty.cpp @@ -33,13 +33,17 @@ std::shared_ptr PictureLayerProperty::construct(const QDomElement& el) PictureLayerProperty::Layer PictureLayerProperty::layerFromString(const QString& str) { if (str == "eraser1") { - return ERASER1; + return ZONEERASER1; } else if (str == "painter2") { - return PAINTER2; + return ZONEPAINTER2; } else if (str == "eraser3") { - return ERASER3; + return ZONEERASER3; + } else if (str == "foreground") { + return ZONEFG; + } else if (str == "background") { + return ZONEBG; } else { - return NO_OP; + return ZONENOOP; } } @@ -47,15 +51,21 @@ QString PictureLayerProperty::layerToString(Layer layer) { QString str; switch (layer) { - case ERASER1: + case ZONEERASER1: str = "eraser1"; break; - case PAINTER2: + case ZONEPAINTER2: str = "painter2"; break; - case ERASER3: + case ZONEERASER3: str = "eraser3"; break; + case ZONEFG: + str = "foreground"; + break; + case ZONEBG: + str = "background"; + break; default: str = ""; break; @@ -64,4 +74,4 @@ QString PictureLayerProperty::layerToString(Layer layer) { } PictureLayerProperty::PictureLayerProperty(PictureLayerProperty::Layer layer) : m_layer(layer) {} -} // namespace output \ No newline at end of file +} // namespace output diff --git a/src/core/filters/output/PictureLayerProperty.h b/src/core/filters/output/PictureLayerProperty.h index 392764c18..c6ce23bdf 100644 --- a/src/core/filters/output/PictureLayerProperty.h +++ b/src/core/filters/output/PictureLayerProperty.h @@ -16,9 +16,9 @@ class QString; namespace output { class PictureLayerProperty : public Property { public: - enum Layer { NO_OP, ERASER1, PAINTER2, ERASER3 }; + enum Layer { ZONENOOP, ZONEERASER1, ZONEPAINTER2, ZONEERASER3, ZONEFG, ZONEBG }; - explicit PictureLayerProperty(Layer layer = NO_OP); + explicit PictureLayerProperty(Layer layer = ZONENOOP); explicit PictureLayerProperty(const QDomElement& el); diff --git a/src/core/filters/output/PictureZoneEditor.cpp b/src/core/filters/output/PictureZoneEditor.cpp index 613dbaa4d..28e7513f1 100644 --- a/src/core/filters/output/PictureZoneEditor.cpp +++ b/src/core/filters/output/PictureZoneEditor.cpp @@ -215,27 +215,45 @@ void PictureZoneEditor::paintOverPictureMask(QPainter& painter) { painter.setCompositionMode(QPainter::CompositionMode_Clear); - // First pass: ERASER1 + // First pass: ZONEERASER1 for (const EditableZoneSet::Zone& zone : zones()) { - if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER1) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEERASER1) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - // Second pass: PAINTER2 + // Second pass: ZONEPAINTER2 for (const EditableZoneSet::Zone& zone : zones()) { - if (zone.properties()->locateOrDefault()->layer() == PLP::PAINTER2) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEPAINTER2) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } painter.setCompositionMode(QPainter::CompositionMode_Clear); - // Third pass: ERASER3 + // Third pass: ZONEERASER3 for (const EditableZoneSet::Zone& zone : zones()) { - if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER3) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEERASER3) { + 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); } } diff --git a/src/core/filters/output/PictureZonePropDialog.cpp b/src/core/filters/output/PictureZonePropDialog.cpp index ad1728927..26b3f7e45 100644 --- a/src/core/filters/output/PictureZonePropDialog.cpp +++ b/src/core/filters/output/PictureZonePropDialog.cpp @@ -13,38 +13,50 @@ PictureZonePropDialog::PictureZonePropDialog(std::shared_ptr props, ui.setupUi(this); switch (m_props->locateOrDefault()->layer()) { - case PictureLayerProperty::NO_OP: + case PictureLayerProperty::ZONENOOP: break; - case PictureLayerProperty::ERASER1: - ui.eraser1->setChecked(true); + case PictureLayerProperty::ZONEERASER1: + ui.zoneeraser1->setChecked(true); break; - case PictureLayerProperty::PAINTER2: - ui.painter2->setChecked(true); + case PictureLayerProperty::ZONEPAINTER2: + ui.zonepainter2->setChecked(true); break; - case PictureLayerProperty::ERASER3: - ui.eraser3->setChecked(true); + 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.eraser1, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); - connect(ui.painter2, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); - connect(ui.eraser3, SIGNAL(toggled(bool)), SLOT(itemToggled(bool))); + 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) { - PictureLayerProperty::Layer layer = PictureLayerProperty::NO_OP; + PictureLayerProperty::Layer layer = PictureLayerProperty::ZONENOOP; QObject* const obj = sender(); - if (obj == ui.eraser1) { - layer = PictureLayerProperty::ERASER1; - } else if (obj == ui.painter2) { - layer = PictureLayerProperty::PAINTER2; - } else if (obj == ui.eraser3) { - layer = PictureLayerProperty::ERASER3; + if (obj == ui.zoneeraser1) { + layer = PictureLayerProperty::ZONEERASER1; + } else if (obj == ui.zonepainter2) { + 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 efc5711d6..9b479dc17 100644 --- a/src/core/filters/output/PictureZonePropDialog.ui +++ b/src/core/filters/output/PictureZonePropDialog.ui @@ -13,28 +13,42 @@ Zone Properties - + - + Subtract from all layers - + Add to auto layer - + Subtract from auto layer + + + + Add to foreground + + + + + + + Add to background + + + diff --git a/src/core/filters/output/Settings.cpp b/src/core/filters/output/Settings.cpp index 599a35a7f..8b15919a9 100644 --- a/src/core/filters/output/Settings.cpp +++ b/src/core/filters/output/Settings.cpp @@ -275,7 +275,7 @@ void Settings::setDefaultFillZoneProperties(const PropertySet& props) { PropertySet Settings::initialPictureZoneProps() { PropertySet props; - props.locateOrCreate()->setLayer(PictureLayerProperty::PAINTER2); + props.locateOrCreate()->setLayer(PictureLayerProperty::ZONEPAINTER2); return props; }