kis_imagepipe_brush.cpp 15.7 KB
Newer Older
1 2
/*
 *  Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
3
 *  Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
17
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 */
19
#include "kis_imagepipe_brush.h"
20
#include "kis_pipebrush_parasite.h"
21
#include "kis_brushes_pipe.h"
22

23 24

class KisImageBrushesPipe : public KisBrushesPipe<KisGbrBrush>
Boudewijn Rempt's avatar
Boudewijn Rempt committed
25
{
26 27
public:
    KisImageBrushesPipe()
28 29
        : m_currentBrushIndex(0)
        , m_isInitialized(false)
30 31 32
    {
    }

33

34 35 36 37 38 39 40 41 42 43
    /*
       pre and post are split because:

    21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should
                        correspond to the size of the mask returned by paintDevice()
    21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is
                        returned to the paint op, that is "preparing the randomness for the next call"
    21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular
                        paintInfo.
    */
44
protected:
45 46 47 48 49
    static int selectPre(KisParasite::SelectionMode mode,
                         int index, int rank,
                         const KisPaintInformation& info) {

        qreal angle;
50 51
        qreal velocity;
        qreal capSpeed = 3;
52 53 54 55 56 57 58 59 60 61

        switch (mode) {
        case KisParasite::Constant:
        case KisParasite::Incremental:
        case KisParasite::Random:
            break;
        case KisParasite::Pressure:
            index = static_cast<int>(info.pressure() * (rank - 1) + 0.5);
            break;
        case KisParasite::Angular:
62 63
            // + M_PI_2 + M_PI_4 to be compatible with the gimp
            angle = info.drawingAngle() + M_PI_2 + M_PI_4;
64 65
            angle = normalizeAngle(angle);

66 67
            index = static_cast<int>(angle / (2.0 * M_PI) * rank);
            break;
68 69 70 71 72 73
        case KisParasite::TiltX:
            index = qRound(info.xTilt() / 2.0 * rank) + rank / 2;
            break;
        case KisParasite::TiltY:
            index = qRound(info.yTilt() / 2.0 * rank) + rank / 2;
            break;
74 75 76 77 78 79 80 81 82 83
        case KisParasite::Velocity:
            // log is slow, but allows for nicer dab transition
            velocity = log(info.drawingSpeed() + 1);
            if (velocity > capSpeed) {
                velocity = capSpeed;
            }
            velocity /= capSpeed;
            velocity *= (rank - 1) + 0.5;
            index = qRound(velocity);
            break;
84 85 86 87 88 89 90 91 92
        default:
            warnImage << "Parasite" << mode << "is not implemented";
            index = 0;
        }

        return index;
    }

    static int selectPost(KisParasite::SelectionMode mode,
93
                          int index, int rank,
94 95
                          const KisPaintInformation& info,
                          int seqNo) {
96 97 98 99

        switch (mode) {
        case KisParasite::Constant: break;
        case KisParasite::Incremental:
100
            index = (seqNo >= 0 ? seqNo : (index + 1)) % rank;
101 102
            break;
        case KisParasite::Random:
103
            index = info.randomSource()->generate(0, rank-1);
104 105 106 107
            break;
        case KisParasite::Pressure:
        case KisParasite::Angular:
            break;
108 109
        case KisParasite::TiltX:
        case KisParasite::TiltY:
110
        case KisParasite::Velocity:
111
            break;
112 113 114 115 116 117 118 119
        default:
            warnImage << "Parasite" << mode << "is not implemented";
            index = 0;
        }

        return index;
    }

120
    int chooseNextBrush(const KisPaintInformation& info) override {
121 122
        quint32 brushIndex = 0;

123
        if (!m_isInitialized) {
124 125 126 127 128 129 130
            /**
             * Reset all the indexes to the initial values and do the
             * generation based on parameters.
             */
            for (int i = 0; i < m_parasite.dim; i++) {
                m_parasite.index[i] = 0;
            }
131
            updateBrushIndexes(info, 0);
132 133 134
            m_isInitialized = true;
        }

135
        for (int i = 0; i < m_parasite.dim; i++) {
136 137 138 139
            int index = selectPre(m_parasite.selection[i],
                                  m_parasite.index[i],
                                  m_parasite.rank[i], info);

140 141
            brushIndex += m_parasite.brushesCount[i] * index;
        }
142 143
        brushIndex %= (quint32)m_brushes.size();
        m_currentBrushIndex = brushIndex;
144 145
        return brushIndex;
    }
146

147 148 149 150
    int currentBrushIndex() override {
        return m_currentBrushIndex;
    }

151
    void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
152 153 154
        for (int i = 0; i < m_parasite.dim; i++) {
            m_parasite.index[i] = selectPost(m_parasite.selection[i],
                                             m_parasite.index[i],
155
                                             m_parasite.rank[i],
156 157
                                             info,
                                             seqNo);
158
        }
159 160
    }

161
public:
162
    using KisBrushesPipe<KisGbrBrush>::addBrush;
163
    using KisBrushesPipe<KisGbrBrush>::sizeBrush;
164

165 166 167 168 169 170 171
    void setParasite(const KisPipeBrushParasite& parasite) {
        m_parasite = parasite;
    }

    const KisPipeBrushParasite& parasite() const {
        return m_parasite;
    }
172

173
    void setUseColorAsMask(bool useColorAsMask) {
174
        Q_FOREACH (KisGbrBrush * brush, m_brushes) {
175 176 177 178 179
            brush->setUseColorAsMask(useColorAsMask);
        }
    }

    void makeMaskImage() {
180
        Q_FOREACH (KisGbrBrush * brush, m_brushes) {
181 182 183 184 185
            brush->makeMaskImage();
        }
    }

    bool saveToDevice(QIODevice* dev) const {
186
        Q_FOREACH (KisGbrBrush * brush, m_brushes) {
187
            if (!brush->saveToDevice(dev)) {
188 189 190 191 192
                return false;
            }
        }
        return true;
    }
193

194
    void notifyStrokeStarted() override {
195 196 197
        m_isInitialized = false;
    }

198 199
private:
    KisPipeBrushParasite m_parasite;
200
    int m_currentBrushIndex;
201
    bool m_isInitialized;
202 203 204
};


205
struct KisImagePipeBrush::Private {
206 207
public:
    KisImageBrushesPipe brushesPipe;
208
};
209

210
KisImagePipeBrush::KisImagePipeBrush(const QString& filename)
211 212
    : KisGbrBrush(filename)
    , m_d(new Private())
213 214 215
{
}

216
KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h,
Casper Boemann's avatar
Casper Boemann committed
217
                                     QVector< QVector<KisPaintDevice*> > devices,
218
                                     QVector<KisParasite::SelectionMode > modes)
219
    : KisGbrBrush(QString())
220
    , m_d(new Private())
221 222 223 224 225 226
{
    Q_ASSERT(devices.count() == modes.count());
    Q_ASSERT(devices.count() > 0);
    Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim!

    setName(name);
227 228 229 230

    KisPipeBrushParasite parasite;

    parasite.dim = devices.count();
231
    // XXX Change for multidim! :
232 233 234
    parasite.ncells = devices.at(0).count();
    parasite.rank[0] = parasite.ncells; // ### This can masquerade some bugs, be careful here in the future
    parasite.selection[0] = modes.at(0);
235 236


237 238
    // XXX needsmovement!

239
    parasite.setBrushesCount();
240

241 242
    setParasite(parasite);
    setDevices(devices, w, h);
243
    setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
244 245
}

246
KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs)
247
    : KisGbrBrush(rhs),
248
      m_d(new Private(*rhs.m_d))
249 250 251 252
{
}


253 254
KisImagePipeBrush::~KisImagePipeBrush()
{
255
    delete m_d;
256 257
}

Sven Langkamp's avatar
Sven Langkamp committed
258
bool KisImagePipeBrush::load()
259
{
260
    QFile file(filename());
Laurent Montel's avatar
Laurent Montel committed
261
    file.open(QIODevice::ReadOnly);
262
    bool res = loadFromDevice(&file);
263
    file.close();
264 265 266 267 268 269
    return res;
}

bool KisImagePipeBrush::loadFromDevice(QIODevice *dev)
{
    QByteArray data = dev->readAll();
270
    return initFromData(data);
Sven Langkamp's avatar
Sven Langkamp committed
271 272
}

273
bool KisImagePipeBrush::initFromData(const QByteArray &data)
Sven Langkamp's avatar
Sven Langkamp committed
274
{
275
    if (data.size() == 0) return false;
276 277 278 279
    // XXX: this doesn't correctly load the image pipe brushes yet.

    // XXX: This stuff is in utf-8, too.
    // The first line contains the name -- this means we look until we arrive at the first newline
280
    QByteArray line1;
281

Adrian Page's avatar
Adrian Page committed
282
    qint32 i = 0;
283

284 285
    while (data[i] != '\n' && i < data.size()) {
        line1.append(data[i]);
286 287
        i++;
    }
288
    setName(QString::fromUtf8(line1, line1.size()));
289 290 291 292 293 294

    i++; // Skip past the first newline

    // The second line contains the number of brushes, separated by a space from the parasite

    // XXX: This stuff is in utf-8, too.
295
    QByteArray line2;
296 297
    while (data[i] != '\n' && i < data.size()) {
        line2.append(data[i]);
298 299
        i++;
    }
300

301
    QString paramline = QString::fromUtf8(line2, line2.size());
302 303
    qint32 numOfBrushes = paramline.left(paramline.indexOf(' ')).toUInt();
    QString parasiteString = paramline.mid(paramline.indexOf(' ') + 1);
304

305 306 307
    KisPipeBrushParasite parasite = KisPipeBrushParasite(parasiteString);
    parasite.sanitize();

308 309
    parasiteSelectionString = parasite.selectionMode; // selection mode to return to UI

310
    m_d->brushesPipe.setParasite(parasite);
311 312
    i++; // Skip past the second newline

313
    for (int brushIndex = m_d->brushesPipe.sizeBrush();
314
            brushIndex < numOfBrushes && i < data.size(); brushIndex++) {
315 316

        KisGbrBrush* brush = new KisGbrBrush(name() + '_' + QString().setNum(brushIndex),
317
                                             data,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
318
                                             i);
319

320
        m_d->brushesPipe.addBrush(brush);
321
    }
322

323
    if (numOfBrushes > 0) {
324
        setValid(true);
325 326 327
        setSpacing(m_d->brushesPipe.lastBrush()->spacing());
        setWidth(m_d->brushesPipe.firstBrush()->width());
        setHeight(m_d->brushesPipe.firstBrush()->height());
328
        setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
329 330 331
    }

    return true;
332 333
}

Sven Langkamp's avatar
Sven Langkamp committed
334
bool KisImagePipeBrush::save()
335
{
336
    QFile file(filename());
Laurent Montel's avatar
Laurent Montel committed
337
    file.open(QIODevice::WriteOnly | QIODevice::Truncate);
338 339 340 341 342 343 344
    bool ok = saveToDevice(&file);
    file.close();
    return ok;
}

bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const
{
Adrian Page's avatar
Adrian Page committed
345
    QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
346 347 348
    char const* name = utf8Name.data();
    int len = qstrlen(name);

349
    if (m_d->brushesPipe.parasite().dim >= KisPipeBrushParasite::MaxDim) {
350
        warnImage << "Save to file for pipe brushes with dim != not yet supported!";
351 352 353 354
        return false;
    }

    // Save this pipe brush: first the header, and then all individual brushes consecutively
355
    // XXX: this needs some care for when we have > 1 dimension)
356 357 358 359

    // Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n

    // The name\n
Laurent Montel's avatar
Laurent Montel committed
360
    if (dev->write(name, len) == -1)
361 362
        return false;

363
    if (!dev->putChar('\n'))
364 365 366
        return false;

    // Write the parasite (also writes number of brushes)
367
    if (!m_d->brushesPipe.parasite().saveToDevice(dev))
368 369
        return false;

370
    if (!dev->putChar('\n'))
371 372
        return false;

373 374
    KoResource::saveToDevice(dev);

375
    // <gbr brushes>
376
    return m_d->brushesPipe.saveToDevice(dev);
377
}
378

379 380 381 382 383 384
void KisImagePipeBrush::notifyStrokeStarted()
{
    m_d->brushesPipe.notifyStrokeStarted();
}

void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info)
385
{
386
    m_d->brushesPipe.notifyCachedDabPainted(info);
387 388
}

389 390 391 392 393
void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
{
    m_d->brushesPipe.prepareForSeqNo(info, seqNo);
}

394
void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
395 396
        KisDabShape const& shape,
        const KisPaintInformation& info,
397 398
        double subPixelX , double subPixelY,
        qreal softnessFactor) const
399
{
400
    m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
401 402
}

403 404 405 406 407
QVector<KisGbrBrush *> KisImagePipeBrush::brushes() const
{
    return m_d->brushesPipe.brushes();
}

408 409 410 411
KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice(
    const KoColorSpace * colorSpace,
    KisDabShape const& shape,
    const KisPaintInformation& info, double subPixelX, double subPixelY) const
412
{
413
    return m_d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
414 415
}

416
enumBrushType KisImagePipeBrush::brushType() const
417
{
418
    return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE;
419
}
420

421 422 423 424 425
QString KisImagePipeBrush::parasiteSelection()
{
    return parasiteSelectionString;
}

426
bool KisImagePipeBrush::hasColor() const
427
{
428
    return m_d->brushesPipe.hasColor();
429 430
}

431
void KisImagePipeBrush::makeMaskImage()
432
{
433 434
    m_d->brushesPipe.makeMaskImage();
    setUseColorAsMask(false);
435 436 437 438
}

void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask)
{
439 440
    KisGbrBrush::setUseColorAsMask(useColorAsMask);
    m_d->brushesPipe.setUseColorAsMask(useColorAsMask);
441
}
442

443
const KisBoundary* KisImagePipeBrush::boundary() const
Boudewijn Rempt's avatar
Boudewijn Rempt committed
444
{
445 446 447 448
    KisGbrBrush *brush = m_d->brushesPipe.firstBrush();
    Q_ASSERT(brush);

    return brush->boundary();
449
}
450

Boudewijn Rempt's avatar
Boudewijn Rempt committed
451 452
bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info)
{
453
    return (!m_d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5);
454 455
}

456
KisBrush* KisImagePipeBrush::clone() const
Boudewijn Rempt's avatar
Boudewijn Rempt committed
457
{
458
    return new KisImagePipeBrush(*this);
459 460
}

461 462 463 464 465
QString KisImagePipeBrush::defaultFileExtension() const
{
    return QString(".gih");
}

466 467 468 469 470
quint32 KisImagePipeBrush::brushIndex(const KisPaintInformation& info) const
{
    return m_d->brushesPipe.brushIndex(info);
}

471
qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
472
{
473
    return m_d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info);
474 475
}

476
qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
477
{
478
    return m_d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info);
479 480
}

481 482
void KisImagePipeBrush::setAngle(qreal _angle)
{
483 484
    KisGbrBrush::setAngle(_angle);
    m_d->brushesPipe.setAngle(_angle);
485 486 487 488
}

void KisImagePipeBrush::setScale(qreal _scale)
{
489 490
    KisGbrBrush::setScale(_scale);
    m_d->brushesPipe.setScale(_scale);
491
}
492 493 494

void KisImagePipeBrush::setSpacing(double _spacing)
{
495 496 497 498 499 500 501 502
    KisGbrBrush::setSpacing(_spacing);
    m_d->brushesPipe.setSpacing(_spacing);
}

void KisImagePipeBrush::setBrushType(enumBrushType type)
{
    Q_UNUSED(type);
    qFatal("FATAL: protected member setBrushType has no meaning for KisImagePipeBrush");
503
    // brushType() is a function of hasColor() and useColorAsMask()
504 505 506 507 508 509 510 511 512
}

void KisImagePipeBrush::setHasColor(bool hasColor)
{
    Q_UNUSED(hasColor);
    qFatal("FATAL: protected member setHasColor has no meaning for KisImagePipeBrush");
    // hasColor() is a function of the underlying brushes
}

513
KisGbrBrush* KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const
514
{
515
    return m_d->brushesPipe.currentBrush(info);
516 517 518 519 520 521
}


void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const
{
    return m_d->brushesPipe.testingSelectNextBrush(info);
522
}
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

const KisPipeBrushParasite& KisImagePipeBrush::parasite() const {
    return m_d->brushesPipe.parasite();
}

void KisImagePipeBrush::setParasite(const KisPipeBrushParasite &parasite)
{
    m_d->brushesPipe.setParasite(parasite);
}

void KisImagePipeBrush::setDevices(QVector<QVector<KisPaintDevice *> > devices, int w, int h)
{

    for (int i = 0; i < devices.at(0).count(); i++) {
        m_d->brushesPipe.addBrush(new KisGbrBrush(devices.at(0).at(i), 0, 0, w, h));
    }
}