286 lines
9.0 KiB
C++
286 lines
9.0 KiB
C++
|
/***********************************************************************
|
||
|
* This file is part of Scanned Image Extract.
|
||
|
*
|
||
|
* Scanned Image Extract 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.
|
||
|
*
|
||
|
* Scanned Image Extract 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 Scanned Image Extract. If not, see <http://www.gnu.org/licenses/>
|
||
|
*
|
||
|
*
|
||
|
* Copyright (C) 2015, Dominik Rueß; info@dominik-ruess.de
|
||
|
**********************************************************************/
|
||
|
|
||
|
#include <QtCore/qmath.h>
|
||
|
|
||
|
#include "extracttargets.h"
|
||
|
|
||
|
|
||
|
QMutex ExtractTargets::imageMutex;
|
||
|
QMutex ExtractTargets::fileListMutex;
|
||
|
|
||
|
ExtractTargets::ExtractTargets(QObject *parent)
|
||
|
: QThread(parent)
|
||
|
, _stopped(false)
|
||
|
, _abortCurrentStartNext(false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void ExtractTargets::addTarget(TargetImagePtr target,
|
||
|
SourceFilePtr source,
|
||
|
const bool highPriority)
|
||
|
{
|
||
|
QMutexLocker l(&fileListMutex);
|
||
|
|
||
|
if (_current.get() != 0
|
||
|
&& _current->workOnId == target->workOnId)
|
||
|
{
|
||
|
// already loading -> do nothing
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (highPriority) {
|
||
|
_abortCurrentStartNext = true;
|
||
|
}
|
||
|
|
||
|
QPair<TargetImagePtr, SourceFilePtr> item(target, source);
|
||
|
if (highPriority) {
|
||
|
_targetList.push_front(item);
|
||
|
}
|
||
|
else {
|
||
|
_targetList.push_back(item);
|
||
|
}
|
||
|
|
||
|
// find duplicates and delete (work from back)
|
||
|
for (int i=_targetList.size()-1; i>=0; i--) {
|
||
|
for (int j=i-1; j>=0; j--) {
|
||
|
if (_targetList[i].first->workOnId
|
||
|
== _targetList[j].first->workOnId) {
|
||
|
_targetList.erase(_targetList.begin() + i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_stopped = false;
|
||
|
if (!isRunning()) {
|
||
|
start(QThread::HighPriority);
|
||
|
} else {
|
||
|
_condition.wakeOne();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ExtractTargets::stop()
|
||
|
{
|
||
|
_stopped = true;
|
||
|
}
|
||
|
|
||
|
ExtractTargets::~ExtractTargets()
|
||
|
{
|
||
|
fileListMutex.lock();
|
||
|
_targetList.clear();
|
||
|
_stopped = true;
|
||
|
_condition.wakeAll();
|
||
|
fileListMutex.unlock();
|
||
|
|
||
|
wait();
|
||
|
}
|
||
|
|
||
|
void ExtractTargets::run()
|
||
|
{
|
||
|
while (!_stopped) {
|
||
|
_abortCurrentStartNext = false;
|
||
|
{
|
||
|
QMutexLocker l(&fileListMutex);
|
||
|
if (_targetList.size() == 0) {
|
||
|
_condition.wait(&fileListMutex);
|
||
|
// continue: in the meantime it might have been stopped
|
||
|
// or set empty
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fileListMutex.lock();
|
||
|
_current = _targetList.first().first;
|
||
|
SourceFilePtr currentSource = _targetList.first().second;
|
||
|
fileListMutex.unlock();
|
||
|
|
||
|
imageMutex.lock();
|
||
|
extract(_current, currentSource);
|
||
|
imageMutex.unlock();
|
||
|
|
||
|
if (_stopped || _abortCurrentStartNext) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
fileListMutex.lock();
|
||
|
_targetList.pop_front();
|
||
|
fileListMutex.unlock();
|
||
|
|
||
|
if (!_stopped) {
|
||
|
emit doneTarget(_current);
|
||
|
}
|
||
|
_current = TargetImagePtr();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ExtractTargets::extract(TargetImagePtr target, SourceFilePtr source)
|
||
|
{
|
||
|
QImage out;
|
||
|
if (source->imageOrig.isNull()
|
||
|
|| source->imageOrig.width() == 0
|
||
|
|| source->imageOrig.height() == 0)
|
||
|
{
|
||
|
target->backmap = BackMapPtr();
|
||
|
target->image = QImage();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const QImage& sourceImage = source->imageOrig;
|
||
|
|
||
|
const double cropPerc = target->boundary->getCrop() < 0 ? _cropPercentage : target->boundary->getCrop();
|
||
|
target->boundary->setCrop(cropPerc);
|
||
|
const int width = _norm(target->boundary->corners()[0] * (1-cropPerc)
|
||
|
-target->boundary->corners()[3] * (1-cropPerc));
|
||
|
const int height = _norm(target->boundary->corners()[1] * (1-cropPerc)
|
||
|
-target->boundary->corners()[0] * (1-cropPerc));
|
||
|
|
||
|
out = QImage(width, height, QImage::Format_RGB32);
|
||
|
out.fill(QColor(0,0,0));
|
||
|
|
||
|
// check correct order, such that it does not reflect in any dimension
|
||
|
// (no flipping)
|
||
|
|
||
|
double anglesSource[4];
|
||
|
QPointF center(0,0);
|
||
|
for (int i=0;i<4;i++) {
|
||
|
center += target->boundary->corners()[0];
|
||
|
}
|
||
|
center /= 4;
|
||
|
center = target->boundary->mapToScene(center);
|
||
|
for (int i=0; i<4; i++) {
|
||
|
QPointF curr = target->boundary->mapToScene(target->boundary->corners()[i])-center;
|
||
|
anglesSource[i] = qAtan2(curr.y(), curr.x());
|
||
|
}
|
||
|
int numNeg = 0;
|
||
|
for (int i=0; i<4; i++) {
|
||
|
const double sgn1 = (anglesSource[i]-anglesSource[(i+3)%4]);
|
||
|
//const double sgn2 = (anglesTarget[i]-anglesTarget[(i+3)%4]);
|
||
|
if (sgn1 < 0) numNeg++;
|
||
|
}
|
||
|
|
||
|
// either double reflected (numNeg == 2) or only one side
|
||
|
if (numNeg >= 2) {
|
||
|
QPointF corners[4];
|
||
|
if (numNeg > 2) {
|
||
|
corners[0] = target->boundary->corners()[0];
|
||
|
corners[1] = target->boundary->corners()[3];
|
||
|
corners[2] = target->boundary->corners()[2];
|
||
|
corners[3] = target->boundary->corners()[1];
|
||
|
} else {
|
||
|
corners[0] = target->boundary->corners()[2];
|
||
|
corners[1] = target->boundary->corners()[3];
|
||
|
corners[2] = target->boundary->corners()[0];
|
||
|
corners[3] = target->boundary->corners()[1];
|
||
|
|
||
|
}
|
||
|
target->boundary->setCorners(corners);
|
||
|
// start over again
|
||
|
extract(target, source);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// now also check the orientation, it has to be
|
||
|
// similar to the source. meaning roughly upright -> upright
|
||
|
QPointF currRot = target->boundary->mapToScene(target->boundary->corners()[2])
|
||
|
-target->boundary->mapToScene(target->boundary->corners()[1]);
|
||
|
const double resultAngle = qAtan2( currRot.y(),
|
||
|
currRot.x() );
|
||
|
|
||
|
const int num90DegreeBins = qRound((resultAngle)/(M_PI/2.0));
|
||
|
|
||
|
QPointF dirX(target->boundary->corners()[3]* (1-cropPerc)
|
||
|
-target->boundary->corners()[0]* (1-cropPerc)),
|
||
|
dirY(target->boundary->corners()[0]* (1-cropPerc)
|
||
|
-target->boundary->corners()[1]* (1-cropPerc));
|
||
|
QSizeF size(_norm(dirX), _norm(dirY)) ;
|
||
|
dirX /= size.width();
|
||
|
dirY /= size.height();
|
||
|
QPointF center(0.5*(target->boundary->mapToScene(target->boundary->corners()[0])
|
||
|
+ target->boundary->mapToScene(target->boundary->corners()[2])));
|
||
|
|
||
|
const int sourceHeight = sourceImage.height();
|
||
|
const int sourceWidth = sourceImage.width();
|
||
|
|
||
|
target->backmap = BackMapPtr(new BackMap(resultAngle,
|
||
|
center,
|
||
|
size,
|
||
|
source->scale));
|
||
|
|
||
|
#pragma omp parallel for
|
||
|
for (int y=0; y<height; y++) {
|
||
|
if (!_stopped && !_abortCurrentStartNext)
|
||
|
{
|
||
|
for (int x=0; x<width; x++) {
|
||
|
//QPointF pos(y -height/2.0, x -width/2.0);
|
||
|
QPointF pos(target->boundary->corners()[1]* (1-cropPerc)
|
||
|
+ y*dirY + x*dirX);
|
||
|
pos = target->boundary->mapToScene(pos);
|
||
|
const int xSource = qRound(pos.x());
|
||
|
const int ySource = qRound(pos.y());
|
||
|
if (xSource >= 0
|
||
|
&& xSource < sourceWidth
|
||
|
&& ySource >= 0
|
||
|
&& ySource < sourceHeight) {
|
||
|
out.setPixel(x,y, sourceImage.pixel(xSource, ySource));
|
||
|
} else {
|
||
|
out.setPixel(x,y,qRgb(0,0,0));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_stopped || _abortCurrentStartNext) {
|
||
|
target->width = 0;
|
||
|
target->height = 0;
|
||
|
target->backmap = BackMapPtr();
|
||
|
target->image = QImage();
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
target->width = (double)width * source->scale;
|
||
|
target->height = (double)height * source->scale;
|
||
|
}
|
||
|
|
||
|
|
||
|
// finally, add normalizing rotation and user rotation:
|
||
|
out = out.transformed(QTransform().rotate( num90DegreeBins*90).rotate(target->rotation*90));
|
||
|
|
||
|
target->determinedRotation = num90DegreeBins;
|
||
|
target->image = out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline double ExtractTargets::_norm(const QPointF& p)
|
||
|
{
|
||
|
return qSqrt(p.x()*p.x() + p.y()*p.y());
|
||
|
}
|
||
|
|
||
|
|
||
|
inline double ExtractTargets::_dot(const QPointF& p1, const QPointF& p2)
|
||
|
{
|
||
|
return p1.x() * p2.x() + p1.y() * p2.y();
|
||
|
}
|
||
|
|
||
|
inline double ExtractTargets::_norm2(const QPointF& p)
|
||
|
{
|
||
|
return p.x()*p.x() + p.y()*p.y();
|
||
|
}
|