File indexing completed on 2024-05-12 15:56:08

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0004  *  SPDX-FileCopyrightText: 2007 Eric Lamarque <eric.lamarque@free.fr>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 #include <QtEndian>
0009 
0010 #include "kis_abr_brush_collection.h"
0011 #include "kis_abr_brush.h"
0012 
0013 #include <QDomElement>
0014 #include <QFile>
0015 #include <QImage>
0016 #include <QPoint>
0017 #include <QColor>
0018 #include <QByteArray>
0019 #include <kis_debug.h>
0020 #include <QString>
0021 #include <QBuffer>
0022 #include <QFileInfo>
0023 #include <KoMD5Generator.h>
0024 #include <klocalizedstring.h>
0025 
0026 #include <KoColor.h>
0027 
0028 
0029 struct AbrInfo {
0030     //big endian
0031     short version;
0032     short subversion;
0033     // count of the images (brushes) in the abr file
0034     short count;
0035 };
0036 
0037 /// save the QImages as png files to directory image_tests
0038 static QImage convertToQImage(char * buffer, qint32 width, qint32 height)
0039 {
0040     // create 8-bit indexed image
0041     QImage img(width, height, QImage::Format_RGB32);
0042     int pos = 0;
0043     int value = 0;
0044     for (int y = 0; y < height; y++) {
0045         QRgb *pixel = reinterpret_cast<QRgb *>(img.scanLine(y));
0046         for (int x = 0; x < width; x++, pos++) {
0047             value = 255 - buffer[pos];
0048             pixel[x] = qRgb(value, value , value);
0049         }
0050 
0051     }
0052 
0053     return img;
0054 }
0055 
0056 static qint32 rle_decode(QDataStream & abr, char *buffer, qint32 height)
0057 {
0058     qint32 n;
0059     char ptmp;
0060     char ch;
0061     int i, j, c;
0062     short *cscanline_len;
0063     char *data = buffer;
0064 
0065     // read compressed size foreach scanline
0066     cscanline_len = new short[ height ];
0067     for (i = 0; i < height; i++) {
0068         // short
0069         abr >> cscanline_len[i];
0070     }
0071 
0072     // unpack each scanline data
0073     for (i = 0; i < height; i++) {
0074         for (j = 0; j < cscanline_len[i];) {
0075             // char
0076             if (!abr.device()->getChar(&ptmp)) {
0077                 break;
0078             }
0079             n = ptmp;
0080 
0081             j++;
0082             if (n >= 128)     // force sign
0083                 n -= 256;
0084             if (n < 0) {      // copy the following char -n + 1 times
0085                 if (n == -128)  // it's a nop
0086                     continue;
0087                 n = -n + 1;
0088                 // char
0089                 if (!abr.device()->getChar(&ch)) {
0090                     break;
0091                 }
0092 
0093                 j++;
0094                 for (c = 0; c < n; c++, data++) {
0095                     *data = ch;
0096                 }
0097             }
0098             else {
0099                 // read the following n + 1 chars (no compr)
0100                 for (c = 0; c < n + 1; c++, j++, data++) {
0101                     // char
0102                     if (!abr.device()->getChar(data))  {
0103                         break;
0104                     }
0105                 }
0106             }
0107         }
0108     }
0109     delete [] cscanline_len;
0110     return 0;
0111 }
0112 
0113 
0114 static QString abr_v1_brush_name(const QString filename, qint32 id)
0115 {
0116     QString result = filename;
0117     int pos = filename.lastIndexOf('.');
0118     result.remove(pos, 4);
0119     QTextStream(&result) << "_" << id;
0120     return result;
0121 }
0122 
0123 static bool abr_supported_content(AbrInfo *abr_hdr)
0124 {
0125     switch (abr_hdr->version) {
0126     case 1:
0127     case 2:
0128         return true;
0129         break;
0130     case 6:
0131         if (abr_hdr->subversion == 1 || abr_hdr->subversion == 2)
0132             return true;
0133         break;
0134     }
0135     return false;
0136 }
0137 
0138 static bool abr_reach_8BIM_section(QDataStream & abr, const QString name)
0139 {
0140     char tag[4];
0141     char tagname[5];
0142     qint32 section_size = 0;
0143     int r;
0144 
0145     // find 8BIMname section
0146     while (!abr.atEnd()) {
0147         r = abr.readRawData(tag, 4);
0148 
0149         if (r != 4) {
0150             warnKrita << "Error: Cannot read 8BIM tag ";
0151             return false;
0152         }
0153 
0154         if (strncmp(tag, "8BIM", 4)) {
0155             warnKrita << "Error: Start tag not 8BIM but " << (int)tag[0] << (int)tag[1] << (int)tag[2] << (int)tag[3] << " at position " << abr.device()->pos();
0156             return false;
0157         }
0158 
0159         r = abr.readRawData(tagname, 4);
0160 
0161         if (r != 4) {
0162             warnKrita << "Error: Cannot read 8BIM tag name";
0163             return false;
0164         }
0165         tagname[4] = '\0';
0166 
0167         QString s1 = QString::fromLatin1(tagname, 4);
0168 
0169         if (!s1.compare(name)) {
0170             return true;
0171         }
0172 
0173         // long
0174         abr >> section_size;
0175         abr.device()->seek(abr.device()->pos() + section_size);
0176     }
0177     return true;
0178 }
0179 
0180 static qint32 find_sample_count_v6(QDataStream & abr, AbrInfo *abr_info)
0181 {
0182     qint64 origin;
0183     qint32 sample_section_size;
0184     qint32 sample_section_end;
0185     qint32 samples = 0;
0186     qint32 data_start;
0187 
0188     qint32 brush_size;
0189     qint32 brush_end;
0190 
0191     if (!abr_supported_content(abr_info))
0192         return 0;
0193 
0194     origin = abr.device()->pos();
0195 
0196     if (!abr_reach_8BIM_section(abr, "samp")) {
0197         // reset to origin
0198         abr.device()->seek(origin);
0199         return 0;
0200     }
0201 
0202     // long
0203     abr >> sample_section_size;
0204     sample_section_end = sample_section_size + abr.device()->pos();
0205 
0206     if(sample_section_end < 0 || sample_section_end > abr.device()->size())
0207         return 0;
0208 
0209     data_start = abr.device()->pos();
0210 
0211     while ((!abr.atEnd()) && (abr.device()->pos() < sample_section_end)) {
0212         // read long
0213         abr >> brush_size;
0214         brush_end = brush_size;
0215         // complement to 4
0216         while (brush_end % 4 != 0) brush_end++;
0217 
0218         qint64 newPos = abr.device()->pos() + brush_end;
0219         if(newPos > 0 && newPos < abr.device()->size()) {
0220             abr.device()->seek(newPos);
0221         }
0222         else
0223             return 0;
0224 
0225         samples++;
0226     }
0227 
0228     // set stream to samples data
0229     abr.device()->seek(data_start);
0230 
0231     //dbgKrita <<"samples : "<< samples;
0232     return samples;
0233 }
0234 
0235 
0236 
0237 static bool abr_read_content(QDataStream & abr, AbrInfo *abr_hdr)
0238 {
0239 
0240     abr >> abr_hdr->version;
0241     abr_hdr->subversion = 0;
0242     abr_hdr->count = 0;
0243 
0244     switch (abr_hdr->version) {
0245     case 1:
0246     case 2:
0247         abr >> abr_hdr->count;
0248         break;
0249     case 6:
0250         abr >> abr_hdr->subversion;
0251         abr_hdr->count = find_sample_count_v6(abr, abr_hdr);
0252         break;
0253     default:
0254         // unknown versions
0255         break;
0256     }
0257     // next bytes in abr are samples data
0258 
0259     return true;
0260 }
0261 
0262 
0263 static QString abr_read_ucs2_text(QDataStream & abr)
0264 {
0265     quint32 name_size;
0266     quint32 buf_size;
0267     uint   i;
0268     /* two-bytes characters encoded (UCS-2)
0269     *  format:
0270     *   long : size - number of characters in string
0271     *   data : zero terminated UCS-2 string
0272     */
0273 
0274     // long
0275     abr >> name_size;
0276     if (name_size == 0) {
0277         return QString();
0278     }
0279 
0280     //buf_size = name_size * 2;
0281     buf_size = name_size;
0282 
0283     //name_ucs2 = (char*) malloc (buf_size * sizeof (char));
0284     //name_ucs2 = new char[buf_size];
0285 
0286     ushort * name_ucs2 = new ushort[buf_size];
0287     for (i = 0; i < buf_size ; i++) {
0288         //* char*/
0289         //abr >> name_ucs2[i];
0290 
0291         // I will use ushort as that is input to fromUtf16
0292         abr >>  name_ucs2[i];
0293     }
0294     QString name_utf8 = QString::fromUtf16(name_ucs2, buf_size);
0295     delete [] name_ucs2;
0296 
0297     return name_utf8;
0298 }
0299 
0300 
0301 quint32 KisAbrBrushCollection::abr_brush_load_v6(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
0302 {
0303     Q_UNUSED(image_ID);
0304     qint32 brush_size = 0;
0305     qint32 brush_end = 0;
0306     qint32 next_brush = 0;
0307 
0308     qint32 top, left, bottom, right;
0309     top = left = bottom = right = 0;
0310     short depth;
0311     char compression;
0312 
0313     qint32 width = 0;
0314     qint32 height = 0;
0315     qint32 size = 0;
0316 
0317     qint32 layer_ID = -1;
0318 
0319     char *buffer;
0320 
0321     abr >> brush_size;
0322     brush_end = brush_size;
0323     // complement to 4
0324     while (brush_end % 4 != 0) {
0325         brush_end++;
0326     }
0327 
0328     next_brush = abr.device()->pos() + brush_end;
0329 
0330     // discard key
0331     abr.device()->seek(abr.device()->pos() + 37);
0332     if (abr_hdr->subversion == 1)
0333         // discard short coordinates and unknown short
0334         abr.device()->seek(abr.device()->pos() + 10);
0335     else
0336         // discard unknown bytes
0337         abr.device()->seek(abr.device()->pos() + 264);
0338 
0339     // long
0340     abr >> top;
0341     abr >> left;
0342     abr >> bottom;
0343     abr >> right;
0344     // short
0345     abr >> depth;
0346     // char
0347     abr.device()->getChar(&compression);
0348 
0349     width = right - left;
0350     height = bottom - top;
0351     size = width * (depth >> 3) * height;
0352 
0353     // remove .abr and add some id, so something like test.abr -> test_12345
0354     QString name = abr_v1_brush_name(filename, id);
0355 
0356     buffer = (char*)malloc(size);
0357 
0358     // data decoding
0359     if (!compression) {
0360         // not compressed - read raw bytes as brush data
0361         //fread (buffer, size, 1, abr);
0362         abr.readRawData(buffer, size);
0363     } else {
0364         rle_decode(abr, buffer, height);
0365     }
0366 
0367     if (width < quint16_MAX && height < quint16_MAX) {
0368         // filename - filename of the file , e.g. test.abr
0369         // name - test_number_of_the_brush, e.g test_1, test_2
0370         KisAbrBrushSP abrBrush;
0371         QImage brushTipImage = convertToQImage(buffer, width, height);
0372         if (m_abrBrushes->contains(name)) {
0373             abrBrush = m_abrBrushes.data()->operator[](name);
0374         }
0375         else {
0376             abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this));
0377             QBuffer buf;
0378             buf.open(QFile::ReadWrite);
0379             brushTipImage.save(&buf, "PNG");
0380             abrBrush->setMD5Sum(KoMD5Generator::generateHash(buf.data()));
0381         }
0382 
0383         abrBrush->setBrushTipImage(brushTipImage);
0384         // XXX: call extra setters on abrBrush for other options of ABR brushes
0385         abrBrush->setValid(true);
0386         abrBrush->setName(name);
0387         m_abrBrushes.data()->operator[](name) = abrBrush;
0388 
0389     }
0390 
0391     free(buffer);
0392     abr.device()->seek(next_brush);
0393 
0394     layer_ID = id;
0395     return layer_ID;
0396 }
0397 
0398 
0399 qint32 KisAbrBrushCollection::abr_brush_load_v12(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
0400 {
0401     Q_UNUSED(image_ID);
0402     short brush_type;
0403     qint32 brush_size;
0404     qint32 next_brush;
0405 
0406     qint32 top, left, bottom, right;
0407     qint16 depth;
0408     char compression;
0409     QString name;
0410 
0411     qint32 width, height;
0412     qint32 size;
0413 
0414     qint32 layer_ID = -1;
0415     char   *buffer;
0416 
0417     // short
0418     abr >> brush_type;
0419     // long
0420     abr >> brush_size;
0421     next_brush = abr.device()->pos() + brush_size;
0422 
0423     if (brush_type == 1) {
0424         // computed brush
0425         // FIXME: support it!
0426         warnKrita  << "WARNING: computed brush unsupported, skipping.";
0427         abr.device()->seek(abr.device()->pos() + next_brush);
0428         // TODO: test also this one abr.skipRawData(next_brush);
0429     }
0430     else if (brush_type == 2) {
0431         // sampled brush
0432         // discard 4 misc bytes and 2 spacing bytes
0433         abr.device()->seek(abr.device()->pos() + 6);
0434 
0435         if (abr_hdr->version == 2)
0436             name = abr_read_ucs2_text(abr);
0437         if (name.isNull()) {
0438             name = abr_v1_brush_name(filename, id);
0439         }
0440 
0441         // discard 1 byte for antialiasing and 4 x short for short bounds
0442         abr.device()->seek(abr.device()->pos() + 9);
0443 
0444         // long
0445         abr >> top;
0446         abr >> left;
0447         abr >> bottom;
0448         abr >> right;
0449         // short
0450         abr >> depth;
0451         // char
0452         abr.device()->getChar(&compression);
0453 
0454         width = right - left;
0455         height = bottom - top;
0456         size = width * (depth >> 3) * height;
0457 
0458         /* FIXME: support wide brushes */
0459         if (height > 16384) {
0460             warnKrita << "WARNING: wide brushes not supported";
0461             abr.device()->seek(next_brush);
0462         }
0463         else {
0464             buffer = (char*)malloc(size);
0465 
0466             if (!compression) {
0467                 // not compressed - read raw bytes as brush data
0468                 abr.readRawData(buffer, size);
0469             } else {
0470                 rle_decode(abr, buffer, height);
0471             }
0472 
0473             KisAbrBrushSP abrBrush;
0474             QImage brushTipImage = convertToQImage(buffer, width, height);
0475             if (m_abrBrushes->contains(name)) {
0476                 abrBrush = m_abrBrushes.data()->operator[](name);
0477             }
0478             else {
0479                 abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this));
0480                 QBuffer buf;
0481                 buf.open(QFile::ReadWrite);
0482                 brushTipImage.save(&buf, "PNG");
0483                 abrBrush->setMD5Sum(KoMD5Generator::generateHash(buf.data()));
0484             }
0485 
0486             abrBrush->setBrushTipImage(brushTipImage);
0487             // XXX: call extra setters on abrBrush for other options of ABR brushes   free (buffer);
0488             abrBrush->setValid(true);
0489             abrBrush->setName(name);
0490             m_abrBrushes.data()->operator[](name) = abrBrush;
0491             layer_ID = 1;
0492         }
0493     }
0494     else {
0495         warnKrita << "Unknown ABR brush type, skipping.";
0496         abr.device()->seek(next_brush);
0497     }
0498 
0499     return layer_ID;
0500 }
0501 
0502 
0503 qint32 KisAbrBrushCollection::abr_brush_load(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
0504 {
0505     qint32 layer_ID = -1;
0506     switch (abr_hdr->version) {
0507     case 1:
0508         Q_FALLTHROUGH();
0509         // fall through, version 1 and 2 are compatible
0510     case 2:
0511         layer_ID = abr_brush_load_v12(abr, abr_hdr, filename, image_ID, id);
0512         break;
0513     case 6:
0514         layer_ID = abr_brush_load_v6(abr, abr_hdr, filename, image_ID, id);
0515         break;
0516     }
0517 
0518     return layer_ID;
0519 }
0520 
0521 
0522 KisAbrBrushCollection::KisAbrBrushCollection(const QString& filename)
0523     : m_isLoaded(false)
0524     , m_lastModified()
0525     , m_filename(filename)
0526     , m_abrBrushes(new QMap<QString, KisAbrBrushSP>())
0527 {
0528 }
0529 
0530 KisAbrBrushCollection::KisAbrBrushCollection(const KisAbrBrushCollection& rhs)
0531     : m_isLoaded(rhs.m_isLoaded)
0532     , m_lastModified(rhs.m_lastModified)
0533 {
0534     m_abrBrushes.reset(new QMap<QString, KisAbrBrushSP>());
0535     for (auto it = rhs.m_abrBrushes->begin();
0536          it != rhs.m_abrBrushes->end();
0537          ++it) {
0538 
0539         m_abrBrushes->insert(it.key(), KisAbrBrushSP(new KisAbrBrush(*it.value(), this)));
0540     }
0541 }
0542 
0543 bool KisAbrBrushCollection::load()
0544 {
0545     m_isLoaded = true;
0546     QFile file(filename());
0547     QFileInfo info(file);
0548     m_lastModified = info.lastModified();
0549     // check if the file is open correctly
0550     if (!file.open(QIODevice::ReadOnly)) {
0551         warnKrita << "Can't open file " << filename();
0552         return false;
0553     }
0554 
0555     bool res = loadFromDevice(&file);
0556     file.close();
0557 
0558     return res;
0559 
0560 }
0561 
0562 bool KisAbrBrushCollection::loadFromDevice(QIODevice *dev)
0563 {
0564     AbrInfo abr_hdr;
0565     qint32 image_ID;
0566     int i;
0567     qint32 layer_ID;
0568 
0569     QByteArray ba = dev->readAll();
0570     QBuffer buf(&ba);
0571     buf.open(QIODevice::ReadOnly);
0572     QDataStream abr(&buf);
0573 
0574 
0575     if (!abr_read_content(abr, &abr_hdr)) {
0576         warnKrita << "Error: cannot parse ABR file: " << filename();
0577         return false;
0578     }
0579 
0580     if (!abr_supported_content(&abr_hdr)) {
0581         warnKrita << "ERROR: unable to decode abr format version " << abr_hdr.version << "(subver " << abr_hdr.subversion << ")";
0582         return false;
0583     }
0584 
0585     if (abr_hdr.count == 0) {
0586         errKrita << "ERROR: no sample brush found in " << filename();
0587         return false;
0588     }
0589 
0590     image_ID = 123456;
0591 
0592     for (i = 0; i < abr_hdr.count; i++) {
0593         layer_ID = abr_brush_load(abr, &abr_hdr, QFileInfo(filename()).fileName(), image_ID, i + 1);
0594         if (layer_ID == -1) {
0595             warnKrita << "Warning: problem loading brush #" << i << " in " << filename();
0596         }
0597     }
0598 
0599     return true;
0600 
0601 }
0602 
0603 bool KisAbrBrushCollection::save()
0604 {
0605     return false;
0606 }
0607 
0608 bool KisAbrBrushCollection::saveToDevice(QIODevice */*dev*/) const
0609 {
0610     return false;
0611 }
0612 
0613 bool KisAbrBrushCollection::isLoaded() const
0614 {
0615     return m_isLoaded;
0616 }
0617 
0618 QImage KisAbrBrushCollection::image() const
0619 {
0620     if (m_abrBrushes->size() > 0) {
0621         return m_abrBrushes->values().first()->image();
0622     }
0623     return QImage();
0624 }
0625 
0626 void KisAbrBrushCollection::toXML(QDomDocument& d, QDomElement& e) const
0627 {
0628     Q_UNUSED(d);
0629     Q_UNUSED(e);
0630     // Do nothing...
0631 }
0632 
0633 QString KisAbrBrushCollection::defaultFileExtension() const
0634 {
0635     return QString(".abr");
0636 }