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 &parasite)
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 }