diff --git a/src/app/DefaultParamsDialog.cpp b/src/app/DefaultParamsDialog.cpp index ffa239535..c9c1c2f6a 100644 --- a/src/app/DefaultParamsDialog.cpp +++ b/src/app/DefaultParamsDialog.cpp @@ -48,6 +48,7 @@ DefaultParamsDialog::DefaultParamsDialog(QWidget* parent) thresholdMethodBox->addItem(tr("Otsu"), OTSU); thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); thresholdMethodBox->addItem(tr("Wolf"), WOLF); + thresholdMethodBox->addItem(tr("Bradley"), BRADLEY); thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS); thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV); thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV); @@ -665,8 +666,8 @@ std::unique_ptr DefaultParamsDialog::buildParams() const { blackWhiteOptions.setBinarizationMethod(binarizationMethod); blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value()); blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value()); - if (binarizationMethod == SAUVOLA || binarizationMethod == EDGEPLUS || binarizationMethod == BLURDIV - || binarizationMethod == EDGEDIV) { + if (binarizationMethod == SAUVOLA || binarizationMethod == BRADLEY || binarizationMethod == EDGEPLUS + || binarizationMethod == BLURDIV || binarizationMethod == EDGEDIV) { blackWhiteOptions.setWindowSize(sauvolaWindowSize->value()); } else if (binarizationMethod == WOLF) { blackWhiteOptions.setWindowSize(wolfWindowSize->value()); diff --git a/src/core/filters/output/BlackWhiteOptions.cpp b/src/core/filters/output/BlackWhiteOptions.cpp index 235789cfe..88e8551bf 100644 --- a/src/core/filters/output/BlackWhiteOptions.cpp +++ b/src/core/filters/output/BlackWhiteOptions.cpp @@ -72,6 +72,8 @@ BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str return WOLF; } else if (str == "sauvola") { return SAUVOLA; + } else if (str == "bradley") { + return BRADLEY; } else if (str == "edgeplus") { return EDGEPLUS; } else if (str == "blurdiv") { @@ -95,6 +97,9 @@ QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) { case WOLF: str = "wolf"; break; + case BRADLEY: + str = "bradley"; + break; case EDGEPLUS: str = "edgeplus"; break; diff --git a/src/core/filters/output/BlackWhiteOptions.h b/src/core/filters/output/BlackWhiteOptions.h index 2709b6428..171da6ef4 100644 --- a/src/core/filters/output/BlackWhiteOptions.h +++ b/src/core/filters/output/BlackWhiteOptions.h @@ -9,7 +9,7 @@ class QDomDocument; class QDomElement; namespace output { -enum BinarizationMethod { OTSU, SAUVOLA, WOLF, EDGEPLUS, BLURDIV, EDGEDIV }; +enum BinarizationMethod { OTSU, SAUVOLA, WOLF, BRADLEY, EDGEPLUS, BLURDIV, EDGEDIV }; class BlackWhiteOptions { public: diff --git a/src/core/filters/output/OptionsWidget.cpp b/src/core/filters/output/OptionsWidget.cpp index 9111a7c28..cd19356e2 100644 --- a/src/core/filters/output/OptionsWidget.cpp +++ b/src/core/filters/output/OptionsWidget.cpp @@ -42,6 +42,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec thresholdMethodBox->addItem(tr("Otsu"), OTSU); thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA); thresholdMethodBox->addItem(tr("Wolf"), WOLF); + thresholdMethodBox->addItem(tr("Bradley"), BRADLEY); thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS); thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV); thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV); @@ -54,6 +55,8 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec QPointer sauvolaBinarizationOptionsWidget = new SauvolaBinarizationOptionsWidget(m_settings); QPointer wolfBinarizationOptionsWidget = new WolfBinarizationOptionsWidget(m_settings); + QPointer bradleyBinarizationOptionsWidget + = new SauvolaBinarizationOptionsWidget(m_settings); QPointer edgeplusBinarizationOptionsWidget = new SauvolaBinarizationOptionsWidget(m_settings); QPointer blurdivBinarizationOptionsWidget @@ -67,6 +70,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec addBinarizationOptionsWidget(otsuBinarizationOptionsWidget); addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget); addBinarizationOptionsWidget(wolfBinarizationOptionsWidget); + addBinarizationOptionsWidget(bradleyBinarizationOptionsWidget); addBinarizationOptionsWidget(edgeplusBinarizationOptionsWidget); addBinarizationOptionsWidget(blurdivBinarizationOptionsWidget); addBinarizationOptionsWidget(edgedivBinarizationOptionsWidget); diff --git a/src/core/filters/output/OutputGenerator.cpp b/src/core/filters/output/OutputGenerator.cpp index 423027e77..b9a241010 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, + 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); } @@ -1219,9 +1258,11 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping const ZoneSet& fillZones, BinaryImage* autoPictureMask, BinaryImage* specklesImage) { + QImage maybeNormalized, maybeSmoothed; + BinaryImage bwContent, bwContentMaskOutput, bwContentOutput; OutputImageBuilder imageBuilder; - QImage maybeNormalized = transformToWorkingCs(m_renderParams.normalizeIllumination()); + maybeNormalized = transformToWorkingCs(m_renderParams.normalizeIllumination()); if (m_dbg) { m_dbg->add(maybeNormalized, "maybeNormalized"); } @@ -1233,9 +1274,8 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping m_status.throwIfCancelled(); - if (m_renderParams.binaryOutput()) { - QImage maybeSmoothed; - // We only do smoothing if we are going to do binarization later. + if (m_renderParams.binaryOutput() || m_renderParams.mixedOutput()) { + // BW mask begin if (!m_renderParams.needSavitzkyGolaySmoothing()) { maybeSmoothed = maybeNormalized; } else { @@ -1246,12 +1286,7 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping m_status.throwIfCancelled(); } - // don't destroy as it's needed for color segmentation - if (!m_renderParams.needColorSegmentation()) { - maybeNormalized = QImage(); - } - - BinaryImage bwContent = binarize(maybeSmoothed, m_contentAreaInWorkingCs); + bwContent = binarize(maybeSmoothed, m_contentAreaInWorkingCs); maybeSmoothed = QImage(); // Save memory. if (m_dbg) { m_dbg->add(bwContent, "binarized_and_cropped"); @@ -1262,6 +1297,15 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping morphologicalSmoothInPlace(bwContent); m_status.throwIfCancelled(); } + // BW mask end + } + + // (BW only / Color segment) begin + if (m_renderParams.binaryOutput()) { + // don't destroy as it's needed for color segmentation + if (!m_renderParams.needColorSegmentation()) { + maybeNormalized = QImage(); + } BinaryImage dst(m_targetSize, WHITE); rasterOp(dst, m_croppedContentRect, bwContent, m_contentRectInWorkingCs.topLeft()); @@ -1314,9 +1358,9 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping } return imageBuilder.build(); } + // (BW only / Color segment) end - BinaryImage bwContentMaskOutput; - BinaryImage bwContentOutput; + // Mixed begin if (m_renderParams.mixedOutput()) { BinaryImage bwMask(m_workingBoundingRect.size(), BLACK); processPictureZones(bwMask, pictureZones, GrayImage(maybeNormalized)); @@ -1334,7 +1378,7 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping } m_status.throwIfCancelled(); - modifyBinarizationMask(bwMask, m_workingBoundingRect, pictureZones); + modifyBinarizationMask(bwMask, bwContent, m_workingBoundingRect, pictureZones); fillMarginsInPlace(bwMask, m_contentAreaInWorkingCs, BLACK); if (m_dbg) { m_dbg->add(bwMask, "bwMask with zones"); @@ -1342,31 +1386,6 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping m_status.throwIfCancelled(); if (m_renderParams.needBinarization()) { - QImage maybeSmoothed; - if (!m_renderParams.needSavitzkyGolaySmoothing()) { - maybeSmoothed = maybeNormalized; - } else { - maybeSmoothed = smoothToGrayscale(maybeNormalized, m_dpi); - if (m_dbg) { - m_dbg->add(maybeSmoothed, "smoothed"); - } - m_status.throwIfCancelled(); - } - - BinaryImage bwMaskFilled(bwMask); - fillMarginsInPlace(bwMaskFilled, m_contentAreaInWorkingCs, WHITE); - BinaryImage bwContent = binarize(maybeSmoothed, bwMaskFilled); - bwMaskFilled.release(); - maybeSmoothed = QImage(); // Save memory. - if (m_dbg) { - m_dbg->add(bwContent, "binarized_and_cropped"); - } - m_status.throwIfCancelled(); - - if (m_renderParams.needMorphologicalSmoothing()) { - morphologicalSmoothInPlace(bwContent); - } - // It's important to keep despeckling the very last operation // affecting the binary part of the output. That's because // we will be reconstructing the input to this despeckling @@ -1398,7 +1417,7 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping combineImages(maybeNormalized, segmentedImage, bwMask); } } - + bwContent.release(); // Save memory. if (m_dbg) { m_dbg->add(maybeNormalized, "combined"); } @@ -1419,6 +1438,7 @@ std::unique_ptr OutputGenerator::Processor::processWithoutDewarping bwContentMaskOutput = BinaryImage(m_targetSize, BLACK); rasterOp(bwContentMaskOutput, m_croppedContentRect, bwMask, m_contentRectInWorkingCs.topLeft()); } + // Mixed end assert(!m_targetSize.isEmpty()); QImage dst(m_targetSize, maybeNormalized.format()); @@ -1554,7 +1574,34 @@ std::unique_ptr OutputGenerator::Processor::processWithDewarping(Zo } m_status.throwIfCancelled(); - modifyBinarizationMask(warpedBwMask, m_workingBoundingRect, pictureZones); + // BW mask begin + BinaryImage warpedBwContent; + QImage maybeSmoothed; + if (!m_renderParams.needSavitzkyGolaySmoothing()) { + maybeSmoothed = warpedGrayOutput; + } else { + maybeSmoothed = smoothToGrayscale(warpedGrayOutput, m_dpi); + if (m_dbg) { + m_dbg->add(maybeSmoothed, "smoothed"); + } + m_status.throwIfCancelled(); + } + + warpedBwContent = binarize(maybeSmoothed, m_contentAreaInWorkingCs); + maybeSmoothed = QImage(); // Save memory. + if (m_dbg) { + m_dbg->add(warpedBwContent, "binarized_and_cropped"); + } + m_status.throwIfCancelled(); + + if (m_renderParams.needMorphologicalSmoothing()) { + morphologicalSmoothInPlace(warpedBwContent); + m_status.throwIfCancelled(); + } + // BW mask end + + modifyBinarizationMask(warpedBwMask, warpedBwContent, m_workingBoundingRect, pictureZones); + warpedBwContent.release(); // Save memory. if (m_dbg) { m_dbg->add(warpedBwMask, "warpedBwMask with zones"); } @@ -1898,36 +1945,61 @@ BinaryImage OutputGenerator::Processor::estimateBinarizationMask(const GrayImage } void OutputGenerator::Processor::modifyBinarizationMask(BinaryImage& bwMask, + BinaryImage& bwContent, const QRect& maskRect, const ZoneSet& zones) const { QTransform xform = m_xform.transform(); xform *= QTransform().translate(-maskRect.x(), -maskRect.y()); + BinaryImage bwContentBG(bwContent); 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: ZONEFG + BinaryImageXOR(bwMask, bwContent, WHITE); + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEFG) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); + PolygonRasterizer::fill(bwContentBG, WHITE, xform.map(poly), Qt::WindingFill); + } + } + BinaryImageXOR(bwMask, bwContent, WHITE); + + // Pass 3: ZONEBG + BinaryImageXOR(bwMask, bwContentBG, BLACK); for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::PAINTER2) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEBG) { const QPolygonF poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); } } + BinaryImageXOR(bwMask, bwContentBG, BLACK); - // Pass 1: ERASER3 + // Pass 4: ZONEPAINTER2 for (const Zone& zone : zones) { - if (zone.properties().locateOrDefault()->layer() == PLP::ERASER3) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEPAINTER2) { + const QPolygonF poly(zone.spline().toPolygon()); + PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill); + } + } + + // Pass 5: ZONEERASER3 + for (const Zone& zone : zones) { + if (zone.properties().locateOrDefault()->layer() == PLP::ZONEERASER3) { const QPolygonF poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bwMask, BLACK, xform.map(poly), Qt::WindingFill); } } + + BinaryImageXOR(bwContent, bwMask, WHITE); } @@ -2219,43 +2291,51 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const { break; } case SAUVOLA: { - double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); binarized = binarizeSauvola(image, windowsSize, thresholdCoef, thresholdDelta); break; } case WOLF: { - double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound(); - auto upperBound = (unsigned char) blackWhiteOptions.getWolfUpperBound(); - double thresholdCoef = blackWhiteOptions.getWolfCoef(); + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound(); + const auto upperBound = (unsigned char) blackWhiteOptions.getWolfUpperBound(); + const double thresholdCoef = blackWhiteOptions.getWolfCoef(); binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, thresholdCoef, thresholdDelta); break; } + case BRADLEY: { + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); + + binarized = binarizeBradley(image, windowsSize, thresholdCoef, thresholdDelta); + break; + } case EDGEPLUS: { - double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); binarized = binarizeEdgeDiv(image, windowsSize, thresholdCoef, 0.0, thresholdDelta); break; } case BLURDIV: { - double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); binarized = binarizeEdgeDiv(image, windowsSize, 0.0, thresholdCoef, thresholdDelta); break; } case EDGEDIV: { - double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); - QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); - double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); + const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); + const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); + const double thresholdCoef = blackWhiteOptions.getSauvolaCoef(); binarized = binarizeEdgeDiv(image, windowsSize, thresholdCoef, thresholdCoef, thresholdDelta); break; 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..19b9eb303 100644 --- a/src/core/filters/output/PictureZoneEditor.cpp +++ b/src/core/filters/output/PictureZoneEditor.cpp @@ -213,29 +213,47 @@ void PictureZoneEditor::paintOverPictureMask(QPainter& painter) { using PLP = PictureLayerProperty; + // Pass 1: ZONEERASER1 painter.setCompositionMode(QPainter::CompositionMode_Clear); - // First pass: ERASER1 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); } } + // Pass 2: ZONEFG painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - // Second pass: PAINTER2 for (const EditableZoneSet::Zone& zone : zones()) { - if (zone.properties()->locateOrDefault()->layer() == PLP::PAINTER2) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEFG) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } + // Pass 3: ZONEBG + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + for (const EditableZoneSet::Zone& zone : zones()) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEBG) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); + } + } + + // Pass 4: ZONEPAINTER2 + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + for (const EditableZoneSet::Zone& zone : zones()) { + if (zone.properties()->locateOrDefault()->layer() == PLP::ZONEPAINTER2) { + painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); + } + } + + // Pass 5: ZONEERASER3 painter.setCompositionMode(QPainter::CompositionMode_Clear); - // Third pass: ERASER3 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); } } 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; } diff --git a/src/imageproc/Binarize.cpp b/src/imageproc/Binarize.cpp index e816490d7..1ecb5d9bf 100644 --- a/src/imageproc/Binarize.cpp +++ b/src/imageproc/Binarize.cpp @@ -201,6 +201,73 @@ BinaryImage binarizeWolf(const QImage& src, return bwImg; } // binarizeWolf +BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const double k, const double delta) { + if (windowSize.isEmpty()) { + throw std::invalid_argument("binarizeBradley: invalid windowSize"); + } + + if (src.isNull()) { + return BinaryImage(); + } + + QImage gray(toGrayscale(src)); + const int w = gray.width(); + const int h = gray.height(); + + IntegralImage integralImage(w, h); + + uint8_t* grayLine = gray.bits(); + const int grayBpl = gray.bytesPerLine(); + + for (int y = 0; y < h; ++y) { + integralImage.beginRow(); + for (int x = 0; x < w; ++x) { + const uint32_t pixel = grayLine[x]; + integralImage.push(pixel); + } + grayLine += grayBpl; + } + + const int windowLowerHalf = windowSize.height() >> 1; + const int windowUpperHalf = windowSize.height() - windowLowerHalf; + const int windowLeftHalf = windowSize.width() >> 1; + const int windowRightHalf = windowSize.width() - windowLeftHalf; + + BinaryImage bwImg(w, h); + uint32_t* bwLine = bwImg.data(); + const int bwWpl = bwImg.wordsPerLine(); + + grayLine = gray.bits(); + for (int y = 0; y < h; ++y) { + const int top = std::max(0, y - windowLowerHalf); + const int bottom = std::min(h, y + windowUpperHalf); // exclusive + for (int x = 0; x < w; ++x) { + const int left = std::max(0, x - windowLeftHalf); + const int right = std::min(w, x + windowRightHalf); // exclusive + const int area = (bottom - top) * (right - left); + assert(area > 0); // because windowSize > 0 and w > 0 and h > 0 + const QRect rect(left, top, right - left, bottom - top); + const double windowSum = integralImage.sum(rect); + + const double rArea = 1.0 / area; + const double mean = windowSum * rArea; + const double threshold = (k < 1.0) ? (mean * (1.0 - k)) : 0; + const uint32_t msb = uint32_t(1) << 31; + const uint32_t mask = msb >> (x & 31); + if (int(grayLine[x]) < (threshold + delta)) { + // black + bwLine[x >> 5] |= mask; + } else { + // white + bwLine[x >> 5] &= ~mask; + } + } + grayLine += grayBpl; + bwLine += bwWpl; + } + return bwImg; +} // binarizeBradley + BinaryImage binarizeEdgeDiv(const QImage& src, const QSize windowSize, const double kep, diff --git a/src/imageproc/Binarize.h b/src/imageproc/Binarize.h index aabe9d204..0adc332cf 100644 --- a/src/imageproc/Binarize.h +++ b/src/imageproc/Binarize.h @@ -61,6 +61,14 @@ BinaryImage binarizeWolf(const QImage& src, double k = 0.3, double delta = 0.0); +/** + * \brief Image binarization using Bradley's adaptive thresholding method. + * + * Derek Bradley, Gerhard Roth. 2005. "Adaptive Thresholding Using the Integral Image". + * http://www.scs.carleton.ca/~roth/iit-publications-iti/docs/gerh-50002.pdf + */ +BinaryImage binarizeBradley(const QImage& src, QSize windowSize, double k = 0.34, double delta = 0.0); + /** * \brief Image binarization using EdgeDiv (EdgePlus & BlurDiv) local/global thresholding method. *