Skip to content

Commit

Permalink
1.0.19: feature ScanTailor-Advanced#50: foreground and background zones
Browse files Browse the repository at this point in the history
  • Loading branch information
zvezdochiot committed Jul 20, 2023
1 parent be3b2f1 commit 7e8dcd6
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 6 deletions.
78 changes: 74 additions & 4 deletions src/core/filters/output/OutputGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1320,6 +1359,7 @@ std::unique_ptr<OutputImage> 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");
}
Expand All @@ -1334,7 +1374,11 @@ std::unique_ptr<OutputImage> 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");
Expand Down Expand Up @@ -1540,6 +1584,7 @@ std::unique_ptr<OutputImage> 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");
}
Expand All @@ -1554,7 +1599,11 @@ std::unique_ptr<OutputImage> 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");
}
Expand Down Expand Up @@ -1898,6 +1947,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();
Expand All @@ -1921,7 +1971,27 @@ void OutputGenerator::Processor::modifyBinarizationMask(BinaryImage& bwMask,
}
}

// Pass 1: ZONEERASER3
// Pass 3: ZONEBG
for (const Zone& zone : zones) {
if (zone.properties().locateOrDefault<PLP>()->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<PLP>()->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<PLP>()->layer() == PLP::ZONEERASER3) {
const QPolygonF poly(zone.spline().toPolygon());
Expand Down
10 changes: 10 additions & 0 deletions src/core/filters/output/PictureLayerProperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/core/filters/output/PictureLayerProperty.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
18 changes: 18 additions & 0 deletions src/core/filters/output/PictureZoneEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PLP>()->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<PLP>()->layer() == PLP::ZONEBG) {
painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill);
}
}
} // PictureZoneEditor::paintOverPictureMask

void PictureZoneEditor::showPropertiesDialog(const EditableZoneSet::Zone& zone) {
Expand Down
14 changes: 13 additions & 1 deletion src/core/filters/output/PictureZonePropDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@ PictureZonePropDialog::PictureZonePropDialog(std::shared_ptr<PropertySet> 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) {
Expand All @@ -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<PictureLayerProperty>()->setLayer(layer);

emit updated();
}
} // namespace output
} // namespace output
14 changes: 14 additions & 0 deletions src/core/filters/output/PictureZonePropDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="zoneforeground">
<property name="text">
<string>Add to foreground</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="zonebackground">
<property name="text">
<string>Add to background</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand Down

0 comments on commit 7e8dcd6

Please sign in to comment.