1/* This file is part of the KDE project
2 Copyright (C) 2002-2005 Nadeem Hasan <nhasan@kde.org>
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License (LGPL) as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8*/
9
10#include "pcx.h"
11
12#include <QtGui/QImage>
13
14#include <kdebug.h>
15
16static QDataStream &operator>>( QDataStream &s, RGB &rgb )
17{
18 quint8 r, g, b;
19
20 s >> r >> g >> b;
21 rgb.r = r;
22 rgb.g = g;
23 rgb.b = b;
24
25 return s;
26}
27
28static QDataStream &operator>>( QDataStream &s, Palette &pal )
29{
30 for ( int i=0; i<16; ++i )
31 s >> pal.rgb[ i ];
32
33 return s;
34}
35
36static QDataStream &operator>>( QDataStream &s, PCXHEADER &ph )
37{
38 quint8 m, ver, enc, bpp;
39 s >> m >> ver >> enc >> bpp;
40 ph.Manufacturer = m;
41 ph.Version = ver;
42 ph.Encoding = enc;
43 ph.Bpp = bpp;
44 quint16 xmin, ymin, xmax, ymax;
45 s >> xmin >> ymin >> xmax >> ymax;
46 ph.XMin = xmin;
47 ph.YMin = ymin;
48 ph.XMax = xmax;
49 ph.YMax = ymax;
50 quint16 hdpi, ydpi;
51 s >> hdpi >> ydpi;
52 ph.HDpi = hdpi;
53 ph.YDpi = ydpi;
54 Palette colorMap;
55 quint8 res, np;
56 s >> colorMap >> res >> np;
57 ph.ColorMap = colorMap;
58 ph.Reserved = res;
59 ph.NPlanes = np;
60 quint16 bytesperline;
61 s >> bytesperline; ph.BytesPerLine = bytesperline;
62 quint16 paletteinfo;
63 s >> paletteinfo; ph.PaletteInfo = paletteinfo;
64 quint16 hscreensize, vscreensize;
65 s >> hscreensize; ph.HScreenSize = hscreensize;
66 s >> vscreensize; ph.VScreenSize = vscreensize;
67
68 // Skip the rest of the header
69 quint8 byte;
70 while ( s.device()->pos() < 128 )
71 s >> byte;
72
73 return s;
74}
75
76static QDataStream &operator<<( QDataStream &s, const RGB &rgb )
77{
78 s << rgb.r << rgb.g << rgb.b;
79
80 return s;
81}
82
83static QDataStream &operator<<( QDataStream &s, const Palette &pal )
84{
85 for ( int i=0; i<16; ++i )
86 s << pal.rgb[ i ];
87
88 return s;
89}
90
91static QDataStream &operator<<( QDataStream &s, const PCXHEADER &ph )
92{
93 s << ph.Manufacturer;
94 s << ph.Version;
95 s << ph.Encoding;
96 s << ph.Bpp;
97 s << ph.XMin << ph.YMin << ph.XMax << ph.YMax;
98 s << ph.HDpi << ph.YDpi;
99 s << ph.ColorMap;
100 s << ph.Reserved;
101 s << ph.NPlanes;
102 s << ph.BytesPerLine;
103 s << ph.PaletteInfo;
104 s << ph.HScreenSize;
105 s << ph.VScreenSize;
106
107 quint8 byte = 0;
108 for ( int i=0; i<54; ++i )
109 s << byte;
110
111 return s;
112}
113
114PCXHEADER::PCXHEADER()
115{
116 // Initialize all data to zero
117 QByteArray dummy( 128, 0 );
118 dummy.fill( 0 );
119 QDataStream s( &dummy, QIODevice::ReadOnly );
120 s >> *this;
121}
122
123static void readLine( QDataStream &s, QByteArray &buf, const PCXHEADER &header )
124{
125 quint32 i=0;
126 quint32 size = buf.size();
127 quint8 byte, count;
128
129 if ( header.isCompressed() )
130 {
131 // Uncompress the image data
132 while ( i < size )
133 {
134 count = 1;
135 s >> byte;
136 if ( byte > 0xc0 )
137 {
138 count = byte - 0xc0;
139 s >> byte;
140 }
141 while ( count-- && i < size )
142 buf[ i++ ] = byte;
143 }
144 }
145 else
146 {
147 // Image is not compressed (possible?)
148 while ( i < size )
149 {
150 s >> byte;
151 buf[ i++ ] = byte;
152 }
153 }
154}
155
156static void readImage1( QImage &img, QDataStream &s, const PCXHEADER &header )
157{
158 QByteArray buf( header.BytesPerLine, 0 );
159
160 img = QImage( header.width(), header.height(), QImage::Format_Mono );
161 img.setNumColors( 2 );
162
163 for ( int y=0; y<header.height(); ++y )
164 {
165 if ( s.atEnd() )
166 {
167 img = QImage();
168 return;
169 }
170
171 readLine( s, buf, header );
172 uchar *p = img.scanLine( y );
173 unsigned int bpl = qMin((quint16)((header.width()+7)/8), header.BytesPerLine);
174 for ( unsigned int x=0; x< bpl; ++x )
175 p[ x ] = buf[x];
176 }
177
178 // Set the color palette
179 img.setColor( 0, qRgb( 0, 0, 0 ) );
180 img.setColor( 1, qRgb( 255, 255, 255 ) );
181}
182
183static void readImage4( QImage &img, QDataStream &s, const PCXHEADER &header )
184{
185 QByteArray buf( header.BytesPerLine*4, 0 );
186 QByteArray pixbuf( header.width(), 0 );
187
188 img = QImage( header.width(), header.height(), QImage::Format_Indexed8 );
189 img.setNumColors( 16 );
190
191 for ( int y=0; y<header.height(); ++y )
192 {
193 if ( s.atEnd() )
194 {
195 img = QImage();
196 return;
197 }
198
199 pixbuf.fill( 0 );
200 readLine( s, buf, header );
201
202 for ( int i=0; i<4; i++ )
203 {
204 quint32 offset = i*header.BytesPerLine;
205 for ( int x=0; x<header.width(); ++x )
206 if ( buf[ offset + ( x/8 ) ] & ( 128 >> ( x%8 ) ) )
207 pixbuf[ x ] = (int)(pixbuf[ x ]) + ( 1 << i );
208 }
209
210 uchar *p = img.scanLine( y );
211 for ( int x=0; x<header.width(); ++x )
212 p[ x ] = pixbuf[ x ];
213 }
214
215 // Read the palette
216 for ( int i=0; i<16; ++i )
217 img.setColor( i, header.ColorMap.color( i ) );
218}
219
220static void readImage8( QImage &img, QDataStream &s, const PCXHEADER &header )
221{
222 QByteArray buf( header.BytesPerLine, 0 );
223
224 img = QImage( header.width(), header.height(), QImage::Format_Indexed8 );
225 img.setNumColors( 256 );
226
227 for ( int y=0; y<header.height(); ++y )
228 {
229 if ( s.atEnd() )
230 {
231 img = QImage();
232 return;
233 }
234
235 readLine( s, buf, header );
236
237 uchar *p = img.scanLine( y );
238 unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width());
239 for ( unsigned int x=0; x<bpl; ++x )
240 p[ x ] = buf[ x ];
241 }
242
243 quint8 flag;
244 s >> flag;
245 kDebug( 399 ) << "Palette Flag: " << flag;
246
247 if ( flag == 12 && ( header.Version == 5 || header.Version == 2 ) )
248 {
249 // Read the palette
250 quint8 r, g, b;
251 for ( int i=0; i<256; ++i )
252 {
253 s >> r >> g >> b;
254 img.setColor( i, qRgb( r, g, b ) );
255 }
256 }
257}
258
259static void readImage24( QImage &img, QDataStream &s, const PCXHEADER &header )
260{
261 QByteArray r_buf( header.BytesPerLine, 0 );
262 QByteArray g_buf( header.BytesPerLine, 0 );
263 QByteArray b_buf( header.BytesPerLine, 0 );
264
265 img = QImage( header.width(), header.height(), QImage::Format_RGB32 );
266
267 for ( int y=0; y<header.height(); ++y )
268 {
269 if ( s.atEnd() )
270 {
271 img = QImage();
272 return;
273 }
274
275 readLine( s, r_buf, header );
276 readLine( s, g_buf, header );
277 readLine( s, b_buf, header );
278
279 uint *p = ( uint * )img.scanLine( y );
280 for ( int x=0; x<header.width(); ++x )
281 p[ x ] = qRgb( r_buf[ x ], g_buf[ x ], b_buf[ x ] );
282 }
283}
284
285static void writeLine( QDataStream &s, QByteArray &buf )
286{
287 quint32 i = 0;
288 quint32 size = buf.size();
289 quint8 count, data;
290 char byte;
291
292 while ( i < size )
293 {
294 count = 1;
295 byte = buf[ i++ ];
296
297 while ( ( i < size ) && ( byte == buf[ i ] ) && ( count < 63 ) )
298 {
299 ++i;
300 ++count;
301 }
302
303 data = byte;
304
305 if ( count > 1 || data >= 0xc0 )
306 {
307 count |= 0xc0;
308 s << count;
309 }
310
311 s << data;
312 }
313}
314
315static void writeImage1( QImage &img, QDataStream &s, PCXHEADER &header )
316{
317 img = img.convertToFormat( QImage::Format_Mono );
318
319 header.Bpp = 1;
320 header.NPlanes = 1;
321 header.BytesPerLine = img.bytesPerLine();
322
323 s << header;
324
325 QByteArray buf( header.BytesPerLine, 0 );
326
327 for ( int y=0; y<header.height(); ++y )
328 {
329 quint8 *p = img.scanLine( y );
330
331 // Invert as QImage uses reverse palette for monochrome images?
332 for ( int i=0; i<header.BytesPerLine; ++i )
333 buf[ i ] = ~p[ i ];
334
335 writeLine( s, buf );
336 }
337}
338
339static void writeImage4( QImage &img, QDataStream &s, PCXHEADER &header )
340{
341 header.Bpp = 1;
342 header.NPlanes = 4;
343 header.BytesPerLine = header.width()/8;
344
345 for ( int i=0; i<16; ++i )
346 header.ColorMap.setColor( i, img.color( i ) );
347
348 s << header;
349
350 QByteArray buf[ 4 ];
351
352 for ( int i=0; i<4; ++i )
353 buf[ i ].resize( header.BytesPerLine );
354
355 for ( int y=0; y<header.height(); ++y )
356 {
357 quint8 *p = img.scanLine( y );
358
359 for ( int i=0; i<4; ++i )
360 buf[ i ].fill( 0 );
361
362 for ( int x=0; x<header.width(); ++x )
363 {
364 for ( int i=0; i<4; ++i )
365 if ( *( p+x ) & ( 1 << i ) )
366 buf[ i ][ x/8 ] = (int)(buf[ i ][ x/8 ])| 1 << ( 7-x%8 );
367 }
368
369 for ( int i=0; i<4; ++i )
370 writeLine( s, buf[ i ] );
371 }
372}
373
374static void writeImage8( QImage &img, QDataStream &s, PCXHEADER &header )
375{
376 header.Bpp = 8;
377 header.NPlanes = 1;
378 header.BytesPerLine = img.bytesPerLine();
379
380 s << header;
381
382 QByteArray buf( header.BytesPerLine, 0 );
383
384 for ( int y=0; y<header.height(); ++y )
385 {
386 quint8 *p = img.scanLine( y );
387
388 for ( int i=0; i<header.BytesPerLine; ++i )
389 buf[ i ] = p[ i ];
390
391 writeLine( s, buf );
392 }
393
394 // Write palette flag
395 quint8 byte = 12;
396 s << byte;
397
398 // Write palette
399 for ( int i=0; i<256; ++i )
400 s << RGB::from( img.color( i ) );
401}
402
403static void writeImage24( QImage &img, QDataStream &s, PCXHEADER &header )
404{
405 header.Bpp = 8;
406 header.NPlanes = 3;
407 header.BytesPerLine = header.width();
408
409 s << header;
410
411 QByteArray r_buf( header.width(), 0 );
412 QByteArray g_buf( header.width(), 0 );
413 QByteArray b_buf( header.width(), 0 );
414
415 for ( int y=0; y<header.height(); ++y )
416 {
417 uint *p = ( uint * )img.scanLine( y );
418
419 for ( int x=0; x<header.width(); ++x )
420 {
421 QRgb rgb = *p++;
422 r_buf[ x ] = qRed( rgb );
423 g_buf[ x ] = qGreen( rgb );
424 b_buf[ x ] = qBlue( rgb );
425 }
426
427 writeLine( s, r_buf );
428 writeLine( s, g_buf );
429 writeLine( s, b_buf );
430 }
431}
432
433
434PCXHandler::PCXHandler()
435{
436}
437
438bool PCXHandler::canRead() const
439{
440 if (canRead(device()))
441 {
442 setFormat("pcx");
443 return true;
444 }
445 return false;
446}
447
448bool PCXHandler::read(QImage *outImage)
449{
450 QDataStream s( device() );
451 s.setByteOrder( QDataStream::LittleEndian );
452
453 if ( s.device()->size() < 128 )
454 {
455 return false;
456 }
457
458 PCXHEADER header;
459
460 s >> header;
461
462 if ( header.Manufacturer != 10 || s.atEnd())
463 {
464 return false;
465 }
466
467 int w = header.width();
468 int h = header.height();
469
470 kDebug( 399 ) << "Manufacturer: " << header.Manufacturer;
471 kDebug( 399 ) << "Version: " << header.Version;
472 kDebug( 399 ) << "Encoding: " << header.Encoding;
473 kDebug( 399 ) << "Bpp: " << header.Bpp;
474 kDebug( 399 ) << "Width: " << w;
475 kDebug( 399 ) << "Height: " << h;
476 kDebug( 399 ) << "Window: " << header.XMin << "," << header.XMax << ","
477 << header.YMin << "," << header.YMax << endl;
478 kDebug( 399 ) << "BytesPerLine: " << header.BytesPerLine;
479 kDebug( 399 ) << "NPlanes: " << header.NPlanes;
480
481 QImage img;
482
483 if ( header.Bpp == 1 && header.NPlanes == 1 )
484 {
485 readImage1( img, s, header );
486 }
487 else if ( header.Bpp == 1 && header.NPlanes == 4 )
488 {
489 readImage4( img, s, header );
490 }
491 else if ( header.Bpp == 8 && header.NPlanes == 1 )
492 {
493 readImage8( img, s, header );
494 }
495 else if ( header.Bpp == 8 && header.NPlanes == 3 )
496 {
497 readImage24( img, s, header );
498 }
499
500 kDebug( 399 ) << "Image Bytes: " << img.numBytes();
501 kDebug( 399 ) << "Image Bytes Per Line: " << img.bytesPerLine();
502 kDebug( 399 ) << "Image Depth: " << img.depth();
503
504 if ( !img.isNull() )
505 {
506 *outImage = img;
507 return true;
508 }
509 else
510 {
511 return false;
512 }
513}
514
515bool PCXHandler::write(const QImage &image)
516{
517 QDataStream s( device() );
518 s.setByteOrder( QDataStream::LittleEndian );
519
520 QImage img = image;
521
522 int w = img.width();
523 int h = img.height();
524
525 kDebug( 399 ) << "Width: " << w;
526 kDebug( 399 ) << "Height: " << h;
527 kDebug( 399 ) << "Depth: " << img.depth();
528 kDebug( 399 ) << "BytesPerLine: " << img.bytesPerLine();
529 kDebug( 399 ) << "Num Colors: " << img.numColors();
530
531 PCXHEADER header;
532
533 header.Manufacturer = 10;
534 header.Version = 5;
535 header.Encoding = 1;
536 header.XMin = 0;
537 header.YMin = 0;
538 header.XMax = w-1;
539 header.YMax = h-1;
540 header.HDpi = 300;
541 header.YDpi = 300;
542 header.Reserved = 0;
543 header.PaletteInfo =1;
544
545 if ( img.depth() == 1 )
546 {
547 writeImage1( img, s, header );
548 }
549 else if ( img.depth() == 8 && img.numColors() <= 16 )
550 {
551 writeImage4( img, s, header );
552 }
553 else if ( img.depth() == 8 )
554 {
555 writeImage8( img, s, header );
556 }
557 else if ( img.depth() == 32 )
558 {
559 writeImage24( img, s, header );
560 }
561
562 return true;
563}
564
565QByteArray PCXHandler::name() const
566{
567 return "pcx";
568}
569
570bool PCXHandler::canRead(QIODevice *device)
571{
572 if (!device) {
573 qWarning("PCXHandler::canRead() called with no device");
574 return false;
575 }
576
577 qint64 oldPos = device->pos();
578
579 char head[1];
580 qint64 readBytes = device->read(head, sizeof(head));
581 if (readBytes != sizeof(head)) {
582 if (device->isSequential()) {
583 while (readBytes > 0)
584 device->ungetChar(head[readBytes-- - 1]);
585 } else {
586 device->seek(oldPos);
587 }
588 return false;
589 }
590
591 if (device->isSequential()) {
592 while (readBytes > 0)
593 device->ungetChar(head[readBytes-- - 1]);
594 } else {
595 device->seek(oldPos);
596 }
597
598 return qstrncmp(head, "\012", 1) == 0;
599}
600
601class PCXPlugin : public QImageIOPlugin
602{
603public:
604 QStringList keys() const;
605 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
606 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
607};
608
609QStringList PCXPlugin::keys() const
610{
611 return QStringList() << "pcx" << "PCX";
612}
613
614QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
615{
616 if (format == "pcx" || format == "PCX")
617 return Capabilities(CanRead | CanWrite);
618 if (!format.isEmpty())
619 return 0;
620 if (!device->isOpen())
621 return 0;
622
623 Capabilities cap;
624 if (device->isReadable() && PCXHandler::canRead(device))
625 cap |= CanRead;
626 if (device->isWritable())
627 cap |= CanWrite;
628 return cap;
629}
630
631QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format) const
632{
633 QImageIOHandler *handler = new PCXHandler;
634 handler->setDevice(device);
635 handler->setFormat(format);
636 return handler;
637}
638
639Q_EXPORT_STATIC_PLUGIN(PCXPlugin)
640Q_EXPORT_PLUGIN2(pcx, PCXPlugin)
641
642/* vim: et sw=2 ts=2
643*/
644
645