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 4456679 commit 28a6df1
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 57 deletions.
92 changes: 82 additions & 10 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 @@ -609,7 +648,7 @@ void removeAutoPictureZones(ZoneSet& pictureZones) {

Zone createPictureZoneFromPoly(const QPolygonF& polygon) {
PropertySet propertySet;
propertySet.locateOrCreate<output::PictureLayerProperty>()->setLayer(output::PictureLayerProperty::PAINTER2);
propertySet.locateOrCreate<output::PictureLayerProperty>()->setLayer(output::PictureLayerProperty::ZONEPAINTER2);
propertySet.locateOrCreate<output::ZoneCategoryProperty>()->setZoneCategory(ZoneCategoryProperty::AUTO);
return Zone(SerializableSpline(polygon), propertySet);
}
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,12 @@ 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);
bwMaskOut.release(); // Save memory.
fillMarginsInPlace(bwMask, m_contentAreaInWorkingCs, BLACK);
if (m_dbg) {
m_dbg->add(bwMask, "bwMask with zones");
Expand Down Expand Up @@ -1540,6 +1585,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 +1600,12 @@ 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);
bwMaskOut.release(); // Save memory.
if (m_dbg) {
m_dbg->add(warpedBwMask, "warpedBwMask with zones");
}
Expand Down Expand Up @@ -1898,32 +1949,53 @@ 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();
xform *= QTransform().translate(-maskRect.x(), -maskRect.y());

using PLP = PictureLayerProperty;

// Pass 1: ERASER1
// Pass 1: ZONEERASER1
for (const Zone& zone : zones) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ERASER1) {
if (zone.properties().locateOrDefault<PLP>()->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<PLP>()->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<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::PAINTER2) {
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 1: ERASER3
// Pass 5: ZONEERASER3
for (const Zone& zone : zones) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ERASER3) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ZONEERASER3) {
const QPolygonF poly(zone.spline().toPolygon());
PolygonRasterizer::fill(bwMask, BLACK, xform.map(poly), Qt::WindingFill);
}
Expand Down
26 changes: 18 additions & 8 deletions src/core/filters/output/PictureLayerProperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,39 @@ std::shared_ptr<Property> 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;
}
}

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;
Expand All @@ -64,4 +74,4 @@ QString PictureLayerProperty::layerToString(Layer layer) {
}

PictureLayerProperty::PictureLayerProperty(PictureLayerProperty::Layer layer) : m_layer(layer) {}
} // namespace output
} // namespace output
4 changes: 2 additions & 2 deletions src/core/filters/output/PictureLayerProperty.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
30 changes: 24 additions & 6 deletions src/core/filters/output/PictureZoneEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PLP>()->layer() == PLP::ERASER1) {
if (zone.properties()->locateOrDefault<PLP>()->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<PLP>()->layer() == PLP::PAINTER2) {
if (zone.properties()->locateOrDefault<PLP>()->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<PLP>()->layer() == PLP::ERASER3) {
if (zone.properties()->locateOrDefault<PLP>()->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<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);
}
}
Expand Down
48 changes: 30 additions & 18 deletions src/core/filters/output/PictureZonePropDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,50 @@ PictureZonePropDialog::PictureZonePropDialog(std::shared_ptr<PropertySet> props,
ui.setupUi(this);

switch (m_props->locateOrDefault<PictureLayerProperty>()->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<PictureLayerProperty>()->setLayer(layer);

emit updated();
}
} // namespace output
} // namespace output
Loading

0 comments on commit 28a6df1

Please sign in to comment.