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 }