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 }