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 ¤tArgs; 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 ¤tArgs, 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