File indexing completed on 2024-05-12 15:56:11
0001 /* 0002 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org> 0003 * SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 #include "kis_imagepipe_brush.h" 0008 #include "kis_pipebrush_parasite.h" 0009 #include "kis_brushes_pipe.h" 0010 #include <KisOptimizedBrushOutline.h> 0011 0012 class KisImageBrushesPipe : public KisBrushesPipe<KisGbrBrush> 0013 { 0014 public: 0015 KisImageBrushesPipe() 0016 : m_currentBrushIndex(0) 0017 , m_isInitialized(false) 0018 { 0019 } 0020 0021 0022 /* 0023 pre and post are split because: 0024 0025 21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should 0026 correspond to the size of the mask returned by paintDevice() 0027 21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is 0028 returned to the paint op, that is "preparing the randomness for the next call" 0029 21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular 0030 paintInfo. 0031 */ 0032 protected: 0033 static int selectPre(KisParasite::SelectionMode mode, 0034 int index, int rank, 0035 const KisPaintInformation& info) { 0036 0037 qreal angle; 0038 qreal velocity; 0039 qreal capSpeed = 3; 0040 0041 switch (mode) { 0042 case KisParasite::Constant: 0043 case KisParasite::Incremental: 0044 case KisParasite::Random: 0045 break; 0046 case KisParasite::Pressure: 0047 index = static_cast<int>(info.pressure() * (rank - 1) + 0.5); 0048 break; 0049 case KisParasite::Angular: 0050 // + M_PI_2 + M_PI_4 to be compatible with the gimp 0051 angle = info.drawingAngle() + M_PI_2 + M_PI_4; 0052 angle = normalizeAngle(angle); 0053 0054 index = static_cast<int>(angle / (2.0 * M_PI) * rank); 0055 break; 0056 case KisParasite::TiltX: 0057 index = qRound(info.xTilt() / 2.0 * rank) + rank / 2; 0058 break; 0059 case KisParasite::TiltY: 0060 index = qRound(info.yTilt() / 2.0 * rank) + rank / 2; 0061 break; 0062 case KisParasite::Velocity: 0063 // log is slow, but allows for nicer dab transition 0064 velocity = log(info.drawingSpeed() + 1); 0065 if (velocity > capSpeed) { 0066 velocity = capSpeed; 0067 } 0068 velocity /= capSpeed; 0069 velocity *= (rank - 1) + 0.5; 0070 index = qRound(velocity); 0071 break; 0072 default: 0073 warnImage << "Parasite" << mode << "is not implemented"; 0074 index = 0; 0075 } 0076 0077 return index; 0078 } 0079 0080 static int selectPost(KisParasite::SelectionMode mode, 0081 int index, int rank, 0082 const KisPaintInformation& info, 0083 int seqNo) { 0084 0085 switch (mode) { 0086 case KisParasite::Constant: break; 0087 case KisParasite::Incremental: 0088 index = (seqNo >= 0 ? seqNo : (index + 1)) % rank; 0089 break; 0090 case KisParasite::Random: 0091 index = info.randomSource()->generate(0, rank-1); 0092 break; 0093 case KisParasite::Pressure: 0094 case KisParasite::Angular: 0095 break; 0096 case KisParasite::TiltX: 0097 case KisParasite::TiltY: 0098 case KisParasite::Velocity: 0099 break; 0100 default: 0101 warnImage << "Parasite" << mode << "is not implemented"; 0102 index = 0; 0103 } 0104 0105 return index; 0106 } 0107 0108 int chooseNextBrush(const KisPaintInformation& info) override { 0109 quint32 brushIndex = 0; 0110 0111 if (!m_isInitialized) { 0112 /** 0113 * Reset all the indexes to the initial values and do the 0114 * generation based on parameters. 0115 */ 0116 for (int i = 0; i < m_parasite.dim; i++) { 0117 m_parasite.index[i] = 0; 0118 } 0119 updateBrushIndexes(info, 0); 0120 m_isInitialized = true; 0121 } 0122 0123 for (int i = 0; i < m_parasite.dim; i++) { 0124 int index = selectPre(m_parasite.selection[i], 0125 m_parasite.index[i], 0126 m_parasite.rank[i], info); 0127 0128 brushIndex += m_parasite.brushesCount[i] * index; 0129 } 0130 brushIndex %= (quint32)m_brushes.size(); 0131 m_currentBrushIndex = brushIndex; 0132 return brushIndex; 0133 } 0134 0135 void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override { 0136 for (int i = 0; i < m_parasite.dim; i++) { 0137 m_parasite.index[i] = selectPost(m_parasite.selection[i], 0138 m_parasite.index[i], 0139 m_parasite.rank[i], 0140 info, 0141 seqNo); 0142 } 0143 } 0144 0145 public: 0146 using KisBrushesPipe<KisGbrBrush>::addBrush; 0147 using KisBrushesPipe<KisGbrBrush>::sizeBrush; 0148 0149 int currentBrushIndex() override { 0150 return m_currentBrushIndex; 0151 } 0152 0153 void setParasite(const KisPipeBrushParasite& parasite) { 0154 m_parasite = parasite; 0155 } 0156 0157 const KisPipeBrushParasite& parasite() const { 0158 return m_parasite; 0159 } 0160 0161 void setAdjustmentMidPoint(quint8 value) { 0162 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0163 brush->setAdjustmentMidPoint(value); 0164 } 0165 } 0166 0167 void setBrightnessAdjustment(qreal value) { 0168 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0169 brush->setBrightnessAdjustment(value); 0170 } 0171 } 0172 0173 void setContrastAdjustment(qreal value) { 0174 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0175 brush->setContrastAdjustment(value); 0176 } 0177 } 0178 0179 void setAutoAdjustMidPoint(bool value) { 0180 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0181 brush->setAutoAdjustMidPoint(value); 0182 } 0183 } 0184 0185 void makeMaskImage(bool preserveAlpha) { 0186 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0187 brush->makeMaskImage(preserveAlpha); 0188 } 0189 } 0190 0191 bool saveToDevice(QIODevice* dev) const { 0192 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0193 if (!brush->saveToDevice(dev)) { 0194 return false; 0195 } 0196 } 0197 return true; 0198 } 0199 0200 void coldInitBrush() { 0201 Q_FOREACH (KisGbrBrushSP brush, m_brushes) { 0202 brush->coldInitBrush(); 0203 } 0204 } 0205 0206 void notifyStrokeStarted() override { 0207 m_isInitialized = false; 0208 } 0209 0210 private: 0211 KisPipeBrushParasite m_parasite; 0212 int m_currentBrushIndex; 0213 bool m_isInitialized; 0214 }; 0215 0216 0217 struct KisImagePipeBrush::Private { 0218 public: 0219 KisImageBrushesPipe brushesPipe; 0220 }; 0221 0222 KisImagePipeBrush::KisImagePipeBrush(const QString& filename) 0223 : KisGbrBrush(filename) 0224 , d(new Private()) 0225 { 0226 } 0227 0228 KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h, 0229 QVector< QVector<KisPaintDevice*> > devices, 0230 QVector<KisParasite::SelectionMode > modes) 0231 : KisGbrBrush(QString()) 0232 , d(new Private()) 0233 { 0234 Q_ASSERT(devices.count() == modes.count()); 0235 Q_ASSERT(devices.count() > 0); 0236 Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim! 0237 0238 setName(name); 0239 0240 KisPipeBrushParasite parasite; 0241 0242 parasite.dim = devices.count(); 0243 // XXX Change for multidim! : 0244 parasite.ncells = devices.at(0).count(); 0245 parasite.rank[0] = parasite.ncells; // ### This can masquerade some bugs, be careful here in the future 0246 parasite.selection[0] = modes.at(0); 0247 0248 0249 // XXX needsmovement! 0250 0251 parasite.setBrushesCount(); 0252 0253 setParasite(parasite); 0254 setDevices(devices, w, h); 0255 setBrushTipImage(d->brushesPipe.firstBrush()->brushTipImage()); 0256 } 0257 0258 KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs) 0259 : KisGbrBrush(rhs), 0260 d(new Private(*rhs.d)) 0261 { 0262 } 0263 0264 KoResourceSP KisImagePipeBrush::clone() const 0265 { 0266 return KoResourceSP(new KisImagePipeBrush(*this)); 0267 } 0268 0269 KisImagePipeBrush::~KisImagePipeBrush() 0270 { 0271 delete d; 0272 } 0273 0274 bool KisImagePipeBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) 0275 { 0276 Q_UNUSED(resourcesInterface); 0277 0278 QByteArray data = dev->readAll(); 0279 return initFromData(data); 0280 } 0281 0282 bool KisImagePipeBrush::initFromData(const QByteArray &data) 0283 { 0284 if (data.size() == 0) return false; 0285 // XXX: this doesn't correctly load the image pipe brushes yet. 0286 0287 // XXX: This stuff is in utf-8, too. 0288 // The first line contains the name -- this means we look until we arrive at the first newline 0289 QByteArray line1; 0290 0291 qint32 i = 0; 0292 0293 while (i < data.size() && data[i] != '\n') { 0294 line1.append(data[i]); 0295 i++; 0296 } 0297 setName(QString::fromUtf8(line1, line1.size())); 0298 0299 i++; // Skip past the first newline 0300 0301 // The second line contains the number of brushes, separated by a space from the parasite 0302 0303 // XXX: This stuff is in utf-8, too. 0304 QByteArray line2; 0305 while (i < data.size() && data[i] != '\n') { 0306 line2.append(data[i]); 0307 i++; 0308 } 0309 0310 QString paramline = QString::fromUtf8(line2, line2.size()); 0311 qint32 numOfBrushes = paramline.left(paramline.indexOf(' ')).toUInt(); 0312 QString parasiteString = paramline.mid(paramline.indexOf(' ') + 1); 0313 0314 KisPipeBrushParasite parasite = KisPipeBrushParasite(parasiteString); 0315 parasite.sanitize(); 0316 0317 parasiteSelectionString = parasite.selectionMode; // selection mode to return to UI 0318 0319 d->brushesPipe.setParasite(parasite); 0320 i++; // Skip past the second newline 0321 0322 for (int brushIndex = d->brushesPipe.sizeBrush(); 0323 brushIndex < numOfBrushes && i < data.size(); brushIndex++) { 0324 0325 KisGbrBrushSP brush = KisGbrBrushSP(new KisGbrBrush(name() + '_' + QString().setNum(brushIndex), 0326 data, 0327 i)); 0328 0329 d->brushesPipe.addBrush(brush); 0330 } 0331 0332 if (numOfBrushes > 0) { 0333 setValid(true); 0334 setSpacing(d->brushesPipe.lastBrush()->spacing()); 0335 setWidth(d->brushesPipe.firstBrush()->width()); 0336 setHeight(d->brushesPipe.firstBrush()->height()); 0337 setBrushTipImage(d->brushesPipe.firstBrush()->brushTipImage()); 0338 setBrushApplication(d->brushesPipe.firstBrush()->brushApplication()); 0339 setBrushType(d->brushesPipe.isImageType() ? PIPE_IMAGE : PIPE_MASK); 0340 setHasColorAndTransparency(d->brushesPipe.hasColorAndTransparency()); 0341 } 0342 0343 return true; 0344 } 0345 0346 bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const 0347 { 0348 QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8 0349 char const* name = utf8Name.data(); 0350 int len = qstrlen(name); 0351 0352 if (d->brushesPipe.parasite().dim >= KisPipeBrushParasite::MaxDim) { 0353 warnImage << "Save to file for pipe brushes with dim != not yet supported!"; 0354 return false; 0355 } 0356 0357 // Save this pipe brush: first the header, and then all individual brushes consecutively 0358 // XXX: this needs some care for when we have > 1 dimension) 0359 0360 // Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n 0361 0362 // The name\n 0363 if (dev->write(name, len) == -1) 0364 return false; 0365 0366 if (!dev->putChar('\n')) 0367 return false; 0368 0369 // Write the parasite (also writes number of brushes) 0370 if (!d->brushesPipe.parasite().saveToDevice(dev)) 0371 return false; 0372 0373 if (!dev->putChar('\n')) 0374 return false; 0375 0376 // <gbr brushes> 0377 return d->brushesPipe.saveToDevice(dev); 0378 } 0379 0380 void KisImagePipeBrush::notifyStrokeStarted() 0381 { 0382 d->brushesPipe.notifyStrokeStarted(); 0383 } 0384 0385 void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) 0386 { 0387 d->brushesPipe.prepareForSeqNo(info, seqNo); 0388 } 0389 0390 void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, 0391 KisDabShape const& shape, 0392 const KisPaintInformation& info, 0393 double subPixelX , double subPixelY, 0394 qreal softnessFactor, qreal lightnessStrength) const 0395 { 0396 d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength); 0397 } 0398 0399 void KisImagePipeBrush::notifyBrushIsGoingToBeClonedForStroke() 0400 { 0401 d->brushesPipe.notifyBrushIsGoingToBeClonedForStroke(); 0402 } 0403 0404 QVector<KisGbrBrushSP> KisImagePipeBrush::brushes() const 0405 { 0406 return d->brushesPipe.brushes(); 0407 } 0408 0409 KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice( 0410 const KoColorSpace * colorSpace, 0411 KisDabShape const& shape, 0412 const KisPaintInformation& info, 0413 double subPixelX, double subPixelY) const 0414 { 0415 return d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY); 0416 } 0417 0418 QString KisImagePipeBrush::parasiteSelection() 0419 { 0420 return parasiteSelectionString; 0421 } 0422 0423 void KisImagePipeBrush::makeMaskImage(bool preserveAlpha) 0424 { 0425 KisGbrBrush::makeMaskImage(preserveAlpha); 0426 d->brushesPipe.makeMaskImage(preserveAlpha); 0427 setBrushType(PIPE_MASK); 0428 } 0429 0430 void KisImagePipeBrush::setAdjustmentMidPoint(quint8 value) 0431 { 0432 KisGbrBrush::setAdjustmentMidPoint(value); 0433 d->brushesPipe.setAdjustmentMidPoint(value); 0434 } 0435 0436 void KisImagePipeBrush::setBrightnessAdjustment(qreal value) 0437 { 0438 KisGbrBrush::setBrightnessAdjustment(value); 0439 d->brushesPipe.setBrightnessAdjustment(value); 0440 } 0441 0442 void KisImagePipeBrush::setContrastAdjustment(qreal value) 0443 { 0444 KisGbrBrush::setContrastAdjustment(value); 0445 d->brushesPipe.setContrastAdjustment(value); 0446 } 0447 0448 void KisImagePipeBrush::setAutoAdjustMidPoint(bool value) 0449 { 0450 KisGbrBrush::setAutoAdjustMidPoint(value); 0451 d->brushesPipe.setAutoAdjustMidPoint(value); 0452 } 0453 0454 KisOptimizedBrushOutline KisImagePipeBrush::outline(bool forcePreciseOutline) const 0455 { 0456 Q_UNUSED(forcePreciseOutline); 0457 0458 KisGbrBrushSP brush = d->brushesPipe.firstBrush(); 0459 Q_ASSERT(brush); 0460 0461 return brush->outline(); 0462 } 0463 0464 bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info) 0465 { 0466 return (!d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5); 0467 } 0468 0469 QString KisImagePipeBrush::defaultFileExtension() const 0470 { 0471 return QString(".gih"); 0472 } 0473 0474 quint32 KisImagePipeBrush::brushIndex() const 0475 { 0476 return d->brushesPipe.currentBrushIndex(); 0477 } 0478 0479 qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const 0480 { 0481 return d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info); 0482 } 0483 0484 qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const 0485 { 0486 return d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info); 0487 } 0488 0489 void KisImagePipeBrush::setAngle(qreal _angle) 0490 { 0491 KisGbrBrush::setAngle(_angle); 0492 d->brushesPipe.setAngle(_angle); 0493 } 0494 0495 void KisImagePipeBrush::setScale(qreal _scale) 0496 { 0497 KisGbrBrush::setScale(_scale); 0498 d->brushesPipe.setScale(_scale); 0499 } 0500 0501 void KisImagePipeBrush::setSpacing(double _spacing) 0502 { 0503 KisGbrBrush::setSpacing(_spacing); 0504 d->brushesPipe.setSpacing(_spacing); 0505 } 0506 0507 void KisImagePipeBrush::setBrushApplication(enumBrushApplication brushApplication) 0508 { 0509 //Set all underlying brushes to use the same brush Application 0510 KisGbrBrush::setBrushApplication(brushApplication); 0511 d->brushesPipe.setBrushApplication(brushApplication); 0512 } 0513 0514 void KisImagePipeBrush::setGradient(KoAbstractGradientSP gradient) { 0515 //Set all underlying brushes to use the same gradient 0516 KisGbrBrush::setGradient(gradient); 0517 d->brushesPipe.setGradient(gradient); 0518 } 0519 0520 KisGbrBrushSP KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const 0521 { 0522 return d->brushesPipe.currentBrush(info); 0523 } 0524 0525 0526 void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const 0527 { 0528 return d->brushesPipe.testingSelectNextBrush(info); 0529 } 0530 0531 const KisPipeBrushParasite& KisImagePipeBrush::parasite() const { 0532 return d->brushesPipe.parasite(); 0533 } 0534 0535 void KisImagePipeBrush::setParasite(const KisPipeBrushParasite ¶site) 0536 { 0537 d->brushesPipe.setParasite(parasite); 0538 } 0539 0540 void KisImagePipeBrush::setDevices(QVector<QVector<KisPaintDevice *> > devices, int w, int h) 0541 { 0542 0543 for (int i = 0; i < devices.at(0).count(); i++) { 0544 d->brushesPipe.addBrush(KisGbrBrushSP(new KisGbrBrush(devices.at(0).at(i), 0, 0, w, h))); 0545 } 0546 } 0547 0548 void KisImagePipeBrush::coldInitBrush() 0549 { 0550 d->brushesPipe.coldInitBrush(); 0551 }