KImageFormats

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

KDE's Doxygen guidelines are available online.