Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_abr_brush_collection.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2010 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
4 * SPDX-FileCopyrightText: 2007 Eric Lamarque <eric.lamarque@free.fr>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8#include <QtEndian>
9
11#include "kis_abr_brush.h"
12
13#include <QDomElement>
14#include <QFile>
15#include <QImage>
16#include <QPoint>
17#include <QColor>
18#include <QByteArray>
19#include <kis_debug.h>
20#include <QString>
21#include <QBuffer>
22#include <QFileInfo>
23#include <KoMD5Generator.h>
24#include <klocalizedstring.h>
25
26#include <KoColor.h>
27
28
29struct AbrInfo {
30 //big endian
31 short version;
33 // count of the images (brushes) in the abr file
34 short count;
35};
36
38static QImage convertToQImage(char * buffer, qint32 width, qint32 height)
39{
40 // create 8-bit indexed image
41 QImage img(width, height, QImage::Format_RGB32);
42 int pos = 0;
43 int value = 0;
44 for (int y = 0; y < height; y++) {
45 QRgb *pixel = reinterpret_cast<QRgb *>(img.scanLine(y));
46 for (int x = 0; x < width; x++, pos++) {
47 value = 255 - buffer[pos];
48 pixel[x] = qRgb(value, value , value);
49 }
50
51 }
52
53 return img;
54}
55
56static qint32 rle_decode(QDataStream & abr, char *buffer, qint32 height)
57{
58 qint32 n;
59 char ptmp;
60 char ch;
61 int i, j, c;
62 short *cscanline_len;
63 char *data = buffer;
64
65 // read compressed size foreach scanline
66 cscanline_len = new short[ height ];
67 for (i = 0; i < height; i++) {
68 // short
69 abr >> cscanline_len[i];
70 }
71
72 // unpack each scanline data
73 for (i = 0; i < height; i++) {
74 for (j = 0; j < cscanline_len[i];) {
75 // char
76 if (!abr.device()->getChar(&ptmp)) {
77 break;
78 }
79 n = ptmp;
80
81 j++;
82 if (n >= 128) // force sign
83 n -= 256;
84 if (n < 0) { // copy the following char -n + 1 times
85 if (n == -128) // it's a nop
86 continue;
87 n = -n + 1;
88 // char
89 if (!abr.device()->getChar(&ch)) {
90 break;
91 }
92
93 j++;
94 for (c = 0; c < n; c++, data++) {
95 *data = ch;
96 }
97 }
98 else {
99 // read the following n + 1 chars (no compr)
100 for (c = 0; c < n + 1; c++, j++, data++) {
101 // char
102 if (!abr.device()->getChar(data)) {
103 break;
104 }
105 }
106 }
107 }
108 }
109 delete [] cscanline_len;
110 return 0;
111}
112
113
114static QString abr_v1_brush_name(const QString filename, qint32 id)
115{
116 QString result = filename;
117 int pos = filename.lastIndexOf('.');
118 result.remove(pos, 4);
119 QTextStream(&result) << "_" << id;
120 return result;
121}
122
123static bool abr_supported_content(AbrInfo *abr_hdr)
124{
125 switch (abr_hdr->version) {
126 case 1:
127 case 2:
128 return true;
129 break;
130 case 6:
131 if (abr_hdr->subversion == 1 || abr_hdr->subversion == 2)
132 return true;
133 break;
134 }
135 return false;
136}
137
138static bool abr_reach_8BIM_section(QDataStream & abr, const QString name)
139{
140 char tag[4];
141 char tagname[5];
142 qint32 section_size = 0;
143 int r;
144
145 // find 8BIMname section
146 while (!abr.atEnd()) {
147 r = abr.readRawData(tag, 4);
148
149 if (r != 4) {
150 warnKrita << "Error: Cannot read 8BIM tag ";
151 return false;
152 }
153
154 if (strncmp(tag, "8BIM", 4)) {
155 warnKrita << "Error: Start tag not 8BIM but " << (int)tag[0] << (int)tag[1] << (int)tag[2] << (int)tag[3] << " at position " << abr.device()->pos();
156 return false;
157 }
158
159 r = abr.readRawData(tagname, 4);
160
161 if (r != 4) {
162 warnKrita << "Error: Cannot read 8BIM tag name";
163 return false;
164 }
165 tagname[4] = '\0';
166
167 QString s1 = QString::fromLatin1(tagname, 4);
168
169 if (!s1.compare(name)) {
170 return true;
171 }
172
173 // long
174 abr >> section_size;
175 abr.device()->seek(abr.device()->pos() + section_size);
176 }
177 return true;
178}
179
180static qint32 find_sample_count_v6(QDataStream & abr, AbrInfo *abr_info)
181{
182 qint64 origin;
183 qint32 sample_section_size;
184 qint32 sample_section_end;
185 qint32 samples = 0;
186 qint32 data_start;
187
188 qint32 brush_size;
189 qint32 brush_end;
190
191 if (!abr_supported_content(abr_info))
192 return 0;
193
194 origin = abr.device()->pos();
195
196 if (!abr_reach_8BIM_section(abr, "samp")) {
197 // reset to origin
198 abr.device()->seek(origin);
199 return 0;
200 }
201
202 // long
203 abr >> sample_section_size;
204 sample_section_end = sample_section_size + abr.device()->pos();
205
206 if(sample_section_end < 0 || sample_section_end > abr.device()->size())
207 return 0;
208
209 data_start = abr.device()->pos();
210
211 while ((!abr.atEnd()) && (abr.device()->pos() < sample_section_end)) {
212 // read long
213 abr >> brush_size;
214 brush_end = brush_size;
215 // complement to 4
216 while (brush_end % 4 != 0) brush_end++;
217
218 qint64 newPos = abr.device()->pos() + brush_end;
219 if(newPos > 0 && newPos < abr.device()->size()) {
220 abr.device()->seek(newPos);
221 }
222 else
223 return 0;
224
225 samples++;
226 }
227
228 // set stream to samples data
229 abr.device()->seek(data_start);
230
231 //dbgKrita <<"samples : "<< samples;
232 return samples;
233}
234
235
236
237static bool abr_read_content(QDataStream & abr, AbrInfo *abr_hdr)
238{
239
240 abr >> abr_hdr->version;
241 abr_hdr->subversion = 0;
242 abr_hdr->count = 0;
243
244 switch (abr_hdr->version) {
245 case 1:
246 case 2:
247 abr >> abr_hdr->count;
248 break;
249 case 6:
250 abr >> abr_hdr->subversion;
251 abr_hdr->count = find_sample_count_v6(abr, abr_hdr);
252 break;
253 default:
254 // unknown versions
255 break;
256 }
257 // next bytes in abr are samples data
258
259 return true;
260}
261
262
263static QString abr_read_ucs2_text(QDataStream & abr)
264{
265 quint32 name_size;
266 quint32 buf_size;
267 uint i;
268 /* two-bytes characters encoded (UCS-2)
269 * format:
270 * long : size - number of characters in string
271 * data : zero terminated UCS-2 string
272 */
273
274 // long
275 abr >> name_size;
276 if (name_size == 0) {
277 return QString();
278 }
279
280 //buf_size = name_size * 2;
281 buf_size = name_size;
282
283 //name_ucs2 = (char*) malloc (buf_size * sizeof (char));
284 //name_ucs2 = new char[buf_size];
285
286 ushort * name_ucs2 = new ushort[buf_size];
287 for (i = 0; i < buf_size ; i++) {
288 //* char*/
289 //abr >> name_ucs2[i];
290
291 // I will use ushort as that is input to fromUtf16
292 abr >> name_ucs2[i];
293 }
294 QString name_utf8 = QString::fromUtf16(name_ucs2, buf_size);
295 delete [] name_ucs2;
296
297 return name_utf8;
298}
299
300
301quint32 KisAbrBrushCollection::abr_brush_load_v6(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
302{
303 Q_UNUSED(image_ID);
304 qint32 brush_size = 0;
305 qint32 brush_end = 0;
306 qint32 next_brush = 0;
307
308 qint32 top, left, bottom, right;
309 top = left = bottom = right = 0;
310 short depth;
311 char compression;
312
313 qint32 width = 0;
314 qint32 height = 0;
315 qint32 size = 0;
316
317 qint32 layer_ID = -1;
318
319 char *buffer;
320
321 abr >> brush_size;
322 brush_end = brush_size;
323 // complement to 4
324 while (brush_end % 4 != 0) {
325 brush_end++;
326 }
327
328 next_brush = abr.device()->pos() + brush_end;
329
330 // discard key
331 abr.device()->seek(abr.device()->pos() + 37);
332 if (abr_hdr->subversion == 1)
333 // discard short coordinates and unknown short
334 abr.device()->seek(abr.device()->pos() + 10);
335 else
336 // discard unknown bytes
337 abr.device()->seek(abr.device()->pos() + 264);
338
339 // long
340 abr >> top;
341 abr >> left;
342 abr >> bottom;
343 abr >> right;
344 // short
345 abr >> depth;
346 // char
347 abr.device()->getChar(&compression);
348
349 width = right - left;
350 height = bottom - top;
351 size = width * (depth >> 3) * height;
352
353 // remove .abr and add some id, so something like test.abr -> test_12345
354 QString name = abr_v1_brush_name(filename, id);
355
356 buffer = (char*)malloc(size);
357
358 // data decoding
359 if (!compression) {
360 // not compressed - read raw bytes as brush data
361 //fread (buffer, size, 1, abr);
362 abr.readRawData(buffer, size);
363 } else {
364 rle_decode(abr, buffer, height);
365 }
366
367 if (width < quint16_MAX && height < quint16_MAX) {
368 // filename - filename of the file , e.g. test.abr
369 // name - test_number_of_the_brush, e.g test_1, test_2
370 KisAbrBrushSP abrBrush;
371 QImage brushTipImage = convertToQImage(buffer, width, height);
372 if (m_abrBrushes->contains(name)) {
373 abrBrush = m_abrBrushes.data()->operator[](name);
374 }
375 else {
376 abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this));
377 QBuffer buf;
378 buf.open(QFile::ReadWrite);
379 brushTipImage.save(&buf, "PNG");
380 abrBrush->setMD5Sum(KoMD5Generator::generateHash(buf.data()));
381 }
382
383 abrBrush->setBrushTipImage(brushTipImage);
384 // XXX: call extra setters on abrBrush for other options of ABR brushes
385 abrBrush->setValid(true);
386 abrBrush->setName(name);
387 m_abrBrushes.data()->operator[](name) = abrBrush;
388
389 }
390
391 free(buffer);
392 abr.device()->seek(next_brush);
393
394 layer_ID = id;
395 return layer_ID;
396}
397
398
399qint32 KisAbrBrushCollection::abr_brush_load_v12(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
400{
401 Q_UNUSED(image_ID);
402 short brush_type;
403 qint32 brush_size;
404 qint32 next_brush;
405
406 qint32 top, left, bottom, right;
407 qint16 depth;
408 char compression;
409 QString name;
410
411 qint32 width, height;
412 qint32 size;
413
414 qint32 layer_ID = -1;
415 char *buffer;
416
417 // short
418 abr >> brush_type;
419 // long
420 abr >> brush_size;
421 next_brush = abr.device()->pos() + brush_size;
422
423 if (brush_type == 1) {
424 // computed brush
425 // FIXME: support it!
426 warnKrita << "WARNING: computed brush unsupported, skipping.";
427 abr.device()->seek(abr.device()->pos() + next_brush);
428 // TODO: test also this one abr.skipRawData(next_brush);
429 }
430 else if (brush_type == 2) {
431 // sampled brush
432 // discard 4 misc bytes and 2 spacing bytes
433 abr.device()->seek(abr.device()->pos() + 6);
434
435 if (abr_hdr->version == 2)
436 name = abr_read_ucs2_text(abr);
437 if (name.isNull()) {
438 name = abr_v1_brush_name(filename, id);
439 }
440
441 // discard 1 byte for antialiasing and 4 x short for short bounds
442 abr.device()->seek(abr.device()->pos() + 9);
443
444 // long
445 abr >> top;
446 abr >> left;
447 abr >> bottom;
448 abr >> right;
449 // short
450 abr >> depth;
451 // char
452 abr.device()->getChar(&compression);
453
454 width = right - left;
455 height = bottom - top;
456 size = width * (depth >> 3) * height;
457
458 /* FIXME: support wide brushes */
459 if (height > 16384) {
460 warnKrita << "WARNING: wide brushes not supported";
461 abr.device()->seek(next_brush);
462 }
463 else {
464 buffer = (char*)malloc(size);
465
466 if (!compression) {
467 // not compressed - read raw bytes as brush data
468 abr.readRawData(buffer, size);
469 } else {
470 rle_decode(abr, buffer, height);
471 }
472
473 KisAbrBrushSP abrBrush;
474 QImage brushTipImage = convertToQImage(buffer, width, height);
475 if (m_abrBrushes->contains(name)) {
476 abrBrush = m_abrBrushes.data()->operator[](name);
477 }
478 else {
479 abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this));
480 QBuffer buf;
481 buf.open(QFile::ReadWrite);
482 brushTipImage.save(&buf, "PNG");
483 abrBrush->setMD5Sum(KoMD5Generator::generateHash(buf.data()));
484 }
485
486 abrBrush->setBrushTipImage(brushTipImage);
487 // XXX: call extra setters on abrBrush for other options of ABR brushes free (buffer);
488 abrBrush->setValid(true);
489 abrBrush->setName(name);
490 m_abrBrushes.data()->operator[](name) = abrBrush;
491 layer_ID = 1;
492 }
493 }
494 else {
495 warnKrita << "Unknown ABR brush type, skipping.";
496 abr.device()->seek(next_brush);
497 }
498
499 return layer_ID;
500}
501
502
503qint32 KisAbrBrushCollection::abr_brush_load(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
504{
505 qint32 layer_ID = -1;
506 switch (abr_hdr->version) {
507 case 1:
508 Q_FALLTHROUGH();
509 // fall through, version 1 and 2 are compatible
510 case 2:
511 layer_ID = abr_brush_load_v12(abr, abr_hdr, filename, image_ID, id);
512 break;
513 case 6:
514 layer_ID = abr_brush_load_v6(abr, abr_hdr, filename, image_ID, id);
515 break;
516 }
517
518 return layer_ID;
519}
520
521
523 : m_isLoaded(false)
524 , m_lastModified()
525 , m_filename(filename)
526 , m_abrBrushes(new QMap<QString, KisAbrBrushSP>())
527{
528}
529
531 : m_isLoaded(rhs.m_isLoaded)
532 , m_lastModified(rhs.m_lastModified)
533{
534 m_abrBrushes.reset(new QMap<QString, KisAbrBrushSP>());
535 for (auto it = rhs.m_abrBrushes->begin();
536 it != rhs.m_abrBrushes->end();
537 ++it) {
538
539 m_abrBrushes->insert(it.key(), KisAbrBrushSP(new KisAbrBrush(*it.value(), this)));
540 }
541}
542
544{
545 m_isLoaded = true;
546 QFile file(filename());
547 QFileInfo info(file);
548 m_lastModified = info.lastModified();
549 // 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 bool res = loadFromDevice(&file);
556 file.close();
557
558 return res;
559
560}
561
563{
564 AbrInfo abr_hdr;
565 qint32 image_ID;
566 int i;
567 qint32 layer_ID;
568
569 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 image_ID = 123456;
591
592 for (i = 0; i < abr_hdr.count; i++) {
593 layer_ID = abr_brush_load(abr, &abr_hdr, QFileInfo(filename()).fileName(), image_ID, i + 1);
594 if (layer_ID == -1) {
595 warnKrita << "Warning: problem loading brush #" << i << " in " << filename();
596 }
597 }
598
599 return true;
600
601}
602
604{
605 return false;
606}
607
608bool KisAbrBrushCollection::saveToDevice(QIODevice */*dev*/) const
609{
610 return false;
611}
612
614{
615 return m_isLoaded;
616}
617
619{
620 if (m_abrBrushes->size() > 0) {
621 return m_abrBrushes->values().first()->image();
622 }
623 return QImage();
624}
625
626void KisAbrBrushCollection::toXML(QDomDocument& d, QDomElement& e) const
627{
628 Q_UNUSED(d);
629 Q_UNUSED(e);
630 // Do nothing...
631}
632
634{
635 return QString(".abr");
636}
float value(const T *src, size_t ch)
QPointF s1
unsigned int uint
bool saveToDevice(QIODevice *dev) const
quint32 abr_brush_load_v6(QDataStream &abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
qint32 abr_brush_load_v12(QDataStream &abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
bool loadFromDevice(QIODevice *dev)
KisAbrBrushCollection(const QString &filename)
Construct brush to load filename later as brush.
QSharedPointer< QMap< QString, KisAbrBrushSP > > m_abrBrushes
qint32 abr_brush_load(QDataStream &abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id)
void toXML(QDomDocument &d, QDomElement &e) const
static QString generateHash(const QString &filename)
generateHash reads the given file and generates a hex-encoded md5sum for the file.
QSharedPointer< KisAbrBrush > KisAbrBrushSP
static bool abr_reach_8BIM_section(QDataStream &abr, const QString name)
static QString abr_v1_brush_name(const QString filename, qint32 id)
static qint32 find_sample_count_v6(QDataStream &abr, AbrInfo *abr_info)
static bool abr_read_content(QDataStream &abr, AbrInfo *abr_hdr)
static bool abr_supported_content(AbrInfo *abr_hdr)
static QString abr_read_ucs2_text(QDataStream &abr)
static QImage convertToQImage(char *buffer, qint32 width, qint32 height)
save the QImages as png files to directory image_tests
static qint32 rle_decode(QDataStream &abr, char *buffer, qint32 height)
unsigned int QRgb
#define errKrita
Definition kis_debug.h:107
#define warnKrita
Definition kis_debug.h:87
const quint16 quint16_MAX
Definition kis_global.h:25