Skip to content

Commit

Permalink
A major architecture re-design
Browse files Browse the repository at this point in the history
The re-design aims to achieve two goals:
1. Move dewarping to the Deskew stage.
2. Don't use the concept of DPI at all.

This commit does achieve both goals though it breaks the command-line
mode which is to be fixed later.
  • Loading branch information
Tulon committed Jun 19, 2015
1 parent 6a2214f commit 3631ce3
Show file tree
Hide file tree
Showing 255 changed files with 9,683 additions and 10,320 deletions.
121 changes: 121 additions & 0 deletions AbstractImageTransform.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) 2015 Joseph Artsimovich <joseph.artsimovich@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef ABSTRACT_IMAGE_TRANSFORM_H_
#define ABSTRACT_IMAGE_TRANSFORM_H_

#include <QtGlobal>
#include <memory>
#include <functional>

class AffineImageTransform;
class AffineTransformedImage;
class QColor;
class QImage;
class QSize;
class QRect;
class QPointF;
class QPolygonF;
class QTransform;
class QString;

class AbstractImageTransform
{
// Member-wise copying is OK.
public:
virtual ~AbstractImageTransform() {}

virtual std::unique_ptr<AbstractImageTransform> clone() const = 0;

virtual bool isAffine() const = 0;

/**
* @brief Produces a hash of the transform.
*
* The hash has to be resistent to loss of accuracy of floating point
* values that happens when saving a project to an XML based format.
* Implementations should use RoundingHasher to address this problem.
*/
virtual QString fingerprint() const = 0;

/**
* @brief Dimensions of original image.
*/
virtual QSize const& origSize() const = 0;

/**
* @brief The crop area in original image coordinates.
*/
virtual QPolygonF const& origCropArea() const = 0;

/**
* @brief The crop area in transformed coordinates.
*/
virtual QPolygonF transformedCropArea() const = 0;

/**
* @brief Applies additional scaling to transformed space.
*
* A point (x, y) in transformed space before this call becomes point
* (x * xscale, y * yscale) after this call. For convenience, returns
* a QTransform that maps points in transformed space before scaling
* to corresponding points after scaling.
*/
virtual QTransform scale(qreal xscale, qreal yscale) = 0;

/**
* If the concrete class implementing this interface is AffineImageTransform,
* the image and the transform are wrapped into AffineTransformedImage
* as they are. Otherwise, the transform is applied on the image provided,
* resulting in a new image and a new affine transform. The new transform
* will be a translation-only transform, to ensure that:
* @code
* transform.transformedCropArea() == transform.toAffine().transformedCropArea()
* @endcode
*/
virtual AffineTransformedImage toAffine(
QImage const& image, QColor const& outside_color) const = 0;

/**
* This version of toAffine() can be viewed as a dry run for the full version.
*/
virtual AffineImageTransform toAffine() const = 0;

/**
* Similar to a full version of toAffine(), except instead of producing some
* intermediate image plus a follow-up affine transformation, this one
* produces an image that represents the specified area of transformed space
* exactly, without requiring a follow-up transformation.
*/
virtual QImage materialize(QImage const& image,
QRect const& target_rect, QColor const& outside_color) const = 0;

/**
* @brief Returns a function for mapping points from original image coordinates
* to transformed space.
*/
virtual std::function<QPointF(QPointF const&)> forwardMapper() const = 0;

/**
* @brief Returns a function for mapping points from transformed space
* to original image coordinates.
*/
virtual std::function<QPointF(QPointF const&)> backwardMapper() const = 0;
};

#endif
169 changes: 169 additions & 0 deletions AffineImageTransform.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) 2015 Joseph Artsimovich <joseph.artsimovich@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "AffineImageTransform.h"
#include "AffineTransformedImage.h"
#include "RoundingHasher.h"
#include "imageproc/Transform.h"
#include <QSizeF>
#include <QRectF>
#include <QLineF>
#include <QPointF>
#include <QString>
#include <QCryptographicHash>
#include <assert.h>

AffineImageTransform::AffineImageTransform(QSize const& orig_size)
: m_origSize(orig_size)
, m_origCropArea(QRectF(QPointF(0, 0), orig_size))
, m_transform()
{
}

AffineImageTransform::~AffineImageTransform()
{
}

QString
AffineImageTransform::fingerprint() const
{
RoundingHasher hash(QCryptographicHash::Sha1);
hash << "AffineImageTransform";
hash << m_origSize << m_origCropArea;
hash << m_transform.m11() << m_transform.m21() << m_transform.dx();
hash << m_transform.m12() << m_transform.m22() << m_transform.dy();
return QString::fromUtf8(hash.result().toHex());
}

std::unique_ptr<AbstractImageTransform>
AffineImageTransform::clone() const
{
return std::unique_ptr<AbstractImageTransform>(new AffineImageTransform(*this));
}

void
AffineImageTransform::setTransform(QTransform const& transform)
{
m_transform = transform;
}

QPolygonF
AffineImageTransform::transformedCropArea() const
{
return m_transform.map(m_origCropArea);
}

void
AffineImageTransform::adjustForScaledOrigImage(QSize const& orig_size)
{
double const old2new_xscale = double(orig_size.width()) / m_origSize.width();
double const old2new_yscale = double(orig_size.height()) / m_origSize.height();

m_transform.scale(1.0 / old2new_xscale, 1.0 / old2new_yscale);
for (QPointF& pt : m_origCropArea) {
pt.rx() *= old2new_xscale;
pt.ry() *= old2new_yscale;
}
m_origSize = orig_size;
}

void
AffineImageTransform::translateSoThatPointBecomes(
QPointF const& transformed_pt, QPointF const& target_pos)
{
QPointF const delta(target_pos - transformed_pt);
QTransform translation;
translation.translate(delta.x(), delta.y());
m_transform *= translation;
}

QTransform
AffineImageTransform::scale(qreal xscale, qreal yscale)
{
QTransform scaling_transform;
scaling_transform.scale(xscale, yscale);

m_transform *= scaling_transform;

return scaling_transform;
}

void
AffineImageTransform::scaleTo(QSizeF const& size, Qt::AspectRatioMode mode)
{
assert(!size.isEmpty());

QSizeF const transformed_size(transformedCropArea().boundingRect().size());
QSizeF const desired_size(transformed_size.scaled(size, mode));

double const xscale = desired_size.width() / transformed_size.width();
double const yscale = desired_size.height() / transformed_size.height();

m_transform *= QTransform().scale(xscale, yscale);
}

void
AffineImageTransform::rotate(qreal degrees)
{
m_transform *= QTransform().rotate(degrees);
}

AffineTransformedImage
AffineImageTransform::toAffine(QImage const& image, QColor const&) const
{
assert(!image.isNull());
assert(image.size() == m_origSize);
return AffineTransformedImage(image, *this);
}

AffineImageTransform
AffineImageTransform::toAffine() const
{
return *this;
}

QImage
AffineImageTransform::materialize(QImage const& image,
QRect const& target_rect, QColor const& outside_color) const
{
assert(!image.isNull());
assert(!target_rect.isEmpty());

return imageproc::transform(
image, m_transform, target_rect,
imageproc::OutsidePixels::assumeColor(outside_color)
);
}

std::function<QPointF(QPointF const&)>
AffineImageTransform::forwardMapper() const
{
QTransform transform(m_transform);
return [=](QPointF const& pt) {
return transform.map(pt);
};
}

std::function<QPointF(QPointF const&)>
AffineImageTransform::backwardMapper() const
{
QTransform inverted(m_transform.inverted());
return [=](QPointF const& pt) {
return inverted.map(pt);
};
}
Loading

0 comments on commit 3631ce3

Please sign in to comment.