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 quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
72 quint8 Version; // Version information·
73 // 0 = Version 2.5 of PC Paintbrush·
74 // 2 = Version 2.8 w/palette information·
75 // 3 = Version 2.8 w/o palette information·
76 // 4 = PC Paintbrush for Windows(Plus for
77 // Windows uses Ver 5)·
78 // 5 = Version 3.0 and > of PC Paintbrush
79 // and PC Paintbrush +, includes
80 // Publisher's Paintbrush . Includes
81 // 24-bit .PCX files·
82 quint8 Encoding; // 1 = .PCX run length encoding
83 quint8 Bpp; // Number of bits to represent a pixel
84 // (per Plane) - 1, 2, 4, or 8·
85 quint16 XMin;
86 quint16 YMin;
87 quint16 XMax;
88 quint16 YMax;
89 quint16 HDpi;
90 quint16 YDpi;
91 Palette ColorMap;
92 quint8 Reserved; // Should be set to 0.
93 quint8 NPlanes; // Number of color planes
94 quint16 BytesPerLine; // Number of bytes to allocate for a scanline
95 // plane. MUST be an EVEN number. Do NOT
96 // calculate from Xmax-Xmin.·
97 quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW,
98 // 2 = Grayscale ( ignored in PB IV/ IV + )·
99 quint16 HScreenSize; // Horizontal screen size in pixels. New field
100 // found only in PB IV/IV Plus
101 quint16 VScreenSize; // Vertical screen size in pixels. New field
102 // found only in PB IV/IV Plus
103};
104
105#pragma pack(pop)
106
107static QDataStream &operator>>(QDataStream &s, RGB &rgb)
108{
109 quint8 r;
110 quint8 g;
111 quint8 b;
112
113 s >> r >> g >> b;
114 rgb.r = r;
115 rgb.g = g;
116 rgb.b = b;
117
118 return s;
119}
120
122{
123 for (int i = 0; i < 16; ++i) {
124 s >> pal.rgb[i];
125 }
126
127 return s;
128}
129
130static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
131{
132 quint8 m;
133 quint8 ver;
134 quint8 enc;
135 quint8 bpp;
136 s >> m >> ver >> enc >> bpp;
137 ph.Manufacturer = m;
138 ph.Version = ver;
139 ph.Encoding = enc;
140 ph.Bpp = bpp;
141 quint16 xmin;
142 quint16 ymin;
143 quint16 xmax;
144 quint16 ymax;
145 s >> xmin >> ymin >> xmax >> ymax;
146 ph.XMin = xmin;
147 ph.YMin = ymin;
148 ph.XMax = xmax;
149 ph.YMax = ymax;
150 quint16 hdpi;
151 quint16 ydpi;
152 s >> hdpi >> ydpi;
153 ph.HDpi = hdpi;
154 ph.YDpi = ydpi;
155 Palette colorMap;
156 quint8 res;
157 quint8 np;
158 s >> colorMap >> res >> np;
159 ph.ColorMap = colorMap;
160 ph.Reserved = res;
161 ph.NPlanes = np;
162 quint16 bytesperline;
163 s >> bytesperline;
164 ph.BytesPerLine = bytesperline;
165 quint16 paletteinfo;
166 s >> paletteinfo;
167 ph.PaletteInfo = paletteinfo;
168 quint16 hscreensize;
169 quint16 vscreensize;
170 s >> hscreensize;
171 ph.HScreenSize = hscreensize;
172 s >> vscreensize;
173 ph.VScreenSize = vscreensize;
174
175 // Skip the rest of the header
176 quint8 byte;
177 for (auto i = 0; i < 54; ++i) {
178 s >> byte;
179 }
180
181 return s;
182}
183
184static QDataStream &operator<<(QDataStream &s, const RGB rgb)
185{
186 s << rgb.r << rgb.g << rgb.b;
187
188 return s;
189}
190
191static QDataStream &operator<<(QDataStream &s, const Palette &pal)
192{
193 for (int i = 0; i < 16; ++i) {
194 s << pal.rgb[i];
195 }
196
197 return s;
198}
199
200static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph)
201{
202 s << ph.Manufacturer;
203 s << ph.Version;
204 s << ph.Encoding;
205 s << ph.Bpp;
206 s << ph.XMin << ph.YMin << ph.XMax << ph.YMax;
207 s << ph.HDpi << ph.YDpi;
208 s << ph.ColorMap;
209 s << ph.Reserved;
210 s << ph.NPlanes;
211 s << ph.BytesPerLine;
212 s << ph.PaletteInfo;
213 s << ph.HScreenSize;
214 s << ph.VScreenSize;
215
216 quint8 byte = 0;
217 for (int i = 0; i < 54; ++i) {
218 s << byte;
219 }
220
221 return s;
222}
223
224PCXHEADER::PCXHEADER()
225{
226 // Initialize all data to zero
227 QByteArray dummy(128, 0);
228 dummy.fill(0);
230 s >> *this;
231}
232
233static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
234{
235 quint32 i = 0;
236 quint32 size = buf.size();
237 quint8 byte;
238 quint8 count;
239
240 if (header.isCompressed()) {
241 // Uncompress the image data
242 while (i < size) {
243 count = 1;
244 s >> byte;
245 if (byte > 0xc0) {
246 count = byte - 0xc0;
247 s >> byte;
248 }
249 while (count-- && i < size) {
250 buf[i++] = byte;
251 }
252 }
253 } else {
254 // Image is not compressed (possible?)
255 while (i < size) {
256 s >> byte;
257 buf[i++] = byte;
258 }
259 }
260
261 return (s.status() == QDataStream::Ok);
262}
263
264static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
265{
266 QByteArray buf(header.BytesPerLine, 0);
267
268 img = imageAlloc(header.width(), header.height(), QImage::Format_Mono);
269 img.setColorCount(2);
270
271 if (img.isNull()) {
272 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
273 return false;
274 }
275
276 for (int y = 0; y < header.height(); ++y) {
277 if (s.atEnd()) {
278 return false;
279 }
280
281 if (!readLine(s, buf, header)) {
282 return false;
283 }
284
285 uchar *p = img.scanLine(y);
286 unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine);
287 for (unsigned int x = 0; x < bpl; ++x) {
288 p[x] = buf[x];
289 }
290 }
291
292 // Set the color palette
293 img.setColor(0, qRgb(0, 0, 0));
294 img.setColor(1, qRgb(255, 255, 255));
295
296 return true;
297}
298
299static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
300{
301 QByteArray buf(header.BytesPerLine * 4, 0);
302 QByteArray pixbuf(header.width(), 0);
303
304 img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
305 img.setColorCount(16);
306 if (img.isNull()) {
307 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
308 return false;
309 }
310
311 for (int y = 0; y < header.height(); ++y) {
312 if (s.atEnd()) {
313 return false;
314 }
315
316 pixbuf.fill(0);
317 if (!readLine(s, buf, header)) {
318 return false;
319 }
320
321 for (int i = 0; i < 4; i++) {
322 quint32 offset = i * header.BytesPerLine;
323 for (int x = 0; x < header.width(); ++x) {
324 if (buf[offset + (x / 8)] & (128 >> (x % 8))) {
325 pixbuf[x] = (int)(pixbuf[x]) + (1 << i);
326 }
327 }
328 }
329
330 uchar *p = img.scanLine(y);
331 if (!p) {
332 qWarning() << "Failed to get scanline for" << y << "might be out of bounds";
333 }
334 for (int x = 0; x < header.width(); ++x) {
335 p[x] = pixbuf[x];
336 }
337 }
338
339 // Read the palette
340 for (int i = 0; i < 16; ++i) {
341 img.setColor(i, header.ColorMap.color(i));
342 }
343
344 return true;
345}
346
347static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
348{
349 QByteArray buf(header.BytesPerLine, 0);
350
351 img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
352 img.setColorCount(256);
353
354 if (img.isNull()) {
355 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
356 return false;
357 }
358
359 for (int y = 0; y < header.height(); ++y) {
360 if (s.atEnd()) {
361 return false;
362 }
363
364 if (!readLine(s, buf, header)) {
365 return false;
366 }
367
368 uchar *p = img.scanLine(y);
369 if (!p) {
370 return false;
371 }
372
373 unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
374 for (unsigned int x = 0; x < bpl; ++x) {
375 p[x] = buf[x];
376 }
377 }
378
379 // by specification, the extended palette starts at file.size() - 769
380 quint8 flag = 0;
381 if (auto device = s.device()) {
382 if (device->isSequential()) {
383 while (flag != 12 && s.status() == QDataStream::Ok) {
384 s >> flag;
385 }
386 }
387 else {
388 device->seek(device->size() - 769);
389 s >> flag;
390 }
391 }
392
393 // qDebug() << "Palette Flag: " << flag;
394 if (flag == 12 && (header.Version == 5 || header.Version == 2)) {
395 // Read the palette
396 quint8 r;
397 quint8 g;
398 quint8 b;
399 for (int i = 0; i < 256; ++i) {
400 s >> r >> g >> b;
401 img.setColor(i, qRgb(r, g, b));
402 }
403 }
404
405 return (s.status() == QDataStream::Ok);
406}
407
408static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
409{
410 QByteArray r_buf(header.BytesPerLine, 0);
411 QByteArray g_buf(header.BytesPerLine, 0);
412 QByteArray b_buf(header.BytesPerLine, 0);
413
414 img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32);
415
416 if (img.isNull()) {
417 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
418 return false;
419 }
420
421 for (int y = 0; y < header.height(); ++y) {
422 if (s.atEnd()) {
423 return false;
424 }
425
426 if (!readLine(s, r_buf, header)) {
427 return false;
428 }
429 if (!readLine(s, g_buf, header)) {
430 return false;
431 }
432 if (!readLine(s, b_buf, header)) {
433 return false;
434 }
435
436 uint *p = (uint *)img.scanLine(y);
437 for (int x = 0; x < header.width(); ++x) {
438 p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
439 }
440 }
441
442 return true;
443}
444
445static bool writeLine(QDataStream &s, QByteArray &buf)
446{
447 quint32 i = 0;
448 quint32 size = buf.size();
449 quint8 count;
450 quint8 data;
451 char byte;
452
453 while (i < size) {
454 count = 1;
455 byte = buf[i++];
456
457 while ((i < size) && (byte == buf[i]) && (count < 63)) {
458 ++i;
459 ++count;
460 }
461
462 data = byte;
463
464 if (count > 1 || data >= 0xc0) {
465 count |= 0xc0;
466 s << count;
467 }
468
469 s << data;
470 }
471 return (s.status() == QDataStream::Ok);
472}
473
474static bool writeImage1(QImage &img, QDataStream &s, PCXHEADER &header)
475{
476 if (img.format() != QImage::Format_Mono) {
478 }
479 if (img.isNull() || img.colorCount() < 1) {
480 return false;
481 }
482 auto rgb = img.color(0);
483 auto minIsBlack = (qRed(rgb) + qGreen(rgb) + qBlue(rgb)) / 3 < 127;
484
485 header.Bpp = 1;
486 header.NPlanes = 1;
487 header.BytesPerLine = img.bytesPerLine();
488 if (header.BytesPerLine == 0) {
489 return false;
490 }
491
492 s << header;
493
494 QByteArray buf(header.BytesPerLine, 0);
495
496 for (int y = 0; y < header.height(); ++y) {
497 quint8 *p = img.scanLine(y);
498
499 // Invert as QImage uses reverse palette for monochrome images?
500 for (int i = 0; i < header.BytesPerLine; ++i) {
501 buf[i] = minIsBlack ? p[i] : ~p[i];
502 }
503
504 if (!writeLine(s, buf)) {
505 return false;
506 }
507 }
508 return true;
509}
510
511static bool writeImage4(QImage &img, QDataStream &s, PCXHEADER &header)
512{
513 header.Bpp = 1;
514 header.NPlanes = 4;
515 header.BytesPerLine = header.width() / 8;
516 if (header.BytesPerLine == 0) {
517 return false;
518 }
519
520 for (int i = 0; i < 16; ++i) {
521 header.ColorMap.setColor(i, img.color(i));
522 }
523
524 s << header;
525
526 QByteArray buf[4];
527
528 for (int i = 0; i < 4; ++i) {
529 buf[i].resize(header.BytesPerLine);
530 }
531
532 for (int y = 0; y < header.height(); ++y) {
533 quint8 *p = img.scanLine(y);
534
535 for (int i = 0; i < 4; ++i) {
536 buf[i].fill(0);
537 }
538
539 for (int x = 0; x < header.width(); ++x) {
540 for (int i = 0; i < 4; ++i) {
541 if (*(p + x) & (1 << i)) {
542 buf[i][x / 8] = (int)(buf[i][x / 8]) | 1 << (7 - x % 8);
543 }
544 }
545 }
546
547 for (int i = 0; i < 4; ++i) {
548 if (!writeLine(s, buf[i])) {
549 return false;
550 }
551 }
552 }
553 return true;
554}
555
556static bool writeImage8(QImage &img, QDataStream &s, PCXHEADER &header)
557{
558 header.Bpp = 8;
559 header.NPlanes = 1;
560 header.BytesPerLine = img.bytesPerLine();
561 if (header.BytesPerLine == 0) {
562 return false;
563 }
564
565 s << header;
566
567 QByteArray buf(header.BytesPerLine, 0);
568
569 for (int y = 0; y < header.height(); ++y) {
570 quint8 *p = img.scanLine(y);
571
572 for (int i = 0; i < header.BytesPerLine; ++i) {
573 buf[i] = p[i];
574 }
575
576 if (!writeLine(s, buf)) {
577 return false;
578 }
579 }
580
581 // Write palette flag
582 quint8 byte = 12;
583 s << byte;
584
585 // Write palette
586 for (int i = 0; i < 256; ++i) {
587 s << RGB::from(img.color(i));
588 }
589
590 return (s.status() == QDataStream::Ok);
591}
592
593static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
594{
595 header.Bpp = 8;
596 header.NPlanes = 3;
597 header.BytesPerLine = header.width();
598 if (header.BytesPerLine == 0) {
599 return false;
600 }
601
602 if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_RGB32) {
604 }
605 if (img.isNull()) {
606 return false;
607 }
608
609 s << header;
610
611 QByteArray r_buf(header.width(), 0);
612 QByteArray g_buf(header.width(), 0);
613 QByteArray b_buf(header.width(), 0);
614
615 for (int y = 0; y < header.height(); ++y) {
616 auto p = (QRgb*)img.scanLine(y);
617
618 for (int x = 0; x < header.width(); ++x) {
619 QRgb rgb = *p++;
620 r_buf[x] = qRed(rgb);
621 g_buf[x] = qGreen(rgb);
622 b_buf[x] = qBlue(rgb);
623 }
624
625 if (!writeLine(s, r_buf)) {
626 return false;
627 }
628 if (!writeLine(s, g_buf)) {
629 return false;
630 }
631 if (!writeLine(s, b_buf)) {
632 return false;
633 }
634 }
635
636 return true;
637}
638
639PCXHandler::PCXHandler()
640{
641}
642
643bool PCXHandler::canRead() const
644{
645 if (canRead(device())) {
646 setFormat("pcx");
647 return true;
648 }
649 return false;
650}
651
652bool PCXHandler::read(QImage *outImage)
653{
654 QDataStream s(device());
656
657 if (s.device()->size() < 128) {
658 return false;
659 }
660
661 PCXHEADER header;
662
663 s >> header;
664
665 if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
666 return false;
667 }
668
669 auto ok = false;
670 QImage img;
671 if (header.Bpp == 1 && header.NPlanes == 1) {
672 ok = readImage1(img, s, header);
673 } else if (header.Bpp == 1 && header.NPlanes == 4) {
674 ok = readImage4(img, s, header);
675 } else if (header.Bpp == 8 && header.NPlanes == 1) {
676 ok = readImage8(img, s, header);
677 } else if (header.Bpp == 8 && header.NPlanes == 3) {
678 ok = readImage24(img, s, header);
679 }
680
681 if (img.isNull() || !ok) {
682 return false;
683 }
684
685 img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000));
686 img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000));
687 *outImage = img;
688 return true;
689}
690
691bool PCXHandler::write(const QImage &image)
692{
693 QDataStream s(device());
695
696 QImage img = image;
697
698 const int w = img.width();
699 const int h = img.height();
700
701 if (w > 65536 || h > 65536) {
702 return false;
703 }
704
705 PCXHEADER header;
706
707 header.Manufacturer = 10;
708 header.Version = 5;
709 header.Encoding = 1;
710 header.XMin = 0;
711 header.YMin = 0;
712 header.XMax = w - 1;
713 header.YMax = h - 1;
714 header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000);
715 header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000);
716 header.Reserved = 0;
717 header.PaletteInfo = 1;
718
719 auto ok = false;
720 if (img.depth() == 1) {
721 ok = writeImage1(img, s, header);
722 } else if (img.depth() == 8 && img.colorCount() <= 16) {
723 ok = writeImage4(img, s, header);
724 } else if (img.depth() == 8) {
725 ok = writeImage8(img, s, header);
726 } else if (img.depth() >= 24) {
727 ok = writeImage24(img, s, header);
728 }
729
730 return ok;
731}
732
733bool PCXHandler::canRead(QIODevice *device)
734{
735 if (!device) {
736 qWarning("PCXHandler::canRead() called with no device");
737 return false;
738 }
739
740 qint64 oldPos = device->pos();
741
742 char head[1];
743 qint64 readBytes = device->read(head, sizeof(head));
744 if (readBytes != sizeof(head)) {
745 if (device->isSequential()) {
746 while (readBytes > 0) {
747 device->ungetChar(head[readBytes-- - 1]);
748 }
749 } else {
750 device->seek(oldPos);
751 }
752 return false;
753 }
754
755 if (device->isSequential()) {
756 while (readBytes > 0) {
757 device->ungetChar(head[readBytes-- - 1]);
758 }
759 } else {
760 device->seek(oldPos);
761 }
762
763 return qstrncmp(head, "\012", 1) == 0;
764}
765
766QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
767{
768 if (format == "pcx") {
769 return Capabilities(CanRead | CanWrite);
770 }
771 if (!format.isEmpty()) {
772 return {};
773 }
774 if (!device->isOpen()) {
775 return {};
776 }
777
778 Capabilities cap;
779 if (device->isReadable() && PCXHandler::canRead(device)) {
780 cap |= CanRead;
781 }
782 if (device->isWritable()) {
783 cap |= CanWrite;
784 }
785 return cap;
786}
787
788QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format) const
789{
790 QImageIOHandler *handler = new PCXHandler;
791 handler->setDevice(device);
792 handler->setFormat(format);
793 return handler;
794}
795
796#include "moc_pcx_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &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
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
int depth() const const
int dotsPerMeterX() const const
int dotsPerMeterY() const const
Format format() 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)
typedef Capabilities
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
bool isWritable() const const
virtual qint64 pos() const const
QByteArray read(qint64 maxSize)
virtual bool seek(qint64 pos)
virtual qint64 size() const const
void ungetChar(char c)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.