Skip to content

Commit

Permalink
1.0.19: feature #50: foreground and background zones
Browse files Browse the repository at this point in the history
  • Loading branch information
zvezdochiot committed Jul 25, 2023
1 parent 6df8b7d commit b65ee43
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 118 deletions.
228 changes: 149 additions & 79 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,
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 @@ -1219,9 +1258,12 @@ std::unique_ptr<OutputImage> 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());
// BW mask begin
maybeNormalized = transformToWorkingCs(m_renderParams.normalizeIllumination());
if (m_dbg) {
m_dbg->add(maybeNormalized, "maybeNormalized");
}
Expand All @@ -1233,34 +1275,34 @@ std::unique_ptr<OutputImage> 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.needSavitzkyGolaySmoothing()) {
maybeSmoothed = maybeNormalized;
} else {
maybeSmoothed = smoothToGrayscale(maybeNormalized, m_dpi);
if (m_dbg) {
m_dbg->add(maybeSmoothed, "smoothed");
}
m_status.throwIfCancelled();
if (!m_renderParams.needSavitzkyGolaySmoothing()) {
maybeSmoothed = maybeNormalized;
} else {
maybeSmoothed = smoothToGrayscale(maybeNormalized, m_dpi);
if (m_dbg) {
m_dbg->add(maybeSmoothed, "smoothed");
}
m_status.throwIfCancelled();
}

// don't destroy as it's needed for color segmentation
if (!m_renderParams.needColorSegmentation()) {
maybeNormalized = QImage();
}
bwContent = binarize(maybeSmoothed, m_contentAreaInWorkingCs);
maybeSmoothed = QImage(); // Save memory.
if (m_dbg) {
m_dbg->add(bwContent, "binarized_and_cropped");
}
m_status.throwIfCancelled();

BinaryImage bwContent = binarize(maybeSmoothed, m_contentAreaInWorkingCs);
maybeSmoothed = QImage(); // Save memory.
if (m_dbg) {
m_dbg->add(bwContent, "binarized_and_cropped");
}
if (m_renderParams.needMorphologicalSmoothing()) {
morphologicalSmoothInPlace(bwContent);
m_status.throwIfCancelled();
}
// BW mask end

if (m_renderParams.needMorphologicalSmoothing()) {
morphologicalSmoothInPlace(bwContent);
m_status.throwIfCancelled();
// (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);
Expand Down Expand Up @@ -1314,9 +1356,9 @@ std::unique_ptr<OutputImage> 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));
Expand All @@ -1334,39 +1376,14 @@ std::unique_ptr<OutputImage> 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");
}
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
Expand Down Expand Up @@ -1398,7 +1415,7 @@ std::unique_ptr<OutputImage> OutputGenerator::Processor::processWithoutDewarping
combineImages(maybeNormalized, segmentedImage, bwMask);
}
}

bwContent.release(); // Save memory.
if (m_dbg) {
m_dbg->add(maybeNormalized, "combined");
}
Expand All @@ -1419,6 +1436,7 @@ std::unique_ptr<OutputImage> OutputGenerator::Processor::processWithoutDewarping
bwContentMaskOutput = BinaryImage(m_targetSize, BLACK);
rasterOp<RopSrc>(bwContentMaskOutput, m_croppedContentRect, bwMask, m_contentRectInWorkingCs.topLeft());
}
// Mixed end

assert(!m_targetSize.isEmpty());
QImage dst(m_targetSize, maybeNormalized.format());
Expand Down Expand Up @@ -1554,7 +1572,34 @@ std::unique_ptr<OutputImage> 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");
}
Expand Down Expand Up @@ -1898,36 +1943,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<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: ZONEFG
BinaryImageXOR(bwMask, bwContent, WHITE);
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());
PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill);
PolygonRasterizer::fill(bwContentBG, WHITE, xform.map(poly), Qt::WindingFill);
}
}
BinaryImageXOR(bwMask, bwContent, WHITE);

// Pass 1: ERASER3
// Pass 3: ZONEBG
BinaryImageXOR(bwMask, bwContentBG, BLACK);
for (const Zone& zone : zones) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ERASER3) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ZONEBG) {
const QPolygonF poly(zone.spline().toPolygon());
PolygonRasterizer::fill(bwMask, WHITE, xform.map(poly), Qt::WindingFill);
}
}
BinaryImageXOR(bwMask, bwContentBG, BLACK);

// Pass 4: 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 5: ZONEERASER3
for (const Zone& zone : zones) {
if (zone.properties().locateOrDefault<PLP>()->layer() == PLP::ZONEERASER3) {
const QPolygonF poly(zone.spline().toPolygon());
PolygonRasterizer::fill(bwMask, BLACK, xform.map(poly), Qt::WindingFill);
}
}

BinaryImageXOR(bwContent, bwMask, WHITE);
}


Expand Down Expand Up @@ -2219,43 +2289,43 @@ 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 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;
Expand Down
Loading

0 comments on commit b65ee43

Please sign in to comment.