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 | |
16 | static 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 | |
28 | static 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 | |
36 | static QDataStream &( 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 | |
76 | static QDataStream &operator<<( QDataStream &s, const RGB &rgb ) |
77 | { |
78 | s << rgb.r << rgb.g << rgb.b; |
79 | |
80 | return s; |
81 | } |
82 | |
83 | static 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 | |
91 | static QDataStream &( 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 | |
114 | 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 | |
123 | static void ( QDataStream &s, QByteArray &buf, const PCXHEADER & ) |
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 | |
156 | static void ( QImage &img, QDataStream &s, const PCXHEADER & ) |
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 | |
183 | static void ( QImage &img, QDataStream &s, const PCXHEADER & ) |
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 | |
220 | static void ( QImage &img, QDataStream &s, const PCXHEADER & ) |
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 | |
259 | static void ( QImage &img, QDataStream &s, const PCXHEADER & ) |
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 | |
285 | static 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 | |
315 | static void ( QImage &img, QDataStream &s, PCXHEADER & ) |
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 | |
339 | static void ( QImage &img, QDataStream &s, PCXHEADER & ) |
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 | |
374 | static void ( QImage &img, QDataStream &s, PCXHEADER & ) |
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 | |
403 | static void ( QImage &img, QDataStream &s, PCXHEADER & ) |
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 | |
434 | PCXHandler::PCXHandler() |
435 | { |
436 | } |
437 | |
438 | bool PCXHandler::canRead() const |
439 | { |
440 | if (canRead(device())) |
441 | { |
442 | setFormat("pcx" ); |
443 | return true; |
444 | } |
445 | return false; |
446 | } |
447 | |
448 | bool 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 ; |
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 | |
515 | bool 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 ; |
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 | |
565 | QByteArray PCXHandler::name() const |
566 | { |
567 | return "pcx" ; |
568 | } |
569 | |
570 | bool 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 | |
601 | class PCXPlugin : public QImageIOPlugin |
602 | { |
603 | public: |
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 | |
609 | QStringList PCXPlugin::keys() const |
610 | { |
611 | return QStringList() << "pcx" << "PCX" ; |
612 | } |
613 | |
614 | QImageIOPlugin::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 | |
631 | QImageIOHandler *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 | |
639 | Q_EXPORT_STATIC_PLUGIN(PCXPlugin) |
640 | Q_EXPORT_PLUGIN2(pcx, PCXPlugin) |
641 | |
642 | /* vim: et sw=2 ts=2 |
643 | */ |
644 | |
645 | |