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

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_paintop.h"
0008 
0009 #include <QPainterPath>
0010 #include <QTransform>
0011 
0012 
0013 #include <brushengine/kis_paint_information.h>
0014 #include "kis_liquify_transform_worker.h"
0015 #include "kis_algebra_2d.h"
0016 #include "kis_liquify_properties.h"
0017 #include "kis_spacing_information.h"
0018 #include "kis_timing_information.h"
0019 
0020 
0021 struct KisLiquifyPaintop::Private
0022 {
0023     Private(const KisLiquifyProperties &_props, KisLiquifyTransformWorker *_worker)
0024         : props(_props), worker(_worker) {}
0025 
0026     KisLiquifyProperties props;
0027     KisLiquifyTransformWorker *worker;
0028 };
0029 
0030 KisLiquifyPaintop::KisLiquifyPaintop(const KisLiquifyProperties &props, KisLiquifyTransformWorker *worker)
0031     : m_d(new Private(props, worker))
0032 {
0033 }
0034 
0035 KisLiquifyPaintop::~KisLiquifyPaintop()
0036 {
0037 }
0038 
0039 QPainterPath KisLiquifyPaintop::brushOutline(const KisLiquifyProperties &props,
0040                                              const KisPaintInformation &info)
0041 {
0042     const qreal diameter = props.size();
0043     const qreal reverseCoeff = props.reverseDirection() ? -1.0 : 1.0;
0044 
0045     QPainterPath outline;
0046     outline.addEllipse(-0.5 * diameter, -0.5 * diameter,
0047                        diameter, diameter);
0048 
0049     switch (props.mode()) {
0050     case KisLiquifyProperties::MOVE:
0051     case KisLiquifyProperties::SCALE:
0052         break;
0053     case KisLiquifyProperties::ROTATE: {
0054         QPainterPath p;
0055         p.lineTo(-3.0, 4.0);
0056         p.moveTo(0.0, 0.0);
0057         p.lineTo(-3.0, -4.0);
0058 
0059         QTransform S;
0060         if (diameter < 15.0) {
0061             const qreal scale = diameter / 15.0;
0062             S = QTransform::fromScale(scale, scale);
0063         }
0064         QTransform R;
0065         R.rotateRadians(-reverseCoeff * 0.5 * M_PI);
0066         QTransform T = QTransform::fromTranslate(0.5 * diameter, 0.0);
0067 
0068         p = (S * R * T).map(p);
0069         outline.addPath(p);
0070 
0071         break;
0072     }
0073     case KisLiquifyProperties::OFFSET: {
0074         qreal normalAngle = info.drawingAngle() + reverseCoeff * 0.5 * M_PI;
0075 
0076         QPainterPath p = KisAlgebra2D::smallArrow();
0077 
0078         const qreal offset = qMax(0.8 * diameter, 15.0);
0079 
0080         QTransform R;
0081         R.rotateRadians(normalAngle);
0082         QTransform T = QTransform::fromTranslate(offset, 0.0);
0083         p = (T * R).map(p);
0084 
0085         outline.addPath(p);
0086 
0087         break;
0088     }
0089     case KisLiquifyProperties::UNDO:
0090         break;
0091     case KisLiquifyProperties::N_MODES:
0092         qFatal("Not supported mode");
0093     }
0094 
0095     return outline;
0096 }
0097 
0098 // TODO: Reduce code duplication between KisLiquifyPaintop and KisPaintOp. It might be possible to
0099 // make them both subclasses of some more general base class.
0100 void KisLiquifyPaintop::updateSpacing(const KisPaintInformation &info,
0101                                KisDistanceInformation &currentDistance) const
0102 {
0103     KisPaintInformation pi(info);
0104     KisSpacingInformation spacingInfo;
0105     {
0106         KisPaintInformation::DistanceInformationRegistrar r
0107             = pi.registerDistanceInformation(&currentDistance);
0108         spacingInfo = updateSpacingImpl(pi);
0109     }
0110 
0111     currentDistance.updateSpacing(spacingInfo);
0112 }
0113 
0114 void KisLiquifyPaintop::updateTiming(const KisPaintInformation &info,
0115                                      KisDistanceInformation &currentDistance) const
0116 {
0117     KisPaintInformation pi(info);
0118     KisTimingInformation timingInfo;
0119     {
0120         KisPaintInformation::DistanceInformationRegistrar r
0121             = pi.registerDistanceInformation(&currentDistance);
0122         timingInfo = updateTimingImpl(pi);
0123     }
0124 
0125     currentDistance.updateTiming(timingInfo);
0126 }
0127 
0128 KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi)
0129 {
0130     const qreal size = computeSize(pi);
0131 
0132     const qreal spacing = m_d->props.spacing() * size;
0133 
0134     const qreal reverseCoeff =
0135         m_d->props.mode() !=
0136         KisLiquifyProperties::UNDO &&
0137         m_d->props.reverseDirection() ? -1.0 : 1.0;
0138     const qreal amount = m_d->props.amountHasPressure() ?
0139         pi.pressure() * reverseCoeff * m_d->props.amount():
0140         reverseCoeff * m_d->props.amount();
0141 
0142     const bool useWashMode = m_d->props.useWashMode();
0143     const qreal flow = m_d->props.flow();
0144 
0145     switch (m_d->props.mode()) {
0146     case KisLiquifyProperties::MOVE: {
0147         const qreal offsetLength = size * amount;
0148         m_d->worker->translatePoints(pi.pos(),
0149                                      pi.drawingDirectionVector() * offsetLength,
0150                                      size, useWashMode, flow);
0151 
0152         break;
0153     }
0154     case KisLiquifyProperties::SCALE:
0155         m_d->worker->scalePoints(pi.pos(),
0156                                  amount,
0157                                  size, useWashMode, flow);
0158         break;
0159     case KisLiquifyProperties::ROTATE:
0160         m_d->worker->rotatePoints(pi.pos(),
0161                                   2.0 * M_PI * amount,
0162                                   size, useWashMode, flow);
0163         break;
0164     case KisLiquifyProperties::OFFSET: {
0165         const qreal offsetLength = size * amount;
0166         m_d->worker->translatePoints(pi.pos(),
0167                                      KisAlgebra2D::rightUnitNormal(pi.drawingDirectionVector()) * offsetLength,
0168                                      size, useWashMode, flow);
0169         break;
0170     }
0171     case KisLiquifyProperties::UNDO:
0172         m_d->worker->undoPoints(pi.pos(),
0173                                 amount,
0174                                 size);
0175 
0176         break;
0177     case KisLiquifyProperties::N_MODES:
0178         qFatal("Not supported mode");
0179     }
0180 
0181     return KisSpacingInformation(spacing);
0182 }
0183 
0184 KisSpacingInformation KisLiquifyPaintop::updateSpacingImpl(const KisPaintInformation &pi) const
0185 {
0186     return KisSpacingInformation(m_d->props.spacing() * computeSize(pi));
0187 }
0188 
0189 KisTimingInformation KisLiquifyPaintop::updateTimingImpl(const KisPaintInformation &pi) const
0190 {
0191     Q_UNUSED(pi);
0192     // Don't use airbrushing.
0193     return KisTimingInformation();
0194 }
0195 
0196 qreal KisLiquifyPaintop::computeSize(const KisPaintInformation &pi) const
0197 {
0198     static const qreal sizeToSigmaCoeff = 1.0 / 3.0;
0199     return sizeToSigmaCoeff *
0200         (m_d->props.sizeHasPressure() ?
0201          pi.pressure() * m_d->props.size():
0202          m_d->props.size());
0203 }