File indexing completed on 2024-12-22 04:16:54

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_liquify_transform_strategy.h"
0008 
0009 #include <algorithm>
0010 
0011 #include <QPointF>
0012 #include <QPainter>
0013 #include <QPainterPath>
0014 
0015 #include "KoPointerEvent.h"
0016 
0017 #include "kis_coordinates_converter.h"
0018 #include "tool_transform_args.h"
0019 #include "transform_transaction_properties.h"
0020 #include "krita_utils.h"
0021 #include "kis_cursor.h"
0022 #include "kis_transform_utils.h"
0023 #include "kis_algebra_2d.h"
0024 #include "kis_liquify_paint_helper.h"
0025 #include "kis_liquify_transform_worker.h"
0026 #include "KoCanvasResourceProvider.h"
0027 #include "kis_tool_utils.h"
0028 
0029 
0030 struct KisLiquifyTransformStrategy::Private
0031 {
0032     Private(KisLiquifyTransformStrategy *_q,
0033             const KisCoordinatesConverter *_converter,
0034             ToolTransformArgs &_currentArgs,
0035             TransformTransactionProperties &_transaction,
0036             const KoCanvasResourceProvider *_manager)
0037         : manager(_manager),
0038           q(_q),
0039           converter(_converter),
0040           currentArgs(_currentArgs),
0041           transaction(_transaction),
0042           helper(_converter),
0043           recalculateOnNextRedraw(false)
0044     {
0045     }
0046 
0047     const KoCanvasResourceProvider *manager;
0048 
0049     KisLiquifyTransformStrategy * const q;
0050 
0051     /// standard members ///
0052 
0053     const KisCoordinatesConverter *converter;
0054 
0055     //////
0056     ToolTransformArgs &currentArgs;
0057     //////
0058     TransformTransactionProperties &transaction;
0059 
0060     QTransform paintingTransform;
0061     QPointF paintingOffset;
0062 
0063     QTransform handlesTransform;
0064 
0065     /// custom members ///
0066 
0067     QImage transformedImage;
0068 
0069     // size-gesture-related
0070     QPointF lastMouseWidgetPos;
0071     QPointF startResizeImagePos;
0072     QPoint startResizeGlobalCursorPos;
0073 
0074     KisLiquifyPaintHelper helper;
0075 
0076     bool recalculateOnNextRedraw;
0077 
0078     void recalculateTransformations();
0079     inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
0080 };
0081 
0082 KisLiquifyTransformStrategy::KisLiquifyTransformStrategy(const KisCoordinatesConverter *converter,
0083                                                          ToolTransformArgs &currentArgs,
0084                                                          TransformTransactionProperties &transaction,
0085                                                          const KoCanvasResourceProvider *manager)
0086 
0087     : m_d(new Private(this, converter, currentArgs, transaction, manager))
0088 {
0089 }
0090 
0091 KisLiquifyTransformStrategy::~KisLiquifyTransformStrategy()
0092 {
0093 }
0094 
0095 QPainterPath KisLiquifyTransformStrategy::getCursorOutline() const
0096 {
0097     return m_d->helper.brushOutline(*m_d->currentArgs.liquifyProperties());
0098 }
0099 
0100 void KisLiquifyTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive)
0101 {
0102     Q_UNUSED(mousePos);
0103     Q_UNUSED(perspectiveModifierActive);
0104     Q_UNUSED(shiftModifierActive);
0105 }
0106 
0107 QCursor KisLiquifyTransformStrategy::getCurrentCursor() const
0108 {
0109     return Qt::BlankCursor;
0110 }
0111 
0112 void KisLiquifyTransformStrategy::paint(QPainter &gc)
0113 {
0114     // Draw preview image
0115 
0116     if (m_d->recalculateOnNextRedraw) {
0117         m_d->recalculateTransformations();
0118         m_d->recalculateOnNextRedraw = false;
0119     }
0120 
0121     gc.save();
0122 
0123     gc.setOpacity(m_d->transaction.basePreviewOpacity());
0124     gc.setTransform(m_d->paintingTransform, true);
0125     gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
0126 
0127     gc.restore();
0128 }
0129 
0130 void KisLiquifyTransformStrategy::externalConfigChanged()
0131 {
0132     if (!m_d->currentArgs.liquifyWorker()) return;
0133     m_d->recalculateTransformations();
0134 }
0135 
0136 bool KisLiquifyTransformStrategy::acceptsClicks() const
0137 {
0138     return true;
0139 }
0140 
0141 bool KisLiquifyTransformStrategy::beginPrimaryAction(KoPointerEvent *event)
0142 {
0143     m_d->helper.configurePaintOp(*m_d->currentArgs.liquifyProperties(), m_d->currentArgs.liquifyWorker());
0144     m_d->helper.startPaint(event, m_d->manager);
0145 
0146     m_d->recalculateTransformations();
0147 
0148     return true;
0149 }
0150 
0151 void KisLiquifyTransformStrategy::continuePrimaryAction(KoPointerEvent *event)
0152 {
0153     m_d->helper.continuePaint(event);
0154 
0155     // the updates should be compressed
0156     m_d->recalculateOnNextRedraw = true;
0157     emit requestCanvasUpdate();
0158 }
0159 
0160 bool KisLiquifyTransformStrategy::endPrimaryAction(KoPointerEvent *event)
0161 {
0162     if (m_d->helper.endPaint(event)) {
0163         m_d->recalculateTransformations();
0164         emit requestCanvasUpdate();
0165     }
0166 
0167     return true;
0168 }
0169 
0170 void KisLiquifyTransformStrategy::hoverActionCommon(KoPointerEvent *event)
0171 {
0172     m_d->helper.hoverPaint(event);
0173 }
0174 
0175 void KisLiquifyTransformStrategy::activateAlternateAction(KisTool::AlternateAction action)
0176 {
0177     if (action == KisTool::SampleFgNode || action == KisTool::SampleBgNode ||
0178         action == KisTool::SampleFgImage || action == KisTool::SampleBgImage) {
0179 
0180         KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
0181         props->setReverseDirection(!props->reverseDirection());
0182         emit requestUpdateOptionWidget();
0183     }
0184 }
0185 
0186 void KisLiquifyTransformStrategy::deactivateAlternateAction(KisTool::AlternateAction action)
0187 {
0188     if (action == KisTool::SampleFgNode || action == KisTool::SampleBgNode ||
0189         action == KisTool::SampleFgImage || action == KisTool::SampleBgImage) {
0190 
0191         KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
0192         props->setReverseDirection(!props->reverseDirection());
0193         emit requestUpdateOptionWidget();
0194     }
0195 }
0196 
0197 bool KisLiquifyTransformStrategy::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
0198 {
0199     if (action == KisTool::ChangeSize || action == KisTool::ChangeSizeSnap) {
0200         QPointF widgetPoint = m_d->converter->documentToWidget(event->point);
0201         m_d->lastMouseWidgetPos = widgetPoint;
0202         m_d->startResizeImagePos = m_d->converter->documentToImage(event->point);
0203         m_d->startResizeGlobalCursorPos = QCursor::pos();
0204         return true;
0205     } else if (action == KisTool::SampleFgNode || action == KisTool::SampleBgNode ||
0206                action == KisTool::SampleFgImage || action == KisTool::SampleBgImage) {
0207 
0208         return beginPrimaryAction(event);
0209     }
0210 
0211     return false;
0212 }
0213 
0214 void KisLiquifyTransformStrategy::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
0215 {
0216     if (action == KisTool::ChangeSize || action == KisTool::ChangeSizeSnap) {
0217         QPointF widgetPoint = m_d->converter->documentToWidget(event->point);
0218 
0219         QPointF diff = widgetPoint - m_d->lastMouseWidgetPos;
0220 
0221         KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
0222         const qreal linearizedOffset = diff.x() / KisTransformUtils::scaleFromAffineMatrix(m_d->converter->imageToWidgetTransform());
0223         const qreal newSize = qBound(props->minSize(), props->size() + linearizedOffset, props->maxSize());
0224         if (action == KisTool::ChangeSizeSnap) {
0225             props->setSize(floor(newSize));
0226         } else {
0227             props->setSize(newSize);
0228         }
0229         m_d->currentArgs.saveLiquifyTransformMode();
0230 
0231         m_d->lastMouseWidgetPos = widgetPoint;
0232 
0233         emit requestCursorOutlineUpdate(m_d->startResizeImagePos);
0234     } else if (action == KisTool::SampleFgNode || action == KisTool::SampleBgNode ||
0235                action == KisTool::SampleFgImage || action == KisTool::SampleBgImage) {
0236 
0237         return continuePrimaryAction(event);
0238     }
0239 }
0240 
0241 bool KisLiquifyTransformStrategy::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
0242 {
0243     Q_UNUSED(event);
0244 
0245     if (action == KisTool::ChangeSize || action == KisTool::ChangeSizeSnap) {
0246         KisToolUtils::setCursorPos(m_d->startResizeGlobalCursorPos);
0247         return true;
0248     } else if (action == KisTool::SampleFgNode || action == KisTool::SampleBgNode ||
0249                action == KisTool::SampleFgImage || action == KisTool::SampleBgImage) {
0250         return endPrimaryAction(event);
0251     }
0252 
0253     return false;
0254 }
0255 
0256 inline QPointF KisLiquifyTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization)
0257 {
0258     return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt);
0259 }
0260 
0261 void KisLiquifyTransformStrategy::Private::recalculateTransformations()
0262 {
0263     KIS_ASSERT_RECOVER_RETURN(currentArgs.liquifyWorker());
0264 
0265     QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter);
0266 
0267     QTransform resultThumbTransform = q->thumbToImageTransform() * scaleTransform;
0268     qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultThumbTransform);
0269     bool useFlakeOptimization = scale < 1.0 &&
0270         !KisTransformUtils::thumbnailTooSmall(resultThumbTransform, q->originalImage().rect());
0271 
0272     paintingOffset = transaction.originalTopLeft();
0273     if (!q->originalImage().isNull()) {
0274         if (useFlakeOptimization) {
0275             transformedImage = q->originalImage().transformed(resultThumbTransform);
0276             paintingTransform = QTransform();
0277         } else {
0278             transformedImage = q->originalImage();
0279             paintingTransform = resultThumbTransform;
0280         }
0281 
0282         QTransform imageToRealThumbTransform =
0283             useFlakeOptimization ?
0284             scaleTransform :
0285             q->thumbToImageTransform().inverted();
0286 
0287         QPointF origTLInFlake =
0288             imageToRealThumbTransform.map(transaction.originalTopLeft());
0289 
0290         transformedImage =
0291             currentArgs.liquifyWorker()->runOnQImage(transformedImage,
0292                                                      origTLInFlake,
0293                                                      imageToRealThumbTransform,
0294                                                      &paintingOffset);
0295     } else {
0296         transformedImage = q->originalImage();
0297         paintingOffset = imageToThumb(transaction.originalTopLeft(), false);
0298         paintingTransform = resultThumbTransform;
0299     }
0300 
0301     handlesTransform = scaleTransform;
0302     emit q->requestImageRecalculation();
0303 }
0304