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

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