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"