Skip to content

Commit 91f5ba1

Browse files
Merge pull request #153 from pablogventura/fix/deskew-oblique-regression-145
CI green; local build and deskew tests pass.
2 parents c2e0222 + c3831d0 commit 91f5ba1

9 files changed

Lines changed: 80 additions & 38 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Go to [this repository](https://github.com/ScanTailor-Advanced/scantailor-libs-b
8787

8888
**Application ID:** the manifest uses `org.scantailor.Advanced` so it does **not** replace the legacy `com.github._4lex4.*` Flatpak. Author docs: [for app authors](https://docs.flathub.org/docs/for-app-authors/).
8989

90-
**Linux – GitHub Releases (.deb / AppImage, [issue #64](https://github.com/ScanTailor-Advanced/scantailor-advanced/issues/64)):** Version tags matching `v*` run [`.github/workflows/release.yml`](.github/workflows/release.yml), which produces a `.deb` ([`build-deb.sh`](build-deb.sh)) and an AppImage attached to the GitHub Release when the workflow is enabled. Report problems with those binaries in [issue #64](https://github.com/ScanTailor-Advanced/scantailor-advanced/issues/64).
90+
**Linux – GitHub Releases (.deb / AppImage, [issue #64](https://github.com/ScanTailor-Advanced/scantailor-advanced/issues/64)):** Version tags matching `v*` run [`.github/workflows/release.yml`](.github/workflows/release.yml), which produces a `.deb` ([`build-deb.sh`](build-deb.sh)) and an AppImage attached to the GitHub Release when the workflow is enabled. The AppImage is built on **Ubuntu 24.04** (`ubuntu-latest`) and requires a compatible **glibc** (typically **Ubuntu 24.04+** or equivalent). On **Ubuntu 22.04** and similar older bases, use the **`.deb`** package or build from source. Report problems with those binaries in [issue #64](https://github.com/ScanTailor-Advanced/scantailor-advanced/issues/64).
9191

9292
**Community examples / test data:** See also [scantailor-testing](https://github.com/ImageProcessing-ElectronicPublications/scantailor-testing) (community repository; issue [#43](https://github.com/ScanTailor-Advanced/scantailor-advanced/issues/43)).
9393

src/core/DefaultParams.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ DefaultParams::DeskewParams::DeskewParams() : m_deskewAngleDeg(0.0), m_mode(MODE
7171
DefaultParams::DeskewParams::DeskewParams(const QDomElement& el)
7272
: m_deskewAngleDeg(el.attribute("deskewAngleDeg").toDouble()),
7373
m_mode((el.attribute("mode") == "manual") ? MODE_MANUAL : MODE_AUTO),
74-
m_autoOblique(el.attribute("autoOblique", "1") != "0") {}
74+
m_autoOblique(el.attribute("autoOblique", "0") != "0") {}
7575

7676
QDomElement DefaultParams::DeskewParams::toXml(QDomDocument& doc, const QString& name) const {
7777
QDomElement el(doc.createElement(name));

src/core/filters/deskew/ImageView.cpp

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ void ImageView::onPaint(QPainter& painter, const InteractionState& interaction)
100100
painter.setWorldMatrixEnabled(false);
101101
painter.setRenderHints(QPainter::Antialiasing, false);
102102

103-
const double w = maxViewportRect().width();
104-
const double h = maxViewportRect().height();
103+
const QRectF contentArea(getContentAreaRect());
105104
const QPointF center(getImageRotationOrigin());
106105

107106
// Draw the semi-transparent grid.
@@ -110,26 +109,26 @@ void ImageView::onPaint(QPainter& painter, const InteractionState& interaction)
110109
pen.setWidth(1);
111110
painter.setPen(pen);
112111
QVector<QLineF> lines;
113-
for (double y = center.y(); (y -= m_cellSize) > 0.0;) {
114-
lines.push_back(QLineF(0.5, y, w - 0.5, y));
112+
for (double y = center.y(); (y -= m_cellSize) > contentArea.top();) {
113+
lines.push_back(QLineF(contentArea.left() + 0.5, y, contentArea.right() - 0.5, y));
115114
}
116-
for (double y = center.y(); (y += m_cellSize) < h;) {
117-
lines.push_back(QLineF(0.5, y, w - 0.5, y));
115+
for (double y = center.y(); (y += m_cellSize) < contentArea.bottom();) {
116+
lines.push_back(QLineF(contentArea.left() + 0.5, y, contentArea.right() - 0.5, y));
118117
}
119-
for (double x = center.x(); (x -= m_cellSize) > 0.0;) {
120-
lines.push_back(QLineF(x, 0.5, x, h - 0.5));
118+
for (double x = center.x(); (x -= m_cellSize) > contentArea.left();) {
119+
lines.push_back(QLineF(x, contentArea.top() + 0.5, x, contentArea.bottom() - 0.5));
121120
}
122-
for (double x = center.x(); (x += m_cellSize) < w;) {
123-
lines.push_back(QLineF(x, 0.5, x, h - 0.5));
121+
for (double x = center.x(); (x += m_cellSize) < contentArea.right();) {
122+
lines.push_back(QLineF(x, contentArea.top() + 0.5, x, contentArea.bottom() - 0.5));
124123
}
125124
painter.drawLines(lines);
126125

127126
// Draw the horizontal and vertical line crossing at the center.
128127
pen.setColor(QColor(0, 0, 0xd1));
129128
painter.setPen(pen);
130129
painter.setBrush(Qt::NoBrush);
131-
painter.drawLine(QPointF(0.5, center.y()), QPointF(w - 0.5, center.y()));
132-
painter.drawLine(QPointF(center.x(), 0.5), QPointF(center.x(), h - 0.5));
130+
painter.drawLine(QPointF(contentArea.left() + 0.5, center.y()), QPointF(contentArea.right() - 0.5, center.y()));
131+
painter.drawLine(QPointF(center.x(), contentArea.top() + 0.5), QPointF(center.x(), contentArea.bottom() - 0.5));
133132
// Draw the rotation arcs.
134133
// Those will look like this ( )
135134
const QRectF arcSquare(getRotationArcSquare());
@@ -247,27 +246,39 @@ void ImageView::dragFinished() {
247246
}
248247

249248
/**
250-
* Get the point at the center of the widget, in widget coordinates.
251-
* The point may be adjusted to to ensure it's at the center of a pixel.
249+
* Get the point at the center of the widget content area, in widget coordinates.
252250
*/
253251
QPointF ImageView::getImageRotationOrigin() const {
254-
const QRectF viewportRect(maxViewportRect());
255-
return QPointF(std::floor(0.5 * viewportRect.width()) + 0.5, std::floor(0.5 * viewportRect.height()) + 0.5);
252+
const QRectF contentArea(getContentAreaRect());
253+
return QPointF(std::floor(0.5 * contentArea.width()) + 0.5 + contentArea.x(),
254+
std::floor(0.5 * contentArea.height()) + 0.5 + contentArea.y());
256255
}
257256

258-
/**
259-
* Get the square in widget coordinates where two rotation arcs will be drawn.
260-
*/
261-
QRectF ImageView::getRotationArcSquare() const {
257+
QRectF ImageView::getContentAreaRect() const {
262258
const double hMargin
263259
= 0.5 * m_handlePixmap.width()
264-
+ verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar());
260+
+ (verticalScrollBar()->isVisible()
261+
? verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar())
262+
: 0.0);
265263
const double vMargin
266264
= 0.5 * m_handlePixmap.height()
267-
+ horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, horizontalScrollBar());
265+
+ (horizontalScrollBar()->isVisible()
266+
? horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, horizontalScrollBar())
267+
: 0.0);
268+
269+
QRectF content(maxViewportRect());
270+
content.adjust(hMargin, vMargin, -hMargin, -vMargin);
271+
if (content.isEmpty()) {
272+
return maxViewportRect();
273+
}
274+
return content;
275+
}
268276

269-
QRectF reducedScreenRect(maxViewportRect());
270-
reducedScreenRect.adjust(hMargin, vMargin, -hMargin, -vMargin);
277+
/**
278+
* Get the square in widget coordinates where two rotation arcs will be drawn.
279+
*/
280+
QRectF ImageView::getRotationArcSquare() const {
281+
const QRectF reducedScreenRect(getContentAreaRect());
271282

272283
QSizeF arcSize(1.0, m_maxRotationSin);
273284
arcSize.scale(reducedScreenRect.size(), Qt::KeepAspectRatio);
@@ -291,15 +302,7 @@ std::pair<QPointF, QPointF> ImageView::getRotationHandles(const QRectF& arcSquar
291302
}
292303

293304
QRectF ImageView::getObliqueArcSquare() const {
294-
const double hMargin
295-
= 0.5 * m_handlePixmap.width()
296-
+ verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar());
297-
const double vMargin
298-
= 0.5 * m_handlePixmap.height()
299-
+ horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, horizontalScrollBar());
300-
301-
QRectF reducedScreenRect(maxViewportRect());
302-
reducedScreenRect.adjust(hMargin, vMargin, -hMargin, -vMargin);
305+
const QRectF reducedScreenRect(getContentAreaRect());
303306

304307
const double obliqueSin = std::sin(m_maxObliqueDeg * constants::DEG2RAD);
305308
QSizeF arcSize(obliqueSin, 1.0);

src/core/filters/deskew/ImageView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class ImageView : public ImageViewBase, private InteractionHandler {
6161

6262
QPointF getImageRotationOrigin() const;
6363

64+
/** Viewport area inset for scroll bars and handle pixmap margins. */
65+
QRectF getContentAreaRect() const;
66+
6467
QRectF getRotationArcSquare() const;
6568

6669
std::pair<QPointF, QPointF> getRotationHandles(const QRectF& arcSquare) const;

src/core/filters/deskew/OptionsWidget.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
#include <utility>
77

8+
#include <core/DefaultParams.h>
9+
#include <core/DefaultParamsProvider.h>
10+
811
#include "ApplyDialog.h"
912
#include "Params.h"
1013
#include "Settings.h"
@@ -27,6 +30,16 @@ Params mergeParamsForApply(const std::unique_ptr<Params>& existing,
2730
applyOblique ? cur.obliqueMode() : existing->obliqueMode());
2831
}
2932

33+
void setDefaultAutoOblique(const bool enabled) {
34+
DefaultParamsProvider& provider = DefaultParamsProvider::getInstance();
35+
const DefaultParams& current = provider.getParams();
36+
auto updated = std::make_unique<DefaultParams>(current);
37+
DefaultParams::DeskewParams deskewParams(current.getDeskewParams());
38+
deskewParams.setAutoOblique(enabled);
39+
updated->setDeskewParams(deskewParams);
40+
provider.setParams(std::move(updated), provider.getProfileName());
41+
}
42+
3043
} // namespace
3144

3245
const double OptionsWidget::MAX_ANGLE = 45.0;
@@ -41,6 +54,9 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
4154
angleSpinBox->adjustSize();
4255
setSpinBoxUnknownState();
4356
topEdgeCheckBox->setChecked(!m_settings->algoContentBased());
57+
autoObliqueCheckBox->setChecked(DefaultParamsProvider::getInstance().getParams().getDeskewParams().isAutoOblique());
58+
obliqueManualBtn->setChecked(true);
59+
obliqueAutoBtn->setChecked(false);
4460

4561
setupUiConnections();
4662
}
@@ -135,6 +151,7 @@ void OptionsWidget::postUpdateUI(const UiData& uiData) {
135151
updateObliqueModeIndication(uiData.obliqueMode());
136152
setSpinBoxKnownState(degreesToSpinBox(uiData.effectiveDeskewAngle()));
137153
obliqueSpinBox->setValue(m_uiData.effectiveObliqueAngle());
154+
autoObliqueCheckBox->setChecked(DefaultParamsProvider::getInstance().getParams().getDeskewParams().isAutoOblique());
138155
}
139156

140157
void OptionsWidget::spinBoxValueChanged(const double value) {
@@ -156,6 +173,7 @@ void OptionsWidget::modeChanged(const bool autoMode) {
156173
if (m_uiData.obliqueMode() == MODE_AUTO) {
157174
m_uiData.setEffectiveObliqueAngle(0.0);
158175
}
176+
m_settings->setPendingAutoOblique(m_pageId, autoObliqueCheckBox->isChecked());
159177
m_settings->clearPageParams(m_pageId);
160178
emit reloadRequested();
161179
} else {
@@ -252,6 +270,15 @@ void OptionsWidget::topEdgeToggled(bool checked) {
252270
}
253271
}
254272

273+
void OptionsWidget::autoObliqueCheckBoxToggled(const bool checked) {
274+
setDefaultAutoOblique(checked);
275+
m_settings->setPendingAutoOblique(m_pageId, checked);
276+
if (autoBtn->isChecked()) {
277+
m_settings->clearPageParams(m_pageId);
278+
emit reloadRequested();
279+
}
280+
}
281+
255282
void OptionsWidget::obliqueSpinBoxValueChanged(double value) {
256283
auto block = m_connectionManager.getScopedBlock();
257284

@@ -271,6 +298,7 @@ void OptionsWidget::setupUiConnections() {
271298
CONNECT(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool)));
272299
CONNECT(obliqueAutoBtn, SIGNAL(toggled(bool)), this, SLOT(obliqueModeChanged(bool)));
273300
CONNECT(topEdgeCheckBox, SIGNAL(toggled(bool)), this, SLOT(topEdgeToggled(bool)));
301+
CONNECT(autoObliqueCheckBox, SIGNAL(toggled(bool)), this, SLOT(autoObliqueCheckBoxToggled(bool)));
274302
CONNECT(applyDeskewBtn, SIGNAL(clicked()), this, SLOT(showDeskewDialog()));
275303
}
276304

@@ -279,7 +307,7 @@ void OptionsWidget::setupUiConnections() {
279307
/*========================== OptionsWidget::UiData =========================*/
280308

281309
OptionsWidget::UiData::UiData()
282-
: m_effDeskewAngle(0.0), m_effObliqueAngle(0.0), m_mode(MODE_AUTO), m_obliqueMode(MODE_AUTO) {}
310+
: m_effDeskewAngle(0.0), m_effObliqueAngle(0.0), m_mode(MODE_AUTO), m_obliqueMode(MODE_MANUAL) {}
283311

284312
OptionsWidget::UiData::~UiData() = default;
285313
} // namespace deskew

src/core/filters/deskew/OptionsWidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class OptionsWidget : public FilterOptionsWidget, private Ui::OptionsWidget {
9292

9393
void topEdgeToggled(bool checked);
9494

95+
void autoObliqueCheckBoxToggled(bool checked);
96+
9597
void showDeskewDialog();
9698

9799
void appliedTo(const std::set<PageId>& pages, bool applyDeskew, bool applyOblique);

src/core/filters/deskew/OptionsWidget.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
<bool>true</bool>
168168
</property>
169169
<property name="checked">
170-
<bool>true</bool>
170+
<bool>false</bool>
171171
</property>
172172
<property name="autoExclusive">
173173
<bool>true</bool>

src/core/filters/deskew/Task.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ FilterResultPtr Task::process(const TaskStatus& status, FilterData data) {
120120

121121
status.throwIfCancelled();
122122

123-
bool autoObliqueEnabled = true;
123+
bool autoObliqueEnabled = false;
124124
if (priorParamsBeforeRecompute) {
125125
autoObliqueEnabled = priorParamsBeforeRecompute->autoOblique();
126126
} else if (const auto pending = m_settings->takePendingAutoOblique(m_pageId)) {

src/core/tests/TestDeskewParams.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (C) 2019 Joseph Artsimovich <joseph.artsimovich@gmail.com>, 4lex4 <4lex49@zoho.com>
22
// Use of this source code is governed by the GNU GPLv3 license that can be found in the LICENSE file.
33

4+
#include <DefaultParams.h>
45
#include <Dpi.h>
56
#include <ImageTransformation.h>
67
#include <filters/deskew/Dependencies.h>
@@ -141,6 +142,11 @@ BOOST_AUTO_TEST_CASE(params_missing_autoOblique_attribute_defaults_true) {
141142
BOOST_CHECK(restored.autoOblique());
142143
}
143144

145+
BOOST_AUTO_TEST_CASE(default_params_deskew_auto_oblique_off_by_default) {
146+
const DefaultParams::DeskewParams deskew;
147+
BOOST_CHECK(!deskew.isAutoOblique());
148+
}
149+
144150
BOOST_AUTO_TEST_SUITE_END()
145151

146152
BOOST_AUTO_TEST_SUITE(ImageTransformationObliqueTestSuite)

0 commit comments

Comments
 (0)