Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_gbr_brush.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
3 * SPDX-FileCopyrightText: 2003 Patrick Julien <freak@codepimps.org>
4 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
5 * SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
6 * SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be>
7 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
8 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 */
12#include <sys/types.h>
13#include <QtEndian>
14
15#include "kis_gbr_brush.h"
16
17#include <QDomElement>
18#include <QFile>
19#include <QImage>
20#include <QPoint>
21
22#include <kis_debug.h>
23#include <klocalizedstring.h>
24
25#include <KoColor.h>
27
28#include "kis_datamanager.h"
29#include "kis_paint_device.h"
30#include "kis_global.h"
31#include "kis_image.h"
32
34 quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
35 quint32 version; /* brush file version # */
36 quint32 width; /* width of brush */
37 quint32 height; /* height of brush */
38 quint32 bytes; /* depth of brush in bytes */
39};
40
43 quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
44 quint32 version; /* brush file version # */
45 quint32 width; /* width of brush */
46 quint32 height; /* height of brush */
47 quint32 bytes; /* depth of brush in bytes */
48
49 /* The following are only defined in version 2 */
50 quint32 magic_number; /* GIMP brush magic number */
51 quint32 spacing; /* brush spacing as % of width & height, 0 - 1000 */
52};
53
54// Needed, or the GIMP won't open it!
55quint32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0);
56
57
59
60 QByteArray data;
61 quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
62 quint32 version; /* brush file version # */
63 quint32 bytes; /* depth of brush in bytes */
64 quint32 magic_number; /* GIMP brush magic number */
65};
66
67#define DEFAULT_SPACING 0.25
68
69KisGbrBrush::KisGbrBrush(const QString& filename)
70 : KisColorfulBrush(filename)
71 , d(new Private)
72{
74}
75
76KisGbrBrush::KisGbrBrush(const QString& filename,
77 const QByteArray& data,
78 qint32 & dataPos)
79 : KisColorfulBrush(filename)
80 , d(new Private)
81{
83
84 d->data = QByteArray::fromRawData(data.data() + dataPos, data.size() - dataPos);
85 init();
86 d->data.clear();
87 dataPos += d->header_size + (width() * height() * d->bytes);
88}
89
90KisGbrBrush::KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h)
92 , d(new Private)
93{
95 initFromPaintDev(image, x, y, w, h);
96}
97
98KisGbrBrush::KisGbrBrush(const QImage& image, const QString& name)
100 , d(new Private)
101{
103
105 setName(name);
106}
107
109 : KisColorfulBrush(rhs)
110 , d(new Private(*rhs.d))
111{
112 d->data = QByteArray();
113}
114
116{
117 return KoResourceSP(new KisGbrBrush(*this));
118}
119
121{
122 delete d;
123}
124
125bool KisGbrBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
126{
127 Q_UNUSED(resourcesInterface);
128 d->data = dev->readAll();
129 return init();
130}
131
133{
135
136 if (sizeof(GimpBrushHeader) > (uint)d->data.size()) {
137 qWarning() << filename() << "GBR could not be loaded: expected header size larger than bytearray size. Header Size:" << sizeof(GimpBrushHeader) << "Byte array size" << d->data.size();
138 return false;
139 }
140
141 memcpy(&bh, d->data.constData(), sizeof(GimpBrushHeader));
142 bh.header_size = qFromBigEndian(bh.header_size);
144
145 bh.version = qFromBigEndian(bh.version);
146 d->version = bh.version;
147
148 bh.width = qFromBigEndian(bh.width);
149 bh.height = qFromBigEndian(bh.height);
150
151 bh.bytes = qFromBigEndian(bh.bytes);
152 d->bytes = bh.bytes;
153
154 bh.magic_number = qFromBigEndian(bh.magic_number);
156
157 if (bh.version == 1) {
158 // No spacing in version 1 files so use Gimp default
159 bh.spacing = static_cast<int>(DEFAULT_SPACING * 100);
160 }
161 else {
162 bh.spacing = qFromBigEndian(bh.spacing);
163
164 if (bh.spacing > 1000) {
165 qWarning() << filename() << "GBR could not be loaded, spacing above 1000. Spacing:" << bh.spacing;
166 return false;
167 }
168 }
169
170 setSpacing(bh.spacing / 100.0);
171
172 if (bh.header_size > (uint)d->data.size() || bh.header_size == 0) {
173 qWarning() << "GBR could not be loaded: header size larger than bytearray size. Header Size:" << bh.header_size << "Byte array size" << d->data.size();
174 return false;
175 }
176
177 QString name;
178
179 if (bh.version == 1) {
180 // Version 1 has no magic number or spacing, so the name
181 // is at a different offset. Character encoding is undefined.
182 const char *text = d->data.constData() + sizeof(GimpBrushV1Header);
183 name = QString::fromLatin1(text, bh.header_size - sizeof(GimpBrushV1Header) - 1);
184 }
185 else {
186 // ### Version = 3->cinepaint; may be float16 data!
187 // Version >=2: UTF-8 encoding is used
188 name = QString::fromUtf8(d->data.constData() + sizeof(GimpBrushHeader),
189 bh.header_size - sizeof(GimpBrushHeader) - 1);
190 }
191
192 setName(name);
193
194 if (bh.width == 0 || bh.height == 0) {
195 qWarning() << filename() << "GBR loading failed: width or height is 0" << bh.width << bh.height;
196 return false;
197 }
198
199 QImage::Format imageFormat;
200
201 if (bh.bytes == 1) {
202 imageFormat = QImage::Format_Indexed8;
203 } else {
204 imageFormat = QImage::Format_ARGB32;
205 }
206
207 QImage image(QImage(bh.width, bh.height, imageFormat));
208
209 if (image.isNull()) {
210 qWarning() << filename() << "GBR loading failed; image could not be created from following dimensions" << bh.width << bh.height
211 << "QImage::Format" << imageFormat;
212 return false;
213 }
214
215 qint32 k = bh.header_size;
216
217 if (bh.bytes == 1) {
218 QVector<QRgb> table;
219 for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i));
220 image.setColorTable(table);
221 // Grayscale
222
223 if (static_cast<qint32>(k + bh.width * bh.height) > d->data.size()) {
224 qWarning() << filename() << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height
225 << "expected byte array size:" << (k + (bh.width * bh.height)) << "actual byte array size" << d->data.size();
226 return false;
227 }
228
232
233 for (quint32 y = 0; y < bh.height; y++) {
234 uchar *pixel = reinterpret_cast<uchar *>(image.scanLine(y));
235 for (quint32 x = 0; x < bh.width; x++, k++) {
236 qint32 val = 255 - static_cast<uchar>(d->data.at(k));
237 *pixel = val;
238 ++pixel;
239 }
240 }
241 } else if (bh.bytes == 4) {
242 // RGBA
243
244 if (static_cast<qint32>(k + (bh.width * bh.height * 4)) > d->data.size()) {
245 qWarning() << filename() << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height
246 << "expected byte array size:" << (k + (bh.width * bh.height * 4)) << "actual byte array size" << d->data.size();
247 return false;
248 }
249
252
253 for (quint32 y = 0; y < bh.height; y++) {
254 QRgb *pixel = reinterpret_cast<QRgb *>(image.scanLine(y));
255 for (quint32 x = 0; x < bh.width; x++, k += 4) {
256 *pixel = qRgba(d->data.at(k), d->data.at(k + 1), d->data.at(k + 2), d->data.at(k + 3));
257 ++pixel;
258 }
259 }
260
262 }
263 else {
264 warnKrita << filename() << "WARNING: loading of GBR brushes with" << bh.bytes << "bytes per pixel is not supported";
265 return false;
266 }
267
268 setWidth(image.width());
269 setHeight(image.height());
270 if (!d->data.isEmpty()) {
271 d->data.clear(); // Save some memory, we're using enough of it as it is.
272 }
273 setValid(image.width() != 0 && image.height() != 0);
275 return true;
276}
277
278bool KisGbrBrush::initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h)
279{
280 // Forcefully convert to RGBA8
281 // XXX profile and exposure?
283 setName(image->objectName());
284
287
288 return true;
289}
290
291bool KisGbrBrush::saveToDevice(QIODevice* dev) const
292{
293 if (!valid() || brushTipImage().isNull()) {
294 qWarning() << "this brush is not valid, set a brush tip image" << filename();
295 return false;
296 }
298 QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
299 char const* name = utf8Name.data();
300 int nameLength = qstrlen(name);
301 int wrote;
302
303 bh.header_size = qToBigEndian((quint32)sizeof(GimpBrushHeader) + nameLength + 1);
304 bh.version = qToBigEndian((quint32)2); // Only RGBA8 data needed atm, no cinepaint stuff
305 bh.width = qToBigEndian((quint32)width());
306 bh.height = qToBigEndian((quint32)height());
307 // Hardcoded, 4 bytes RGBA or 1 byte GREY
308 if (!isImageType()) {
309 bh.bytes = qToBigEndian((quint32)1);
310 }
311 else {
312 bh.bytes = qToBigEndian((quint32)4);
313 }
314 bh.magic_number = qToBigEndian((quint32)GimpV2BrushMagic);
315 bh.spacing = qToBigEndian(static_cast<quint32>(spacing() * 100.0));
316
317 // Write header: first bh, then the name
318 QByteArray bytes = QByteArray::fromRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader));
319 wrote = dev->write(bytes);
320 bytes.clear();
321
322 if (wrote == -1) {
323 return false;
324 }
325
326 wrote = dev->write(name, nameLength + 1);
327
328 if (wrote == -1) {
329 return false;
330 }
331
332 int k = 0;
333
334 QImage image = brushTipImage();
335
336 if (!isImageType()) {
337 bytes.resize(width() * height());
338 for (qint32 y = 0; y < height(); y++) {
339 for (qint32 x = 0; x < width(); x++) {
340 QRgb c = image.pixel(x, y);
341 bytes[k++] = static_cast<char>(255 - qRed(c)); // red == blue == green
342 }
343 }
344 } else {
345 bytes.resize(width() * height() * 4);
346 for (qint32 y = 0; y < height(); y++) {
347 for (qint32 x = 0; x < width(); x++) {
348 // order for gimp brushes, v2 is: RGBA
349 QRgb pixel = image.pixel(x, y);
350 bytes[k++] = static_cast<char>(qRed(pixel));
351 bytes[k++] = static_cast<char>(qGreen(pixel));
352 bytes[k++] = static_cast<char>(qBlue(pixel));
353 bytes[k++] = static_cast<char>(qAlpha(pixel));
354 }
355 }
356 }
357
358 wrote = dev->write(bytes);
359 if (wrote == -1) {
360 return false;
361 }
362 return true;
363}
364
365void KisGbrBrush::setBrushTipImage(const QImage& image)
366{
368 setValid(true);
369}
370
371void KisGbrBrush::makeMaskImage(bool preserveAlpha)
372{
373 if (!isImageType()) {
374 return;
375 }
376
377 QImage brushTip = brushTipImage();
378
379 if (!preserveAlpha) {
380 const int imageWidth = brushTip.width();
381 const int imageHeight = brushTip.height();
382 QImage image(imageWidth, imageHeight, QImage::Format_Indexed8);
383 QVector<QRgb> table;
384 for (int i = 0; i < 256; ++i) {
385 table.append(qRgb(i, i, i));
386 }
387 image.setColorTable(table);
388
389 for (int y = 0; y < imageHeight; y++) {
390 QRgb *pixel = reinterpret_cast<QRgb *>(brushTip.scanLine(y));
391 uchar * dstPixel = image.scanLine(y);
392 for (int x = 0; x < imageWidth; x++) {
393 QRgb c = pixel[x];
394 float alpha = qAlpha(c) / 255.0f;
395 // linear interpolation with maximum gray value which is transparent in the mask
396 //int a = (qGray(c) * alpha) + ((1.0 - alpha) * 255);
397 // single multiplication version
398 int a = 255 + int(alpha * (qGray(c) - 255));
399 dstPixel[x] = (uchar)a;
400 }
401 }
404 }
405 else {
406 setBrushTipImage(brushTip);
408 }
409
413}
414
415void KisGbrBrush::toXML(QDomDocument& d, QDomElement& e) const
416{
417 predefinedBrushToXML("gbr_brush", e);
419}
420
422{
423 return QString(".gbr");
424}
unsigned int uint
virtual void setSpacing(double spacing)
qint32 width() const
virtual void setBrushType(enumBrushType type)
void predefinedBrushToXML(const QString &type, QDomElement &e) const
void resetOutlineCache()
double spacing() const
virtual void setBrushTipImage(const QImage &image)
void setWidth(qint32 width)
void clearBrushPyramid()
qint32 height() const
void setHeight(qint32 height)
virtual void setBrushApplication(enumBrushApplication brushApplication)
virtual bool isImageType() const
QImage brushTipImage() const override
brushImage the image the brush tip can paint with. Not all brush types have a single image.
void setHasColorAndTransparency(bool value)
void toXML(QDomDocument &d, QDomElement &e) const override
void setBrushTipImage(const QImage &image) override
bool initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h)
bool saveToDevice(QIODevice *dev) const override
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
QString defaultFileExtension() const override
KoResourceSP clone() const override
void toXML(QDomDocument &d, QDomElement &e) const override
virtual void makeMaskImage(bool preserveAlpha)
KisGbrBrush(const QString &filename)
Construct brush to load filename later as brush.
~KisGbrBrush() override
Private *const d
#define DEFAULT_SPACING
@ IMAGE
Definition kis_brush.h:33
@ MASK
Definition kis_brush.h:32
@ ALPHAMASK
Definition kis_brush.h:39
@ LIGHTNESSMAP
Definition kis_brush.h:41
unsigned int QRgb
#define warnKrita
Definition kis_debug.h:87
quint32 const GimpV2BrushMagic
QSharedPointer< KoResource > KoResourceSP
All fields are in MSB on disk!
void setValid(bool valid)
void setName(const QString &name)
QImage image
QString filename
QString name