File indexing completed on 2025-02-02 04:22:20
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com> 0003 * SPDX-FileCopyrightText: 2010 Ricardo Cabello <hello@mrdoob.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kis_sketch_paintop.h" 0009 #include "kis_sketch_paintop_settings.h" 0010 0011 #include <cmath> 0012 #include <QRect> 0013 0014 #include <KoColor.h> 0015 #include <KoColorSpace.h> 0016 0017 #include <kis_image.h> 0018 #include <kis_debug.h> 0019 0020 #include <kis_global.h> 0021 #include <kis_paint_device.h> 0022 #include <kis_painter.h> 0023 #include <kis_types.h> 0024 #include <kis_paintop_plugin_utils.h> 0025 #include <brushengine/kis_paintop.h> 0026 #include <brushengine/kis_paint_information.h> 0027 #include <kis_fixed_paint_device.h> 0028 0029 #include <kis_dab_cache.h> 0030 #include "kis_lod_transform.h" 0031 #include <KoResourceLoadResult.h> 0032 0033 0034 #include <QtGlobal> 0035 0036 /* 0037 * Based on Harmony project https://github.com/mrdoob/harmony/ 0038 */ 0039 // chrome : diff 0.2, sketchy : 0.3, fur: 0.5 0040 // fur : distance / thresholdDistance 0041 0042 // shaded: opacity per line :/ 0043 // ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0 0044 // chrome: color per line :/ 0045 //this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )"; 0046 0047 // long fur 0048 // from: count + offset * -random 0049 // to: i point - (offset * -random) + random * 2 0050 // probability distance / thresholdDistance 0051 0052 // shaded: probability : paint always - 0.0 density 0053 0054 KisSketchPaintOp::KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) 0055 : KisPaintOp(painter) 0056 , m_opacityOption(settings.data()) 0057 , m_sizeOption(settings.data()) 0058 , m_rotationOption(settings.data()) 0059 , m_rateOption(settings.data()) 0060 , m_densityOption(settings.data()) 0061 , m_lineWidthOption(settings.data()) 0062 , m_offsetScaleOption(settings.data()) 0063 { 0064 Q_UNUSED(image); 0065 Q_UNUSED(node); 0066 0067 m_airbrushOption.read(settings.data()); 0068 m_sketchProperties.read(settings.data()); 0069 m_brushOption.readOptionSetting(settings, settings->resourcesInterface(), settings->canvasResourcesInterface()); 0070 0071 m_brush = m_brushOption.brush(); 0072 m_dabCache = new KisDabCache(m_brush); 0073 0074 m_painter = 0; 0075 m_count = 0; 0076 } 0077 0078 KisSketchPaintOp::~KisSketchPaintOp() 0079 { 0080 delete m_painter; 0081 delete m_dabCache; 0082 } 0083 0084 QList<KoResourceLoadResult> KisSketchPaintOp::prepareLinkedResources(const KisPaintOpSettingsSP settings, KisResourcesInterfaceSP resourcesInterface) 0085 { 0086 KisBrushOptionProperties brushOption; 0087 return brushOption.prepareLinkedResources(settings, resourcesInterface); 0088 } 0089 0090 void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth) 0091 { 0092 //Both drawWuLine() and the drawDDALine produce nicer 1px lines than the drawLine() 0093 if (m_sketchProperties.antiAliasing) { 0094 if (lineWidth == 1.0) { 0095 m_painter->drawWuLine(start, end); 0096 } 0097 else { 0098 m_painter->drawLine(start, end, lineWidth, true); 0099 } 0100 } 0101 else { 0102 if (lineWidth == 1.0) { 0103 m_painter->drawDDALine(start, end); 0104 } 0105 else { 0106 m_painter->drawLine(start, end, lineWidth, false); 0107 } 0108 } 0109 } 0110 0111 void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation) 0112 { 0113 QRect dstRect; 0114 m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(), 0115 painter()->paintColor(), 0116 info.pos(), 0117 KisDabShape(scale, 1.0, rotation), 0118 info, 1.0, 0119 &dstRect); 0120 0121 m_brushBoundingBox = dstRect; 0122 m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(), 0123 0.5 * m_brushBoundingBox.height()); 0124 } 0125 0126 void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, 0127 KisDistanceInformation *currentDistance) 0128 { 0129 // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster 0130 // than it is supposed to. 0131 if (pi1.pos() == pi2.pos()) { 0132 KisPaintOp::paintLine(pi1, pi2, currentDistance); 0133 } 0134 else { 0135 doPaintLine(pi1, pi2); 0136 } 0137 } 0138 void KisSketchPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) 0139 { 0140 if (!m_brush || !painter()) return; 0141 0142 if (!m_dab) { 0143 m_dab = source()->createCompositionSourceDevice(); 0144 m_painter = new KisPainter(m_dab); 0145 m_painter->setPaintColor(painter()->paintColor()); 0146 } 0147 else { 0148 m_dab->clear(); 0149 } 0150 0151 QPointF prevMouse = pi1.pos(); 0152 QPointF mousePosition = pi2.pos(); 0153 m_points.append(mousePosition); 0154 0155 0156 const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device()); 0157 const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2); 0158 if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return; 0159 0160 const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2) * m_sketchProperties.lineWidth); 0161 0162 const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2) * m_sketchProperties.offset * 0.01; 0163 const double rotation = m_rotationOption.apply(pi2); 0164 const double currentProbability = m_densityOption.apply(pi2) * m_sketchProperties.probability; 0165 0166 // shaded: does not draw this line, chrome does, fur does 0167 if (m_sketchProperties.makeConnection) { 0168 drawConnection(prevMouse, mousePosition, currentLineWidth); 0169 } 0170 0171 0172 qreal thresholdDistance = 0.0; 0173 0174 // update the mask for simple mode only once 0175 // determine the radius 0176 if (m_count == 0 && m_sketchProperties.simpleMode) { 0177 updateBrushMask(pi2, 1.0, 0.0); 0178 //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5; 0179 m_radius = 0.5 * qMax(m_brush->width(), m_brush->height()); 0180 } 0181 0182 if (!m_sketchProperties.simpleMode) { 0183 updateBrushMask(pi2, scale, rotation); 0184 m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5; 0185 thresholdDistance = pow(m_radius, 2); 0186 } 0187 0188 if (m_sketchProperties.simpleMode) { 0189 // update the radius according scale in simple mode 0190 thresholdDistance = pow(m_radius * scale, 2); 0191 } 0192 0193 // determine density 0194 const qreal density = thresholdDistance * currentProbability; 0195 0196 // probability behaviour 0197 qreal probability = 1.0 - currentProbability; 0198 0199 QColor painterColor = painter()->paintColor().toQColor(); 0200 QColor randomColor; 0201 KoColor color(m_dab->colorSpace()); 0202 0203 int w = m_maskDab->bounds().width(); 0204 quint8 opacityU8 = 0; 0205 quint8 * pixel; 0206 qreal distance; 0207 QPoint positionInMask; 0208 QPointF diff; 0209 0210 int size = m_points.size(); 0211 // MAIN LOOP 0212 for (int i = 0; i < size; i++) { 0213 diff = m_points.at(i) - mousePosition; 0214 distance = diff.x() * diff.x() + diff.y() * diff.y(); 0215 0216 // circle test 0217 bool makeConnection = false; 0218 if (m_sketchProperties.simpleMode) { 0219 if (distance < thresholdDistance) { 0220 makeConnection = true; 0221 } 0222 // mask test 0223 } 0224 else { 0225 if (m_brushBoundingBox.contains(m_points.at(i))) { 0226 positionInMask = (diff + m_hotSpot).toPoint(); 0227 uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize()); 0228 if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) { 0229 pixel = m_maskDab->data() + pos; 0230 opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel); 0231 if (opacityU8 != 0) { 0232 makeConnection = true; 0233 } 0234 } 0235 } 0236 0237 } 0238 0239 if (!makeConnection) { 0240 // check next point 0241 continue; 0242 } 0243 0244 if (m_sketchProperties.distanceDensity) { 0245 probability = distance / density; 0246 } 0247 0248 KisRandomSourceSP randomSource = pi2.randomSource(); 0249 0250 // density check 0251 if (randomSource->generateNormalized() >= probability) { 0252 QPointF offsetPt = diff * currentOffsetScale; 0253 0254 if (m_sketchProperties.randomRGB) { 0255 /** 0256 * Since the order of calculation of function 0257 * parameters is not defined by C++ standard, we 0258 * should generate values in an external code snippet 0259 * which has a definite order of execution. 0260 */ 0261 qreal r1 = randomSource->generateNormalized(); 0262 qreal r2 = randomSource->generateNormalized(); 0263 qreal r3 = randomSource->generateNormalized(); 0264 0265 // some color transformation per line goes here 0266 randomColor.setRgbF(r1 * painterColor.redF(), 0267 r2 * painterColor.greenF(), 0268 r3 * painterColor.blueF()); 0269 color.fromQColor(randomColor); 0270 m_painter->setPaintColor(color); 0271 } 0272 0273 // distance based opacity 0274 quint8 opacity = OPACITY_OPAQUE_U8; 0275 if (m_sketchProperties.distanceOpacity) { 0276 opacity *= qRound((1.0 - (distance / thresholdDistance))); 0277 } 0278 0279 if (m_sketchProperties.randomOpacity) { 0280 opacity *= randomSource->generateNormalized(); 0281 } 0282 0283 m_painter->setOpacity(opacity); 0284 0285 if (m_sketchProperties.magnetify) { 0286 drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth); 0287 } 0288 else { 0289 drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth); 0290 } 0291 0292 0293 0294 } 0295 }// end of MAIN LOOP 0296 0297 m_count++; 0298 0299 QRect rc = m_dab->extent(); 0300 quint8 origOpacity = m_opacityOption.apply(painter(), pi2); 0301 0302 painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); 0303 painter()->renderMirrorMask(rc, m_dab); 0304 painter()->setOpacity(origOpacity); 0305 } 0306 0307 0308 0309 KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) 0310 { 0311 doPaintLine(info, info); 0312 return updateSpacingImpl(info); 0313 } 0314 0315 KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const 0316 { 0317 return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, 0318 KisLodTransform::lodToScale(painter()->device()), 0319 &m_airbrushOption, nullptr, info); 0320 } 0321 0322 KisTimingInformation KisSketchPaintOp::updateTimingImpl(const KisPaintInformation &info) const 0323 { 0324 return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); 0325 }