1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2002-2005 Nadeem Hasan <nhasan@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "pcx_p.h"
9#include "scanlineconverter_p.h"
10#include "util_p.h"
11
12#include <QColor>
13#include <QColorSpace>
14#include <QDataStream>
15#include <QImage>
16#include <QLoggingCategory>
17
18#ifdef QT_DEBUG
19Q_LOGGING_CATEGORY(LOG_PCXPLUGIN, "kf.imageformats.plugins.pcx", QtDebugMsg)
20#else
21Q_LOGGING_CATEGORY(LOG_PCXPLUGIN, "kf.imageformats.plugins.pcx", QtWarningMsg)
22#endif
23
24#pragma pack(push, 1)
25class RGB
26{
27public:
28 quint8 r;
29 quint8 g;
30 quint8 b;
31
32 static RGB from(const QRgb color)
33 {
34 RGB c;
35 c.r = qRed(rgb: color);
36 c.g = qGreen(rgb: color);
37 c.b = qBlue(rgb: color);
38 return c;
39 }
40};
41
42class Palette
43{
44public:
45 void setColor(int i, const QRgb color)
46 {
47 RGB &c = rgb[i];
48 c.r = qRed(rgb: color);
49 c.g = qGreen(rgb: color);
50 c.b = qBlue(rgb: color);
51 }
52
53 QRgb color(int i) const
54 {
55 return qRgb(r: rgb[i].r, g: rgb[i].g, b: rgb[i].b);
56 }
57
58 class RGB rgb[16];
59};
60
61class PCXHEADER
62{
63public:
64 PCXHEADER();
65
66 inline int width() const
67 {
68 return (XMax - XMin) + 1;
69 }
70 inline int height() const
71 {
72 return (YMax - YMin) + 1;
73 }
74 inline bool isCompressed() const
75 {
76 return (Encoding == 1);
77 }
78 /*!
79 * \brief isValid
80 * Checks if the header data are valid for the PCX.
81 * \note Put here the header sanity checks.
82 * \return True if the header is a valid PCX header, otherwise false.
83 */
84 inline bool isValid() const
85 {
86 return Manufacturer == 10 && BytesPerLine != 0;
87 }
88 /*!
89 * \brief isSupported
90 * \return True if the header is valid and the PCX format is supported by the plugin. Otherwise false.
91 */
92 inline bool isSupported() const
93 {
94 return isValid() && format() != QImage::Format_Invalid;
95 }
96 inline QImage::Format format() const
97 {
98 auto fmt = QImage::Format_Invalid;
99 if (Bpp == 1 && NPlanes == 1) {
100 fmt = QImage::Format_Mono;
101 } else if (Bpp == 1 && NPlanes == 4) {
102 fmt = QImage::Format_Indexed8;
103 } else if (Bpp == 1 && NPlanes == 3) {
104 fmt = QImage::Format_Indexed8;
105 } else if (Bpp == 4 && NPlanes == 1) {
106 fmt = QImage::Format_Indexed8;
107 } else if (Bpp == 2 && NPlanes == 1) {
108 fmt = QImage::Format_Indexed8;
109 } else if (Bpp == 8 && NPlanes == 1) {
110 fmt = QImage::Format_Indexed8;
111 } else if (Bpp == 8 && NPlanes == 3) {
112 fmt = QImage::Format_RGB32;
113 } else if (Bpp == 8 && NPlanes == 4) {
114 fmt = QImage::Format_ARGB32;
115 }
116 return fmt;
117 }
118
119 quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
120 quint8 Version; // Version information·
121 // 0 = Version 2.5 of PC Paintbrush·
122 // 2 = Version 2.8 w/palette information·
123 // 3 = Version 2.8 w/o palette information·
124 // 4 = PC Paintbrush for Windows(Plus for
125 // Windows uses Ver 5)·
126 // 5 = Version 3.0 and > of PC Paintbrush
127 // and PC Paintbrush +, includes
128 // Publisher's Paintbrush . Includes
129 // 24-bit .PCX files·
130 quint8 Encoding; // 1 = .PCX run length encoding
131 quint8 Bpp; // Number of bits to represent a pixel
132 // (per Plane) - 1, 2, 4, or 8·
133 quint16 XMin;
134 quint16 YMin;
135 quint16 XMax;
136 quint16 YMax;
137 quint16 HDpi;
138 quint16 YDpi;
139 Palette ColorMap;
140 quint8 Reserved; // Should be set to 0.
141 quint8 NPlanes; // Number of color planes
142 quint16 BytesPerLine; // Number of bytes to allocate for a scanline
143 // plane. MUST be an EVEN number. Do NOT
144 // calculate from Xmax-Xmin.·
145 quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW,
146 // 2 = Grayscale ( ignored in PB IV/ IV + )·
147 quint16 HScreenSize; // Horizontal screen size in pixels. New field
148 // found only in PB IV/IV Plus
149 quint16 VScreenSize; // Vertical screen size in pixels. New field
150 // found only in PB IV/IV Plus
151
152 quint8 unused[54];
153};
154
155#pragma pack(pop)
156
157static QDataStream &operator>>(QDataStream &s, RGB &rgb)
158{
159 quint8 r;
160 quint8 g;
161 quint8 b;
162
163 s >> r >> g >> b;
164 rgb.r = r;
165 rgb.g = g;
166 rgb.b = b;
167
168 return s;
169}
170
171static QDataStream &operator>>(QDataStream &s, Palette &pal)
172{
173 for (int i = 0; i < 16; ++i) {
174 s >> pal.rgb[i];
175 }
176
177 return s;
178}
179
180static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
181{
182 quint8 m;
183 quint8 ver;
184 quint8 enc;
185 quint8 bpp;
186 s >> m >> ver >> enc >> bpp;
187 ph.Manufacturer = m;
188 ph.Version = ver;
189 ph.Encoding = enc;
190 ph.Bpp = bpp;
191 quint16 xmin;
192 quint16 ymin;
193 quint16 xmax;
194 quint16 ymax;
195 s >> xmin >> ymin >> xmax >> ymax;
196 ph.XMin = xmin;
197 ph.YMin = ymin;
198 ph.XMax = xmax;
199 ph.YMax = ymax;
200 quint16 hdpi;
201 quint16 ydpi;
202 s >> hdpi >> ydpi;
203 ph.HDpi = hdpi;
204 ph.YDpi = ydpi;
205 Palette colorMap;
206 quint8 res;
207 quint8 np;
208 s >> colorMap >> res >> np;
209 ph.ColorMap = colorMap;
210 ph.Reserved = res;
211 ph.NPlanes = np;
212 quint16 bytesperline;
213 s >> bytesperline;
214 ph.BytesPerLine = bytesperline;
215 quint16 paletteinfo;
216 s >> paletteinfo;
217 ph.PaletteInfo = paletteinfo;
218 quint16 hscreensize;
219 quint16 vscreensize;
220 s >> hscreensize;
221 ph.HScreenSize = hscreensize;
222 s >> vscreensize;
223 ph.VScreenSize = vscreensize;
224
225 // Skip the rest of the header
226 for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) {
227 s >> ph.unused[i];
228 }
229
230 return s;
231}
232
233static QDataStream &operator<<(QDataStream &s, const RGB rgb)
234{
235 s << rgb.r << rgb.g << rgb.b;
236
237 return s;
238}
239
240static QDataStream &operator<<(QDataStream &s, const Palette &pal)
241{
242 for (int i = 0; i < 16; ++i) {
243 s << pal.rgb[i];
244 }
245
246 return s;
247}
248
249static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph)
250{
251 s << ph.Manufacturer;
252 s << ph.Version;
253 s << ph.Encoding;
254 s << ph.Bpp;
255 s << ph.XMin << ph.YMin << ph.XMax << ph.YMax;
256 s << ph.HDpi << ph.YDpi;
257 s << ph.ColorMap;
258 s << ph.Reserved;
259 s << ph.NPlanes;
260 s << ph.BytesPerLine;
261 s << ph.PaletteInfo;
262 s << ph.HScreenSize;
263 s << ph.VScreenSize;
264
265 for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) {
266 s << ph.unused[i];
267 }
268
269 return s;
270}
271
272PCXHEADER::PCXHEADER()
273{
274 // Initialize all data to zero
275 QByteArray dummy(128, 0);
276 dummy.fill(c: 0);
277 QDataStream s(&dummy, QIODevice::ReadOnly);
278 s >> *this;
279}
280
281bool peekHeader(QIODevice *d, PCXHEADER& h)
282{
283 auto head = d->peek(maxlen: sizeof(PCXHEADER));
284 if (size_t(head.size()) < sizeof(PCXHEADER)) {
285 return false;
286 }
287
288 QDataStream ds(head);
289 ds.setByteOrder(QDataStream::LittleEndian);
290 ds >> h;
291
292 return ds.status() == QDataStream::Ok && h.isValid();
293}
294
295static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
296{
297 quint32 i = 0;
298 quint32 size = buf.size();
299 quint8 byte;
300 quint8 count;
301
302 if (header.isCompressed()) {
303 // Uncompress the image data
304 while (i < size) {
305 count = 1;
306 s >> byte;
307 if (byte > 0xc0) {
308 count = byte - 0xc0;
309 s >> byte;
310 }
311 while (count-- && i < size) {
312 buf[i++] = byte;
313 }
314 }
315 } else {
316 // Image is not compressed (possible?)
317 while (i < size) {
318 s >> byte;
319 buf[i++] = byte;
320 }
321 }
322
323 return (s.status() == QDataStream::Ok);
324}
325
326static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
327{
328 QByteArray buf(header.BytesPerLine, 0);
329
330 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
331 img.setColorCount(2);
332
333 if (img.isNull()) {
334 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
335 return false;
336 }
337
338 for (int y = 0; y < header.height(); ++y) {
339 if (s.atEnd()) {
340 return false;
341 }
342
343 if (!readLine(s, buf, header)) {
344 return false;
345 }
346
347 uchar *p = img.scanLine(y);
348 unsigned int bpl = qMin(a: (quint16)((header.width() + 7) / 8), b: header.BytesPerLine);
349 for (unsigned int x = 0; x < bpl; ++x) {
350 p[x] = buf[x];
351 }
352 }
353
354 // Set the color palette
355 img.setColor(i: 0, c: qRgb(r: 0, g: 0, b: 0));
356 img.setColor(i: 1, c: qRgb(r: 255, g: 255, b: 255));
357
358 return true;
359}
360
361static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
362{
363 QByteArray buf(header.BytesPerLine * header.NPlanes, 0);
364 QByteArray pixbuf(header.width(), 0);
365
366 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
367 img.setColorCount(16);
368 if (img.isNull()) {
369 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
370 return false;
371 }
372
373 if (header.BytesPerLine < (header.width() + 7) / 8) {
374 qCWarning(LOG_PCXPLUGIN) << "PCX image has invalid BytesPerLine value";
375 return false;
376 }
377
378 for (int y = 0; y < header.height(); ++y) {
379 if (s.atEnd()) {
380 return false;
381 }
382
383 pixbuf.fill(c: 0);
384 if (!readLine(s, buf, header)) {
385 return false;
386 }
387
388 for (int i = 0; i < header.NPlanes; i++) {
389 quint32 offset = i * header.BytesPerLine;
390 for (int x = 0; x < header.width(); ++x) {
391 if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
392 pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
393 }
394 }
395 }
396
397 uchar *p = img.scanLine(y);
398 if (!p) {
399 qCWarning(LOG_PCXPLUGIN) << "Failed to get scanline for" << y << "might be out of bounds";
400 }
401 for (int x = 0; x < header.width(); ++x) {
402 p[x] = pixbuf[x];
403 }
404 }
405
406 // Read the palette
407 for (int i = 0; i < 16; ++i) {
408 img.setColor(i, c: header.ColorMap.color(i));
409 }
410
411 return true;
412}
413
414static bool readImage2(QImage &img, QDataStream &s, const PCXHEADER &header)
415{
416 QByteArray buf(header.BytesPerLine, 0);
417
418 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
419 img.setColorCount(4);
420
421 if (img.isNull()) {
422 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
423 return false;
424 }
425
426 for (int y = 0; y < header.height(); ++y) {
427 if (s.atEnd()) {
428 return false;
429 }
430
431 if (!readLine(s, buf, header)) {
432 return false;
433 }
434
435 uchar *p = img.scanLine(y);
436 if (!p) {
437 return false;
438 }
439
440 const unsigned int bpl = std::min(a: header.BytesPerLine, b: static_cast<quint16>(header.width() / 4));
441 for (unsigned int x = 0; x < bpl; ++x) {
442 p[x * 4] = (buf[x] >> 6) & 3;
443 p[x * 4 + 1] = (buf[x] >> 4) & 3;
444 p[x * 4 + 2] = (buf[x] >> 2) & 3;
445 p[x * 4 + 3] = buf[x] & 3;
446 }
447 }
448
449 // Read the palette
450 for (int i = 0; i < 4; ++i) {
451 img.setColor(i, c: header.ColorMap.color(i));
452 }
453
454 return (s.status() == QDataStream::Ok);
455}
456
457static bool readImage4v2(QImage &img, QDataStream &s, const PCXHEADER &header)
458{
459 QByteArray buf(header.BytesPerLine, 0);
460
461 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
462 img.setColorCount(16);
463
464 if (img.isNull()) {
465 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
466 return false;
467 }
468
469 for (int y = 0; y < header.height(); ++y) {
470 if (s.atEnd()) {
471 return false;
472 }
473
474 if (!readLine(s, buf, header)) {
475 return false;
476 }
477
478 uchar *p = img.scanLine(y);
479 if (!p) {
480 return false;
481 }
482
483 const unsigned int bpl = std::min(a: header.BytesPerLine, b: static_cast<quint16>(header.width() / 2));
484 for (unsigned int x = 0; x < bpl; ++x) {
485 p[x * 2] = (buf[x] & 240) >> 4;
486 p[x * 2 + 1] = buf[x] & 15;
487 }
488 }
489
490 // Read the palette
491 for (int i = 0; i < 16; ++i) {
492 img.setColor(i, c: header.ColorMap.color(i));
493 }
494
495 return (s.status() == QDataStream::Ok);
496}
497
498static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
499{
500 QByteArray buf(header.BytesPerLine, 0);
501
502 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
503 img.setColorCount(256);
504
505 if (img.isNull()) {
506 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
507 return false;
508 }
509
510 for (int y = 0; y < header.height(); ++y) {
511 if (s.atEnd()) {
512 return false;
513 }
514
515 if (!readLine(s, buf, header)) {
516 return false;
517 }
518
519 uchar *p = img.scanLine(y);
520 if (!p) {
521 return false;
522 }
523
524 unsigned int bpl = qMin(a: header.BytesPerLine, b: (quint16)header.width());
525 for (unsigned int x = 0; x < bpl; ++x) {
526 p[x] = buf[x];
527 }
528 }
529
530 // by specification, the extended palette starts at file.size() - 769
531 quint8 flag = 0;
532 if (auto device = s.device()) {
533 if (device->isSequential()) {
534 while (flag != 12 && s.status() == QDataStream::Ok) {
535 s >> flag;
536 }
537 }
538 else {
539 device->seek(pos: device->size() - 769);
540 s >> flag;
541 }
542 }
543
544 if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
545 // Read the palette
546 quint8 r;
547 quint8 g;
548 quint8 b;
549 for (int i = 0; i < 256; ++i) {
550 s >> r >> g >> b;
551 img.setColor(i, c: qRgb(r, g, b));
552 }
553 }
554
555 return (s.status() == QDataStream::Ok);
556}
557
558static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
559{
560 QByteArray r_buf(header.BytesPerLine, 0);
561 QByteArray g_buf(header.BytesPerLine, 0);
562 QByteArray b_buf(header.BytesPerLine, 0);
563 QByteArray a_buf(header.BytesPerLine, char(0xFF));
564
565 img = imageAlloc(width: header.width(), height: header.height(), format: header.format());
566
567 if (img.isNull()) {
568 qCWarning(LOG_PCXPLUGIN) << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
569 return false;
570 }
571
572 const unsigned int bpl = std::min(a: header.BytesPerLine, b: static_cast<quint16>(header.width()));
573
574 for (int y = 0; y < header.height(); ++y) {
575 if (s.atEnd()) {
576 return false;
577 }
578
579 if (!readLine(s, buf&: r_buf, header)) {
580 return false;
581 }
582 if (!readLine(s, buf&: g_buf, header)) {
583 return false;
584 }
585 if (!readLine(s, buf&: b_buf, header)) {
586 return false;
587 }
588 if (header.NPlanes == 4 && !readLine(s, buf&: a_buf, header)) {
589 return false;
590 }
591
592 auto p = reinterpret_cast<QRgb *>(img.scanLine(y));
593 for (unsigned int x = 0; x < bpl; ++x) {
594 p[x] = qRgba(r: r_buf[x], g: g_buf[x], b: b_buf[x], a: a_buf[x]);
595 }
596 }
597
598 return true;
599}
600
601static bool writeLine(QDataStream &s, QByteArray &buf)
602{
603 quint32 i = 0;
604 quint32 size = buf.size();
605 quint8 count;
606 quint8 data;
607 char byte;
608
609 while (i < size) {
610 count = 1;
611 byte = buf[i++];
612
613 while ((i < size) && (byte == buf[i]) && (count < 63)) {
614 ++i;
615 ++count;
616 }
617
618 data = byte;
619
620 if (count > 1 || data >= 0xc0) {
621 count |= 0xc0;
622 s << count;
623 }
624
625 s << data;
626 }
627 return (s.status() == QDataStream::Ok);
628}
629
630static bool writeImage1(const QImage &image, QDataStream &s, PCXHEADER &header)
631{
632 auto tfmt = image.format();
633 if (tfmt != QImage::Format_Mono)
634 tfmt = QImage::Format_Mono;
635
636 if (image.isNull() || image.colorCount() < 1) {
637 return false;
638 }
639
640 ScanLineConverter scl(tfmt);
641 if (image.height() > 0) {
642 scl.convertedScanLine(image, y: 0); // required to calculate bytesPerLine
643 }
644
645 header.Bpp = 1;
646 header.NPlanes = 1;
647 header.BytesPerLine = scl.bytesPerLine();
648 if (header.BytesPerLine == 0) {
649 header.BytesPerLine = image.bytesPerLine();
650 }
651 if (header.BytesPerLine == 0) {
652 return false;
653 }
654
655 s << header;
656
657 auto rgb = image.color(i: 0);
658 auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
659 QByteArray buf(header.BytesPerLine, 0);
660 for (int y = 0; y < header.height(); ++y) {
661 auto p = scl.convertedScanLine(image, y);
662
663 // Invert as QImage uses reverse palette for monochrome images?
664 for (int i = 0; i < header.BytesPerLine; ++i) {
665 buf[i] = minIsBlack ? p[i] : ~p[i];
666 }
667
668 if (!writeLine(s, buf)) {
669 return false;
670 }
671 }
672 return true;
673}
674
675static bool writeImage4(const QImage &image, QDataStream &s, PCXHEADER &header)
676{
677 header.Bpp = 1;
678 header.NPlanes = 4;
679 header.BytesPerLine = header.width() / 8;
680 if (header.BytesPerLine == 0) {
681 return false;
682 }
683
684 for (int i = 0; i < 16; ++i) {
685 header.ColorMap.setColor(i, color: image.color(i));
686 }
687
688 s << header;
689
690 QByteArray buf[4];
691
692 for (int i = 0; i < 4; ++i) {
693 buf[i].resize(size: header.BytesPerLine);
694 }
695
696 for (int y = 0; y < header.height(); ++y) {
697 auto p = image.constScanLine(y);
698
699 for (int i = 0; i < 4; ++i) {
700 buf[i].fill(c: 0);
701 }
702
703 for (int x = 0; x < header.width(); ++x) {
704 for (int i = 0; i < 4; ++i) {
705 if (*(p + x) & (1 << i)) {
706 buf[i][x / 8] = (int)(buf[i][x / 8]) | 1 << (7 - x % 8);
707 }
708 }
709 }
710
711 for (int i = 0; i < 4; ++i) {
712 if (!writeLine(s, buf&: buf[i])) {
713 return false;
714 }
715 }
716 }
717 return true;
718}
719
720static bool writeImage8(const QImage &image, QDataStream &s, PCXHEADER &header)
721{
722 auto tfmt = image.format();
723 if (tfmt == QImage::Format_Grayscale16)
724 tfmt = QImage::Format_Grayscale8;
725
726 ScanLineConverter scl(tfmt);
727 if (image.height() > 0) {
728 scl.convertedScanLine(image, y: 0); // required to calculate bytesPerLine
729 }
730
731 header.Bpp = 8;
732 header.NPlanes = 1;
733 header.BytesPerLine = scl.bytesPerLine();
734 if (header.BytesPerLine == 0) {
735 header.BytesPerLine = image.bytesPerLine();
736 }
737 if (header.BytesPerLine == 0) {
738 return false;
739 }
740
741 s << header;
742
743 QByteArray buf(header.BytesPerLine, 0);
744
745 for (int y = 0; y < header.height(); ++y) {
746 auto p = scl.convertedScanLine(image, y);
747
748 for (int i = 0; i < header.BytesPerLine; ++i) {
749 buf[i] = p[i];
750 }
751
752 if (!writeLine(s, buf)) {
753 return false;
754 }
755 }
756
757 // Write palette flag
758 quint8 byte = 12;
759 s << byte;
760
761 // Write palette
762 for (int i = 0; i < 256; ++i) {
763 if (tfmt != QImage::Format_Indexed8)
764 s << RGB::from(color: qRgb(r: i, g: i, b: i));
765 else
766 s << RGB::from(color: image.color(i));
767 }
768
769 return (s.status() == QDataStream::Ok);
770}
771
772static bool writeImage24(const QImage &image, QDataStream &s, PCXHEADER &header)
773{
774 auto hasAlpha = image.hasAlphaChannel();
775 header.Bpp = 8;
776 header.NPlanes = hasAlpha ? 4 : 3;
777 header.BytesPerLine = header.width();
778 if (header.BytesPerLine == 0) {
779 return false;
780 }
781
782 auto tcs = QColorSpace();
783 auto tfmt = image.format();
784 auto cs = image.colorSpace();
785 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && tfmt == QImage::Format_CMYK8888) {
786 tcs = QColorSpace(QColorSpace::SRgb);
787 tfmt = QImage::Format_RGB32;
788 }
789 if (tfmt != QImage::Format_ARGB32 && tfmt != QImage::Format_RGB32) {
790 tfmt = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32;
791 }
792
793 s << header;
794
795 QByteArray r_buf(header.width(), 0);
796 QByteArray g_buf(header.width(), 0);
797 QByteArray b_buf(header.width(), 0);
798 QByteArray a_buf(header.width(), char(0xFF));
799
800 ScanLineConverter scl(tfmt);
801 scl.setTargetColorSpace(tcs);
802 for (int y = 0; y < header.height(); ++y) {
803 auto p = reinterpret_cast<const QRgb *>(scl.convertedScanLine(image, y));
804
805 for (int x = 0; x < header.width(); ++x) {
806 auto &&rgb = p[x];
807 r_buf[x] = qRed(rgb);
808 g_buf[x] = qGreen(rgb);
809 b_buf[x] = qBlue(rgb);
810 a_buf[x] = qAlpha(rgb);
811 }
812
813 if (!writeLine(s, buf&: r_buf)) {
814 return false;
815 }
816 if (!writeLine(s, buf&: g_buf)) {
817 return false;
818 }
819 if (!writeLine(s, buf&: b_buf)) {
820 return false;
821 }
822 if (hasAlpha && !writeLine(s, buf&: a_buf)) {
823 return false;
824 }
825 }
826
827 return true;
828}
829
830class PCXHandlerPrivate
831{
832public:
833 PCXHandlerPrivate() {}
834 ~PCXHandlerPrivate() {}
835
836 PCXHEADER m_header;
837};
838
839PCXHandler::PCXHandler()
840 : QImageIOHandler()
841 , d(new PCXHandlerPrivate)
842{
843}
844
845bool PCXHandler::canRead() const
846{
847 if (canRead(device: device())) {
848 setFormat("pcx");
849 return true;
850 }
851 return false;
852}
853
854bool PCXHandler::read(QImage *outImage)
855{
856 QDataStream s(device());
857 s.setByteOrder(QDataStream::LittleEndian);
858
859 if (s.device()->size() < 128) {
860 return false;
861 }
862
863 auto&& header = d->m_header;
864 s >> header;
865
866 if (s.status() != QDataStream::Ok || s.atEnd()) {
867 return false;
868 }
869
870 if (!header.isSupported()) {
871 return false;
872 }
873
874 auto ok = false;
875 QImage img;
876 if (header.Bpp == 1 && header.NPlanes == 1) {
877 ok = readImage1(img, s, header);
878 } else if (header.Bpp == 1 && (header.NPlanes == 4 || header.NPlanes == 3)) {
879 ok = readImage4(img, s, header);
880 } else if (header.Bpp == 2 && header.NPlanes == 1) {
881 ok = readImage2(img, s, header);
882 } else if (header.Bpp == 4 && header.NPlanes == 1) {
883 ok = readImage4v2(img, s, header);
884 } else if (header.Bpp == 8 && header.NPlanes == 1) {
885 ok = readImage8(img, s, header);
886 } else if (header.Bpp == 8 && (header.NPlanes == 3 || header.NPlanes == 4)) {
887 ok = readImage24(img, s, header);
888 }
889
890 if (img.isNull() || !ok) {
891 return false;
892 }
893
894 auto hres = dpi2ppm(dpi: header.HDpi);
895 if (hres > 0) {
896 img.setDotsPerMeterX(hres);
897 }
898 auto vres = dpi2ppm(dpi: header.YDpi);
899 if (vres > 0) {
900 img.setDotsPerMeterY(vres);
901 }
902 *outImage = img;
903 return true;
904}
905
906bool PCXHandler::write(const QImage &image)
907{
908 QDataStream s(device());
909 s.setByteOrder(QDataStream::LittleEndian);
910
911 const int w = image.width();
912 const int h = image.height();
913
914 if (w > 65536 || h > 65536) {
915 return false;
916 }
917
918 PCXHEADER header;
919
920 header.Manufacturer = 10;
921 header.Version = 5;
922 header.Encoding = 1;
923 header.XMin = 0;
924 header.YMin = 0;
925 header.XMax = w - 1;
926 header.YMax = h - 1;
927 header.HDpi = qRoundOrZero_T<quint16>(d: dppm2dpi(ppm: image.dotsPerMeterX()));
928 header.YDpi = qRoundOrZero_T<quint16>(d: dppm2dpi(ppm: image.dotsPerMeterY()));
929 header.Reserved = 0;
930 header.PaletteInfo = 1;
931
932 auto ok = false;
933 if (image.depth() == 1) {
934 ok = writeImage1(image, s, header);
935 } else if (image.format() == QImage::Format_Indexed8 && image.colorCount() <= 16) {
936 ok = writeImage4(image, s, header);
937 } else if (image.depth() == 8 || image.format() == QImage::Format_Grayscale16) {
938 ok = writeImage8(image, s, header);
939 } else if (image.depth() >= 16) {
940 ok = writeImage24(image, s, header);
941 }
942
943 return ok;
944}
945
946bool PCXHandler::supportsOption(ImageOption option) const
947{
948 if (option == QImageIOHandler::Size) {
949 return true;
950 }
951 if (option == QImageIOHandler::ImageFormat) {
952 return true;
953 }
954 return false;
955}
956
957QVariant PCXHandler::option(ImageOption option) const
958{
959 QVariant v;
960
961 if (option == QImageIOHandler::Size) {
962 auto&& header = d->m_header;
963 if (header.isSupported()) {
964 v = QVariant::fromValue(value: QSize(header.width(), header.height()));
965 } else if (auto dev = device()) {
966 if (peekHeader(d: dev, h&: header) && header.isSupported()) {
967 v = QVariant::fromValue(value: QSize(header.width(), header.height()));
968 }
969 }
970 }
971
972 if (option == QImageIOHandler::ImageFormat) {
973 auto&& header = d->m_header;
974 if (header.isSupported()) {
975 v = QVariant::fromValue(value: header.format());
976 } else if (auto dev = device()) {
977 if (peekHeader(d: dev, h&: header) && header.isSupported()) {
978 v = QVariant::fromValue(value: header.format());
979 }
980 }
981 }
982
983 return v;
984}
985
986bool PCXHandler::canRead(QIODevice *device)
987{
988 if (!device) {
989 qCWarning(LOG_PCXPLUGIN) << "PCXHandler::canRead() called with no device";
990 return false;
991 }
992
993 PCXHEADER header;
994 if (!peekHeader(d: device, h&: header)) {
995 return false;
996 }
997 return header.isSupported();
998}
999
1000QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
1001{
1002 if (format == "pcx") {
1003 return Capabilities(CanRead | CanWrite);
1004 }
1005 if (!format.isEmpty()) {
1006 return {};
1007 }
1008 if (!device->isOpen()) {
1009 return {};
1010 }
1011
1012 Capabilities cap;
1013 if (device->isReadable() && PCXHandler::canRead(device)) {
1014 cap |= CanRead;
1015 }
1016 if (device->isWritable()) {
1017 cap |= CanWrite;
1018 }
1019 return cap;
1020}
1021
1022QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format) const
1023{
1024 QImageIOHandler *handler = new PCXHandler;
1025 handler->setDevice(device);
1026 handler->setFormat(format);
1027 return handler;
1028}
1029
1030#include "moc_pcx_p.cpp"
1031

source code of kimageformats/src/imageformats/pcx.cpp