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