1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-01-18
7  * Description : a widget class to edit perspective.
8  *
9  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "perspectivewidget.h"
26 
27 // C++ includes
28 
29 #include <cstdio>
30 #include <cstdlib>
31 #include <cmath>
32 
33 // Qt includes
34 
35 #include <QRegion>
36 #include <QPainter>
37 #include <QPen>
38 #include <QBrush>
39 #include <QImage>
40 #include <QResizeEvent>
41 #include <QMouseEvent>
42 #include <QPaintEvent>
43 #include <QPixmap>
44 #include <QPolygon>
45 
46 // KDE includes
47 
48 #include <klocalizedstring.h>
49 
50 // Local includes
51 
52 #include "digikam_debug.h"
53 #include "perspectivetriangle.h"
54 #include "dpixelsaliasfilter.h"
55 
56 namespace DigikamEditorPerspectiveToolPlugin
57 {
58 
59 class Q_DECL_HIDDEN PerspectiveWidget::Private
60 {
61 public:
62 
63     enum ResizingMode
64     {
65         ResizingNone = 0,
66         ResizingTopLeft,
67         ResizingTopRight,
68         ResizingBottomLeft,
69         ResizingBottomRight
70     };
71 
72 public:
73 
Private()74     explicit Private()
75       : antiAliasing            (false),
76         drawWhileMoving         (true),
77         drawGrid                (false),
78         inverseTransformation   (false),
79         validPerspective        (true),
80         data                    (nullptr),
81         width                   (0),
82         height                  (0),
83         origW                   (0),
84         origH                   (0),
85         currentResizing         (ResizingNone),
86         guideSize               (1),
87         guideColor              (Qt::red),
88         pixmap                  (nullptr),
89         iface                   (nullptr)
90     {
91     }
92 
93     bool        antiAliasing;
94     bool        drawWhileMoving;
95     bool        drawGrid;
96     bool        inverseTransformation;
97     bool        validPerspective;
98 
99     uint*       data;
100     int         width;
101     int         height;
102     int         origW;
103     int         origH;
104 
105     int         currentResizing;
106 
107     int         guideSize;
108 
109     QRect       rect;
110 
111     // Transformed center area for mouse position control.
112 
113     QPoint      transformedCenter;
114 
115     // Draggable local region selection corners.
116 
117     QRect       topLeftCorner;
118     QRect       topRightCorner;
119     QRect       bottomLeftCorner;
120     QRect       bottomRightCorner;
121 
122     QPoint      topLeftPoint;
123     QPoint      topRightPoint;
124     QPoint      bottomLeftPoint;
125     QPoint      bottomRightPoint;
126     QPoint      spot;
127 
128     QColor      guideColor;
129     QColor      bgColor;
130 
131     // 60 points will be stored to compute a grid of 15x15 lines.
132 
133     QPolygon    grid;
134 
135     QPixmap*    pixmap;
136 
137     ImageIface* iface;
138     DImg        preview;
139 };
140 
PerspectiveWidget(int w,int h,QWidget * const parent)141 PerspectiveWidget::PerspectiveWidget(int w, int h, QWidget* const parent)
142     : QWidget(parent),
143       d      (new Private)
144 {
145     setAttribute(Qt::WA_DeleteOnClose);
146     setMinimumSize(w, h);
147     setMouseTracking(true);
148 
149     d->bgColor  = palette().color(QPalette::Window);
150     d->iface    = new ImageIface(QSize(w, h));
151     d->preview  = d->iface->setPreviewSize(QSize(w, h));
152     d->width    = d->iface->previewSize().width();
153     d->height   = d->iface->previewSize().height();
154     d->origW    = d->iface->originalSize().width();
155     d->origH    = d->iface->originalSize().height();
156     d->preview.setIccProfile( d->iface->original()->getIccProfile() );
157 
158     d->pixmap   = new QPixmap(w, h);
159     d->rect     = QRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
160     d->grid     = QPolygon(60);
161 
162     reset();
163 }
164 
~PerspectiveWidget()165 PerspectiveWidget::~PerspectiveWidget()
166 {
167     delete d->iface;
168     delete d->pixmap;
169     delete d;
170 }
171 
resizeEvent(QResizeEvent * e)172 void PerspectiveWidget::resizeEvent(QResizeEvent* e)
173 {
174     int old_w            = d->width;
175     int old_h            = d->height;
176 
177     delete d->pixmap;
178     int w                = e->size().width();
179     int h                = e->size().height();
180     d->preview           = d->iface->setPreviewSize(QSize(w, h));
181     d->width             = d->iface->previewSize().width();
182     d->height            = d->iface->previewSize().height();
183     d->preview.setIccProfile( d->iface->original()->getIccProfile() );
184 
185     d->pixmap            = new QPixmap(w, h);
186     QRect oldRect        = d->rect;
187     d->rect              = QRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
188 
189     float xFactor        = (float)d->rect.width()  / (float)(oldRect.width());
190     float yFactor        = (float)d->rect.height() / (float)(oldRect.height());
191 
192     d->topLeftPoint      = QPoint(lroundf(d->topLeftPoint.x()*xFactor),
193                                   lroundf(d->topLeftPoint.y()*yFactor));
194     d->topRightPoint     = QPoint(lroundf(d->topRightPoint.x()*xFactor),
195                                   lroundf(d->topRightPoint.y()*yFactor));
196     d->bottomLeftPoint   = QPoint(lroundf(d->bottomLeftPoint.x()*xFactor),
197                                   lroundf(d->bottomLeftPoint.y()*yFactor));
198     d->bottomRightPoint  = QPoint(lroundf(d->bottomRightPoint.x()*xFactor),
199                                   lroundf(d->bottomRightPoint.y()*yFactor));
200     d->transformedCenter = QPoint(lroundf(d->transformedCenter.x()*xFactor),
201                                   lroundf(d->transformedCenter.y()*yFactor));
202 
203     d->spot.setX((int)((float)d->spot.x() * ( (float)d->width / (float)old_w)));
204     d->spot.setY((int)((float)d->spot.y() * ( (float)d->height / (float)old_h)));
205 
206     updatePixmap();
207 }
208 
imageIface() const209 ImageIface* PerspectiveWidget::imageIface() const
210 {
211     return d->iface;
212 }
213 
getTopLeftCorner() const214 QPoint PerspectiveWidget::getTopLeftCorner() const
215 {
216     return QPoint( lroundf((float)(d->topLeftPoint.x()*d->origW) / (float)d->width),
217                    lroundf((float)(d->topLeftPoint.y()*d->origH) / (float)d->height));
218 }
219 
getTopRightCorner() const220 QPoint PerspectiveWidget::getTopRightCorner() const
221 {
222     return QPoint( lroundf((float)(d->topRightPoint.x()*d->origW) / (float)d->width),
223                    lroundf((float)(d->topRightPoint.y()*d->origH) / (float)d->height));
224 }
225 
getBottomLeftCorner() const226 QPoint PerspectiveWidget::getBottomLeftCorner() const
227 {
228     return QPoint( lroundf((float)(d->bottomLeftPoint.x()*d->origW) / (float)d->width),
229                    lroundf((float)(d->bottomLeftPoint.y()*d->origH) / (float)d->height));
230 }
231 
getBottomRightCorner() const232 QPoint PerspectiveWidget::getBottomRightCorner() const
233 {
234     return QPoint( lroundf((float)(d->bottomRightPoint.x()*d->origW) / (float)d->width),
235                    lroundf((float)(d->bottomRightPoint.y()*d->origH) / (float)d->height));
236 }
237 
getTargetSize() const238 QRect PerspectiveWidget::getTargetSize() const
239 {
240     QPolygon perspectiveArea;
241 
242     perspectiveArea.putPoints(0, 4,
243                               getTopLeftCorner().x(),     getTopLeftCorner().y(),
244                               getTopRightCorner().x(),    getTopRightCorner().y(),
245                               getBottomRightCorner().x(), getBottomRightCorner().y(),
246                               getBottomLeftCorner().x(),  getBottomLeftCorner().y());
247 
248     return perspectiveArea.boundingRect();
249 }
250 
getAngleTopLeft() const251 float PerspectiveWidget::getAngleTopLeft() const
252 {
253     PerspectiveTriangle topLeft(getTopLeftCorner(), getTopRightCorner(), getBottomLeftCorner());
254 
255     return topLeft.angleBAC();
256 }
257 
getAngleTopRight() const258 float PerspectiveWidget::getAngleTopRight() const
259 {
260     PerspectiveTriangle topLeft(getTopRightCorner(), getBottomRightCorner(), getTopLeftCorner());
261 
262     return topLeft.angleBAC();
263 }
264 
getAngleBottomLeft() const265 float PerspectiveWidget::getAngleBottomLeft() const
266 {
267     PerspectiveTriangle topLeft(getBottomLeftCorner(), getTopLeftCorner(), getBottomRightCorner());
268 
269     return topLeft.angleBAC();
270 }
271 
getAngleBottomRight() const272 float PerspectiveWidget::getAngleBottomRight() const
273 {
274     PerspectiveTriangle topLeft(getBottomRightCorner(), getBottomLeftCorner(), getTopRightCorner());
275 
276     return topLeft.angleBAC();
277 }
278 
reset()279 void PerspectiveWidget::reset()
280 {
281     d->topLeftPoint.setX(0);
282     d->topLeftPoint.setY(0);
283 
284     d->topRightPoint.setX(d->width-1);
285     d->topRightPoint.setY(0);
286 
287     d->bottomLeftPoint.setX(0);
288     d->bottomLeftPoint.setY(d->height-1);
289 
290     d->bottomRightPoint.setX(d->width-1);
291     d->bottomRightPoint.setY(d->height-1);
292 
293     d->spot.setX(d->width  / 2);
294     d->spot.setY(d->height / 2);
295 
296     d->antiAliasing = true;
297     updatePixmap();
298     update();
299 }
300 
applyPerspectiveAdjustment()301 void PerspectiveWidget::applyPerspectiveAdjustment()
302 {
303     DImg* const orgImage = d->iface->original();
304 
305     if (!orgImage)
306     {
307         return;
308     }
309 
310     DImg destImage(orgImage->width(), orgImage->height(), orgImage->sixteenBit(), orgImage->hasAlpha());
311 
312     DColor background(0, 0, 0, orgImage->hasAlpha() ? 0 : 255, orgImage->sixteenBit());
313 
314     // Perform perspective adjustment.
315 
316     buildPerspective(QPoint(0, 0), QPoint(d->origW, d->origH),
317                      getTopLeftCorner(), getTopRightCorner(),
318                      getBottomLeftCorner(), getBottomRightCorner(),
319                      orgImage, &destImage, background);
320 
321     // Perform an auto-cropping around the image.
322 
323     DImg targetImg = destImage.copy(getTargetSize());
324 
325     FilterAction action(QLatin1String("digikam:PerspectiveAdjustment"), 1);
326     action.setDisplayableName(i18n("Perspective Adjustment Tool"));
327 
328     action.addParameter(QLatin1String("topLeftPointX"),     d->topLeftPoint.x());
329     action.addParameter(QLatin1String("topLeftPointY"),     d->topLeftPoint.y());
330     action.addParameter(QLatin1String("topRightPointX"),    d->topRightPoint.x());
331     action.addParameter(QLatin1String("topRightPointY"),    d->topRightPoint.y());
332 
333     action.addParameter(QLatin1String("bottomLeftPointX"),  d->bottomLeftPoint.x());
334     action.addParameter(QLatin1String("bottomLeftPointY"),  d->bottomLeftPoint.y());
335     action.addParameter(QLatin1String("bottomRightPointX"), d->bottomRightPoint.x());
336     action.addParameter(QLatin1String("bottomRightPointY"), d->bottomRightPoint.y());
337 
338     action.addParameter(QLatin1String("spotX"),             d->spot.x());
339     action.addParameter(QLatin1String("spotY"),             d->spot.y());
340 
341     action.addParameter(QLatin1String("antiAliasing"),      d->antiAliasing);
342 
343     // Update target image.
344 
345     d->iface->setOriginal(i18n("Perspective Adjustment"), action, targetImg);
346 }
347 
slotInverseTransformationChanged(bool isEnabled)348 void PerspectiveWidget::slotInverseTransformationChanged(bool isEnabled)
349 {
350     d->inverseTransformation = isEnabled;
351     updatePixmap();
352     update();
353 }
354 
slotToggleAntiAliasing(bool a)355 void PerspectiveWidget::slotToggleAntiAliasing(bool a)
356 {
357     d->antiAliasing = a;
358     updatePixmap();
359     update();
360 }
361 
slotToggleDrawWhileMoving(bool draw)362 void PerspectiveWidget::slotToggleDrawWhileMoving(bool draw)
363 {
364     d->drawWhileMoving = draw;
365 }
366 
slotToggleDrawGrid(bool grid)367 void PerspectiveWidget::slotToggleDrawGrid(bool grid)
368 {
369     d->drawGrid = grid;
370     updatePixmap();
371     update();
372 }
373 
slotChangeGuideColor(const QColor & color)374 void PerspectiveWidget::slotChangeGuideColor(const QColor& color)
375 {
376     d->guideColor = color;
377     updatePixmap();
378     update();
379 }
380 
slotChangeGuideSize(int size)381 void PerspectiveWidget::slotChangeGuideSize(int size)
382 {
383     d->guideSize = size;
384     updatePixmap();
385     update();
386 }
387 
setBackgroundColor(const QColor & bg)388 void PerspectiveWidget::setBackgroundColor(const QColor& bg)
389 {
390     d->bgColor = bg;
391     updatePixmap();
392     update();
393 }
394 
updatePixmap()395 void PerspectiveWidget::updatePixmap()
396 {
397     d->topLeftCorner.setRect(d->topLeftPoint.x() + d->rect.topLeft().x(),
398                              d->topLeftPoint.y() + d->rect.topLeft().y(), 8, 8);
399     d->topRightCorner.setRect(d->topRightPoint.x() - 7 + d->rect.topLeft().x(),
400                               d->topRightPoint.y() + d->rect.topLeft().y(), 8, 8);
401     d->bottomLeftCorner.setRect(d->bottomLeftPoint.x() + d->rect.topLeft().x(),
402                                 d->bottomLeftPoint.y() - 7 + d->rect.topLeft().y(), 8, 8);
403     d->bottomRightCorner.setRect(d->bottomRightPoint.x() - 7 + d->rect.topLeft().x(),
404                                  d->bottomRightPoint.y() - 7 + d->rect.topLeft().y(), 8, 8);
405 
406     // Compute the grid array
407 
408     int gXS = d->width  / 15;
409     int gYS = d->height / 15;
410 
411     for (int i = 0 ; i < 15 ; ++i)
412     {
413         int j = i*4;
414 
415         // Horizontal line.
416 
417         d->grid.setPoint(j  , 0,        i*gYS);
418         d->grid.setPoint(j+1, d->width, i*gYS);
419 
420         // Vertical line.
421 
422         d->grid.setPoint(j+2, i*gXS, 0);
423         d->grid.setPoint(j+3, i*gXS, d->height);
424     }
425 
426     // Draw background
427 
428     d->pixmap->fill(d->bgColor);
429 
430     if      (d->inverseTransformation)
431     {
432         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
433                                                 d->topLeftPoint, d->topRightPoint,
434                                                 d->bottomLeftPoint, d->bottomRightPoint);
435 
436         d->iface->setPreview(d->preview);
437         d->iface->paint(d->pixmap, d->rect);
438     }
439 
440     // if we are resizing with the mouse, compute and draw only if drawWhileMoving is set
441 
442     else if (((d->currentResizing == Private::ResizingNone) || d->drawWhileMoving) &&
443              d->validPerspective)
444     {
445         // Create preview image
446 
447         DImg destImage(d->preview.width(), d->preview.height(),
448                        d->preview.sixteenBit(), d->preview.hasAlpha());
449 
450         DColor background(d->bgColor);
451 
452         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
453                                                 d->topLeftPoint, d->topRightPoint,
454                                                 d->bottomLeftPoint, d->bottomRightPoint,
455                                                 &d->preview, &destImage, background);
456 
457         d->iface->setPreview(destImage);
458 
459         // Draw image
460 
461         d->iface->paint(d->pixmap, d->rect);
462     }
463     else if (d->validPerspective)
464     {
465         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
466                                                 d->topLeftPoint, d->topRightPoint,
467                                                 d->bottomLeftPoint, d->bottomRightPoint);
468     }
469 
470     // Drawing selection borders.
471 
472     QPainter p(d->pixmap);
473     p.setPen(QPen(QColor(255, 64, 64), 1, Qt::SolidLine));
474     p.drawLine(d->topLeftPoint     + d->rect.topLeft(), d->topRightPoint    + d->rect.topLeft());
475     p.drawLine(d->topRightPoint    + d->rect.topLeft(), d->bottomRightPoint + d->rect.topLeft());
476     p.drawLine(d->bottomRightPoint + d->rect.topLeft(), d->bottomLeftPoint  + d->rect.topLeft());
477     p.drawLine(d->bottomLeftPoint  + d->rect.topLeft(), d->topLeftPoint     + d->rect.topLeft());
478 
479     // Drawing selection corners.
480 
481     QBrush brush(QColor(255, 64, 64));
482     p.fillRect(d->topLeftCorner,     brush);
483     p.fillRect(d->topRightCorner,    brush);
484     p.fillRect(d->bottomLeftCorner,  brush);
485     p.fillRect(d->bottomRightCorner, brush);
486 
487     // Drawing the grid.
488 
489     if (d->drawGrid)
490     {
491         for (int i = 0 ; i < d->grid.size() ; i += 4)
492         {
493             // Horizontal line.
494 
495             p.drawLine(d->grid.point(i)+d->rect.topLeft(), d->grid.point(i+1)+d->rect.topLeft());
496 
497             // Vertical line.
498 
499             p.drawLine(d->grid.point(i+2)+d->rect.topLeft(), d->grid.point(i+3)+d->rect.topLeft());
500         }
501     }
502 
503     // Drawing transformed center.
504 
505     p.setPen(QPen(QColor(255, 64, 64), 3, Qt::SolidLine));
506     p.drawEllipse( d->transformedCenter.x()+d->rect.topLeft().x()-2,
507                    d->transformedCenter.y()+d->rect.topLeft().y()-2, 4, 4 );
508 
509     // Drawing vertical and horizontal guide lines.
510 
511     if (!d->inverseTransformation)
512     {
513         int xspot = d->spot.x() + d->rect.x();
514         int yspot = d->spot.y() + d->rect.y();
515         p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
516         p.drawLine(xspot, d->rect.top(), xspot, d->rect.bottom());
517         p.drawLine(d->rect.left(), yspot, d->rect.right(), yspot);
518         p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
519         p.drawLine(xspot, d->rect.top(), xspot, d->rect.bottom());
520         p.drawLine(d->rect.left(), yspot, d->rect.right(), yspot);
521     }
522 
523     p.end();
524 
525     emit signalPerspectiveChanged(getTargetSize(), getAngleTopLeft(), getAngleTopRight(),
526                                   getAngleBottomLeft(), getAngleBottomRight(), d->validPerspective);
527 }
528 
buildPerspective(const QPoint & orignTopLeft,const QPoint & orignBottomRight,const QPoint & transTopLeft,const QPoint & transTopRight,const QPoint & transBottomLeft,const QPoint & transBottomRight,DImg * const orgImage,DImg * const destImage,const DColor & background)529 QPoint PerspectiveWidget::buildPerspective(const QPoint& orignTopLeft, const QPoint& orignBottomRight,
530                                            const QPoint& transTopLeft, const QPoint& transTopRight,
531                                            const QPoint& transBottomLeft, const QPoint& transBottomRight,
532                                            DImg* const orgImage, DImg* const destImage,
533                                            const DColor& background)
534 {
535     PerspectiveMatrix matrix, transform;
536     double scalex;
537     double scaley;
538 
539     double x1  = (double)orignTopLeft.x();
540     double y1  = (double)orignTopLeft.y();
541 
542     double x2  = (double)orignBottomRight.x();
543     double y2  = (double)orignBottomRight.y();
544 
545     double tx1 = (double)transTopLeft.x();
546     double ty1 = (double)transTopLeft.y();
547 
548     double tx2 = (double)transTopRight.x();
549     double ty2 = (double)transTopRight.y();
550 
551     double tx3 = (double)transBottomLeft.x();
552     double ty3 = (double)transBottomLeft.y();
553 
554     double tx4 = (double)transBottomRight.x();
555     double ty4 = (double)transBottomRight.y();
556 
557     scalex = scaley = 1.0;
558 
559     if ((x2 - x1) > 0)
560     {
561         scalex = 1.0 / (double) (x2 - x1);
562     }
563 
564     if ((y2 - y1) > 0)
565     {
566         scaley = 1.0 / (double) (y2 - y1);
567     }
568 
569     // Determine the perspective transform that maps from
570     // the unit cube to the transformed coordinates
571 
572     double dx1, dx2, dx3, dy1, dy2, dy3;
573 
574     dx1 = tx2 - tx4;
575     dx2 = tx3 - tx4;
576     dx3 = tx1 - tx2 + tx4 - tx3;
577 
578     dy1 = ty2 - ty4;
579     dy2 = ty3 - ty4;
580     dy3 = ty1 - ty2 + ty4 - ty3;
581 
582     //  Is the mapping affine?
583 
584     if ((dx3 == 0.0) && (dy3 == 0.0))
585     {
586         matrix.coeff[0][0] = tx2 - tx1;
587         matrix.coeff[0][1] = tx4 - tx2;
588         matrix.coeff[0][2] = tx1;
589         matrix.coeff[1][0] = ty2 - ty1;
590         matrix.coeff[1][1] = ty4 - ty2;
591         matrix.coeff[1][2] = ty1;
592         matrix.coeff[2][0] = 0.0;
593         matrix.coeff[2][1] = 0.0;
594     }
595     else
596     {
597         double det1, det2;
598 
599         det1 = dx3 * dy2 - dy3 * dx2;
600         det2 = dx1 * dy2 - dy1 * dx2;
601 
602         if ((det1 == 0.0) && (det2 == 0.0))
603         {
604             matrix.coeff[2][0] = 1.0;
605         }
606         else
607         {
608             matrix.coeff[2][0] = det1 / det2;
609         }
610 
611         det1 = dx1 * dy3 - dy1 * dx3;
612 
613         if ((det1 == 0.0) && (det2 == 0.0))
614         {
615             matrix.coeff[2][1] = 1.0;
616         }
617         else
618         {
619             matrix.coeff[2][1] = det1 / det2;
620         }
621 
622         matrix.coeff[0][0] = tx2 - tx1 + matrix.coeff[2][0] * tx2;
623         matrix.coeff[0][1] = tx3 - tx1 + matrix.coeff[2][1] * tx3;
624         matrix.coeff[0][2] = tx1;
625 
626         matrix.coeff[1][0] = ty2 - ty1 + matrix.coeff[2][0] * ty2;
627         matrix.coeff[1][1] = ty3 - ty1 + matrix.coeff[2][1] * ty3;
628         matrix.coeff[1][2] = ty1;
629     }
630 
631     matrix.coeff[2][2] = 1.0;
632 
633     // transform is initialized to the identity matrix
634 
635     transform.translate(-x1, -y1);
636     transform.scale    (scalex, scaley);
637     transform.multiply (matrix);
638 
639     if (orgImage && destImage)
640     {
641         if (d->inverseTransformation)
642         {
643             PerspectiveMatrix inverseTransform = transform;
644             inverseTransform.invert();
645 
646             // Transform the matrix so it puts the result into the getTargetSize() rectangle
647 
648             PerspectiveMatrix transformIntoBounds;
649             transformIntoBounds.scale(double(getTargetSize().width())  / double(orgImage->width()),
650                                       double(getTargetSize().height()) / double(orgImage->height()));
651             transformIntoBounds.translate(getTargetSize().left(), getTargetSize().top());
652             inverseTransform.multiply(transformIntoBounds);
653             transformAffine(orgImage, destImage, inverseTransform, background);
654         }
655         else
656         {
657             // Compute perspective transformation to image if image data containers exist.
658 
659             transformAffine(orgImage, destImage, transform, background);
660         }
661     }
662 
663     // Calculate the grid array points.
664 
665     double newX, newY;
666 
667     for (int i = 0 ; i < d->grid.size() ; ++i)
668     {
669         transform.transformPoint(d->grid.point(i).x(), d->grid.point(i).y(), &newX, &newY);
670         d->grid.setPoint(i, lround(newX), lround(newY));
671     }
672 
673     // Calculate and return new image center.
674 
675     double newCenterX, newCenterY;
676     transform.transformPoint(x2/2.0, y2/2.0, &newCenterX, &newCenterY);
677 
678     return QPoint(lround(newCenterX), lround(newCenterY));
679 }
680 
transformAffine(DImg * const orgImage,DImg * const destImage,const PerspectiveMatrix & matrix,const DColor & background)681 void PerspectiveWidget::transformAffine(DImg* const orgImage,
682                                         DImg* const destImage,
683                                         const PerspectiveMatrix& matrix,
684                                         const DColor& background)
685 {
686     PerspectiveMatrix m(matrix);
687 
688     int    x1,   y1, x2, y2;     // target bounding box
689     int    x,    y;              // target coordinates
690     int    u1,   v1, u2, v2;     // source bounding box
691     double uinc, vinc, winc;     // increments in source coordinates
692                                  // per horizontal target coordinate
693 
694     double u[5] = {0.0};         // source coordinates,
695     double v[5] = {0.0};         //   2
696                                  //  / \    0 is sample in the center of pixel
697                                  // 1 0 3   1..4 is offset 1 pixel in each
698                                  //  \ /    direction (in target space)
699                                  //   4
700 
701     double tu[5], tv[5], tw[5];    // undivided source coordinates and divisor
702 
703     uchar* data    = nullptr;
704     uchar* newData = nullptr;
705     // To prevent cppcheck warnings.
706     (void)data;
707     (void)newData;
708 
709     bool   sixteenBit;
710     int    coords;
711     int    width, height;
712     int    bytesDepth;
713     int    offset;
714     uchar* d2   = nullptr;
715     DColor color;
716 
717     bytesDepth  = orgImage->bytesDepth();
718     data        = orgImage->bits();
719     sixteenBit  = orgImage->sixteenBit();
720     width       = orgImage->width();
721     height      = orgImage->height();
722     newData     = destImage->bits();
723     DColor bg   = background;
724 
725     if (sixteenBit)
726     {
727         bg.convertToSixteenBit();
728     }
729 /*
730     destImage->fill(bg);
731 */
732     DPixelsAliasFilter alias;
733 
734     // Find the inverse of the transformation matrix
735 
736     m.invert();
737 
738     u1 = 0;
739     v1 = 0;
740     u2 = u1 + width;
741     v2 = v1 + height;
742 
743     x1 = u1;
744     y1 = v1;
745     x2 = u2;
746     y2 = v2;
747 
748     QScopedArrayPointer<uchar> dest(new uchar[width * bytesDepth]);
749 
750     uinc = m.coeff[0][0];
751     vinc = m.coeff[1][0];
752     winc = m.coeff[2][0];
753 
754     coords = 1;
755 
756     // these loops could be rearranged, depending on which bit of code
757     // you'd most like to write more than once.
758 
759     for (y = y1 ; y < y2 ; ++y)
760     {
761         // set up inverse transform steps
762 
763         tu[0] = uinc * (x1 + 0.5) + m.coeff[0][1] * (y + 0.5) + m.coeff[0][2] - 0.5;
764         tv[0] = vinc * (x1 + 0.5) + m.coeff[1][1] * (y + 0.5) + m.coeff[1][2] - 0.5;
765         tw[0] = winc * (x1 + 0.5) + m.coeff[2][1] * (y + 0.5) + m.coeff[2][2];
766 
767         d2 = dest.data();
768 
769         for (x = x1 ; x < x2 ; ++x)
770         {
771             int i;     //  normalize homogeneous coords
772 
773             for (i = 0 ; i < coords ; ++i)
774             {
775                 if (tw[i] == 1.0)
776                 {
777                     u[i] = tu[i];
778                     v[i] = tv[i];
779                 }
780                 else if (tw[i] != 0.0)
781                 {
782                     u[i] = tu[i] / tw[i];
783                     v[i] = tv[i] / tw[i];
784                 }
785                 else
786                 {
787                     qCDebug(DIGIKAM_DPLUGIN_EDITOR_LOG) << "homogeneous coordinate = 0...\n";
788                 }
789             }
790 
791             //  Set the destination pixels
792 
793             int iu = lround( u [0] );
794             int iv = lround( v [0] );
795 
796             if ((iu >= u1) && (iu < u2) && (iv >= v1) && (iv < v2))
797             {
798                 // u, v coordinates into source
799 
800                 //In inverse transformation we always enable anti-aliasing, because there is always under-sampling
801 
802                 if (d->antiAliasing || d->inverseTransformation)
803                 {
804                     double finalU = u[0] - u1;
805                     double finalV = v[0] - v1;
806 
807                     if (sixteenBit)
808                     {
809                         unsigned short* d16 = reinterpret_cast<unsigned short*>(d2);
810                         alias.pixelAntiAliasing16(reinterpret_cast<unsigned short*>(data), width, height, finalU, finalV, d16+3, d16+2, d16+1, d16);
811                     }
812                     else
813                     {
814                         alias.pixelAntiAliasing(data, width, height, finalU, finalV, d2+3, d2+2, d2+1, d2);
815                     }
816                 }
817                 else
818                 {
819                     int uu = iu - u1;
820                     int vv = iv - v1;
821                     offset = (vv * width * bytesDepth) + (uu * bytesDepth);
822                     color.setColor(data + offset, sixteenBit);
823                     color.setPixel(d2);
824                 }
825 
826                 d2 += bytesDepth;
827             }
828             else // not in source range
829             {
830                 // set to background color
831 
832                 bg.setPixel(d2);
833                 d2 += bytesDepth;
834             }
835 
836             for (i = 0 ; i < coords ; ++i)
837             {
838                 tu[i] += uinc;
839                 tv[i] += vinc;
840                 tw[i] += winc;
841             }
842         }
843 
844         //  set the pixel region row
845 
846         offset = (y - y1) * width * bytesDepth;
847         memcpy(newData + offset, dest.data(), width * bytesDepth);
848     }
849 }
850 
paintEvent(QPaintEvent *)851 void PerspectiveWidget::paintEvent(QPaintEvent*)
852 {
853     QPainter p(this);
854     p.drawPixmap(0, 0, *d->pixmap);
855     p.end();
856 }
857 
mousePressEvent(QMouseEvent * e)858 void PerspectiveWidget::mousePressEvent(QMouseEvent* e)
859 {
860     if ( (e->button() == Qt::LeftButton) && d->rect.contains( e->x(), e->y() ))
861     {
862         if      ( d->topLeftCorner.contains( e->x(), e->y() ) )
863         {
864             d->currentResizing = Private::ResizingTopLeft;
865         }
866         else if ( d->bottomRightCorner.contains( e->x(), e->y() ) )
867         {
868             d->currentResizing = Private::ResizingBottomRight;
869         }
870         else if ( d->topRightCorner.contains( e->x(), e->y() ) )
871         {
872             d->currentResizing = Private::ResizingTopRight;
873         }
874         else if ( d->bottomLeftCorner.contains( e->x(), e->y() ) )
875         {
876             d->currentResizing = Private::ResizingBottomLeft;
877         }
878         else
879         {
880             d->spot.setX(e->x()-d->rect.x());
881             d->spot.setY(e->y()-d->rect.y());
882         }
883     }
884 }
885 
mouseReleaseEvent(QMouseEvent * e)886 void PerspectiveWidget::mouseReleaseEvent(QMouseEvent* e)
887 {
888     if ( d->currentResizing != Private::ResizingNone )
889     {
890         unsetCursor();
891         d->currentResizing = Private::ResizingNone;
892 
893         // in this case, the pixmap has not been drawn on mouse move
894 
895         if (!d->drawWhileMoving)
896         {
897             updatePixmap();
898             update();
899         }
900     }
901     else
902     {
903         d->spot.setX(e->x()-d->rect.x());
904         d->spot.setY(e->y()-d->rect.y());
905         updatePixmap();
906         update();
907     }
908 }
909 
mouseMoveEvent(QMouseEvent * e)910 void PerspectiveWidget::mouseMoveEvent(QMouseEvent* e)
911 {
912     d->validPerspective = true;
913 
914     if ( e->buttons() == Qt::LeftButton )
915     {
916         if ( d->currentResizing != Private::ResizingNone )
917         {
918             QPolygon unusablePoints;
919             QPoint pm(e->x(), e->y());
920 
921             if (!d->rect.contains( pm ))
922             {
923                 if      (pm.x() > d->rect.right())
924                 {
925                     pm.setX(d->rect.right());
926                 }
927                 else if (pm.x() < d->rect.left())
928                 {
929                     pm.setX(d->rect.left());
930                 }
931 
932                 if      (pm.y() > d->rect.bottom())
933                 {
934                     pm.setY(d->rect.bottom());
935                 }
936                 else if (pm.y() < d->rect.top())
937                 {
938                     pm.setY(d->rect.top());
939                 }
940             }
941 
942             if ( d->currentResizing == Private::ResizingTopLeft )
943             {
944                 d->topLeftPoint = pm - d->rect.topLeft();
945                 setCursor( Qt::SizeFDiagCursor );
946 
947                 unusablePoints.putPoints(0, 7,
948                                          d->width-1 + d->rect.x(),              d->height-1 + d->rect.y(),
949                                          0 + d->rect.x(),                       d->height-1 + d->rect.y(),
950                                          0 + d->rect.x(),                       d->bottomLeftPoint.y()-10 + d->rect.y(),
951                                          d->bottomLeftPoint.x() + d->rect.x(),  d->bottomLeftPoint.y()-10 + d->rect.y(),
952                                          d->topRightPoint.x()-10 + d->rect.x(), d->topRightPoint.y() + d->rect.y(),
953                                          d->topRightPoint.x()-10 + d->rect.x(), 0 + d->rect.y(),
954                                          d->width-1 + d->rect.x(),              0 + d->rect.y());
955                 QRegion unusableArea(unusablePoints);
956 
957                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
958                 {
959                     d->validPerspective = false;
960                 }
961             }
962 
963             else if ( d->currentResizing == Private::ResizingTopRight )
964             {
965                 d->topRightPoint = pm - d->rect.topLeft();
966                 setCursor( Qt::SizeBDiagCursor );
967 
968                 unusablePoints.putPoints(0, 7,
969                                          0 + d->rect.x(),                       d->height-1 + d->rect.y(),
970                                          0 + d->rect.x(),                       0 + d->rect.y(),
971                                          d->topLeftPoint.x()+10 + d->rect.x(),  0 + d->rect.y(),
972                                          d->topLeftPoint.x()+10 + d->rect.x(),  d->topLeftPoint.y() + d->rect.y(),
973                                          d->bottomRightPoint.x() + d->rect.x(), d->bottomRightPoint.y()-10 + d->rect.y(),
974                                          d->width-1 + d->rect.x(),              d->bottomRightPoint.y()-10 + d->rect.y(),
975                                          d->width-1 + d->rect.x(),              d->height-1 + d->rect.y());
976                 QRegion unusableArea(unusablePoints);
977 
978                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
979                 {
980                     d->validPerspective = false;
981                 }
982             }
983 
984             else if ( d->currentResizing == Private::ResizingBottomLeft  )
985             {
986                 d->bottomLeftPoint = pm - d->rect.topLeft();
987                 setCursor( Qt::SizeBDiagCursor );
988 
989                 unusablePoints.putPoints(0, 7,
990                                          d->width-1 + d->rect.x(),                 0 + d->rect.y(),
991                                          d->width-1 + d->rect.x(),                 d->height-1 + d->rect.y(),
992                                          d->bottomRightPoint.x()-10 + d->rect.x(), d->height-1 + d->rect.y(),
993                                          d->bottomRightPoint.x()-10 + d->rect.x(), d->bottomRightPoint.y()+10 + d->rect.y(),
994                                          d->topLeftPoint.x() + d->rect.x(),        d->topLeftPoint.y()+10 + d->rect.y(),
995                                          0 + d->rect.x(),                          d->topLeftPoint.y() + d->rect.y(),
996                                          0 + d->rect.x(),                          0 + d->rect.y());
997                 QRegion unusableArea(unusablePoints);
998 
999                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
1000                 {
1001                     d->validPerspective = false;
1002                 }
1003             }
1004 
1005             else if ( d->currentResizing == Private::ResizingBottomRight )
1006             {
1007                 d->bottomRightPoint = pm - d->rect.topLeft();
1008                 setCursor( Qt::SizeFDiagCursor );
1009 
1010                 unusablePoints.putPoints(0, 7,
1011                                          0 + d->rect.x(),                         0 + d->rect.y(),
1012                                          d->width-1 + d->rect.x(),                0 + d->rect.y(),
1013                                          d->width-1 + d->rect.x(),                d->topRightPoint.y()+10 + d->rect.y(),
1014                                          d->topRightPoint.x() + d->rect.x(),      d->topRightPoint.y()+10 + d->rect.y(),
1015                                          d->bottomLeftPoint.x()+10 + d->rect.x(), d->bottomLeftPoint.y() + d->rect.y(),
1016                                          d->bottomLeftPoint.x()+10 + d->rect.x(), d->width-1 + d->rect.y(),
1017                                          0 + d->rect.x(),                         d->width-1 + d->rect.y());
1018                 QRegion unusableArea(unusablePoints);
1019 
1020                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
1021                 {
1022                     d->validPerspective = false;
1023                 }
1024             }
1025 
1026             else
1027             {
1028                 d->spot.setX(e->x()-d->rect.x());
1029                 d->spot.setY(e->y()-d->rect.y());
1030             }
1031 
1032             updatePixmap();
1033 /*
1034             // NOTE ; To hack unusable region
1035 
1036             QPainter p(d->pixmap);
1037             QPainterPath pp;
1038             pp.addPolygon(unusablePoints);
1039             p.fillPath(pp, QColor(128, 128, 128, 128));
1040             p.end();
1041 */
1042             update();
1043         }
1044     }
1045     else
1046     {
1047         if      ( d->topLeftCorner.contains( e->x(), e->y() ) ||
1048              d->bottomRightCorner.contains( e->x(), e->y() ) )
1049         {
1050             setCursor( Qt::SizeFDiagCursor );
1051         }
1052 
1053         else if ( d->topRightCorner.contains( e->x(), e->y() ) ||
1054                   d->bottomLeftCorner.contains( e->x(), e->y() ) )
1055         {
1056             setCursor( Qt::SizeBDiagCursor );
1057         }
1058         else
1059         {
1060             unsetCursor();
1061         }
1062     }
1063 }
1064 
1065 } // namespace DigikamEditorPerspectiveToolPlugin
1066