Krita Source Code Documentation
Loading...
Searching...
No Matches
KoPattern.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2
3 SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
4 SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7 */
8
10
11#include <sys/types.h>
12#include <QtEndian>
13
14#include <limits.h>
15#include <stdlib.h>
16
17#include <QFileInfo>
18#include <QDir>
19#include <QPoint>
20#include <QSize>
21#include <QImage>
22#include <QMap>
23#include <QFile>
24#include <QBuffer>
25#include <QFileInfo>
26#include <QImageReader>
27
28#include <DebugPigment.h>
29#include <klocalizedstring.h>
30#include <kis_pointer_utils.h>
31
32#include <KisMimeDatabase.h>
33
34namespace
35{
36struct GimpPatternHeader {
37 quint32 header_size; /* header_size = sizeof (PatternHeader) + brush name */
38 quint32 version; /* pattern file version # */
39 quint32 width; /* width of pattern */
40 quint32 height; /* height of pattern */
41 quint32 bytes; /* depth of pattern in bytes : 1, 2, 3 or 4*/
42 quint32 magic_number; /* GIMP brush magic number */
43};
44
45// Yes! This is _NOT_ what my pat.txt file says. It's really not 'GIMP', but 'GPAT'
46quint32 const GimpPatternMagic = (('G' << 24) + ('P' << 16) + ('A' << 8) + ('T' << 0));
47}
48
49
50KoPattern::KoPattern(const QString& file)
51 : KoResource(file)
52{
53}
54
55KoPattern::KoPattern(const QImage &image, const QString &name, const QString &filename)
56 : KoResource(QString())
57{
61}
62
63
67
69 : KoResource(rhs)
70 , m_pattern(rhs.m_pattern)
71{
72}
73
75{
76 return KoResourceSP(new KoPattern(*this));
77}
78
79bool KoPattern::loadPatFromDevice(QIODevice *dev)
80{
81 QByteArray bytes = dev->readAll();
82 int dataSize = bytes.size();
83 const char* data = bytes.constData();
84
85 // load Gimp patterns
86 GimpPatternHeader bh;
87 qint32 k;
88 char* name;
89
90 if ((int)sizeof(GimpPatternHeader) > dataSize) {
91 return false;
92 }
93
94 memcpy(&bh, data, sizeof(GimpPatternHeader));
95 bh.header_size = qFromBigEndian(bh.header_size);
96 bh.version = qFromBigEndian(bh.version);
97 bh.width = qFromBigEndian(bh.width);
98 bh.height = qFromBigEndian(bh.height);
99 bh.bytes = qFromBigEndian(bh.bytes);
100 bh.magic_number = qFromBigEndian(bh.magic_number);
101
102 if (bytes.mid(20, 4) != "GPAT") {
103 qWarning() << filename() << "is not a .pat pattern file";
104 return false;
105 }
106
107 if ((int)bh.header_size > dataSize || bh.header_size == 0) {
108 return false;
109 }
110 int size = bh.header_size - sizeof(GimpPatternHeader);
111 name = new char[size];
112 memcpy(name, data + sizeof(GimpPatternHeader), size);
113
114 if (name[size - 1]) {
115 delete[] name;
116 return false;
117 }
118
119 // size -1 so we don't add the end 0 to the QString...
120 QString newName = QString::fromUtf8(name, size - 1);
121 if (!newName.isEmpty()) { // if it's empty, it's better to leave the name that was there before (based on filename)
122 setName(newName);
123 }
124 delete[] name;
125
126 if (bh.width == 0 || bh.height == 0) {
127 return false;
128 }
129
130 QImage::Format imageFormat;
131
132 if (bh.bytes == 1 || bh.bytes == 3) {
133 imageFormat = QImage::Format_RGB32;
134 } else {
135 imageFormat = QImage::Format_ARGB32;
136 }
137
138 QImage pattern = QImage(bh.width, bh.height, imageFormat);
139 if (pattern.isNull()) {
140 return false;
141 }
142 k = bh.header_size;
143
144 if (bh.bytes == 1) {
145 // Grayscale
146 qint32 val;
147 for (quint32 y = 0; y < bh.height; ++y) {
148 QRgb* pixels = reinterpret_cast<QRgb*>( pattern.scanLine(y) );
149 for (quint32 x = 0; x < bh.width; ++x, ++k) {
150 if (k > dataSize) {
151 qWarning() << "failed to load grayscale pattern" << filename();
152 return false;
153 }
154
155 val = data[k];
156 pixels[x] = qRgb(val, val, val);
157 }
158 }
159 // It was grayscale, so make the pattern as small as possible
160 // by converting it to Indexed8
161 pattern.convertTo(QImage::Format_Indexed8);
162 }
163 else if (bh.bytes == 2) {
164 // Grayscale + A
165 qint32 val;
166 qint32 alpha;
167 for (quint32 y = 0; y < bh.height; ++y) {
168 QRgb* pixels = reinterpret_cast<QRgb*>( pattern.scanLine(y) );
169 for (quint32 x = 0; x < bh.width; ++x, ++k) {
170 if (k + 2 > dataSize) {
171 qWarning() << "failed to load grayscale +_ alpha pattern" << filename();
172 return false;
173 }
174
175 val = data[k];
176 alpha = data[k++];
177 pixels[x] = qRgba(val, val, val, alpha);
178 }
179 }
180 }
181 else if (bh.bytes == 3) {
182 // RGB without alpha
183 for (quint32 y = 0; y < bh.height; ++y) {
184 QRgb* pixels = reinterpret_cast<QRgb*>( pattern.scanLine(y) );
185 for (quint32 x = 0; x < bh.width; ++x) {
186 if (k + 3 > dataSize) {
187 qWarning() << "failed to load RGB pattern" << filename();
188 return false;
189 }
190 pixels[x] = qRgb(data[k],
191 data[k + 1],
192 data[k + 2]);
193 k += 3;
194 }
195 }
196 } else if (bh.bytes == 4) {
197 // Has alpha
198 for (quint32 y = 0; y < bh.height; ++y) {
199 QRgb* pixels = reinterpret_cast<QRgb*>( pattern.scanLine(y) );
200 for (quint32 x = 0; x < bh.width; ++x) {
201 if (k + 4 > dataSize) {
202 qWarning() << "failed to load RGB + Alpha pattern" << filename();
203 return false;
204 }
205
206 pixels[x] = qRgba(data[k],
207 data[k + 1],
208 data[k + 2],
209 data[k + 3]);
210 k += 4;
211 }
212 }
213 } else {
214 return false;
215 }
216
217 if (pattern.isNull()) {
218 return false;
219 }
220
222 setValid(true);
223
224 return true;
225
226}
227
228bool KoPattern::savePatToDevice(QIODevice* dev) const
229{
230 // Header: header_size (24+name length),version,width,height,colordepth of brush,magic,name
231 // depth: 1 = greyscale, 2 = greyscale + A, 3 = RGB, 4 = RGBA
232 // magic = "GPAT", as a single uint32, the docs are wrong here!
233 // name is UTF-8 (\0-terminated! The docs say nothing about this!)
234 // _All_ data in network order, it seems! (not mentioned in gimp-2.2.8/devel-docs/pat.txt!!)
235 // We only save RGBA at the moment
236 // Version is 1 for now...
237
238
239
240 GimpPatternHeader ph;
241 QByteArray utf8Name = name().toUtf8();
242 char const* name = utf8Name.data();
243 int nameLength = qstrlen(name);
244
245 ph.header_size = qToBigEndian((quint32)sizeof(GimpPatternHeader) + nameLength + 1); // trailing 0
246 ph.version = qToBigEndian((quint32)1);
247 ph.width = qToBigEndian((quint32)width());
248 ph.height = qToBigEndian((quint32)height());
249 ph.bytes = qToBigEndian((quint32)4);
250 ph.magic_number = qToBigEndian((quint32)GimpPatternMagic);
251
252 QByteArray bytes = QByteArray::fromRawData(reinterpret_cast<char*>(&ph), sizeof(GimpPatternHeader));
253 int wrote = dev->write(bytes);
254 bytes.clear();
255
256 if (wrote == -1)
257 return false;
258
259 wrote = dev->write(name, nameLength + 1); // Trailing 0 apparently!
260 if (wrote == -1)
261 return false;
262
263 int k = 0;
264 bytes.resize(width() * height() * 4);
265 for (qint32 y = 0; y < height(); ++y) {
266 for (qint32 x = 0; x < width(); ++x) {
267 // RGBA only
268 QRgb pixel = m_pattern.pixel(x, y);
269 bytes[k++] = static_cast<char>(qRed(pixel));
270 bytes[k++] = static_cast<char>(qGreen(pixel));
271 bytes[k++] = static_cast<char>(qBlue(pixel));
272 bytes[k++] = static_cast<char>(qAlpha(pixel));
273 }
274 }
275
276 wrote = dev->write(bytes);
277 if (wrote == -1)
278 return false;
279
280 return true;
281}
282
283bool KoPattern::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
284{
285 Q_UNUSED(resourcesInterface);
286
287 QByteArray ba = dev->readAll();
288
289 QBuffer buf(&ba);
290 buf.open(QBuffer::ReadOnly);
291
292 bool result = false;
293
294 if (QImageReader::supportedMimeTypes().contains(KisMimeDatabase::mimeTypeForData(ba).toLatin1())) {
295 QFileInfo fi(filename());
296 QImage image;
297 result = image.load(&buf, fi.suffix().toUpper().toLatin1());
299 }
300 else {
301 result = loadPatFromDevice(&buf);
302 }
303
304 return result;
305
306}
307
308bool KoPattern::saveToDevice(QIODevice *dev) const
309{
310 QFileInfo fi(filename());
311 QString fileExtension = fi.suffix().toUpper();
312
313 bool result = false;
314
315 if (fileExtension == "PAT") {
316 result = savePatToDevice(dev);
317 }
318 else {
319 if (fileExtension.isEmpty()) {
320 fileExtension = "PNG";
321 }
322 result = m_pattern.save(dev, fileExtension.toLatin1());
323 }
324
325 return result;
326}
327
328
329qint32 KoPattern::width() const
330{
331 return m_pattern.width();
332}
333
334qint32 KoPattern::height() const
335{
336 return m_pattern.height();
337}
338
339void KoPattern::setPatternImage(const QImage& image)
340{
344 setValid(true);
345}
346
347
349{
350 return QString(".pat");
351}
352
353
354QImage KoPattern::pattern() const
355{
356 return m_pattern;
357}
358
359void KoPattern::checkForAlpha(const QImage& image) {
360 m_hasAlpha = false;
361 for (int y = 0; y < image.height(); y++) {
362 for (int x = 0; x < image.width(); x++) {
363 if (qAlpha(image.pixel(x, y)) != 255) {
364 m_hasAlpha = true;
365 break;
366 }
367 }
368 }
369}
370
372{
373 return m_hasAlpha;
374}
375
377{
378 if (!hasAlpha()) return clone().dynamicCast<KoPattern>();
379
380 QImage image = this->image();
381
382 for (int y = 0; y < image.height(); ++y) {
383 QRgb *ptr = reinterpret_cast<QRgb*>(image.scanLine(y));
384
385 for (int x = 0; x < image.width(); ++x) {
386 const qreal coeff = qAlpha(*ptr) / 255.0;
387 *ptr = qRgba(qRound(coeff * qRed(*ptr)), qRound(coeff * qGreen(*ptr)), qRound(coeff * qBlue(*ptr)), 255);
388 ptr++;
389 }
390 }
391
392 KoPatternSP flattenedPattern =
393 toQShared(new KoPattern(image, this->name(), this->filename()));
394
395 return flattenedPattern;
396}
static QString mimeTypeForData(const QByteArray ba)
Write API docs here.
Definition KoPattern.h:21
KoPattern(const QString &filename)
Definition KoPattern.cpp:50
bool m_hasAlpha
Definition KoPattern.h:89
QString defaultFileExtension() const override
qint32 width() const
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
~KoPattern() override
Definition KoPattern.cpp:64
void setPatternImage(const QImage &image)
void checkForAlpha(const QImage &image)
bool hasAlpha() const
bool savePatToDevice(QIODevice *dev) const
qint32 height() const
bool saveToDevice(QIODevice *dev) const override
bool loadPatFromDevice(QIODevice *dev)
Definition KoPattern.cpp:79
QImage pattern() const
pattern the actual pattern image
KoPatternSP cloneWithoutAlpha() const
KoResourceSP clone() const override
Definition KoPattern.cpp:74
QImage m_pattern
Definition KoPattern.h:88
unsigned int QRgb
QSharedPointer< KoResource > KoResourceSP
QSharedPointer< T > toQShared(T *ptr)
void setValid(bool valid)
void setName(const QString &name)
QImage image
void setFilename(const QString &filename)
QString filename
void setImage(const QImage &image)
QString name