Skip to content
This repository has been archived by the owner on Nov 29, 2020. It is now read-only.

Commit

Permalink
Auto-detected dewarping grid is now editable.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tulon committed Jan 30, 2011
1 parent a7ce369 commit b0b0c17
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 109 deletions.
161 changes: 70 additions & 91 deletions filters/output/DewarpingView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include "ToLineProjector.h"
#include "XSpline.h"
#include "CylindricalSurfaceDewarper.h"
#include "spfit/SplineFitter.h"
#include "spfit/PolylineModelShape.h"
#include "imageproc/Constants.h"
#include <QCursor>
#include <QLineF>
Expand All @@ -53,7 +55,6 @@ DewarpingView::DewarpingView(
QImage const& image, ImagePixmapUnion const& downscaled_image,
QTransform const& image_to_virt, QPolygonF const& virt_display_area,
QRectF const& virt_content_rect, PageId const& page_id,
IntrusivePtr<Settings> const& settings,
DewarpingMode dewarping_mode,
DistortionModel const& distortion_model,
DepthPerception const& depth_perception)
Expand All @@ -67,64 +68,67 @@ DewarpingView::DewarpingView(
m_dewarpingMode(dewarping_mode),
m_distortionModel(distortion_model),
m_depthPerception(depth_perception),
m_ptrSettings(settings),
m_dragHandler(*this),
m_zoomHandler(*this)
{
setMouseTracking(true);

if (dewarping_mode == DewarpingMode::AUTO) {
interactionState().setDefaultStatusTip(
tr("Modifying auto-generated grid is not yet implemented. Switch to Manual mode if necessary.")
);
} else {
QPolygonF const source_content_rect(virtualToImage().map(virt_content_rect));

XSpline top_spline(m_distortionModel.topCurve().xspline());
XSpline bottom_spline(m_distortionModel.bottomCurve().xspline());
if (top_spline.numControlPoints() < 2) {
QLineF const top_line(source_content_rect[0], source_content_rect[1]);
XSpline new_top_spline;
using namespace spfit;

new_top_spline.appendControlPoint(top_line.p1(), 0);
new_top_spline.appendControlPoint(top_line.pointAt(1.0/3.0), 1);
new_top_spline.appendControlPoint(top_line.pointAt(2.0/3.0), 1);
new_top_spline.appendControlPoint(top_line.p2(), 0);
setMouseTracking(true);

QPolygonF const source_content_rect(virtualToImage().map(virt_content_rect));

top_spline.swap(new_top_spline);
XSpline top_spline(m_distortionModel.topCurve().xspline());
XSpline bottom_spline(m_distortionModel.bottomCurve().xspline());
if (top_spline.numControlPoints() < 2) {
std::vector<QPointF> const& polyline = m_distortionModel.topCurve().polyline();

XSpline new_top_spline;
if (polyline.size() < 2) {
initNewSpline(new_top_spline, source_content_rect[0], source_content_rect[1]);
} else {
initNewSpline(new_top_spline, polyline.front(), polyline.back());
PolylineModelShape model_shape(polyline);
SplineFitter fitter(&new_top_spline, &model_shape);
fitter.fit();
}
if (bottom_spline.numControlPoints() < 2) {
QLineF const bottom_line(source_content_rect[3], source_content_rect[2]);
XSpline new_bottom_spline;

new_bottom_spline.appendControlPoint(bottom_line.p1(), 0);
new_bottom_spline.appendControlPoint(bottom_line.pointAt(1.0/3.0), 1);
new_bottom_spline.appendControlPoint(bottom_line.pointAt(2.0/3.0), 1);
new_bottom_spline.appendControlPoint(bottom_line.p2(), 0);

bottom_spline.swap(new_bottom_spline);
}

m_topSpline.setSpline(top_spline);
m_bottomSpline.setSpline(bottom_spline);

InteractiveXSpline* splines[2] = { &m_topSpline, &m_bottomSpline };
int curve_idx = -1;
BOOST_FOREACH(InteractiveXSpline* spline, splines) {
++curve_idx;
spline->setModifiedCallback(boost::bind(&DewarpingView::curveModified, this, curve_idx));
spline->setDragFinishedCallback(boost::bind(&DewarpingView::dragFinished, this));
spline->setStorageTransform(
boost::bind(&DewarpingView::sourceToWidget, this, _1),
boost::bind(&DewarpingView::widgetToSource, this, _1)
);
makeLastFollower(*spline);
top_spline.swap(new_top_spline);
}
if (bottom_spline.numControlPoints() < 2) {
std::vector<QPointF> const& polyline = m_distortionModel.bottomCurve().polyline();

XSpline new_bottom_spline;
if (polyline.size() < 2) {
initNewSpline(new_bottom_spline, source_content_rect[3], source_content_rect[2]);
} else {
initNewSpline(new_bottom_spline, polyline.front(), polyline.back());
PolylineModelShape model_shape(polyline);
SplineFitter fitter(&new_bottom_spline, &model_shape);
fitter.fit();
}

m_distortionModel.setTopCurve(Curve(m_topSpline.spline()));
m_distortionModel.setBottomCurve(Curve(m_bottomSpline.spline()));
bottom_spline.swap(new_bottom_spline);
}

m_topSpline.setSpline(top_spline);
m_bottomSpline.setSpline(bottom_spline);

InteractiveXSpline* splines[2] = { &m_topSpline, &m_bottomSpline };
int curve_idx = -1;
BOOST_FOREACH(InteractiveXSpline* spline, splines) {
++curve_idx;
spline->setModifiedCallback(boost::bind(&DewarpingView::curveModified, this, curve_idx));
spline->setDragFinishedCallback(boost::bind(&DewarpingView::dragFinished, this));
spline->setStorageTransform(
boost::bind(&DewarpingView::sourceToWidget, this, _1),
boost::bind(&DewarpingView::widgetToSource, this, _1)
);
makeLastFollower(*spline);
}

m_distortionModel.setTopCurve(Curve(m_topSpline.spline()));
m_distortionModel.setBottomCurve(Curve(m_bottomSpline.spline()));

rootInteractionHandler().makeLastFollower(*this);
rootInteractionHandler().makeLastFollower(m_dragHandler);
rootInteractionHandler().makeLastFollower(m_zoomHandler);
Expand All @@ -134,11 +138,21 @@ DewarpingView::~DewarpingView()
{
}

void
DewarpingView::initNewSpline(XSpline& spline, QPointF const& p1, QPointF const& p2)
{
QLineF const line(p1, p2);
spline.appendControlPoint(line.p1(), 0);
spline.appendControlPoint(line.pointAt(1.0/4.0), 1);
spline.appendControlPoint(line.pointAt(2.0/4.0), 1);
spline.appendControlPoint(line.pointAt(3.0/4.0), 1);
spline.appendControlPoint(line.p2(), 0);
}

void
DewarpingView::depthPerceptionChanged(double val)
{
m_depthPerception.setValue(val);
m_ptrSettings->setDepthPerception(m_pageId, m_depthPerception);
update();
}

Expand All @@ -156,11 +170,7 @@ DewarpingView::onPaint(QPainter& painter, InteractionState const& interaction)
painter.setBrush(Qt::NoBrush);

QPen grid_pen;
if (m_dewarpingMode == DewarpingMode::AUTO) { // Which currently means uneditable.
grid_pen.setColor(QColor(0x406dff));
} else {
grid_pen.setColor(Qt::blue);
}
grid_pen.setColor(Qt::blue);
grid_pen.setCosmetic(true);
grid_pen.setWidthF(1.2);

Expand Down Expand Up @@ -213,42 +223,8 @@ DewarpingView::onPaint(QPainter& painter, InteractionState const& interaction)
painter.drawPolyline(QVector<QPointF>::fromStdVector(bottom_curve.polyline()));
}

if (m_dewarpingMode != DewarpingMode::AUTO) {
paintXSpline(painter, interaction, m_topSpline);
paintXSpline(painter, interaction, m_bottomSpline);
}
#if 0
painter.setWorldTransform(QTransform());

std::vector<QPointF> data_points;
for (int i = 0; i < 36; ++i) {
data_points.push_back(QPointF(200 + 100*cos(i*10*constants::DEG2RAD), 200 + 100*sin(i*10*constants::DEG2RAD)));
}
painter.drawPolyline(QVector<QPointF>::fromStdVector(data_points));

XSpline xspline;
xspline.appendControlPoint(data_points.front(), 0);
for (int i = 0; i < 5; ++i) {
xspline.appendControlPoint(QPointF(0, 0), 1);
}
xspline.appendControlPoint(data_points.back(), 0);

boost::dynamic_bitset<> fixed_points(xspline.numControlPoints());
fixed_points.set(0);
fixed_points.set(fixed_points.size() - 1);
xspline.fit(data_points, &fixed_points);

painter.drawPolyline(QVector<QPointF>::fromStdVector(xspline.toPolyline()));

QPen point_pen(Qt::red);
point_pen.setWidthF(4.0);
point_pen.setCosmetic(true);
painter.setPen(point_pen);

for (int i = 0; i < xspline.numControlPoints(); ++i) {
painter.drawPoint(xspline.controlPointPosition(i));
}
#endif
paintXSpline(painter, interaction, m_topSpline);
paintXSpline(painter, interaction, m_bottomSpline);
}

void
Expand Down Expand Up @@ -312,7 +288,10 @@ DewarpingView::curveModified(int curve_idx)
void
DewarpingView::dragFinished()
{
m_ptrSettings->setDistortionModel(m_pageId, m_distortionModel);
if (m_dewarpingMode == DewarpingMode::AUTO) {
m_dewarpingMode = DewarpingMode::MANUAL;
}
emit distortionModelChanged(m_distortionModel);
}

/** Source image coordinates to widget coordinates. */
Expand Down
6 changes: 4 additions & 2 deletions filters/output/DewarpingView.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,20 @@ class DewarpingView : public ImageViewBase, protected InteractionHandler
QImage const& image, ImagePixmapUnion const& downscaled_image,
QTransform const& source_to_virt, QPolygonF const& virt_display_area,
QRectF const& virt_content_rect, PageId const& page_id,
IntrusivePtr<Settings> const& settings,
DewarpingMode dewarping_mode,
DistortionModel const& distortion_model,
DepthPerception const& depth_perception);

virtual ~DewarpingView();
signals:
void distortionModelChanged(DistortionModel const& model);
public slots:
void depthPerceptionChanged(double val);
protected:
virtual void onPaint(QPainter& painter, InteractionState const& interaction);
private:
static void initNewSpline(imageproc::XSpline& spline, QPointF const& p1, QPointF const& p2);

void paintXSpline(
QPainter& painter, InteractionState const& interaction,
InteractiveXSpline const& ispline);
Expand All @@ -76,7 +79,6 @@ public slots:
DewarpingMode m_dewarpingMode;
DistortionModel m_distortionModel;
DepthPerception m_depthPerception;
IntrusivePtr<Settings> m_ptrSettings;
InteractiveXSpline m_topSpline;
InteractiveXSpline m_bottomSpline;
DragHandler m_dragHandler;
Expand Down
44 changes: 39 additions & 5 deletions filters/output/OptionsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ OptionsWidget::tabChanged(ImageViewTab const tab)
reloadIfNecessary();
}

void
OptionsWidget::distortionModelChanged(DistortionModel const& model)
{
m_ptrSettings->setDistortionModel(m_pageId, model);

// Note that OFF remains OFF while AUTO becomes MANUAL.
if (m_dewarpingMode == DewarpingMode::AUTO) {
m_ptrSettings->setDewarpingMode(m_pageId, DewarpingMode::MANUAL);
m_dewarpingMode = DewarpingMode::MANUAL;
updateDewarpingDisplay();
}
}

void
OptionsWidget::colorModeChanged(int const idx)
{
Expand Down Expand Up @@ -437,11 +450,29 @@ OptionsWidget::dewarpingChanged(std::set<PageId> const& pages, DewarpingMode con
if (pages.find(m_pageId) != pages.end()) {
if (m_dewarpingMode != mode) {
m_dewarpingMode = mode;
m_lastTab = TAB_OUTPUT; // Switch to Output after reloading.
updateDpiDisplay();
updateColorsDisplay();
updateDewarpingDisplay();
emit reloadRequested();

// We reload when we switch to auto dewarping, even if we've just
// switched to manual, as we don't store the auto-generated distortion model.
// We also have to reload if we are currently on the "Fill Zones" tab,
// as it makes use of original <-> dewarped coordinate mapping,
// which is too hard to update without reloading. For consistency,
// we reload not just on TAB_FILL_ZONES but on all tabs except TAB_DEWARPING.
// PS: the static original <-> dewarped mappings are constructed
// in Task::UiUpdater::updateUI(). Look for "new DewarpingPointMapper" there.
if (mode == DewarpingMode::AUTO || m_lastTab != TAB_DEWARPING) {
// Switch to the Output tab after reloading.
m_lastTab = TAB_OUTPUT;

// These depend on the value of m_lastTab.
updateDpiDisplay();
updateColorsDisplay();
updateDewarpingDisplay();

emit reloadRequested();
} else {
// This one we have to call anyway, as it depends on m_dewarpingMode.
updateDewarpingDisplay();
}
}
}
}
Expand Down Expand Up @@ -538,6 +569,9 @@ OptionsWidget::reloadIfNecessary()
} else if (!saved_distortion_model.matches(params.distortionModel())) {
emit reloadRequested();
return;
} else if ((saved_dewarping_mode == DewarpingMode::OFF) != (params.dewarpingMode() == DewarpingMode::OFF)) {
emit reloadRequested();
return;
}
}

Expand Down
3 changes: 3 additions & 0 deletions filters/output/OptionsWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace output

class Settings;
class DewarpingParams;
class DistortionModel;

class OptionsWidget
: public FilterOptionsWidget, private Ui::OutputOptionsWidget
Expand All @@ -61,6 +62,8 @@ class OptionsWidget
void depthPerceptionChanged(double val);
public slots:
void tabChanged(ImageViewTab tab);

void distortionModelChanged(DistortionModel const& model);
private slots:
void changeDpiButtonClicked();

Expand Down
5 changes: 0 additions & 5 deletions filters/output/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,6 @@ Settings::setDewarpingMode(PageId const& page_id, DewarpingMode const& mode)
params.setDewarpingMode(mode);
m_perPageParams.insert(it, PerPageParams::value_type(page_id, params));
} else {
if (mode == DewarpingMode::MANUAL && it->second.dewarpingMode() != DewarpingMode::MANUAL) {
// Because we can't yet generate an X-spline based on a polyline,
// we have to reset the distortion model as well. Otherwise it would be uneditable.
it->second.setDistortionModel(DistortionModel());
}
it->second.setDewarpingMode(mode);
}
}
Expand Down
16 changes: 10 additions & 6 deletions filters/output/Task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ Task::UiUpdater::updateUI(FilterUiInterface* ui)
new DewarpingView(
m_origImage, m_downscaledOrigImage,
m_imageToVirt, m_virtDisplayArea, m_virtContentRect,
m_pageId, m_ptrSettings, m_params.dewarpingMode(),
m_pageId, m_params.dewarpingMode(),
m_params.distortionModel(), opt_widget->depthPerception()
)
);
Expand All @@ -488,6 +488,10 @@ Task::UiUpdater::updateUI(FilterUiInterface* ui)
opt_widget, SIGNAL(depthPerceptionChanged(double)),
dewarping_view.get(), SLOT(depthPerceptionChanged(double))
);
QObject::connect(
dewarping_view.get(), SIGNAL(distortionModelChanged(DistortionModel const&)),
opt_widget, SLOT(distortionModelChanged(DistortionModel const&))
);

std::auto_ptr<QWidget> picture_zone_editor;
if (m_pictureMask.isNull()) {
Expand All @@ -508,11 +512,11 @@ Task::UiUpdater::updateUI(FilterUiInterface* ui)
);
}

// It turns out we never need to update the original<->output
// mapping at run time. If dewarping is turned on or off from
// the left panel, there will be a complete reload. If we change
// the dewarping grid from the Dewarping tab, complete reload
// will happen when we switch to another tab.
// We make sure we never need to update the original <-> output
// mapping at run time, that is without reloading.
// In OptionsWidget::dewarpingChanged() we make sure to reload
// if we are on the "Fill Zones" tab, and if not, it will be reloaded
// anyway when another tab is selected.
boost::function<QPointF(QPointF const&)> orig_to_output;
boost::function<QPointF(QPointF const&)> output_to_orig;
if (m_params.dewarpingMode() != DewarpingMode::OFF && m_params.distortionModel().isValid()) {
Expand Down

0 comments on commit b0b0c17

Please sign in to comment.