kis_abr_brush_collection.cpp 15.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 *  Copyright (c) 2010 Boudewijn Rempt <boud@valdyas.org>
 *  Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
 *  Copyright (c) 2007 Eric Lamarque <eric.lamarque@free.fr>
 *
 *  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
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
20
#include <QtEndian>
21 22 23 24 25 26 27 28 29 30

#include "kis_abr_brush_collection.h"
#include "kis_abr_brush.h"

#include <QDomElement>
#include <QFile>
#include <QImage>
#include <QPoint>
#include <QColor>
#include <QByteArray>
31
#include <kis_debug.h>
32
#include <QString>
33
#include <QBuffer>
34

35
#include <klocalizedstring.h>
36 37 38 39

#include <KoColor.h>


40
struct AbrInfo {
41 42 43 44 45 46 47 48
    //big endian
    short version;
    short subversion;
    // count of the images (brushes) in the abr file
    short count;
};

/// save the QImages as png files to directory image_tests
49
static QImage convertToQImage(char * buffer, qint32 width, qint32 height)
50 51
{
    // create 8-bit indexed image
52 53 54
    QImage img(width, height, QImage::Format_RGB32);
    int pos = 0;
    int value = 0;
55
    for (int y = 0; y < height; y++) {
56
        QRgb *pixel = reinterpret_cast<QRgb *>(img.scanLine(y));
57
        for (int x = 0; x < width; x++, pos++) {
58
            value = 255 - buffer[pos];
59
            pixel[x] = qRgb(value, value , value);
60
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
61

62 63
    }

64
    return img;
65 66 67 68 69 70 71 72 73 74 75 76 77
}

static qint32 rle_decode(QDataStream & abr, char *buffer, qint32 height)
{
    qint32 n;
    char ptmp;
    char ch;
    int i, j, c;
    short *cscanline_len;
    char *data = buffer;

    // read compressed size foreach scanline
    cscanline_len = new short[ height ];
78
    for (i = 0; i < height; i++) {
79 80 81 82 83 84 85 86
        // short
        abr >> cscanline_len[i];
    }

    // unpack each scanline data
    for (i = 0; i < height; i++) {
        for (j = 0; j < cscanline_len[i];) {
            // char
87 88 89
            if (!abr.device()->getChar(&ptmp)) {
                break;
            }
90 91 92 93 94 95 96 97 98 99
            n = ptmp;

            j++;
            if (n >= 128)     // force sign
                n -= 256;
            if (n < 0) {      // copy the following char -n + 1 times
                if (n == -128)  // it's a nop
                    continue;
                n = -n + 1;
                // char
100 101 102
                if (!abr.device()->getChar(&ch)) {
                    break;
                }
103 104

                j++;
105
                for (c = 0; c < n; c++, data++) {
106 107
                    *data = ch;
                }
108 109
            }
            else {
110
                // read the following n + 1 chars (no compr)
111
                for (c = 0; c < n + 1; c++, j++, data++) {
112
                    // char
113 114 115
                    if (!abr.device()->getChar(data))  {
                        break;
                    }
116 117 118 119 120 121 122 123 124
                }
            }
        }
    }
    delete [] cscanline_len;
    return 0;
}


125
static QString abr_v1_brush_name(const QString filename, qint32 id)
126 127
{
    QString result = filename;
128
    int pos = filename.lastIndexOf('.');
129
    result.remove(pos, 4);
130 131 132 133 134 135
    QTextStream(&result) << "_" << id;
    return result;
}

static bool abr_supported_content(AbrInfo *abr_hdr)
{
136
    switch (abr_hdr->version) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    case 1:
    case 2:
        return true;
        break;
    case 6:
        if (abr_hdr->subversion == 1 || abr_hdr->subversion == 2)
            return true;
        break;
    }
    return false;
}

static bool abr_reach_8BIM_section(QDataStream & abr, const QString name)
{
    char tag[4];
    char tagname[5];
    qint32 section_size = 0;
    int r;

    // find 8BIMname section
157 158
    while (!abr.atEnd()) {
        r = abr.readRawData(tag, 4);
159 160

        if (r != 4) {
161
            warnKrita << "Error: Cannot read 8BIM tag ";
162 163 164 165
            return false;
        }

        if (strncmp(tag, "8BIM", 4)) {
166
            warnKrita << "Error: Start tag not 8BIM but " << (int)tag[0] << (int)tag[1] << (int)tag[2] << (int)tag[3] << " at position " << abr.device()->pos();
167 168 169
            return false;
        }

170
        r = abr.readRawData(tagname, 4);
171 172

        if (r != 4) {
173
            warnKrita << "Error: Cannot read 8BIM tag name";
174 175 176 177
            return false;
        }
        tagname[4] = '\0';

178
        QString s1 = QString::fromLatin1(tagname, 4);
179

180
        if (!s1.compare(name)) {
181 182 183 184 185
            return true;
        }

        // long
        abr >> section_size;
186
        abr.device()->seek(abr.device()->pos() + section_size);
187 188 189 190 191 192 193 194 195
    }
    return true;
}

static qint32 find_sample_count_v6(QDataStream & abr, AbrInfo *abr_info)
{
    qint64 origin;
    qint32 sample_section_size;
    qint32 sample_section_end;
196
    qint32 samples = 0;
197 198 199 200 201 202 203 204 205 206
    qint32 data_start;

    qint32 brush_size;
    qint32 brush_end;

    if (!abr_supported_content(abr_info))
        return 0;

    origin = abr.device()->pos();

207
    if (!abr_reach_8BIM_section(abr, "samp")) {
208 209 210 211 212 213 214 215 216
        // reset to origin
        abr.device()->seek(origin);
        return 0;
    }

    // long
    abr >> sample_section_size;
    sample_section_end = sample_section_size + abr.device()->pos();

217 218 219
    if(sample_section_end < 0 || sample_section_end > abr.device()->size())
        return 0;

220 221
    data_start = abr.device()->pos();

222
    while ((!abr.atEnd()) && (abr.device()->pos() < sample_section_end)) {
223 224 225 226 227
        // read long
        abr >> brush_size;
        brush_end = brush_size;
        // complement to 4
        while (brush_end % 4 != 0) brush_end++;
228 229 230 231 232 233 234 235

        qint64 newPos = abr.device()->pos() + brush_end;
        if(newPos > 0 && newPos < abr.device()->size()) {
            abr.device()->seek(newPos);
        }
        else
            return 0;

236 237 238 239 240 241
        samples++;
    }

    // set stream to samples data
    abr.device()->seek(data_start);

242
    //dbgKrita <<"samples : "<< samples;
243 244 245 246 247 248 249 250 251 252 253 254
    return samples;
}



static bool abr_read_content(QDataStream & abr, AbrInfo *abr_hdr)
{

    abr >> abr_hdr->version;
    abr_hdr->subversion = 0;
    abr_hdr->count = 0;

255
    switch (abr_hdr->version) {
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
    case 1:
    case 2:
        abr >> abr_hdr->count;
        break;
    case 6:
        abr >> abr_hdr->subversion;
        abr_hdr->count = find_sample_count_v6(abr, abr_hdr);
        break;
    default:
        // unknown versions
        break;
    }
    // next bytes in abr are samples data

    return true;
}


274
static QString abr_read_ucs2_text(QDataStream & abr)
275
{
276 277
    quint32 name_size;
    quint32 buf_size;
278 279 280 281 282 283 284 285 286
    uint   i;
    /* two-bytes characters encoded (UCS-2)
    *  format:
    *   long : size - number of characters in string
    *   data : zero terminated UCS-2 string
    */

    // long
    abr >> name_size;
287
    if (name_size == 0) {
288
        return QString();
289 290 291 292 293 294 295 296 297
    }

    //buf_size = name_size * 2;
    buf_size = name_size;

    //name_ucs2 = (char*) malloc (buf_size * sizeof (char));
    //name_ucs2 = new char[buf_size];

    ushort * name_ucs2 = new ushort[buf_size];
298
    for (i = 0; i < buf_size ; i++) {
299
        //* char*/
300
        //abr >> name_ucs2[i];
301

302 303
        // I will use ushort as that is input to fromUtf16
        abr >>  name_ucs2[i];
304 305 306 307 308 309 310 311
    }
    QString name_utf8 = QString::fromUtf16(name_ucs2, buf_size);
    delete [] name_ucs2;

    return name_utf8;
}


312
quint32 KisAbrBrushCollection::abr_brush_load_v6(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
313 314
{
    Q_UNUSED(image_ID);
315 316 317
    qint32 brush_size = 0;
    qint32 brush_end = 0;
    qint32 next_brush = 0;
318 319

    qint32 top, left, bottom, right;
320
    top = left = bottom = right = 0;
321 322 323
    short depth;
    char compression;

324 325 326
    qint32 width = 0;
    qint32 height = 0;
    qint32 size = 0;
327 328 329 330 331 332 333 334

    qint32 layer_ID = -1;

    char *buffer;

    abr >> brush_size;
    brush_end = brush_size;
    // complement to 4
335
    while (brush_end % 4 != 0) {
336 337 338 339 340 341
        brush_end++;
    }

    next_brush = abr.device()->pos() + brush_end;

    // discard key
342
    abr.device()->seek(abr.device()->pos() + 37);
343 344
    if (abr_hdr->subversion == 1)
        // discard short coordinates and unknown short
345
        abr.device()->seek(abr.device()->pos() + 10);
346 347
    else
        // discard unknown bytes
348
        abr.device()->seek(abr.device()->pos() + 264);
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372

    // long
    abr >> top;
    abr >> left;
    abr >> bottom;
    abr >> right;
    // short
    abr >> depth;
    // char
    abr.device()->getChar(&compression);

    width = right - left;
    height = bottom - top;
    size = width * (depth >> 3) * height;

    // remove .abr and add some id, so something like test.abr -> test_12345
    QString name = abr_v1_brush_name(filename, id);

    buffer = (char*)malloc(size);

    // data decoding
    if (!compression) {
        // not compressed - read raw bytes as brush data
        //fread (buffer, size, 1, abr);
373
        abr.readRawData(buffer, size);
374
    } else {
375
        rle_decode(abr, buffer, height);
376 377
    }

378 379 380 381 382 383 384 385
    if (width < quint16_MAX && height < quint16_MAX) {
        // filename - filename of the file , e.g. test.abr
        // name - test_number_of_the_brush, e.g test_1, test_2
        KisAbrBrush* abrBrush = 0;
        if (m_abrBrushes.contains(name)) {
            abrBrush = m_abrBrushes[name];
        }
        else {
Stefano Bonicatti's avatar
Stefano Bonicatti committed
386 387
            abrBrush = new KisAbrBrush(name, this);
            abrBrush->setMD5(md5());
388 389 390 391 392 393 394
        }

        abrBrush->setBrushTipImage(convertToQImage(buffer, width, height));
        // XXX: call extra setters on abrBrush for other options of ABR brushes
        abrBrush->setValid(true);
        abrBrush->setName(name);
        m_abrBrushes[name] = abrBrush;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
395

396
    }
397

398
    free(buffer);
399 400 401 402 403 404 405
    abr.device()->seek(next_brush);

    layer_ID = id;
    return layer_ID;
}


406
qint32 KisAbrBrushCollection::abr_brush_load_v12(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
{
    Q_UNUSED(image_ID);
    short brush_type;
    qint32 brush_size;
    qint32 next_brush;

    qint32 top, left, bottom, right;
    qint16 depth;
    char compression;
    QString name;

    qint32 width, height;
    qint32 size;

    qint32 layer_ID = -1;
    char   *buffer;

    // short
    abr >> brush_type;
    // long
    abr >> brush_size;
    next_brush = abr.device()->pos() + brush_size;

    if (brush_type == 1) {
        // computed brush
        // FIXME: support it!
        warnKrita  << "WARNING: computed brush unsupported, skipping.";
        abr.device()->seek(abr.device()->pos() + next_brush);
        // TODO: test also this one abr.skipRawData(next_brush);
    }
437
    else if (brush_type == 2) {
438 439 440 441 442
        // sampled brush
        // discard 4 misc bytes and 2 spacing bytes
        abr.device()->seek(abr.device()->pos() + 6);

        if (abr_hdr->version == 2)
443 444 445
            name = abr_read_ucs2_text(abr);
        if (name.isNull()) {
            name = abr_v1_brush_name(filename, id);
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
        }

        // discard 1 byte for antialiasing and 4 x short for short bounds
        abr.device()->seek(abr.device()->pos() + 9);

        // long
        abr >> top;
        abr >> left;
        abr >> bottom;
        abr >> right;
        // short
        abr >> depth;
        // char
        abr.device()->getChar(&compression);

        width = right - left;
        height = bottom - top;
        size = width * (depth >> 3) * height;

        /* FIXME: support wide brushes */
        if (height > 16384) {
            warnKrita << "WARNING: wide brushes not supported";
            abr.device()->seek(next_brush);
        }
        else {
            buffer = (char*)malloc(size);

            if (!compression) {
                // not compressed - read raw bytes as brush data
475 476 477
                abr.readRawData(buffer, size);
            } else {
                rle_decode(abr, buffer, height);
478 479
            }

480 481 482 483 484
            KisAbrBrush* abrBrush = 0;
            if (m_abrBrushes.contains(name)) {
                abrBrush = m_abrBrushes[name];
            }
            else {
Stefano Bonicatti's avatar
Stefano Bonicatti committed
485 486
                abrBrush = new KisAbrBrush(name, this);
                abrBrush->setMD5(md5());
487 488
            }

489
            abrBrush->setBrushTipImage(convertToQImage(buffer, width, height));
490
            // XXX: call extra setters on abrBrush for other options of ABR brushes   free (buffer);
491 492
            abrBrush->setValid(true);
            abrBrush->setName(name);
493
            m_abrBrushes[name] = abrBrush;
494
            layer_ID = 1;
495 496 497 498 499 500 501 502 503 504 505
        }
    }
    else {
        warnKrita << "Unknown ABR brush type, skipping.";
        abr.device()->seek(next_brush);
    }

    return layer_ID;
}


506
qint32 KisAbrBrushCollection::abr_brush_load(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
507 508
{
    qint32 layer_ID = -1;
509
    switch (abr_hdr->version) {
510
    case 1:
511
        Q_FALLTHROUGH();
512 513
        // fall through, version 1 and 2 are compatible
    case 2:
514
        layer_ID = abr_brush_load_v12(abr, abr_hdr, filename, image_ID, id);
515 516
        break;
    case 6:
517
        layer_ID = abr_brush_load_v6(abr, abr_hdr, filename, image_ID, id);
518 519 520 521 522 523 524 525
        break;
    }

    return layer_ID;
}


KisAbrBrushCollection::KisAbrBrushCollection(const QString& filename)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
526
    : KisScalingSizeBrush(filename)
527 528 529
{
}

530
KisAbrBrushCollection::KisAbrBrushCollection(const KisAbrBrushCollection& rhs)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
531
    : KisScalingSizeBrush(rhs)
532 533 534 535 536 537 538 539 540 541 542 543 544 545
{
    for (auto it = rhs.m_abrBrushes.begin();
         it != rhs.m_abrBrushes.end();
         ++it) {

        m_abrBrushes.insert(it.key(), new KisAbrBrush(*it.value(), this));
    }
}

KisBrush* KisAbrBrushCollection::clone() const
{
    return new KisAbrBrushCollection(*this);
}

546 547 548 549
bool KisAbrBrushCollection::load()
{
    QFile file(filename());
    // check if the file is open correctly
550
    if (!file.open(QIODevice::ReadOnly)) {
551
        warnKrita << "Can't open file " << filename();
552
        return false;
553
    }
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569

    bool res = loadFromDevice(&file);
    file.close();

    return res;

}

bool KisAbrBrushCollection::loadFromDevice(QIODevice *dev)
{
    AbrInfo abr_hdr;
    qint32 image_ID;
    int i;
    qint32 layer_ID;

    QByteArray ba = dev->readAll();
570
    QBuffer buf(&ba);
571
    buf.open(QIODevice::ReadOnly);
572
    QDataStream abr(&buf);
573

574

575
    if (!abr_read_content(abr, &abr_hdr)) {
576
        warnKrita << "Error: cannot parse ABR file: " << filename();
577
        return false;
578 579
    }

580
    if (!abr_supported_content(&abr_hdr)) {
581
        warnKrita << "ERROR: unable to decode abr format version " << abr_hdr.version << "(subver " << abr_hdr.subversion << ")";
582
        return false;
583 584
    }

585
    if (abr_hdr.count == 0) {
586
        errKrita << "ERROR: no sample brush found in " << filename();
587
        return false;
588 589 590 591
    }

    image_ID = 123456;

592
    for (i = 0; i < abr_hdr.count; i++) {
Sven Langkamp's avatar
Sven Langkamp committed
593
        layer_ID = abr_brush_load(abr, &abr_hdr, shortFilename(), image_ID, i + 1);
594
        if (layer_ID == -1) {
595 596 597 598 599 600 601 602 603 604 605 606 607
            warnKrita << "Warning: problem loading brush #" << i << " in " << filename();
        }
    }

    return true;

}

bool KisAbrBrushCollection::save()
{
    return false;
}

608 609 610 611 612
bool KisAbrBrushCollection::saveToDevice(QIODevice */*dev*/) const
{
    return false;
}

613 614 615 616 617 618 619
QImage KisAbrBrushCollection::image() const
{
    return QImage();
}

void KisAbrBrushCollection::toXML(QDomDocument& d, QDomElement& e) const
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
620 621
    Q_UNUSED(d);
    Q_UNUSED(e);
622 623 624 625 626 627 628
    // Do nothing...
}

QString KisAbrBrushCollection::defaultFileExtension() const
{
    return QString(".abr");
}