File indexing completed on 2025-01-19 03:51:14

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-01-18
0007  * Description : a widget class to edit perspective.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "perspectivewidget.h"
0017 
0018 // C++ includes
0019 
0020 #include <cstdio>
0021 #include <cstdlib>
0022 #include <cmath>
0023 
0024 // Qt includes
0025 
0026 #include <QRegion>
0027 #include <QPainter>
0028 #include <QPen>
0029 #include <QBrush>
0030 #include <QImage>
0031 #include <QPixmap>
0032 #include <QPolygon>
0033 
0034 // KDE includes
0035 
0036 #include <klocalizedstring.h>
0037 
0038 // Local includes
0039 
0040 #include "digikam_debug.h"
0041 #include "perspectivetriangle.h"
0042 #include "dpixelsaliasfilter.h"
0043 
0044 namespace DigikamEditorPerspectiveToolPlugin
0045 {
0046 
0047 class Q_DECL_HIDDEN PerspectiveWidget::Private
0048 {
0049 public:
0050 
0051     enum ResizingMode
0052     {
0053         ResizingNone = 0,
0054         ResizingTopLeft,
0055         ResizingTopRight,
0056         ResizingBottomLeft,
0057         ResizingBottomRight
0058     };
0059 
0060 public:
0061 
0062     explicit Private()
0063       : antiAliasing            (false),
0064         drawWhileMoving         (true),
0065         drawGrid                (false),
0066         inverseTransformation   (false),
0067         validPerspective        (true),
0068         data                    (nullptr),
0069         width                   (0),
0070         height                  (0),
0071         origW                   (0),
0072         origH                   (0),
0073         currentResizing         (ResizingNone),
0074         guideSize               (1),
0075         guideColor              (Qt::red),
0076         pixmap                  (nullptr),
0077         iface                   (nullptr)
0078     {
0079     }
0080 
0081     bool        antiAliasing;
0082     bool        drawWhileMoving;
0083     bool        drawGrid;
0084     bool        inverseTransformation;
0085     bool        validPerspective;
0086 
0087     uint*       data;
0088     int         width;
0089     int         height;
0090     int         origW;
0091     int         origH;
0092 
0093     int         currentResizing;
0094 
0095     int         guideSize;
0096 
0097     QRect       rect;
0098 
0099     // Transformed center area for mouse position control.
0100 
0101     QPoint      transformedCenter;
0102 
0103     // Draggable local region selection corners.
0104 
0105     QRect       topLeftCorner;
0106     QRect       topRightCorner;
0107     QRect       bottomLeftCorner;
0108     QRect       bottomRightCorner;
0109 
0110     QPoint      topLeftPoint;
0111     QPoint      topRightPoint;
0112     QPoint      bottomLeftPoint;
0113     QPoint      bottomRightPoint;
0114     QPoint      spot;
0115 
0116     QColor      guideColor;
0117     QColor      bgColor;
0118 
0119     // 60 points will be stored to compute a grid of 15x15 lines.
0120 
0121     QPolygon    grid;
0122 
0123     QPixmap*    pixmap;
0124 
0125     ImageIface* iface;
0126     DImg        preview;
0127 };
0128 
0129 PerspectiveWidget::PerspectiveWidget(int w, int h, QWidget* const parent)
0130     : QWidget(parent),
0131       d      (new Private)
0132 {
0133     setAttribute(Qt::WA_DeleteOnClose);
0134     setMinimumSize(w, h);
0135     setMouseTracking(true);
0136 
0137     d->bgColor  = palette().color(QPalette::Window);
0138     d->iface    = new ImageIface(QSize(w, h));
0139     d->preview  = d->iface->setPreviewSize(QSize(w, h));
0140     d->width    = d->iface->previewSize().width();
0141     d->height   = d->iface->previewSize().height();
0142     d->origW    = d->iface->originalSize().width();
0143     d->origH    = d->iface->originalSize().height();
0144     d->preview.setIccProfile( d->iface->original()->getIccProfile() );
0145 
0146     d->pixmap   = new QPixmap(w, h);
0147     d->rect     = QRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
0148     d->grid     = QPolygon(60);
0149 
0150     reset();
0151 }
0152 
0153 PerspectiveWidget::~PerspectiveWidget()
0154 {
0155     delete d->iface;
0156     delete d->pixmap;
0157     delete d;
0158 }
0159 
0160 void PerspectiveWidget::resizeEvent(QResizeEvent* e)
0161 {
0162     int old_w            = d->width;
0163     int old_h            = d->height;
0164 
0165     delete d->pixmap;
0166     int w                = e->size().width();
0167     int h                = e->size().height();
0168     d->preview           = d->iface->setPreviewSize(QSize(w, h));
0169     d->width             = d->iface->previewSize().width();
0170     d->height            = d->iface->previewSize().height();
0171     d->preview.setIccProfile( d->iface->original()->getIccProfile() );
0172 
0173     d->pixmap            = new QPixmap(w, h);
0174     QRect oldRect        = d->rect;
0175     d->rect              = QRect(w/2-d->width/2, h/2-d->height/2, d->width, d->height);
0176 
0177     float xFactor        = (float)d->rect.width()  / (float)(oldRect.width());
0178     float yFactor        = (float)d->rect.height() / (float)(oldRect.height());
0179 
0180     d->topLeftPoint      = QPoint(lroundf(d->topLeftPoint.x()*xFactor),
0181                                   lroundf(d->topLeftPoint.y()*yFactor));
0182     d->topRightPoint     = QPoint(lroundf(d->topRightPoint.x()*xFactor),
0183                                   lroundf(d->topRightPoint.y()*yFactor));
0184     d->bottomLeftPoint   = QPoint(lroundf(d->bottomLeftPoint.x()*xFactor),
0185                                   lroundf(d->bottomLeftPoint.y()*yFactor));
0186     d->bottomRightPoint  = QPoint(lroundf(d->bottomRightPoint.x()*xFactor),
0187                                   lroundf(d->bottomRightPoint.y()*yFactor));
0188     d->transformedCenter = QPoint(lroundf(d->transformedCenter.x()*xFactor),
0189                                   lroundf(d->transformedCenter.y()*yFactor));
0190 
0191     d->spot.setX((int)((float)d->spot.x() * ( (float)d->width / (float)old_w)));
0192     d->spot.setY((int)((float)d->spot.y() * ( (float)d->height / (float)old_h)));
0193 
0194     updatePixmap();
0195 }
0196 
0197 ImageIface* PerspectiveWidget::imageIface() const
0198 {
0199     return d->iface;
0200 }
0201 
0202 QPoint PerspectiveWidget::getTopLeftCorner() const
0203 {
0204     return QPoint( lroundf((float)(d->topLeftPoint.x()*d->origW) / (float)d->width),
0205                    lroundf((float)(d->topLeftPoint.y()*d->origH) / (float)d->height));
0206 }
0207 
0208 QPoint PerspectiveWidget::getTopRightCorner() const
0209 {
0210     return QPoint( lroundf((float)(d->topRightPoint.x()*d->origW) / (float)d->width),
0211                    lroundf((float)(d->topRightPoint.y()*d->origH) / (float)d->height));
0212 }
0213 
0214 QPoint PerspectiveWidget::getBottomLeftCorner() const
0215 {
0216     return QPoint( lroundf((float)(d->bottomLeftPoint.x()*d->origW) / (float)d->width),
0217                    lroundf((float)(d->bottomLeftPoint.y()*d->origH) / (float)d->height));
0218 }
0219 
0220 QPoint PerspectiveWidget::getBottomRightCorner() const
0221 {
0222     return QPoint( lroundf((float)(d->bottomRightPoint.x()*d->origW) / (float)d->width),
0223                    lroundf((float)(d->bottomRightPoint.y()*d->origH) / (float)d->height));
0224 }
0225 
0226 QRect PerspectiveWidget::getTargetSize() const
0227 {
0228     QPolygon perspectiveArea;
0229 
0230     perspectiveArea.putPoints(0, 4,
0231                               getTopLeftCorner().x(),     getTopLeftCorner().y(),
0232                               getTopRightCorner().x(),    getTopRightCorner().y(),
0233                               getBottomRightCorner().x(), getBottomRightCorner().y(),
0234                               getBottomLeftCorner().x(),  getBottomLeftCorner().y());
0235 
0236     return perspectiveArea.boundingRect();
0237 }
0238 
0239 float PerspectiveWidget::getAngleTopLeft() const
0240 {
0241     PerspectiveTriangle topLeft(getTopLeftCorner(), getTopRightCorner(), getBottomLeftCorner());
0242 
0243     return topLeft.angleBAC();
0244 }
0245 
0246 float PerspectiveWidget::getAngleTopRight() const
0247 {
0248     PerspectiveTriangle topLeft(getTopRightCorner(), getBottomRightCorner(), getTopLeftCorner());
0249 
0250     return topLeft.angleBAC();
0251 }
0252 
0253 float PerspectiveWidget::getAngleBottomLeft() const
0254 {
0255     PerspectiveTriangle topLeft(getBottomLeftCorner(), getTopLeftCorner(), getBottomRightCorner());
0256 
0257     return topLeft.angleBAC();
0258 }
0259 
0260 float PerspectiveWidget::getAngleBottomRight() const
0261 {
0262     PerspectiveTriangle topLeft(getBottomRightCorner(), getBottomLeftCorner(), getTopRightCorner());
0263 
0264     return topLeft.angleBAC();
0265 }
0266 
0267 void PerspectiveWidget::reset()
0268 {
0269     d->topLeftPoint.setX(0);
0270     d->topLeftPoint.setY(0);
0271 
0272     d->topRightPoint.setX(d->width-1);
0273     d->topRightPoint.setY(0);
0274 
0275     d->bottomLeftPoint.setX(0);
0276     d->bottomLeftPoint.setY(d->height-1);
0277 
0278     d->bottomRightPoint.setX(d->width-1);
0279     d->bottomRightPoint.setY(d->height-1);
0280 
0281     d->spot.setX(d->width  / 2);
0282     d->spot.setY(d->height / 2);
0283 
0284     d->antiAliasing = true;
0285     updatePixmap();
0286     update();
0287 }
0288 
0289 void PerspectiveWidget::applyPerspectiveAdjustment()
0290 {
0291     DImg* const orgImage = d->iface->original();
0292 
0293     if (!orgImage)
0294     {
0295         return;
0296     }
0297 
0298     DImg destImage(orgImage->width(), orgImage->height(), orgImage->sixteenBit(), orgImage->hasAlpha());
0299 
0300     DColor background(0, 0, 0, orgImage->hasAlpha() ? 0 : 255, orgImage->sixteenBit());
0301 
0302     // Perform perspective adjustment.
0303 
0304     buildPerspective(QPoint(0, 0), QPoint(d->origW, d->origH),
0305                      getTopLeftCorner(), getTopRightCorner(),
0306                      getBottomLeftCorner(), getBottomRightCorner(),
0307                      orgImage, &destImage, background);
0308 
0309     // Perform an auto-cropping around the image.
0310 
0311     DImg targetImg = destImage.copy(getTargetSize());
0312 
0313     FilterAction action(QLatin1String("digikam:PerspectiveAdjustment"), 1);
0314     action.setDisplayableName(i18n("Perspective Adjustment Tool"));
0315 
0316     action.addParameter(QLatin1String("topLeftPointX"),     d->topLeftPoint.x());
0317     action.addParameter(QLatin1String("topLeftPointY"),     d->topLeftPoint.y());
0318     action.addParameter(QLatin1String("topRightPointX"),    d->topRightPoint.x());
0319     action.addParameter(QLatin1String("topRightPointY"),    d->topRightPoint.y());
0320 
0321     action.addParameter(QLatin1String("bottomLeftPointX"),  d->bottomLeftPoint.x());
0322     action.addParameter(QLatin1String("bottomLeftPointY"),  d->bottomLeftPoint.y());
0323     action.addParameter(QLatin1String("bottomRightPointX"), d->bottomRightPoint.x());
0324     action.addParameter(QLatin1String("bottomRightPointY"), d->bottomRightPoint.y());
0325 
0326     action.addParameter(QLatin1String("spotX"),             d->spot.x());
0327     action.addParameter(QLatin1String("spotY"),             d->spot.y());
0328 
0329     action.addParameter(QLatin1String("antiAliasing"),      d->antiAliasing);
0330 
0331     // Update target image.
0332 
0333     d->iface->setOriginal(i18n("Perspective Adjustment"), action, targetImg);
0334 }
0335 
0336 void PerspectiveWidget::slotInverseTransformationChanged(bool isEnabled)
0337 {
0338     d->inverseTransformation = isEnabled;
0339     updatePixmap();
0340     update();
0341 }
0342 
0343 void PerspectiveWidget::slotToggleAntiAliasing(bool a)
0344 {
0345     d->antiAliasing = a;
0346     updatePixmap();
0347     update();
0348 }
0349 
0350 void PerspectiveWidget::slotToggleDrawWhileMoving(bool draw)
0351 {
0352     d->drawWhileMoving = draw;
0353 }
0354 
0355 void PerspectiveWidget::slotToggleDrawGrid(bool grid)
0356 {
0357     d->drawGrid = grid;
0358     updatePixmap();
0359     update();
0360 }
0361 
0362 void PerspectiveWidget::slotChangeGuideColor(const QColor& color)
0363 {
0364     d->guideColor = color;
0365     updatePixmap();
0366     update();
0367 }
0368 
0369 void PerspectiveWidget::slotChangeGuideSize(int size)
0370 {
0371     d->guideSize = size;
0372     updatePixmap();
0373     update();
0374 }
0375 
0376 void PerspectiveWidget::setBackgroundColor(const QColor& bg)
0377 {
0378     d->bgColor = bg;
0379     updatePixmap();
0380     update();
0381 }
0382 
0383 void PerspectiveWidget::updatePixmap()
0384 {
0385     d->topLeftCorner.setRect(d->topLeftPoint.x() + d->rect.topLeft().x(),
0386                              d->topLeftPoint.y() + d->rect.topLeft().y(), 8, 8);
0387     d->topRightCorner.setRect(d->topRightPoint.x() - 7 + d->rect.topLeft().x(),
0388                               d->topRightPoint.y() + d->rect.topLeft().y(), 8, 8);
0389     d->bottomLeftCorner.setRect(d->bottomLeftPoint.x() + d->rect.topLeft().x(),
0390                                 d->bottomLeftPoint.y() - 7 + d->rect.topLeft().y(), 8, 8);
0391     d->bottomRightCorner.setRect(d->bottomRightPoint.x() - 7 + d->rect.topLeft().x(),
0392                                  d->bottomRightPoint.y() - 7 + d->rect.topLeft().y(), 8, 8);
0393 
0394     // Compute the grid array
0395 
0396     int gXS = d->width  / 15;
0397     int gYS = d->height / 15;
0398 
0399     for (int i = 0 ; i < 15 ; ++i)
0400     {
0401         int j = i*4;
0402 
0403         // Horizontal line.
0404 
0405         d->grid.setPoint(j  , 0,        i*gYS);
0406         d->grid.setPoint(j+1, d->width, i*gYS);
0407 
0408         // Vertical line.
0409 
0410         d->grid.setPoint(j+2, i*gXS, 0);
0411         d->grid.setPoint(j+3, i*gXS, d->height);
0412     }
0413 
0414     // Draw background
0415 
0416     d->pixmap->fill(d->bgColor);
0417 
0418     if      (d->inverseTransformation)
0419     {
0420         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
0421                                                 d->topLeftPoint, d->topRightPoint,
0422                                                 d->bottomLeftPoint, d->bottomRightPoint);
0423 
0424         d->iface->setPreview(d->preview);
0425         d->iface->paint(d->pixmap, d->rect);
0426     }
0427 
0428     // if we are resizing with the mouse, compute and draw only if drawWhileMoving is set
0429 
0430     else if (((d->currentResizing == Private::ResizingNone) || d->drawWhileMoving) &&
0431              d->validPerspective)
0432     {
0433         // Create preview image
0434 
0435         DImg destImage(d->preview.width(), d->preview.height(),
0436                        d->preview.sixteenBit(), d->preview.hasAlpha());
0437 
0438         DColor background(d->bgColor);
0439 
0440         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
0441                                                 d->topLeftPoint, d->topRightPoint,
0442                                                 d->bottomLeftPoint, d->bottomRightPoint,
0443                                                 &d->preview, &destImage, background);
0444 
0445         d->iface->setPreview(destImage);
0446 
0447         // Draw image
0448 
0449         d->iface->paint(d->pixmap, d->rect);
0450     }
0451     else if (d->validPerspective)
0452     {
0453         d->transformedCenter = buildPerspective(QPoint(0, 0), QPoint(d->width, d->height),
0454                                                 d->topLeftPoint, d->topRightPoint,
0455                                                 d->bottomLeftPoint, d->bottomRightPoint);
0456     }
0457 
0458     // Drawing selection borders.
0459 
0460     QPainter p(d->pixmap);
0461     p.setPen(QPen(QColor(255, 64, 64), 1, Qt::SolidLine));
0462     p.drawLine(d->topLeftPoint     + d->rect.topLeft(), d->topRightPoint    + d->rect.topLeft());
0463     p.drawLine(d->topRightPoint    + d->rect.topLeft(), d->bottomRightPoint + d->rect.topLeft());
0464     p.drawLine(d->bottomRightPoint + d->rect.topLeft(), d->bottomLeftPoint  + d->rect.topLeft());
0465     p.drawLine(d->bottomLeftPoint  + d->rect.topLeft(), d->topLeftPoint     + d->rect.topLeft());
0466 
0467     // Drawing selection corners.
0468 
0469     QBrush brush(QColor(255, 64, 64));
0470     p.fillRect(d->topLeftCorner,     brush);
0471     p.fillRect(d->topRightCorner,    brush);
0472     p.fillRect(d->bottomLeftCorner,  brush);
0473     p.fillRect(d->bottomRightCorner, brush);
0474 
0475     // Drawing the grid.
0476 
0477     if (d->drawGrid)
0478     {
0479         for (int i = 0 ; i < d->grid.size() ; i += 4)
0480         {
0481             // Horizontal line.
0482 
0483             p.drawLine(d->grid.point(i)+d->rect.topLeft(), d->grid.point(i+1)+d->rect.topLeft());
0484 
0485             // Vertical line.
0486 
0487             p.drawLine(d->grid.point(i+2)+d->rect.topLeft(), d->grid.point(i+3)+d->rect.topLeft());
0488         }
0489     }
0490 
0491     // Drawing transformed center.
0492 
0493     p.setPen(QPen(QColor(255, 64, 64), 3, Qt::SolidLine));
0494     p.drawEllipse( d->transformedCenter.x()+d->rect.topLeft().x()-2,
0495                    d->transformedCenter.y()+d->rect.topLeft().y()-2, 4, 4 );
0496 
0497     // Drawing vertical and horizontal guide lines.
0498 
0499     if (!d->inverseTransformation)
0500     {
0501         int xspot = d->spot.x() + d->rect.x();
0502         int yspot = d->spot.y() + d->rect.y();
0503         p.setPen(QPen(Qt::white, d->guideSize, Qt::SolidLine));
0504         p.drawLine(xspot, d->rect.top(), xspot, d->rect.bottom());
0505         p.drawLine(d->rect.left(), yspot, d->rect.right(), yspot);
0506         p.setPen(QPen(d->guideColor, d->guideSize, Qt::DotLine));
0507         p.drawLine(xspot, d->rect.top(), xspot, d->rect.bottom());
0508         p.drawLine(d->rect.left(), yspot, d->rect.right(), yspot);
0509     }
0510 
0511     p.end();
0512 
0513     Q_EMIT signalPerspectiveChanged(getTargetSize(), getAngleTopLeft(), getAngleTopRight(),
0514                                   getAngleBottomLeft(), getAngleBottomRight(), d->validPerspective);
0515 }
0516 
0517 QPoint PerspectiveWidget::buildPerspective(const QPoint& orignTopLeft, const QPoint& orignBottomRight,
0518                                            const QPoint& transTopLeft, const QPoint& transTopRight,
0519                                            const QPoint& transBottomLeft, const QPoint& transBottomRight,
0520                                            DImg* const orgImage, DImg* const destImage,
0521                                            const DColor& background)
0522 {
0523     PerspectiveMatrix matrix, transform;
0524     double scalex;
0525     double scaley;
0526 
0527     double x1  = (double)orignTopLeft.x();
0528     double y1  = (double)orignTopLeft.y();
0529 
0530     double x2  = (double)orignBottomRight.x();
0531     double y2  = (double)orignBottomRight.y();
0532 
0533     double tx1 = (double)transTopLeft.x();
0534     double ty1 = (double)transTopLeft.y();
0535 
0536     double tx2 = (double)transTopRight.x();
0537     double ty2 = (double)transTopRight.y();
0538 
0539     double tx3 = (double)transBottomLeft.x();
0540     double ty3 = (double)transBottomLeft.y();
0541 
0542     double tx4 = (double)transBottomRight.x();
0543     double ty4 = (double)transBottomRight.y();
0544 
0545     scalex = scaley = 1.0;
0546 
0547     if ((x2 - x1) > 0)
0548     {
0549         scalex = 1.0 / (double) (x2 - x1);
0550     }
0551 
0552     if ((y2 - y1) > 0)
0553     {
0554         scaley = 1.0 / (double) (y2 - y1);
0555     }
0556 
0557     // Determine the perspective transform that maps from
0558     // the unit cube to the transformed coordinates
0559 
0560     double dx1, dx2, dx3, dy1, dy2, dy3;
0561 
0562     dx1 = tx2 - tx4;
0563     dx2 = tx3 - tx4;
0564     dx3 = tx1 - tx2 + tx4 - tx3;
0565 
0566     dy1 = ty2 - ty4;
0567     dy2 = ty3 - ty4;
0568     dy3 = ty1 - ty2 + ty4 - ty3;
0569 
0570     //  Is the mapping affine?
0571 
0572     if ((dx3 == 0.0) && (dy3 == 0.0))
0573     {
0574         matrix.coeff[0][0] = tx2 - tx1;
0575         matrix.coeff[0][1] = tx4 - tx2;
0576         matrix.coeff[0][2] = tx1;
0577         matrix.coeff[1][0] = ty2 - ty1;
0578         matrix.coeff[1][1] = ty4 - ty2;
0579         matrix.coeff[1][2] = ty1;
0580         matrix.coeff[2][0] = 0.0;
0581         matrix.coeff[2][1] = 0.0;
0582     }
0583     else
0584     {
0585         double det1, det2;
0586 
0587         det1 = dx3 * dy2 - dy3 * dx2;
0588         det2 = dx1 * dy2 - dy1 * dx2;
0589 
0590         if ((det1 == 0.0) && (det2 == 0.0))
0591         {
0592             matrix.coeff[2][0] = 1.0;
0593         }
0594         else
0595         {
0596             matrix.coeff[2][0] = det1 / det2;
0597         }
0598 
0599         det1 = dx1 * dy3 - dy1 * dx3;
0600 
0601         if ((det1 == 0.0) && (det2 == 0.0))
0602         {
0603             matrix.coeff[2][1] = 1.0;
0604         }
0605         else
0606         {
0607             matrix.coeff[2][1] = det1 / det2;
0608         }
0609 
0610         matrix.coeff[0][0] = tx2 - tx1 + matrix.coeff[2][0] * tx2;
0611         matrix.coeff[0][1] = tx3 - tx1 + matrix.coeff[2][1] * tx3;
0612         matrix.coeff[0][2] = tx1;
0613 
0614         matrix.coeff[1][0] = ty2 - ty1 + matrix.coeff[2][0] * ty2;
0615         matrix.coeff[1][1] = ty3 - ty1 + matrix.coeff[2][1] * ty3;
0616         matrix.coeff[1][2] = ty1;
0617     }
0618 
0619     matrix.coeff[2][2] = 1.0;
0620 
0621     // transform is initialized to the identity matrix
0622 
0623     transform.translate(-x1, -y1);
0624     transform.scale    (scalex, scaley);
0625     transform.multiply (matrix);
0626 
0627     if (orgImage && destImage)
0628     {
0629         if (d->inverseTransformation)
0630         {
0631             PerspectiveMatrix inverseTransform = transform;
0632             inverseTransform.invert();
0633 
0634             // Transform the matrix so it puts the result into the getTargetSize() rectangle
0635 
0636             PerspectiveMatrix transformIntoBounds;
0637             transformIntoBounds.scale(double(getTargetSize().width())  / double(orgImage->width()),
0638                                       double(getTargetSize().height()) / double(orgImage->height()));
0639             transformIntoBounds.translate(getTargetSize().left(), getTargetSize().top());
0640             inverseTransform.multiply(transformIntoBounds);
0641             transformAffine(orgImage, destImage, inverseTransform, background);
0642         }
0643         else
0644         {
0645             // Compute perspective transformation to image if image data containers exist.
0646 
0647             transformAffine(orgImage, destImage, transform, background);
0648         }
0649     }
0650 
0651     // Calculate the grid array points.
0652 
0653     double newX, newY;
0654 
0655     for (int i = 0 ; i < d->grid.size() ; ++i)
0656     {
0657         transform.transformPoint(d->grid.point(i).x(), d->grid.point(i).y(), &newX, &newY);
0658         d->grid.setPoint(i, lround(newX), lround(newY));
0659     }
0660 
0661     // Calculate and return new image center.
0662 
0663     double newCenterX, newCenterY;
0664     transform.transformPoint(x2/2.0, y2/2.0, &newCenterX, &newCenterY);
0665 
0666     return QPoint(lround(newCenterX), lround(newCenterY));
0667 }
0668 
0669 void PerspectiveWidget::transformAffine(DImg* const orgImage,
0670                                         DImg* const destImage,
0671                                         const PerspectiveMatrix& matrix,
0672                                         const DColor& background)
0673 {
0674     PerspectiveMatrix m(matrix);
0675 
0676     int    x1,   y1, x2, y2;     // target bounding box
0677     int    x,    y;              // target coordinates
0678     int    u1,   v1, u2, v2;     // source bounding box
0679     double uinc, vinc, winc;     // increments in source coordinates
0680                                  // per horizontal target coordinate
0681 
0682     double u[5] = {0.0};         // source coordinates,
0683     double v[5] = {0.0};         //   2
0684                                  //  / \    0 is sample in the center of pixel
0685                                  // 1 0 3   1..4 is offset 1 pixel in each
0686                                  //  \ /    direction (in target space)
0687                                  //   4
0688 
0689     double tu[5], tv[5], tw[5];    // undivided source coordinates and divisor
0690 
0691     uchar* data    = nullptr;
0692     uchar* newData = nullptr;
0693     // To prevent cppcheck warnings.
0694     (void)data;
0695     (void)newData;
0696 
0697     bool   sixteenBit;
0698     int    coords;
0699     int    width, height;
0700     int    bytesDepth;
0701     int    offset;
0702     uchar* d2   = nullptr;
0703     DColor color;
0704 
0705     bytesDepth  = orgImage->bytesDepth();
0706     data        = orgImage->bits();
0707     sixteenBit  = orgImage->sixteenBit();
0708     width       = orgImage->width();
0709     height      = orgImage->height();
0710     newData     = destImage->bits();
0711     DColor bg   = background;
0712 
0713     if (sixteenBit)
0714     {
0715         bg.convertToSixteenBit();
0716     }
0717 /*
0718     destImage->fill(bg);
0719 */
0720     DPixelsAliasFilter alias;
0721 
0722     // Find the inverse of the transformation matrix
0723 
0724     m.invert();
0725 
0726     u1 = 0;
0727     v1 = 0;
0728     u2 = u1 + width;
0729     v2 = v1 + height;
0730 
0731     x1 = u1;
0732     y1 = v1;
0733     x2 = u2;
0734     y2 = v2;
0735 
0736     QScopedArrayPointer<uchar> dest(new uchar[width * bytesDepth]);
0737 
0738     uinc = m.coeff[0][0];
0739     vinc = m.coeff[1][0];
0740     winc = m.coeff[2][0];
0741 
0742     coords = 1;
0743 
0744     // these loops could be rearranged, depending on which bit of code
0745     // you'd most like to write more than once.
0746 
0747     for (y = y1 ; y < y2 ; ++y)
0748     {
0749         // set up inverse transform steps
0750 
0751         tu[0] = uinc * (x1 + 0.5) + m.coeff[0][1] * (y + 0.5) + m.coeff[0][2] - 0.5;
0752         tv[0] = vinc * (x1 + 0.5) + m.coeff[1][1] * (y + 0.5) + m.coeff[1][2] - 0.5;
0753         tw[0] = winc * (x1 + 0.5) + m.coeff[2][1] * (y + 0.5) + m.coeff[2][2];
0754 
0755         d2 = dest.data();
0756 
0757         for (x = x1 ; x < x2 ; ++x)
0758         {
0759             int i;     //  normalize homogeneous coords
0760 
0761             for (i = 0 ; i < coords ; ++i)
0762             {
0763                 if (tw[i] == 1.0)
0764                 {
0765                     u[i] = tu[i];
0766                     v[i] = tv[i];
0767                 }
0768                 else if (tw[i] != 0.0)
0769                 {
0770                     u[i] = tu[i] / tw[i];
0771                     v[i] = tv[i] / tw[i];
0772                 }
0773                 else
0774                 {
0775                     qCDebug(DIGIKAM_DPLUGIN_EDITOR_LOG) << "homogeneous coordinate = 0...\n";
0776                 }
0777             }
0778 
0779             //  Set the destination pixels
0780 
0781             int iu = lround( u [0] );
0782             int iv = lround( v [0] );
0783 
0784             if ((iu >= u1) && (iu < u2) && (iv >= v1) && (iv < v2))
0785             {
0786                 // u, v coordinates into source
0787 
0788                 //In inverse transformation we always enable anti-aliasing, because there is always under-sampling
0789 
0790                 if (d->antiAliasing || d->inverseTransformation)
0791                 {
0792                     double finalU = u[0] - u1;
0793                     double finalV = v[0] - v1;
0794 
0795                     if (sixteenBit)
0796                     {
0797                         unsigned short* d16 = reinterpret_cast<unsigned short*>(d2);
0798                         alias.pixelAntiAliasing16(reinterpret_cast<unsigned short*>(data), width, height, finalU, finalV, d16+3, d16+2, d16+1, d16);
0799                     }
0800                     else
0801                     {
0802                         alias.pixelAntiAliasing(data, width, height, finalU, finalV, d2+3, d2+2, d2+1, d2);
0803                     }
0804                 }
0805                 else
0806                 {
0807                     int uu = iu - u1;
0808                     int vv = iv - v1;
0809                     offset = (vv * width * bytesDepth) + (uu * bytesDepth);
0810                     color.setColor(data + offset, sixteenBit);
0811                     color.setPixel(d2);
0812                 }
0813 
0814                 d2 += bytesDepth;
0815             }
0816             else // not in source range
0817             {
0818                 // set to background color
0819 
0820                 bg.setPixel(d2);
0821                 d2 += bytesDepth;
0822             }
0823 
0824             for (i = 0 ; i < coords ; ++i)
0825             {
0826                 tu[i] += uinc;
0827                 tv[i] += vinc;
0828                 tw[i] += winc;
0829             }
0830         }
0831 
0832         //  set the pixel region row
0833 
0834         offset = (y - y1) * width * bytesDepth;
0835         memcpy(newData + offset, dest.data(), width * bytesDepth);
0836     }
0837 }
0838 
0839 void PerspectiveWidget::paintEvent(QPaintEvent*)
0840 {
0841     QPainter p(this);
0842     p.drawPixmap(0, 0, *d->pixmap);
0843     p.end();
0844 }
0845 
0846 void PerspectiveWidget::mousePressEvent(QMouseEvent* e)
0847 {
0848 
0849 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0850 
0851     if ( (e->button() == Qt::LeftButton) && d->rect.contains( e->position().toPoint().x(), e->position().toPoint().y() ))
0852     {
0853         if      ( d->topLeftCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
0854         {
0855             d->currentResizing = Private::ResizingTopLeft;
0856         }
0857         else if ( d->bottomRightCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
0858         {
0859             d->currentResizing = Private::ResizingBottomRight;
0860         }
0861         else if ( d->topRightCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
0862         {
0863             d->currentResizing = Private::ResizingTopRight;
0864         }
0865         else if ( d->bottomLeftCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
0866         {
0867             d->currentResizing = Private::ResizingBottomLeft;
0868         }
0869         else
0870         {
0871             d->spot.setX(e->position().toPoint().x() - d->rect.x());
0872             d->spot.setY(e->position().toPoint().y() - d->rect.y());
0873         }
0874     }
0875 
0876 #else
0877 
0878     if ( (e->button() == Qt::LeftButton) && d->rect.contains( e->x(), e->y() ))
0879     {
0880         if      ( d->topLeftCorner.contains( e->x(), e->y() ) )
0881         {
0882             d->currentResizing = Private::ResizingTopLeft;
0883         }
0884         else if ( d->bottomRightCorner.contains( e->x(), e->y() ) )
0885         {
0886             d->currentResizing = Private::ResizingBottomRight;
0887         }
0888         else if ( d->topRightCorner.contains( e->x(), e->y() ) )
0889         {
0890             d->currentResizing = Private::ResizingTopRight;
0891         }
0892         else if ( d->bottomLeftCorner.contains( e->x(), e->y() ) )
0893         {
0894             d->currentResizing = Private::ResizingBottomLeft;
0895         }
0896         else
0897         {
0898             d->spot.setX(e->x() - d->rect.x());
0899             d->spot.setY(e->y() - d->rect.y());
0900         }
0901     }
0902 
0903 #endif
0904 
0905 }
0906 
0907 void PerspectiveWidget::mouseReleaseEvent(QMouseEvent* e)
0908 {
0909     if ( d->currentResizing != Private::ResizingNone )
0910     {
0911         unsetCursor();
0912         d->currentResizing = Private::ResizingNone;
0913 
0914         // in this case, the pixmap has not been drawn on mouse move
0915 
0916         if (!d->drawWhileMoving)
0917         {
0918             updatePixmap();
0919             update();
0920         }
0921     }
0922     else
0923     {
0924 
0925 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0926 
0927         d->spot.setX(e->position().toPoint().x() - d->rect.x());
0928         d->spot.setY(e->position().toPoint().y() - d->rect.y());
0929 
0930 #else
0931 
0932         d->spot.setX(e->x() - d->rect.x());
0933         d->spot.setY(e->y() - d->rect.y());
0934 
0935 #endif
0936 
0937         updatePixmap();
0938         update();
0939     }
0940 }
0941 
0942 void PerspectiveWidget::mouseMoveEvent(QMouseEvent* e)
0943 {
0944     d->validPerspective = true;
0945 
0946     if ( e->buttons() == Qt::LeftButton )
0947     {
0948         if ( d->currentResizing != Private::ResizingNone )
0949         {
0950             QPolygon unusablePoints;
0951 
0952 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0953 
0954             QPoint pm(e->position().toPoint().x(), e->position().toPoint().y());
0955 
0956 #else
0957 
0958             QPoint pm(e->x(), e->y());
0959 
0960 #endif
0961 
0962             if (!d->rect.contains( pm ))
0963             {
0964                 if      (pm.x() > d->rect.right())
0965                 {
0966                     pm.setX(d->rect.right());
0967                 }
0968                 else if (pm.x() < d->rect.left())
0969                 {
0970                     pm.setX(d->rect.left());
0971                 }
0972 
0973                 if      (pm.y() > d->rect.bottom())
0974                 {
0975                     pm.setY(d->rect.bottom());
0976                 }
0977                 else if (pm.y() < d->rect.top())
0978                 {
0979                     pm.setY(d->rect.top());
0980                 }
0981             }
0982 
0983             if ( d->currentResizing == Private::ResizingTopLeft )
0984             {
0985                 d->topLeftPoint = pm - d->rect.topLeft();
0986                 setCursor( Qt::SizeFDiagCursor );
0987 
0988                 unusablePoints.putPoints(0, 7,
0989                                          d->width-1 + d->rect.x(),              d->height-1 + d->rect.y(),
0990                                          0 + d->rect.x(),                       d->height-1 + d->rect.y(),
0991                                          0 + d->rect.x(),                       d->bottomLeftPoint.y()-10 + d->rect.y(),
0992                                          d->bottomLeftPoint.x() + d->rect.x(),  d->bottomLeftPoint.y()-10 + d->rect.y(),
0993                                          d->topRightPoint.x()-10 + d->rect.x(), d->topRightPoint.y() + d->rect.y(),
0994                                          d->topRightPoint.x()-10 + d->rect.x(), 0 + d->rect.y(),
0995                                          d->width-1 + d->rect.x(),              0 + d->rect.y());
0996                 QRegion unusableArea(unusablePoints);
0997 
0998                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
0999                 {
1000                     d->validPerspective = false;
1001                 }
1002             }
1003 
1004             else if ( d->currentResizing == Private::ResizingTopRight )
1005             {
1006                 d->topRightPoint = pm - d->rect.topLeft();
1007                 setCursor( Qt::SizeBDiagCursor );
1008 
1009                 unusablePoints.putPoints(0, 7,
1010                                          0 + d->rect.x(),                       d->height-1 + d->rect.y(),
1011                                          0 + d->rect.x(),                       0 + d->rect.y(),
1012                                          d->topLeftPoint.x()+10 + d->rect.x(),  0 + d->rect.y(),
1013                                          d->topLeftPoint.x()+10 + d->rect.x(),  d->topLeftPoint.y() + d->rect.y(),
1014                                          d->bottomRightPoint.x() + d->rect.x(), d->bottomRightPoint.y()-10 + d->rect.y(),
1015                                          d->width-1 + d->rect.x(),              d->bottomRightPoint.y()-10 + d->rect.y(),
1016                                          d->width-1 + d->rect.x(),              d->height-1 + d->rect.y());
1017                 QRegion unusableArea(unusablePoints);
1018 
1019                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
1020                 {
1021                     d->validPerspective = false;
1022                 }
1023             }
1024 
1025             else if ( d->currentResizing == Private::ResizingBottomLeft  )
1026             {
1027                 d->bottomLeftPoint = pm - d->rect.topLeft();
1028                 setCursor( Qt::SizeBDiagCursor );
1029 
1030                 unusablePoints.putPoints(0, 7,
1031                                          d->width-1 + d->rect.x(),                 0 + d->rect.y(),
1032                                          d->width-1 + d->rect.x(),                 d->height-1 + d->rect.y(),
1033                                          d->bottomRightPoint.x()-10 + d->rect.x(), d->height-1 + d->rect.y(),
1034                                          d->bottomRightPoint.x()-10 + d->rect.x(), d->bottomRightPoint.y()+10 + d->rect.y(),
1035                                          d->topLeftPoint.x() + d->rect.x(),        d->topLeftPoint.y()+10 + d->rect.y(),
1036                                          0 + d->rect.x(),                          d->topLeftPoint.y() + d->rect.y(),
1037                                          0 + d->rect.x(),                          0 + d->rect.y());
1038                 QRegion unusableArea(unusablePoints);
1039 
1040                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
1041                 {
1042                     d->validPerspective = false;
1043                 }
1044             }
1045 
1046             else if ( d->currentResizing == Private::ResizingBottomRight )
1047             {
1048                 d->bottomRightPoint = pm - d->rect.topLeft();
1049                 setCursor( Qt::SizeFDiagCursor );
1050 
1051                 unusablePoints.putPoints(0, 7,
1052                                          0 + d->rect.x(),                         0 + d->rect.y(),
1053                                          d->width-1 + d->rect.x(),                0 + d->rect.y(),
1054                                          d->width-1 + d->rect.x(),                d->topRightPoint.y()+10 + d->rect.y(),
1055                                          d->topRightPoint.x() + d->rect.x(),      d->topRightPoint.y()+10 + d->rect.y(),
1056                                          d->bottomLeftPoint.x()+10 + d->rect.x(), d->bottomLeftPoint.y() + d->rect.y(),
1057                                          d->bottomLeftPoint.x()+10 + d->rect.x(), d->width-1 + d->rect.y(),
1058                                          0 + d->rect.x(),                         d->width-1 + d->rect.y());
1059                 QRegion unusableArea(unusablePoints);
1060 
1061                 if ( unusableArea.contains(pm) && !d->inverseTransformation )
1062                 {
1063                     d->validPerspective = false;
1064                 }
1065             }
1066 
1067             else
1068             {
1069 
1070 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1071 
1072                 d->spot.setX(e->position().toPoint().x() - d->rect.x());
1073                 d->spot.setY(e->position().toPoint().y() - d->rect.y());
1074 
1075 #else
1076 
1077                 d->spot.setX(e->x() - d->rect.x());
1078                 d->spot.setY(e->y() - d->rect.y());
1079 
1080 #endif
1081 
1082             }
1083 
1084             updatePixmap();
1085 /*
1086             // NOTE ; To hack unusable region
1087 
1088             QPainter p(d->pixmap);
1089             QPainterPath pp;
1090             pp.addPolygon(unusablePoints);
1091             p.fillPath(pp, QColor(128, 128, 128, 128));
1092             p.end();
1093 */
1094             update();
1095         }
1096     }
1097     else
1098     {
1099 
1100 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1101 
1102         if      ( d->topLeftCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) ||
1103                   d->bottomRightCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
1104         {
1105             setCursor( Qt::SizeFDiagCursor );
1106         }
1107 
1108         else if ( d->topRightCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) ||
1109                   d->bottomLeftCorner.contains( e->position().toPoint().x(), e->position().toPoint().y() ) )
1110         {
1111             setCursor( Qt::SizeBDiagCursor );
1112         }
1113 
1114 #else
1115 
1116         if      ( d->topLeftCorner.contains( e->x(), e->y() ) ||
1117                   d->bottomRightCorner.contains( e->x(), e->y() ) )
1118         {
1119             setCursor( Qt::SizeFDiagCursor );
1120         }
1121 
1122         else if ( d->topRightCorner.contains( e->x(), e->y() ) ||
1123                   d->bottomLeftCorner.contains( e->x(), e->y() ) )
1124         {
1125             setCursor( Qt::SizeBDiagCursor );
1126         }
1127 
1128 #endif
1129 
1130         else
1131         {
1132             unsetCursor();
1133         }
1134     }
1135 }
1136 
1137 } // namespace DigikamEditorPerspectiveToolPlugin
1138 
1139 #include "moc_perspectivewidget.cpp"