File indexing completed on 2024-06-16 04:17:37
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 "spray_brush.h" 0008 0009 #include <KoColor.h> 0010 #include <KoColorSpace.h> 0011 #include <KoColorTransformation.h> 0012 #include <KoCompositeOp.h> 0013 #include <KoMixColorsOp.h> 0014 0015 #include <brushengine/kis_paintop.h> 0016 0017 #include <QVariant> 0018 #include <QHash> 0019 #include <QTransform> 0020 #include <QImage> 0021 0022 #include <kis_random_accessor_ng.h> 0023 #include <kis_random_sub_accessor.h> 0024 0025 #include <kis_paint_device.h> 0026 0027 #include <kis_painter.h> 0028 #include <brushengine/kis_paint_information.h> 0029 #include <kis_fixed_paint_device.h> 0030 #include <kis_cross_device_color_sampler.h> 0031 0032 #include "kis_spray_paintop_settings.h" 0033 0034 #include <cmath> 0035 #include <ctime> 0036 0037 #include <QtGlobal> 0038 0039 SprayBrush::SprayBrush() 0040 { 0041 m_painter = nullptr; 0042 m_transfo = nullptr; 0043 } 0044 0045 SprayBrush::~SprayBrush() 0046 { 0047 delete m_painter; 0048 delete m_transfo; 0049 } 0050 0051 void SprayBrush::setProperties(KisSprayOpOptionData * properties, 0052 KisColorOptionData * colorProperties, 0053 KisSprayShapeOptionData * shapeProperties, 0054 KisSprayShapeDynamicsOptionData * shapeDynamicsProperties, 0055 KisBrushSP brush) 0056 { 0057 m_sprayOpOptionData = properties; 0058 m_sprayOpOption = new KisSprayOpOption(*properties); 0059 m_sprayOpOption->updateDistributions(); 0060 m_colorProperties = colorProperties; 0061 m_shapeProperties = shapeProperties; 0062 m_shapeDynamicsProperties = shapeDynamicsProperties; 0063 m_brush = brush; 0064 if (m_brush) { 0065 m_brush->notifyStrokeStarted(); 0066 } 0067 } 0068 0069 qreal SprayBrush::rotationAngle(KisRandomSourceSP randomSource) 0070 { 0071 qreal rotation = 0.0; 0072 0073 if (m_shapeDynamicsProperties->fixedRotation) { 0074 rotation = deg2rad(m_shapeDynamicsProperties->fixedAngle); 0075 } 0076 0077 if (m_shapeDynamicsProperties->randomRotation) { 0078 0079 qreal randomValue = 0.0; 0080 0081 randomValue = randomSource->generateNormalized(); 0082 0083 rotation = 0084 linearInterpolation(rotation , 0085 M_PI * 2.0 * randomValue, 0086 m_shapeDynamicsProperties->randomRotationWeight); 0087 } 0088 0089 return rotation; 0090 } 0091 0092 void SprayBrush::paint(KisPaintDeviceSP dab, KisPaintDeviceSP source, 0093 const KisPaintInformation& info, 0094 qreal rotation, qreal scale, 0095 qreal additionalScale, 0096 const KoColor &color, const KoColor &bgColor) 0097 { 0098 if (m_sprayOpOption->data.angularDistributionType == KisSprayOpOptionData::ParticleDistribution_Uniform) { 0099 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, m_sprayOpOption->m_uniformDistribution); 0100 } else { 0101 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, m_sprayOpOption->m_angularCurveBasedDistribution); 0102 } 0103 } 0104 0105 template <typename AngularDistribution> 0106 void SprayBrush::paintImpl(KisPaintDeviceSP dab, KisPaintDeviceSP source, 0107 const KisPaintInformation& info, 0108 qreal rotation, qreal scale, 0109 qreal additionalScale, 0110 const KoColor &color, 0111 const KoColor &bgColor, 0112 const AngularDistribution &angularDistribution) 0113 { 0114 if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_Uniform) { 0115 if (m_sprayOpOption->data.radialDistributionCenterBiased) { 0116 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0117 angularDistribution, m_sprayOpOption->m_uniformDistribution); 0118 } else { 0119 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0120 angularDistribution, m_sprayOpOption->m_uniformDistributionPolarDistance); 0121 } 0122 } else if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_Gaussian) { 0123 if (m_sprayOpOption->data.radialDistributionCenterBiased) { 0124 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0125 angularDistribution, m_sprayOpOption->m_normalDistribution); 0126 } else { 0127 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0128 angularDistribution, m_sprayOpOption->m_normalDistributionPolarDistance); 0129 } 0130 } else if (m_sprayOpOption->data.radialDistributionType == KisSprayOpOptionData::ParticleDistribution_ClusterBased) { 0131 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0132 angularDistribution, m_sprayOpOption->m_clusterBasedDistributionPolarDistance); 0133 } else { 0134 paintImpl(dab, source, info, rotation, scale, additionalScale, color, bgColor, 0135 angularDistribution, m_sprayOpOption->m_radialCurveBasedDistributionPolarDistance); 0136 } 0137 } 0138 0139 template <typename AngularDistribution, typename RadialDistribution> 0140 void SprayBrush::paintImpl(KisPaintDeviceSP dab, KisPaintDeviceSP source, 0141 const KisPaintInformation& info, 0142 qreal rotation, qreal scale, 0143 qreal additionalScale, 0144 const KoColor &color, 0145 const KoColor &bgColor, 0146 const AngularDistribution &angularDistribution, 0147 const RadialDistribution &radialDistribution) 0148 { 0149 if (!angularDistribution.isValid() || !radialDistribution.isValid()) { 0150 return; 0151 } 0152 0153 KisRandomSourceSP randomSource = info.randomSource(); 0154 0155 const QSize effectiveSize = m_shapeProperties->effectiveSize(m_sprayOpOptionData->diameter, m_sprayOpOptionData->scale); 0156 0157 // initializing painter 0158 if (!m_painter) { 0159 m_painter = new KisPainter(dab); 0160 m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); 0161 m_painter->setMaskImageSize(effectiveSize.width(), effectiveSize.height()); 0162 m_dabPixelSize = dab->colorSpace()->pixelSize(); 0163 if (m_colorProperties->useRandomHSV) { 0164 m_transfo = dab->colorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>()); 0165 } 0166 0167 m_brushQImage = m_shapeProperties->image; 0168 if (!m_brushQImage.isNull()) { 0169 m_brushQImage = m_brushQImage.scaled(effectiveSize); 0170 } 0171 m_imageDevice = new KisPaintDevice(dab->colorSpace()); 0172 } 0173 0174 0175 qreal x = info.pos().x(); 0176 qreal y = info.pos().y(); 0177 KisRandomAccessorSP accessor = dab->createRandomAccessorNG(); 0178 0179 Q_ASSERT(color.colorSpace()->pixelSize() == dab->pixelSize()); 0180 m_inkColor = color; 0181 KisCrossDeviceColorSampler colorSampler(source, m_inkColor); 0182 0183 // apply size sensor 0184 m_radius = m_sprayOpOption->data.diameter/2 * scale * additionalScale; 0185 0186 // jitter movement 0187 if (m_sprayOpOption->data.jitterMovement) { 0188 x = x + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_sprayOpOption->data.jitterAmount; 0189 y = y + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_sprayOpOption->data.jitterAmount; 0190 } 0191 0192 // this is wrong for every shape except pixel and anti-aliased pixel 0193 0194 0195 if (m_sprayOpOption->data.useDensity) { 0196 m_particlesCount = (m_sprayOpOption->data.coverage * (M_PI * pow2(m_radius)) / pow2(additionalScale)); 0197 } 0198 else { 0199 m_particlesCount = m_sprayOpOption->data.particleCount; 0200 } 0201 0202 QHash<QString, QVariant> params; 0203 qreal nx, ny; 0204 int ix, iy; 0205 0206 qreal angle; 0207 qreal length; 0208 qreal rotationZ = 0.0; 0209 qreal particleScale = 1.0; 0210 0211 bool shouldColor = true; 0212 if (m_colorProperties->fillBackground) { 0213 m_painter->setPaintColor(bgColor); 0214 paintCircle(m_painter, x, y, m_radius); 0215 } 0216 0217 QTransform m; 0218 m.reset(); 0219 m.rotateRadians(-rotation + deg2rad(m_sprayOpOption->data.brushRotation)); 0220 m.scale(m_sprayOpOption->data.scale, m_sprayOpOption->data.scale); 0221 0222 for (quint32 i = 0; i < m_particlesCount; i++) { 0223 // generate random angle 0224 angle = angularDistribution(randomSource) * M_PI * 2; 0225 // generate random length 0226 length = radialDistribution(randomSource); 0227 0228 if (m_shapeDynamicsProperties->enabled) { 0229 // rotation 0230 rotationZ = rotationAngle(randomSource); 0231 0232 if (m_shapeDynamicsProperties->followCursor) { 0233 0234 rotationZ = linearInterpolation(rotationZ, angle, m_shapeDynamicsProperties->followCursorWeight); 0235 } 0236 0237 0238 if (m_shapeDynamicsProperties->followDrawingAngle) { 0239 0240 rotationZ = linearInterpolation(rotationZ, info.drawingAngle(), m_shapeDynamicsProperties->followDrawingAngleWeight); 0241 } 0242 0243 // random size - scale 0244 if (m_shapeDynamicsProperties->randomSize) { 0245 particleScale = randomSource->generateNormalized(); 0246 } 0247 } 0248 // generate polar coordinate 0249 nx = (m_radius * cos(angle) * length); 0250 ny = (m_radius * sin(angle) * length); 0251 0252 // compute the height of the ellipse 0253 ny *= m_sprayOpOption->data.aspect; 0254 0255 // transform 0256 m.map(nx, ny, &nx, &ny); 0257 0258 // color transformation 0259 0260 if (shouldColor) { 0261 if (m_colorProperties->sampleInputColor) { 0262 colorSampler.sampleOldColor(nx + x, ny + y, m_inkColor.data()); 0263 } 0264 0265 // mix the color with background color 0266 if (m_colorProperties->mixBgColor) { 0267 KoMixColorsOp * mixOp = dab->colorSpace()->mixColorsOp(); 0268 0269 const quint8 *colors[2]; 0270 colors[0] = m_inkColor.data(); 0271 colors[1] = bgColor.data(); 0272 0273 qint16 colorWeights[2]; 0274 int MAX_16BIT = 255; 0275 qreal blend = info.pressure(); 0276 0277 colorWeights[0] = static_cast<quint16>(blend * MAX_16BIT); 0278 colorWeights[1] = static_cast<quint16>((1.0 - blend) * MAX_16BIT); 0279 mixOp->mixColors(colors, colorWeights, 2, m_inkColor.data()); 0280 } 0281 0282 if (m_colorProperties->useRandomHSV && m_transfo) { 0283 params["h"] = (m_colorProperties->hue / 180.0) * randomSource->generateNormalized(); 0284 params["s"] = (m_colorProperties->saturation / 100.0) * randomSource->generateNormalized(); 0285 params["v"] = (m_colorProperties->value / 100.0) * randomSource->generateNormalized(); 0286 m_transfo->setParameters(params); 0287 m_transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. 0288 m_transfo->setParameter(4, false);//sets the colorize to false. 0289 m_transfo->transform(m_inkColor.data(), m_inkColor.data() , 1); 0290 } 0291 0292 if (m_colorProperties->useRandomOpacity) { 0293 quint8 alpha = qRound(randomSource->generateNormalized() * OPACITY_OPAQUE_U8); 0294 m_inkColor.setOpacity(alpha); 0295 m_painter->setOpacity(alpha); 0296 } 0297 0298 if (!m_colorProperties->colorPerParticle) { 0299 shouldColor = false; 0300 } 0301 0302 m_painter->setPaintColor(m_inkColor); 0303 } 0304 0305 qreal jitteredWidth = qMax(1.0 * additionalScale, effectiveSize.width() * particleScale * additionalScale); 0306 qreal jitteredHeight = qMax(1.0 * additionalScale, effectiveSize.height() * particleScale * additionalScale); 0307 0308 if (m_shapeProperties->enabled){ 0309 switch (m_shapeProperties->shape){ 0310 // ellipse 0311 case 0: 0312 { 0313 if (effectiveSize.width() == effectiveSize.height()){ 0314 paintCircle(m_painter, nx + x, ny + y, jitteredWidth * 0.5); 0315 } 0316 else { 0317 paintEllipse(m_painter, nx + x, ny + y, jitteredWidth * 0.5 , jitteredHeight * 0.5, rotationZ); 0318 } 0319 break; 0320 } 0321 // rectangle 0322 case 1: 0323 { 0324 paintRectangle(m_painter, nx + x, ny + y, qRound(jitteredWidth) , qRound(jitteredHeight), rotationZ); 0325 break; 0326 } 0327 // wu-particle 0328 case 2: { 0329 paintParticle(accessor, m_inkColor, nx + x, ny + y); 0330 break; 0331 } 0332 // pixel 0333 case 3: { 0334 ix = qRound(nx + x); 0335 iy = qRound(ny + y); 0336 accessor->moveTo(ix, iy); 0337 memcpy(accessor->rawData(), m_inkColor.data(), m_dabPixelSize); 0338 break; 0339 } 0340 case 4: { 0341 if (!m_brushQImage.isNull()) { 0342 0343 QTransform m; 0344 m.rotate(rad2deg(rotationZ)); 0345 m.scale(additionalScale, additionalScale); 0346 0347 if (m_shapeDynamicsProperties->randomSize) { 0348 m.scale(particleScale, particleScale); 0349 } 0350 m_transformed = m_brushQImage.transformed(m, Qt::SmoothTransformation); 0351 m_imageDevice->convertFromQImage(m_transformed, 0); 0352 KisRandomAccessorSP ac = m_imageDevice->createRandomAccessorNG(); 0353 QRect rc = m_transformed.rect(); 0354 0355 if (m_colorProperties->useRandomHSV && m_transfo) { 0356 0357 for (int y = rc.y(); y < rc.y() + rc.height(); y++) { 0358 for (int x = rc.x(); x < rc.x() + rc.width(); x++) { 0359 ac->moveTo(x, y); 0360 m_transfo->transform(ac->rawData(), ac->rawData() , 1); 0361 } 0362 } 0363 } 0364 0365 ix = qRound(nx + x - rc.width() * 0.5); 0366 iy = qRound(ny + y - rc.height() * 0.5); 0367 m_painter->bitBlt(QPoint(ix, iy), m_imageDevice, rc); 0368 m_imageDevice->clear(); 0369 break; 0370 } 0371 } 0372 } 0373 // Auto-brush 0374 } 0375 else { 0376 KisDabShape shape(particleScale * additionalScale, 1.0, -rotationZ); 0377 QPointF hotSpot = m_brush->hotSpot(shape, info); 0378 QPointF pos(nx + x, ny + y); 0379 QPointF pt = pos - hotSpot; 0380 0381 qint32 ix; 0382 qreal xFraction; 0383 qint32 iy; 0384 qreal yFraction; 0385 0386 KisPaintOp::splitCoordinate(pt.x(), &ix, &xFraction); 0387 KisPaintOp::splitCoordinate(pt.y(), &iy, &yFraction); 0388 0389 m_brush->prepareForSeqNo(info, m_dabSeqNo); 0390 0391 //KisFixedPaintDeviceSP dab; 0392 if (m_brush->brushApplication() == IMAGESTAMP) { 0393 m_fixedDab = m_brush->paintDevice(m_fixedDab->colorSpace(), 0394 shape, info, xFraction, yFraction); 0395 0396 if (m_colorProperties->useRandomHSV && m_transfo) { 0397 quint8 * dabPointer = m_fixedDab->data(); 0398 int pixelCount = m_fixedDab->bounds().width() * m_fixedDab->bounds().height(); 0399 m_transfo->transform(dabPointer, dabPointer, pixelCount); 0400 } 0401 0402 } 0403 else { 0404 m_brush->mask(m_fixedDab, m_inkColor, shape, 0405 info, xFraction, yFraction); 0406 } 0407 m_painter->bltFixed(QPoint(ix, iy), m_fixedDab, m_fixedDab->bounds()); 0408 } 0409 if (m_colorProperties->colorPerParticle){ 0410 m_inkColor=color;//reset color// 0411 } 0412 } 0413 // recover from jittering of color, 0414 // m_inkColor.opacity is recovered with every paint 0415 } 0416 0417 0418 0419 void SprayBrush::paintParticle(KisRandomAccessorSP &writeAccessor, const KoColor &color, qreal rx, qreal ry) 0420 { 0421 // opacity top left, right, bottom left, right 0422 KoColor pcolor(color); 0423 //int opacity = pcolor.opacityU8(); 0424 0425 int ipx = int (rx); 0426 int ipy = int (ry); 0427 qreal fx = rx - ipx; 0428 qreal fy = ry - ipy; 0429 0430 qreal btl = (1 - fx) * (1 - fy); 0431 qreal btr = (fx) * (1 - fy); 0432 qreal bbl = (1 - fx) * (fy); 0433 qreal bbr = (fx) * (fy); 0434 0435 // this version overwrite pixels, e.g. when it sprays two particle next 0436 // to each other, the pixel with lower opacity can override other pixel. 0437 // Maybe some kind of compositing using here would be cool 0438 0439 pcolor.setOpacity(btl); 0440 writeAccessor->moveTo(ipx , ipy); 0441 memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); 0442 0443 pcolor.setOpacity(btr); 0444 writeAccessor->moveTo(ipx + 1, ipy); 0445 memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); 0446 0447 pcolor.setOpacity(bbl); 0448 writeAccessor->moveTo(ipx, ipy + 1); 0449 memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); 0450 0451 pcolor.setOpacity(bbr); 0452 writeAccessor->moveTo(ipx + 1, ipy + 1); 0453 memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); 0454 } 0455 0456 void SprayBrush::paintCircle(KisPainter* painter, qreal x, qreal y, qreal radius) 0457 { 0458 QPainterPath path; 0459 path.addEllipse(QPointF(x,y),radius,radius); 0460 painter->fillPainterPath(path); 0461 } 0462 0463 0464 void SprayBrush::paintEllipse(KisPainter* painter, qreal x, qreal y, qreal a, qreal b, qreal angle) 0465 { 0466 QPainterPath path; 0467 path.addEllipse(QPointF(), a, b); 0468 QTransform t; 0469 t.translate(x, y); 0470 t.rotateRadians(angle); 0471 path = t.map(path); 0472 painter->fillPainterPath(path); 0473 } 0474 0475 void SprayBrush::paintRectangle(KisPainter* painter, qreal x, qreal y, qreal width, qreal height, qreal angle) 0476 { 0477 QPainterPath path; 0478 path.addRect(QRectF(-0.5 * width, -0.5 * height, width, height)); 0479 QTransform t; 0480 t.translate(x, y); 0481 t.rotateRadians(angle); 0482 path = t.map(path); 0483 painter->fillPainterPath(path); 0484 } 0485 0486 0487 void SprayBrush::paintOutline(KisPaintDeviceSP dev , const KoColor &outlineColor, qreal posX, qreal posY, qreal radius) 0488 { 0489 QList<QPointF> antiPixels; 0490 KisRandomAccessorSP accessor = dev->createRandomAccessorNG(); 0491 0492 for (int y = -radius + posY; y <= radius + posY; y++) { 0493 for (int x = -radius + posX; x <= radius + posX; x++) { 0494 accessor->moveTo(x, y); 0495 qreal alpha = dev->colorSpace()->opacityU8(accessor->rawData()); 0496 0497 if (alpha != 0) { 0498 // top left 0499 accessor->moveTo(x - 1, y - 1); 0500 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0501 antiPixels.append(QPointF(x - 1, y - 1)); 0502 //continue; 0503 } 0504 0505 // top 0506 accessor->moveTo(x, y - 1); 0507 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0508 antiPixels.append(QPointF(x, y - 1)); 0509 //continue; 0510 } 0511 0512 // top right 0513 accessor->moveTo(x + 1, y - 1); 0514 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0515 antiPixels.append(QPointF(x + 1, y - 1)); 0516 //continue; 0517 } 0518 0519 //left 0520 accessor->moveTo(x - 1, y); 0521 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0522 antiPixels.append(QPointF(x - 1, y)); 0523 //continue; 0524 } 0525 0526 //right 0527 accessor->moveTo(x + 1, y); 0528 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0529 antiPixels.append(QPointF(x + 1, y)); 0530 //continue; 0531 } 0532 0533 // bottom left 0534 accessor->moveTo(x - 1, y + 1); 0535 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0536 antiPixels.append(QPointF(x - 1, y + 1)); 0537 //continue; 0538 } 0539 0540 // bottom 0541 accessor->moveTo(x, y + 1); 0542 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0543 antiPixels.append(QPointF(x, y + 1)); 0544 //continue; 0545 } 0546 0547 // bottom right 0548 accessor->moveTo(x + 1, y + 1); 0549 if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { 0550 antiPixels.append(QPointF(x + 1, y + 1)); 0551 //continue; 0552 } 0553 } 0554 0555 } 0556 } 0557 0558 // anti-alias it 0559 int size = antiPixels.size(); 0560 for (int i = 0; i < size; i++) { 0561 accessor->moveTo(antiPixels[i].x(), antiPixels[i].y()); 0562 memcpy(accessor->rawData(), outlineColor.data(), dev->colorSpace()->pixelSize()); 0563 } 0564 } 0565 0566 void SprayBrush::setFixedDab(KisFixedPaintDeviceSP dab) 0567 { 0568 m_fixedDab = dab; 0569 }