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

0001 /*
0002  *  SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
0003  *  SPDX-FileCopyrightText: 2003 Patrick Julien <freak@codepimps.org>
0004  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0005  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0006  *  SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be>
0007  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
0008  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0009  *
0010  *  SPDX-License-Identifier: GPL-2.0-or-later
0011  */
0012 #include <sys/types.h>
0013 #include <QtEndian>
0014 
0015 #include "kis_gbr_brush.h"
0016 
0017 #include <QDomElement>
0018 #include <QFile>
0019 #include <QImage>
0020 #include <QPoint>
0021 
0022 #include <kis_debug.h>
0023 #include <klocalizedstring.h>
0024 
0025 #include <KoColor.h>
0026 #include <KoColorSpaceRegistry.h>
0027 
0028 #include "kis_datamanager.h"
0029 #include "kis_paint_device.h"
0030 #include "kis_global.h"
0031 #include "kis_image.h"
0032 
0033 struct GimpBrushV1Header {
0034     quint32 header_size;  /*  header_size = sizeof (BrushHeader) + brush name  */
0035     quint32 version;      /*  brush file version #  */
0036     quint32 width;        /*  width of brush  */
0037     quint32 height;       /*  height of brush  */
0038     quint32 bytes;        /*  depth of brush in bytes */
0039 };
0040 
0041 /// All fields are in MSB on disk!
0042 struct GimpBrushHeader {
0043     quint32 header_size;  /*  header_size = sizeof (BrushHeader) + brush name  */
0044     quint32 version;      /*  brush file version #  */
0045     quint32 width;        /*  width of brush  */
0046     quint32 height;       /*  height of brush  */
0047     quint32 bytes;        /*  depth of brush in bytes */
0048 
0049     /*  The following are only defined in version 2 */
0050     quint32 magic_number; /*  GIMP brush magic number  */
0051     quint32 spacing;      /*  brush spacing as % of width & height, 0 - 1000 */
0052 };
0053 
0054 // Needed, or the GIMP won't open it!
0055 quint32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0);
0056 
0057 
0058 struct KisGbrBrush::Private {
0059 
0060     QByteArray data;
0061     quint32 header_size;  /*  header_size = sizeof (BrushHeader) + brush name  */
0062     quint32 version;      /*  brush file version #  */
0063     quint32 bytes;        /*  depth of brush in bytes */
0064     quint32 magic_number; /*  GIMP brush magic number  */
0065 };
0066 
0067 #define DEFAULT_SPACING 0.25
0068 
0069 KisGbrBrush::KisGbrBrush(const QString& filename)
0070     : KisColorfulBrush(filename)
0071     , d(new Private)
0072 {
0073     setSpacing(DEFAULT_SPACING);
0074 }
0075 
0076 KisGbrBrush::KisGbrBrush(const QString& filename,
0077                          const QByteArray& data,
0078                          qint32 & dataPos)
0079     : KisColorfulBrush(filename)
0080     , d(new Private)
0081 {
0082     setSpacing(DEFAULT_SPACING);
0083 
0084     d->data = QByteArray::fromRawData(data.data() + dataPos, data.size() - dataPos);
0085     init();
0086     d->data.clear();
0087     dataPos += d->header_size + (width() * height() * d->bytes);
0088 }
0089 
0090 KisGbrBrush::KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h)
0091     : KisColorfulBrush()
0092     , d(new Private)
0093 {
0094     setSpacing(DEFAULT_SPACING);
0095     initFromPaintDev(image, x, y, w, h);
0096 }
0097 
0098 KisGbrBrush::KisGbrBrush(const QImage& image, const QString& name)
0099     : KisColorfulBrush()
0100     , d(new Private)
0101 {
0102     setSpacing(DEFAULT_SPACING);
0103 
0104     setBrushTipImage(image);
0105     setName(name);
0106 }
0107 
0108 KisGbrBrush::KisGbrBrush(const KisGbrBrush& rhs)
0109     : KisColorfulBrush(rhs)
0110     , d(new Private(*rhs.d))
0111 {
0112     d->data = QByteArray();
0113 }
0114 
0115 KoResourceSP KisGbrBrush::clone() const
0116 {
0117     return KoResourceSP(new KisGbrBrush(*this));
0118 }
0119 
0120 KisGbrBrush::~KisGbrBrush()
0121 {
0122     delete d;
0123 }
0124 
0125 bool KisGbrBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
0126 {
0127     Q_UNUSED(resourcesInterface);
0128     d->data = dev->readAll();
0129     return init();
0130 }
0131 
0132 bool KisGbrBrush::init()
0133 {
0134     GimpBrushHeader bh;
0135 
0136     if (sizeof(GimpBrushHeader) > (uint)d->data.size()) {
0137         qWarning() << filename() << "GBR could not be loaded: expected header size larger than bytearray size. Header Size:" << sizeof(GimpBrushHeader) << "Byte array size" << d->data.size();
0138         return false;
0139     }
0140 
0141     memcpy(&bh, d->data, sizeof(GimpBrushHeader));
0142     bh.header_size = qFromBigEndian(bh.header_size);
0143     d->header_size = bh.header_size;
0144 
0145     bh.version = qFromBigEndian(bh.version);
0146     d->version = bh.version;
0147 
0148     bh.width = qFromBigEndian(bh.width);
0149     bh.height = qFromBigEndian(bh.height);
0150 
0151     bh.bytes = qFromBigEndian(bh.bytes);
0152     d->bytes = bh.bytes;
0153 
0154     bh.magic_number = qFromBigEndian(bh.magic_number);
0155     d->magic_number = bh.magic_number;
0156 
0157     if (bh.version == 1) {
0158         // No spacing in version 1 files so use Gimp default
0159         bh.spacing = static_cast<int>(DEFAULT_SPACING * 100);
0160     }
0161     else {
0162         bh.spacing = qFromBigEndian(bh.spacing);
0163 
0164         if (bh.spacing > 1000) {
0165             qWarning() << filename()  << "GBR could not be loaded, spacing above 1000. Spacing:" << bh.spacing;
0166             return false;
0167         }
0168     }
0169 
0170     setSpacing(bh.spacing / 100.0);
0171 
0172     if (bh.header_size > (uint)d->data.size() || bh.header_size == 0) {
0173         qWarning() << "GBR could not be loaded: header size larger than bytearray size. Header Size:" << bh.header_size << "Byte array size" << d->data.size();
0174         return false;
0175     }
0176 
0177     QString name;
0178 
0179     if (bh.version == 1) {
0180         // Version 1 has no magic number or spacing, so the name
0181         // is at a different offset. Character encoding is undefined.
0182         const char *text = d->data.constData() + sizeof(GimpBrushV1Header);
0183         name = QString::fromLatin1(text, bh.header_size - sizeof(GimpBrushV1Header) - 1);
0184     }
0185     else {
0186         // ### Version = 3->cinepaint; may be float16 data!
0187         // Version >=2: UTF-8 encoding is used
0188         name = QString::fromUtf8(d->data.constData() + sizeof(GimpBrushHeader),
0189                                  bh.header_size - sizeof(GimpBrushHeader) - 1);
0190     }
0191 
0192     setName(name);
0193 
0194     if (bh.width == 0 || bh.height == 0) {
0195         qWarning() << filename()  << "GBR loading failed: width or height is 0" << bh.width << bh.height;
0196         return false;
0197     }
0198 
0199     QImage::Format imageFormat;
0200 
0201     if (bh.bytes == 1) {
0202         imageFormat = QImage::Format_Indexed8;
0203     } else {
0204         imageFormat = QImage::Format_ARGB32;
0205     }
0206 
0207     QImage image(QImage(bh.width, bh.height, imageFormat));
0208 
0209     if (image.isNull()) {
0210         qWarning() << filename()  << "GBR loading failed; image could not be created from following dimensions" << bh.width << bh.height
0211                    << "QImage::Format" << imageFormat;
0212         return false;
0213     }
0214 
0215     qint32 k = bh.header_size;
0216 
0217     if (bh.bytes == 1) {
0218         QVector<QRgb> table;
0219         for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i));
0220         image.setColorTable(table);
0221         // Grayscale
0222 
0223         if (static_cast<qint32>(k + bh.width * bh.height) > d->data.size()) {
0224             qWarning() << filename()  << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height
0225                        << "expected byte array size:" << (k + (bh.width * bh.height)) << "actual byte array size" << d->data.size();
0226             return false;
0227         }
0228 
0229         setBrushApplication(ALPHAMASK);
0230         setBrushType(MASK);
0231         setHasColorAndTransparency(false);
0232 
0233         for (quint32 y = 0; y < bh.height; y++) {
0234             uchar *pixel = reinterpret_cast<uchar *>(image.scanLine(y));
0235             for (quint32 x = 0; x < bh.width; x++, k++) {
0236                 qint32 val = 255 - static_cast<uchar>(d->data[k]);
0237                 *pixel = val;
0238                 ++pixel;
0239             }
0240         }
0241     } else if (bh.bytes == 4) {
0242         // RGBA
0243 
0244         if (static_cast<qint32>(k + (bh.width * bh.height * 4)) > d->data.size()) {
0245             qWarning() << filename()  << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height
0246                        << "expected byte array size:" << (k + (bh.width * bh.height * 4)) << "actual byte array size" << d->data.size();
0247             return false;
0248         }
0249 
0250         setBrushApplication(LIGHTNESSMAP);
0251         setBrushType(IMAGE);
0252 
0253         for (quint32 y = 0; y < bh.height; y++) {
0254             QRgb *pixel = reinterpret_cast<QRgb *>(image.scanLine(y));
0255             for (quint32 x = 0; x < bh.width; x++, k += 4) {
0256                 *pixel = qRgba(d->data[k], d->data[k + 1], d->data[k + 2], d->data[k + 3]);
0257                 ++pixel;
0258             }
0259         }
0260 
0261         setHasColorAndTransparency(!image.allGray());
0262     }
0263     else {
0264         warnKrita << filename()  << "WARNING: loading of GBR brushes with" << bh.bytes << "bytes per pixel is not supported";
0265         return false;
0266     }
0267 
0268     setWidth(image.width());
0269     setHeight(image.height());
0270     if (!d->data.isEmpty()) {
0271         d->data.resize(0); // Save some memory, we're using enough of it as it is.
0272     }
0273     setValid(image.width() != 0 && image.height() != 0);
0274     setBrushTipImage(image);
0275     return true;
0276 }
0277 
0278 bool KisGbrBrush::initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h)
0279 {
0280     // Forcefully convert to RGBA8
0281     // XXX profile and exposure?
0282     setBrushTipImage(image->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()));
0283     setName(image->objectName());
0284 
0285     setBrushType(IMAGE);
0286     setBrushApplication(LIGHTNESSMAP);
0287 
0288     return true;
0289 }
0290 
0291 bool KisGbrBrush::saveToDevice(QIODevice* dev) const
0292 {
0293     if (!valid() || brushTipImage().isNull()) {
0294         qWarning() << "this brush is not valid, set a brush tip image" << filename();
0295         return false;
0296     }
0297     GimpBrushHeader bh;
0298     QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
0299     char const* name = utf8Name.data();
0300     int nameLength = qstrlen(name);
0301     int wrote;
0302 
0303     bh.header_size = qToBigEndian((quint32)sizeof(GimpBrushHeader) + nameLength + 1);
0304     bh.version = qToBigEndian((quint32)2); // Only RGBA8 data needed atm, no cinepaint stuff
0305     bh.width = qToBigEndian((quint32)width());
0306     bh.height = qToBigEndian((quint32)height());
0307     // Hardcoded, 4 bytes RGBA or 1 byte GREY
0308     if (!isImageType()) {
0309         bh.bytes = qToBigEndian((quint32)1);
0310     }
0311     else {
0312         bh.bytes = qToBigEndian((quint32)4);
0313     }
0314     bh.magic_number = qToBigEndian((quint32)GimpV2BrushMagic);
0315     bh.spacing = qToBigEndian(static_cast<quint32>(spacing() * 100.0));
0316 
0317     // Write header: first bh, then the name
0318     QByteArray bytes = QByteArray::fromRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader));
0319     wrote = dev->write(bytes);
0320     bytes.clear();
0321 
0322     if (wrote == -1) {
0323         return false;
0324     }
0325 
0326     wrote = dev->write(name, nameLength + 1);
0327 
0328     if (wrote == -1) {
0329         return false;
0330     }
0331 
0332     int k = 0;
0333 
0334     QImage image = brushTipImage();
0335 
0336     if (!isImageType()) {
0337         bytes.resize(width() * height());
0338         for (qint32 y = 0; y < height(); y++) {
0339             for (qint32 x = 0; x < width(); x++) {
0340                 QRgb c = image.pixel(x, y);
0341                 bytes[k++] = static_cast<char>(255 - qRed(c)); // red == blue == green
0342             }
0343         }
0344     } else {
0345         bytes.resize(width() * height() * 4);
0346         for (qint32 y = 0; y < height(); y++) {
0347             for (qint32 x = 0; x < width(); x++) {
0348                 // order for gimp brushes, v2 is: RGBA
0349                 QRgb pixel = image.pixel(x, y);
0350                 bytes[k++] = static_cast<char>(qRed(pixel));
0351                 bytes[k++] = static_cast<char>(qGreen(pixel));
0352                 bytes[k++] = static_cast<char>(qBlue(pixel));
0353                 bytes[k++] = static_cast<char>(qAlpha(pixel));
0354             }
0355         }
0356     }
0357 
0358     wrote = dev->write(bytes);
0359     if (wrote == -1) {
0360         return false;
0361     }
0362     return true;
0363 }
0364 
0365 void KisGbrBrush::setBrushTipImage(const QImage& image)
0366 {
0367     KisBrush::setBrushTipImage(image);
0368     setValid(true);
0369 }
0370 
0371 void KisGbrBrush::makeMaskImage(bool preserveAlpha)
0372 {
0373     if (!isImageType()) {
0374         return;
0375     }
0376 
0377     QImage brushTip = brushTipImage();
0378 
0379     if (!preserveAlpha) {
0380         const int imageWidth = brushTip.width();
0381         const int imageHeight = brushTip.height();
0382         QImage image(imageWidth, imageHeight, QImage::Format_Indexed8);
0383         QVector<QRgb> table;
0384         for (int i = 0; i < 256; ++i) {
0385             table.append(qRgb(i, i, i));
0386         }
0387         image.setColorTable(table);
0388 
0389         for (int y = 0; y < imageHeight; y++) {
0390             QRgb *pixel = reinterpret_cast<QRgb *>(brushTip.scanLine(y));
0391             uchar * dstPixel = image.scanLine(y);
0392             for (int x = 0; x < imageWidth; x++) {
0393                 QRgb c = pixel[x];
0394                 float alpha = qAlpha(c) / 255.0f;
0395                 // linear interpolation with maximum gray value which is transparent in the mask
0396                 //int a = (qGray(c) * alpha) + ((1.0 - alpha) * 255);
0397                 // single multiplication version
0398                 int a = 255 + int(alpha * (qGray(c) - 255));
0399                 dstPixel[x] = (uchar)a;
0400             }
0401         }
0402         setBrushTipImage(image);
0403         setBrushType(MASK);
0404     }
0405     else {
0406         setBrushTipImage(brushTip);
0407         setBrushType(IMAGE);
0408     }
0409 
0410     setBrushApplication(ALPHAMASK);
0411     resetOutlineCache();
0412     clearBrushPyramid();
0413 }
0414 
0415 void KisGbrBrush::toXML(QDomDocument& d, QDomElement& e) const
0416 {
0417     predefinedBrushToXML("gbr_brush", e);
0418     KisColorfulBrush::toXML(d, e);
0419 }
0420 
0421 QString KisGbrBrush::defaultFileExtension() const
0422 {
0423     return QString(".gbr");
0424 }