KImageFormats

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

KDE's Doxygen guidelines are available online.