File indexing completed on 2024-12-22 04:16:09
0001 /* 0002 * SPDX-FileCopyrightText: 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "hairy_brush.h" 0008 0009 #include <KoColor.h> 0010 #include <KoColorSpace.h> 0011 #include <KoColorTransformation.h> 0012 #include <KoCompositeOpRegistry.h> 0013 0014 #include <QVariant> 0015 #include <QHash> 0016 #include <QVector> 0017 0018 #include <kis_types.h> 0019 #include <kis_random_accessor_ng.h> 0020 #include <kis_cross_device_color_sampler.h> 0021 #include <kis_fixed_paint_device.h> 0022 0023 0024 #include <cmath> 0025 #include <ctime> 0026 0027 0028 HairyBrush::HairyBrush() 0029 { 0030 m_counter = 0; 0031 m_lastAngle = 0.0; 0032 m_oldPressure = 1.0f; 0033 0034 m_saturationId = -1; 0035 } 0036 0037 HairyBrush::~HairyBrush() 0038 { 0039 delete m_transfo; 0040 qDeleteAll(m_bristles.begin(), m_bristles.end()); 0041 m_bristles.clear(); 0042 } 0043 0044 0045 void HairyBrush::initAndCache() 0046 { 0047 m_compositeOp = m_dab->colorSpace()->compositeOp(COMPOSITE_OVER); 0048 m_pixelSize = m_dab->colorSpace()->pixelSize(); 0049 0050 if (m_properties->useSaturation) { 0051 m_transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", m_params); 0052 if (m_transfo) { 0053 m_saturationId = m_transfo->parameterId("s"); 0054 } 0055 } 0056 } 0057 0058 void HairyBrush::fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density) 0059 { 0060 int width = dab->bounds().width(); 0061 int height = dab->bounds().height(); 0062 0063 int centerX = width * 0.5; 0064 int centerY = height * 0.5; 0065 0066 // make mask 0067 Bristle * bristle = nullptr; 0068 qreal alpha; 0069 0070 quint8 * dabPointer = dab->data(); 0071 quint8 pixelSize = dab->pixelSize(); 0072 const KoColorSpace * cs = dab->colorSpace(); 0073 KoColor bristleColor(cs); 0074 0075 KisRandomSource randomSource(0); 0076 0077 for (int y = 0; y < height; y++) { 0078 for (int x = 0; x < width; x++) { 0079 alpha = cs->opacityF(dabPointer); 0080 if (alpha != 0.0) { 0081 if (density == 1.0 || randomSource.generateNormalized() <= density) { 0082 memcpy(bristleColor.data(), dabPointer, pixelSize); 0083 0084 bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle 0085 bristle->setColor(bristleColor); 0086 0087 m_bristles.append(bristle); 0088 } 0089 } 0090 dabPointer += pixelSize; 0091 } 0092 } 0093 } 0094 0095 0096 void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation) 0097 { 0098 m_counter++; 0099 0100 qreal x1 = pi1.pos().x(); 0101 qreal y1 = pi1.pos().y(); 0102 0103 qreal x2 = pi2.pos().x(); 0104 qreal y2 = pi2.pos().y(); 0105 0106 qreal dx = x2 - x1; 0107 qreal dy = y2 - y1; 0108 0109 // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to 0110 // not computing the drag vector properly in paintBezierLine when smoothing is used 0111 //qreal angle = atan2(dy, dx); 0112 qreal angle = rotation; 0113 0114 qreal mousePressure = 1.0; 0115 if (m_properties->useMousePressure) { // want pressure from mouse movement 0116 qreal distance = sqrt(dx * dx + dy * dy); 0117 mousePressure = (1.0 - computeMousePressure(distance)); 0118 scale *= mousePressure; 0119 } 0120 // this pressure controls shear and ink depletion 0121 qreal pressure = mousePressure * (pi2.pressure() * 2); 0122 0123 Bristle *bristle = 0; 0124 KoColor bristleColor(dab->colorSpace()); 0125 0126 m_dabAccessor = dab->createRandomAccessorNG(); 0127 0128 m_dab = dab; 0129 0130 // initialization block 0131 if (firstStroke()) { 0132 initAndCache(); 0133 } 0134 0135 /*If this is first time the brush touches the canvas and 0136 we are using soak ink while ink depletion is enabled...*/ 0137 if (m_properties->inkDepletionEnabled && 0138 firstStroke() && m_properties->useSoakInk) { 0139 if (layer) { 0140 colorifyBristles(layer, pi1.pos()); 0141 } 0142 else { 0143 dbgKrita << "Can't soak the ink from the layer"; 0144 } 0145 } 0146 0147 KisRandomSourceSP randomSource = pi2.randomSource(); 0148 0149 qreal fx1, fy1, fx2, fy2; 0150 qreal randomX, randomY; 0151 qreal shear; 0152 0153 float inkDepletion = 0.0; 0154 int inkDepletionSize = m_properties->inkDepletionCurve.size(); 0155 int bristleCount = m_bristles.size(); 0156 int bristlePathSize; 0157 qreal threshold = 1.0 - pi2.pressure(); 0158 for (int i = 0; i < bristleCount; i++) { 0159 0160 if (!m_bristles.at(i)->enabled()) continue; 0161 bristle = m_bristles[i]; 0162 0163 randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; 0164 randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; 0165 0166 shear = pressure * m_properties->shearFactor; 0167 0168 m_transform.reset(); 0169 m_transform.rotateRadians(-angle); 0170 m_transform.scale(scale, scale); 0171 m_transform.translate(randomX, randomY); 0172 m_transform.shear(shear, shear); 0173 0174 if (firstStroke() || (!m_properties->connectedPath)) { 0175 // transform start dab 0176 m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1); 0177 // transform end dab 0178 m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); 0179 } 0180 else { 0181 // continue the path of the bristle from the previous position 0182 fx1 = bristle->prevX(); 0183 fy1 = bristle->prevY(); 0184 m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); 0185 } 0186 // remember the end point 0187 bristle->setPrevX(fx2); 0188 bristle->setPrevY(fy2); 0189 0190 // all coords relative to device position 0191 fx1 += x1; 0192 fy1 += y1; 0193 0194 fx2 += x2; 0195 fy2 += y2; 0196 0197 if (m_properties->threshold && (bristle->length() < threshold)) continue; 0198 // paint between first and last dab 0199 const QVector<QPointF> bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0); 0200 bristlePathSize = m_trajectory.size(); 0201 0202 // avoid overlapping bristle caps with antialias on 0203 if (m_properties->antialias) { 0204 bristlePathSize -= 1; 0205 } 0206 0207 memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize); 0208 for (int i = 0; i < bristlePathSize ; i++) { 0209 0210 if (m_properties->inkDepletionEnabled) { 0211 inkDepletion = fetchInkDepletion(bristle, inkDepletionSize); 0212 0213 if (m_properties->useSaturation && m_transfo != 0) { 0214 saturationDepletion(bristle, bristleColor, pressure, inkDepletion); 0215 } 0216 0217 if (m_properties->useOpacity) { 0218 opacityDepletion(bristle, bristleColor, pressure, inkDepletion); 0219 } 0220 0221 } 0222 else { 0223 if (bristleColor.opacityU8() != 0) { 0224 bristleColor.setOpacity(bristle->length()); 0225 } 0226 } 0227 0228 addBristleInk(bristle, bristlePath.at(i), bristleColor); 0229 bristle->setInkAmount(1.0 - inkDepletion); 0230 bristle->upIncrement(); 0231 } 0232 0233 } 0234 m_dab = nullptr; 0235 m_dabAccessor = nullptr; 0236 } 0237 0238 0239 inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize) 0240 { 0241 if (bristle->counter() >= inkDepletionSize - 1) { 0242 return m_properties->inkDepletionCurve[inkDepletionSize - 1]; 0243 } else { 0244 return m_properties->inkDepletionCurve[bristle->counter()]; 0245 } 0246 } 0247 0248 0249 void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDepletion) 0250 { 0251 qreal saturation; 0252 if (m_properties->useWeights) { 0253 // new weighted way (experiment) 0254 saturation = ( 0255 (pressure * m_properties->pressureWeight) + 0256 (bristle->length() * m_properties->bristleLengthWeight) + 0257 (bristle->inkAmount() * m_properties->bristleInkAmountWeight) + 0258 ((1.0 - inkDepletion) * m_properties->inkDepletionWeight)) - 1.0; 0259 } 0260 else { 0261 // old way of computing saturation 0262 saturation = ( 0263 pressure * 0264 bristle->length() * 0265 bristle->inkAmount() * 0266 (1.0 - inkDepletion)) - 1.0; 0267 0268 } 0269 m_transfo->setParameter(m_transfo->parameterId("h"), 0.0); 0270 m_transfo->setParameter(m_transfo->parameterId("v"), 0.0); 0271 m_transfo->setParameter(m_saturationId, saturation); 0272 m_transfo->setParameter(3, 1);//sets the type to 0273 m_transfo->setParameter(4, false);//sets the colorize to none. 0274 m_transfo->transform(bristleColor.data(), bristleColor.data() , 1); 0275 } 0276 0277 void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDepletion) 0278 { 0279 qreal opacity = OPACITY_OPAQUE_F; 0280 if (m_properties->useWeights) { 0281 opacity = pressure * m_properties->pressureWeight + 0282 bristle->length() * m_properties->bristleLengthWeight + 0283 bristle->inkAmount() * m_properties->bristleInkAmountWeight + 0284 (1.0 - inkDepletion) * m_properties->inkDepletionWeight; 0285 } 0286 else { 0287 opacity = 0288 bristle->length() * 0289 bristle->inkAmount(); 0290 } 0291 0292 opacity = qBound(0.0, opacity, 1.0); 0293 bristleColor.setOpacity(opacity); 0294 } 0295 0296 inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color) 0297 { 0298 Q_UNUSED(bristle); 0299 if (m_properties->antialias) { 0300 if (m_properties->useCompositing) { 0301 paintParticle(pos, color); 0302 } else { 0303 paintParticle(pos, color, 1.0); 0304 } 0305 } 0306 else { 0307 int ix = qRound(pos.x()); 0308 int iy = qRound(pos.y()); 0309 if (m_properties->useCompositing) { 0310 plotPixel(ix, iy, color); 0311 } 0312 else { 0313 darkenPixel(ix, iy, color); 0314 } 0315 } 0316 } 0317 0318 void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight) 0319 { 0320 // opacity top left, right, bottom left, right 0321 quint8 opacity = color.opacityU8(); 0322 opacity *= weight; 0323 0324 int ipx = int (pos.x()); 0325 int ipy = int (pos.y()); 0326 qreal fx = qAbs(pos.x() - ipx); 0327 qreal fy = qAbs(pos.y() - ipy); 0328 0329 quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); 0330 quint8 btr = qRound((fx) * (1.0 - fy) * opacity); 0331 quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); 0332 quint8 bbr = qRound((fx) * (fy) * opacity); 0333 0334 const KoColorSpace * cs = m_dab->colorSpace(); 0335 0336 m_dabAccessor->moveTo(ipx , ipy); 0337 btl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); 0338 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); 0339 cs->setOpacity(m_dabAccessor->rawData(), btl, 1); 0340 0341 m_dabAccessor->moveTo(ipx + 1, ipy); 0342 btr = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); 0343 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); 0344 cs->setOpacity(m_dabAccessor->rawData(), btr, 1); 0345 0346 m_dabAccessor->moveTo(ipx, ipy + 1); 0347 bbl = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); 0348 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); 0349 cs->setOpacity(m_dabAccessor->rawData(), bbl, 1); 0350 0351 m_dabAccessor->moveTo(ipx + 1, ipy + 1); 0352 bbr = quint8(qBound<quint16>(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); 0353 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); 0354 cs->setOpacity(m_dabAccessor->rawData(), bbr, 1); 0355 } 0356 0357 void HairyBrush::paintParticle(QPointF pos, const KoColor& color) 0358 { 0359 // opacity top left, right, bottom left, right 0360 memcpy(m_color.data(), color.data(), m_pixelSize); 0361 quint8 opacity = color.opacityU8(); 0362 0363 int ipx = int (pos.x()); 0364 int ipy = int (pos.y()); 0365 qreal fx = qAbs(pos.x() - ipx); 0366 qreal fy = qAbs(pos.y() - ipy); 0367 0368 quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); 0369 quint8 btr = qRound((fx) * (1.0 - fy) * opacity); 0370 quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); 0371 quint8 bbr = qRound((fx) * (fy) * opacity); 0372 0373 m_color.setOpacity(btl); 0374 plotPixel(ipx , ipy, m_color); 0375 0376 m_color.setOpacity(btr); 0377 plotPixel(ipx + 1 , ipy, m_color); 0378 0379 m_color.setOpacity(bbl); 0380 plotPixel(ipx , ipy + 1, m_color); 0381 0382 m_color.setOpacity(bbr); 0383 plotPixel(ipx + 1 , ipy + 1, m_color); 0384 } 0385 0386 0387 inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color) 0388 { 0389 m_dabAccessor->moveTo(wx, wy); 0390 m_compositeOp->composite(m_dabAccessor->rawData(), m_pixelSize, color.data() , m_pixelSize, 0, 0, 1, 1, OPACITY_OPAQUE_U8); 0391 } 0392 0393 inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color) 0394 { 0395 m_dabAccessor->moveTo(wx, wy); 0396 if (m_dab->colorSpace()->opacityU8(m_dabAccessor->rawData()) < color.opacityU8()) { 0397 memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize); 0398 } 0399 } 0400 0401 double HairyBrush::computeMousePressure(double distance) 0402 { 0403 static const double scale = 20.0; 0404 static const double minPressure = 0.02; 0405 0406 double oldPressure = m_oldPressure; 0407 0408 double factor = 1.0 - distance / scale; 0409 if (factor < 0.0) factor = 0.0; 0410 0411 double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0; 0412 0413 m_oldPressure = result; 0414 return result; 0415 } 0416 0417 0418 void HairyBrush::colorifyBristles(KisPaintDeviceSP source, QPointF point) 0419 { 0420 KoColor bristleColor(m_dab->colorSpace()); 0421 KisCrossDeviceColorSamplerInt colorSampler(source, bristleColor); 0422 0423 Bristle *b = 0; 0424 int size = m_bristles.size(); 0425 for (int i = 0; i < size; i++) { 0426 b = m_bristles[i]; 0427 int x = qRound(b->x() + point.x()); 0428 int y = qRound(b->y() + point.y()); 0429 0430 colorSampler.sampleOldColor(x, y, bristleColor.data()); 0431 b->setColor(bristleColor); 0432 } 0433 0434 } 0435 0436