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 ¤tDistance) const 0102 { 0103 KisPaintInformation pi(info); 0104 KisSpacingInformation spacingInfo; 0105 { 0106 KisPaintInformation::DistanceInformationRegistrar r 0107 = pi.registerDistanceInformation(¤tDistance); 0108 spacingInfo = updateSpacingImpl(pi); 0109 } 0110 0111 currentDistance.updateSpacing(spacingInfo); 0112 } 0113 0114 void KisLiquifyPaintop::updateTiming(const KisPaintInformation &info, 0115 KisDistanceInformation ¤tDistance) const 0116 { 0117 KisPaintInformation pi(info); 0118 KisTimingInformation timingInfo; 0119 { 0120 KisPaintInformation::DistanceInformationRegistrar r 0121 = pi.registerDistanceInformation(¤tDistance); 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 }