source: trunk/src/kernel/qpngio.cpp@ 8

Last change on this file since 8 was 2, checked in by dmik, 20 years ago

Imported xplatform parts of the official release 3.3.1 from Trolltech

  • Property svn:keywords set to Id
File size: 31.1 KB
Line 
1/****************************************************************************
2** $Id: qpngio.cpp 2 2005-11-16 15:49:26Z dmik $
3**
4** Implementation of PNG QImage IOHandler
5**
6** Created : 970521
7**
8** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
9**
10** This file is part of the kernel module of the Qt GUI Toolkit.
11**
12** This file may be distributed under the terms of the Q Public License
13** as defined by Trolltech AS of Norway and appearing in the file
14** LICENSE.QPL included in the packaging of this file.
15**
16** This file may be distributed and/or modified under the terms of the
17** GNU General Public License version 2 as published by the Free Software
18** Foundation and appearing in the file LICENSE.GPL included in the
19** packaging of this file.
20**
21** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
22** licenses may use this file in accordance with the Qt Commercial License
23** Agreement provided with the Software.
24**
25** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
26** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27**
28** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
29** information about Qt Commercial License Agreements.
30** See http://www.trolltech.com/qpl/ for QPL licensing information.
31** See http://www.trolltech.com/gpl/ for GPL licensing information.
32**
33** Contact info@trolltech.com if any conditions of this licensing are
34** not clear to you.
35**
36**********************************************************************/
37
38#include "qpngio.h"
39
40#ifndef QT_NO_IMAGEIO_PNG
41
42#include "qasyncimageio.h"
43#include "qiodevice.h"
44
45#include <png.h>
46
47
48#ifdef Q_OS_TEMP
49#define CALLBACK_CALL_TYPE __cdecl
50#else
51#define CALLBACK_CALL_TYPE
52#endif
53
54
55/*
56 All PNG files load to the minimal QImage equivalent.
57
58 All QImage formats output to reasonably efficient PNG equivalents.
59 Never to grayscale.
60*/
61
62#if defined(Q_C_CALLBACKS)
63extern "C" {
64#endif
65
66static
67void CALLBACK_CALL_TYPE iod_read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
68{
69 QImageIO* iio = (QImageIO*)png_get_io_ptr(png_ptr);
70 QIODevice* in = iio->ioDevice();
71
72 while (length) {
73 int nr = in->readBlock((char*)data, length);
74 if (nr <= 0) {
75 png_error(png_ptr, "Read Error");
76 return;
77 }
78 length -= nr;
79 }
80}
81
82
83static
84void CALLBACK_CALL_TYPE qpiw_write_fn( png_structp png_ptr, png_bytep data, png_size_t length )
85{
86 QPNGImageWriter* qpiw = (QPNGImageWriter*)png_get_io_ptr( png_ptr );
87 QIODevice* out = qpiw->device();
88
89 uint nr = out->writeBlock( (char*)data, length );
90 if ( nr != length ) {
91 png_error( png_ptr, "Write Error" );
92 return;
93 }
94}
95
96
97static
98void CALLBACK_CALL_TYPE qpiw_flush_fn( png_structp png_ptr )
99{
100 QPNGImageWriter* qpiw = (QPNGImageWriter*)png_get_io_ptr( png_ptr );
101 QIODevice* out = qpiw->device();
102
103 out->flush();
104}
105
106#if defined(Q_C_CALLBACKS)
107}
108#endif
109
110static
111void setup_qt( QImage& image, png_structp png_ptr, png_infop info_ptr, float screen_gamma=0.0 )
112{
113 if ( screen_gamma != 0.0 && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) ) {
114 double file_gamma;
115 png_get_gAMA(png_ptr, info_ptr, &file_gamma);
116 png_set_gamma( png_ptr, screen_gamma, file_gamma );
117 }
118
119 png_uint_32 width;
120 png_uint_32 height;
121 int bit_depth;
122 int color_type;
123 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
124 0, 0, 0);
125
126 if ( color_type == PNG_COLOR_TYPE_GRAY ) {
127 // Black & White or 8-bit grayscale
128 if ( bit_depth == 1 && info_ptr->channels == 1 ) {
129 png_set_invert_mono( png_ptr );
130 png_read_update_info( png_ptr, info_ptr );
131 if (!image.create( width, height, 1, 2, QImage::BigEndian ))
132 return;
133 image.setColor( 1, qRgb(0,0,0) );
134 image.setColor( 0, qRgb(255,255,255) );
135 } else if (bit_depth == 16 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
136 png_set_expand(png_ptr);
137 png_set_strip_16(png_ptr);
138 png_set_gray_to_rgb(png_ptr);
139
140 if (!image.create(width, height, 32))
141 return;
142 image.setAlphaBuffer(TRUE);
143
144 if (QImage::systemByteOrder() == QImage::BigEndian)
145 png_set_swap_alpha(png_ptr);
146
147 png_read_update_info(png_ptr, info_ptr);
148 } else {
149 if ( bit_depth == 16 )
150 png_set_strip_16(png_ptr);
151 else if ( bit_depth < 8 )
152 png_set_packing(png_ptr);
153 int ncols = bit_depth < 8 ? 1 << bit_depth : 256;
154 png_read_update_info(png_ptr, info_ptr);
155 if (!image.create(width, height, 8, ncols))
156 return;
157 for (int i=0; i<ncols; i++) {
158 int c = i*255/(ncols-1);
159 image.setColor( i, qRgba(c,c,c,0xff) );
160 }
161 if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
162 const int g = info_ptr->trans_values.gray;
163 if (g < ncols) {
164 image.setAlphaBuffer(TRUE);
165 image.setColor(g, image.color(g) & RGB_MASK);
166 }
167 }
168 }
169 } else if ( color_type == PNG_COLOR_TYPE_PALETTE
170 && png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)
171 && info_ptr->num_palette <= 256 )
172 {
173 // 1-bit and 8-bit color
174 if ( bit_depth != 1 )
175 png_set_packing( png_ptr );
176 png_read_update_info( png_ptr, info_ptr );
177 png_get_IHDR(png_ptr, info_ptr,
178 &width, &height, &bit_depth, &color_type, 0, 0, 0);
179 if (!image.create(width, height, bit_depth, info_ptr->num_palette,
180 QImage::BigEndian))
181 return;
182 int i = 0;
183 if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
184 image.setAlphaBuffer( TRUE );
185 while ( i < info_ptr->num_trans ) {
186 image.setColor(i, qRgba(
187 info_ptr->palette[i].red,
188 info_ptr->palette[i].green,
189 info_ptr->palette[i].blue,
190 info_ptr->trans[i]
191 )
192 );
193 i++;
194 }
195 }
196 while ( i < info_ptr->num_palette ) {
197 image.setColor(i, qRgba(
198 info_ptr->palette[i].red,
199 info_ptr->palette[i].green,
200 info_ptr->palette[i].blue,
201 0xff
202 )
203 );
204 i++;
205 }
206 } else {
207 // 32-bit
208 if ( bit_depth == 16 )
209 png_set_strip_16(png_ptr);
210
211 png_set_expand(png_ptr);
212
213 if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
214 png_set_gray_to_rgb(png_ptr);
215
216 if (!image.create(width, height, 32))
217 return;
218
219 // Only add filler if no alpha, or we can get 5 channel data.
220 if (!(color_type & PNG_COLOR_MASK_ALPHA)
221 && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
222 png_set_filler(png_ptr, 0xff,
223 QImage::systemByteOrder() == QImage::BigEndian ?
224 PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
225 // We want 4 bytes, but it isn't an alpha channel
226 } else {
227 image.setAlphaBuffer(TRUE);
228 }
229
230 if ( QImage::systemByteOrder() == QImage::BigEndian ) {
231 png_set_swap_alpha(png_ptr);
232 }
233
234 png_read_update_info(png_ptr, info_ptr);
235 }
236
237 // Qt==ARGB==Big(ARGB)==Little(BGRA)
238 if ( QImage::systemByteOrder() == QImage::LittleEndian ) {
239 png_set_bgr(png_ptr);
240 }
241}
242
243
244#if defined(Q_C_CALLBACKS)
245extern "C" {
246#endif
247static void CALLBACK_CALL_TYPE qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
248{
249 qWarning("libpng warning: %s", message);
250}
251
252#if defined(Q_C_CALLBACKS)
253}
254#endif
255
256
257static
258void read_png_image(QImageIO* iio)
259{
260 png_structp png_ptr;
261 png_infop info_ptr;
262 png_infop end_info;
263 png_bytep* row_pointers;
264
265 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
266 if (!png_ptr) {
267 iio->setStatus(-1);
268 return;
269 }
270
271 png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
272
273 info_ptr = png_create_info_struct(png_ptr);
274 if (!info_ptr) {
275 png_destroy_read_struct(&png_ptr, 0, 0);
276 iio->setStatus(-2);
277 return;
278 }
279
280 end_info = png_create_info_struct(png_ptr);
281 if (!end_info) {
282 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
283 iio->setStatus(-3);
284 return;
285 }
286
287 if (setjmp(png_ptr->jmpbuf)) {
288 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
289 iio->setStatus(-4);
290 return;
291 }
292
293 png_set_read_fn(png_ptr, (void*)iio, iod_read_fn);
294 png_read_info(png_ptr, info_ptr);
295
296 QImage image;
297 setup_qt(image, png_ptr, info_ptr, iio->gamma());
298 if (image.isNull()) {
299 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
300 iio->setStatus(-5);
301 return;
302 }
303
304 png_uint_32 width;
305 png_uint_32 height;
306 int bit_depth;
307 int color_type;
308 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
309 0, 0, 0);
310
311 uchar** jt = image.jumpTable();
312 row_pointers=new png_bytep[height];
313
314 for (uint y=0; y<height; y++) {
315 row_pointers[y]=jt[y];
316 }
317
318 png_read_image(png_ptr, row_pointers);
319
320#if 0 // libpng takes care of this.
321png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)
322 if (image.depth()==32 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
323 QRgb trans = 0xFF000000 | qRgb(
324 (info_ptr->trans_values.red << 8 >> bit_depth)&0xff,
325 (info_ptr->trans_values.green << 8 >> bit_depth)&0xff,
326 (info_ptr->trans_values.blue << 8 >> bit_depth)&0xff);
327 for (uint y=0; y<height; y++) {
328 for (uint x=0; x<info_ptr->width; x++) {
329 if (((uint**)jt)[y][x] == trans) {
330 ((uint**)jt)[y][x] &= 0x00FFFFFF;
331 } else {
332 }
333 }
334 }
335 }
336#endif
337
338 image.setDotsPerMeterX(png_get_x_pixels_per_meter(png_ptr,info_ptr));
339 image.setDotsPerMeterY(png_get_y_pixels_per_meter(png_ptr,info_ptr));
340
341#ifndef QT_NO_IMAGE_TEXT
342 png_textp text_ptr;
343 int num_text=0;
344 png_get_text(png_ptr,info_ptr,&text_ptr,&num_text);
345 while (num_text--) {
346 image.setText(text_ptr->key,0,text_ptr->text);
347 text_ptr++;
348 }
349#endif
350
351 delete [] row_pointers;
352
353 if ( image.hasAlphaBuffer() ) {
354 // Many PNG files lie (eg. from PhotoShop). Fortunately this loop will
355 // usually be quick to find those that tell the truth.
356 QRgb* c;
357 int n;
358 if (image.depth()==32) {
359 c = (QRgb*)image.bits();
360 n = image.bytesPerLine() * image.height() / 4;
361 } else {
362 c = image.colorTable();
363 n = image.numColors();
364 }
365 while ( n-- && qAlpha(*c++)==0xff )
366 ;
367 if ( n<0 ) // LIAR!
368 image.setAlphaBuffer(FALSE);
369 }
370
371 iio->setImage(image);
372
373 png_read_end(png_ptr, end_info);
374 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
375
376 iio->setStatus(0);
377}
378
379QPNGImageWriter::QPNGImageWriter(QIODevice* iod) :
380 dev(iod),
381 frames_written(0),
382 disposal(Unspecified),
383 looping(-1),
384 ms_delay(-1),
385 gamma(0.0)
386{
387}
388
389QPNGImageWriter::~QPNGImageWriter()
390{
391}
392
393void QPNGImageWriter::setDisposalMethod(DisposalMethod dm)
394{
395 disposal = dm;
396}
397
398void QPNGImageWriter::setLooping(int loops)
399{
400 looping = loops;
401}
402
403void QPNGImageWriter::setFrameDelay(int msecs)
404{
405 ms_delay = msecs;
406}
407
408void QPNGImageWriter::setGamma(float g)
409{
410 gamma = g;
411}
412
413
414#ifndef QT_NO_IMAGE_TEXT
415static void set_text(const QImage& image, png_structp png_ptr, png_infop info_ptr, bool short_not_long)
416{
417 QValueList<QImageTextKeyLang> keys = image.textList();
418 if ( keys.count() ) {
419 png_textp text_ptr = new png_text[keys.count()];
420 int i=0;
421 for (QValueList<QImageTextKeyLang>::Iterator it=keys.begin();
422 it != keys.end(); ++it)
423 {
424 QString t = image.text(*it);
425 if ( (t.length() <= 200) == short_not_long ) {
426 if ( t.length() < 40 )
427 text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
428 else
429 text_ptr[i].compression = PNG_TEXT_COMPRESSION_zTXt;
430 text_ptr[i].key = (png_charp)(*it).key.data();
431 text_ptr[i].text = (png_charp)t.latin1();
432 //text_ptr[i].text = qstrdup(t.latin1());
433 i++;
434 }
435 }
436 png_set_text(png_ptr, info_ptr, text_ptr, i);
437 //for (int j=0; j<i; j++)
438 //free(text_ptr[i].text);
439 delete [] text_ptr;
440 }
441}
442#endif
443
444bool QPNGImageWriter::writeImage(const QImage& image, int off_x, int off_y)
445{
446 return writeImage(image, -1, off_x, off_y);
447}
448
449bool QPNGImageWriter::writeImage(const QImage& image, int quality_in, int off_x_in, int off_y_in)
450{
451 QPoint offset = image.offset();
452 int off_x = off_x_in + offset.x();
453 int off_y = off_y_in + offset.y();
454
455 png_structp png_ptr;
456 png_infop info_ptr;
457 png_bytep* row_pointers;
458
459 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
460 if (!png_ptr) {
461 return FALSE;
462 }
463
464 png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
465
466 info_ptr = png_create_info_struct(png_ptr);
467 if (!info_ptr) {
468 png_destroy_write_struct(&png_ptr, 0);
469 return FALSE;
470 }
471
472 if (setjmp(png_ptr->jmpbuf)) {
473 png_destroy_write_struct(&png_ptr, &info_ptr);
474 return FALSE;
475 }
476
477 int quality = quality_in;
478 if (quality >= 0) {
479 if (quality > 9) {
480#if defined(QT_CHECK_RANGE)
481 qWarning( "PNG: Quality %d out of range", quality );
482#endif
483 quality = 9;
484 }
485 png_set_compression_level(png_ptr, quality);
486 }
487
488 if (gamma != 0.0) {
489 png_set_gAMA(png_ptr, info_ptr, 1.0/gamma);
490 }
491
492 png_set_write_fn(png_ptr, (void*)this, qpiw_write_fn, qpiw_flush_fn);
493
494 info_ptr->channels =
495 (image.depth() == 32)
496 ? (image.hasAlphaBuffer() ? 4 : 3)
497 : 1;
498
499 png_set_IHDR(png_ptr, info_ptr, image.width(), image.height(),
500 image.depth() == 1 ? 1 : 8 /* per channel */,
501 image.depth() == 32
502 ? image.hasAlphaBuffer()
503 ? PNG_COLOR_TYPE_RGB_ALPHA
504 : PNG_COLOR_TYPE_RGB
505 : PNG_COLOR_TYPE_PALETTE, 0, 0, 0);
506
507
508 //png_set_sBIT(png_ptr, info_ptr, 8);
509 info_ptr->sig_bit.red = 8;
510 info_ptr->sig_bit.green = 8;
511 info_ptr->sig_bit.blue = 8;
512
513 if (image.depth() == 1 && image.bitOrder() == QImage::LittleEndian)
514 png_set_packswap(png_ptr);
515
516 png_colorp palette = 0;
517 png_bytep copy_trans = 0;
518 if (image.numColors()) {
519 // Paletted
520 int num_palette = image.numColors();
521 palette = new png_color[num_palette];
522 png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
523 int* trans = new int[num_palette];
524 int num_trans = 0;
525 for (int i=0; i<num_palette; i++) {
526 QRgb rgb=image.color(i);
527 info_ptr->palette[i].red = qRed(rgb);
528 info_ptr->palette[i].green = qGreen(rgb);
529 info_ptr->palette[i].blue = qBlue(rgb);
530 if (image.hasAlphaBuffer()) {
531 trans[i] = rgb >> 24;
532 if (trans[i] < 255) {
533 num_trans = i+1;
534 }
535 }
536 }
537 if (num_trans) {
538 copy_trans = new png_byte[num_trans];
539 for (int i=0; i<num_trans; i++)
540 copy_trans[i] = trans[i];
541 png_set_tRNS(png_ptr, info_ptr, copy_trans, num_trans, 0);
542 }
543 delete [] trans;
544 }
545
546 if ( image.hasAlphaBuffer() ) {
547 info_ptr->sig_bit.alpha = 8;
548 }
549
550 // Swap ARGB to RGBA (normal PNG format) before saving on
551 // BigEndian machines
552 if ( QImage::systemByteOrder() == QImage::BigEndian ) {
553 png_set_swap_alpha(png_ptr);
554 }
555
556 // Qt==ARGB==Big(ARGB)==Little(BGRA)
557 if ( QImage::systemByteOrder() == QImage::LittleEndian ) {
558 png_set_bgr(png_ptr);
559 }
560
561 if (off_x || off_y) {
562 png_set_oFFs(png_ptr, info_ptr, off_x, off_y, PNG_OFFSET_PIXEL);
563 }
564
565 if ( frames_written > 0 )
566 png_set_sig_bytes(png_ptr, 8);
567
568 if ( image.dotsPerMeterX() > 0 || image.dotsPerMeterY() > 0 ) {
569 png_set_pHYs(png_ptr, info_ptr,
570 image.dotsPerMeterX(), image.dotsPerMeterY(),
571 PNG_RESOLUTION_METER);
572 }
573
574#ifndef QT_NO_IMAGE_TEXT
575 // Write short texts early.
576 set_text(image,png_ptr,info_ptr,TRUE);
577#endif
578
579 png_write_info(png_ptr, info_ptr);
580
581#ifndef QT_NO_IMAGE_TEXT
582 // Write long texts later.
583 set_text(image,png_ptr,info_ptr,FALSE);
584#endif
585
586 if ( image.depth() != 1 )
587 png_set_packing(png_ptr);
588
589 if ( image.depth() == 32 && !image.hasAlphaBuffer() )
590 png_set_filler(png_ptr, 0,
591 QImage::systemByteOrder() == QImage::BigEndian ?
592 PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
593
594 if ( looping >= 0 && frames_written == 0 ) {
595 uchar data[13] = "NETSCAPE2.0";
596 // 0123456789aBC
597 data[0xB] = looping%0x100;
598 data[0xC] = looping/0x100;
599 png_write_chunk(png_ptr, (png_byte*)"gIFx", data, 13);
600 }
601 if ( ms_delay >= 0 || disposal!=Unspecified ) {
602 uchar data[4];
603 data[0] = disposal;
604 data[1] = 0;
605 data[2] = (ms_delay/10)/0x100; // hundredths
606 data[3] = (ms_delay/10)%0x100;
607 png_write_chunk(png_ptr, (png_byte*)"gIFg", data, 4);
608 }
609
610 png_uint_32 width;
611 png_uint_32 height;
612 int bit_depth;
613 int color_type;
614 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
615 0, 0, 0);
616
617 uchar** jt = image.jumpTable();
618 row_pointers=new png_bytep[height];
619 uint y;
620 for (y=0; y<height; y++) {
621 row_pointers[y]=jt[y];
622 }
623 png_write_image(png_ptr, row_pointers);
624 delete [] row_pointers;
625
626 png_write_end(png_ptr, info_ptr);
627 frames_written++;
628
629 if ( palette )
630 delete [] palette;
631 if ( copy_trans )
632 delete [] copy_trans;
633
634 png_destroy_write_struct(&png_ptr, &info_ptr);
635
636 return TRUE;
637}
638
639static
640void write_png_image(QImageIO* iio)
641{
642 QPNGImageWriter writer(iio->ioDevice());
643 int quality = iio->quality();
644 if ( quality >= 0 ) {
645 quality = QMIN( quality, 100 );
646 quality = (100-quality) * 9 / 91; // map [0,100] -> [9,0]
647 }
648 writer.setGamma(iio->gamma());
649 bool ok = writer.writeImage( iio->image(), quality );
650 iio->setStatus( ok ? 0 : -1 );
651}
652
653/*!
654 \class QPNGImagePacker qpngio.h
655 \brief The QPNGImagePacker class creates well-compressed PNG animations.
656
657 \ingroup images
658 \ingroup graphics
659
660 By using transparency, QPNGImagePacker allows you to build a PNG
661 image from a sequence of QImages.
662
663 Images are added using packImage().
664*/
665
666
667/*!
668 Creates an image packer that writes PNG data to IO device \a iod
669 using a \a storage_depth bit encoding (use 8 or 32, depending on
670 the desired quality and compression requirements).
671
672 If the image needs to be modified to fit in a lower-resolution
673 result (e.g. converting from 32-bit to 8-bit), use the \a
674 conversionflags to specify how you'd prefer this to happen.
675
676 \sa Qt::ImageConversionFlags
677*/
678QPNGImagePacker::QPNGImagePacker(QIODevice* iod, int storage_depth,
679 int conversionflags) :
680 QPNGImageWriter(iod),
681 depth(storage_depth),
682 convflags(conversionflags),
683 alignx(1)
684{
685}
686
687/*!
688 Aligns pixel differences to \a x pixels. For example, using 8 can
689 improve playback on certain hardware. Normally the default of
690 1-pixel alignment (i.e. no alignment) gives better compression and
691 performance.
692*/
693void QPNGImagePacker::setPixelAlignment(int x)
694{
695 alignx = x;
696}
697
698/*!
699 Adds the image \a img to the PNG animation, analyzing the
700 differences between this and the previous image to improve
701 compression.
702*/
703bool QPNGImagePacker::packImage(const QImage& img)
704{
705 QImage image = img.convertDepth(32);
706 if ( previous.isNull() ) {
707 // First image
708 writeImage(image.convertDepth(depth,convflags));
709 } else {
710 bool done;
711 int minx, maxx, miny, maxy;
712 int w = image.width();
713 int h = image.height();
714
715 QRgb** jt = (QRgb**)image.jumpTable();
716 QRgb** pjt = (QRgb**)previous.jumpTable();
717
718 // Find left edge of change
719 done = FALSE;
720 for (minx = 0; minx < w && !done; minx++) {
721 for (int ty = 0; ty < h; ty++) {
722 if ( jt[ty][minx] != pjt[ty][minx] ) {
723 done = TRUE;
724 break;
725 }
726 }
727 }
728 minx--;
729
730 // Find right edge of change
731 done = FALSE;
732 for (maxx = w-1; maxx >= 0 && !done; maxx--) {
733 for (int ty = 0; ty < h; ty++) {
734 if ( jt[ty][maxx] != pjt[ty][maxx] ) {
735 done = TRUE;
736 break;
737 }
738 }
739 }
740 maxx++;
741
742 // Find top edge of change
743 done = FALSE;
744 for (miny = 0; miny < h && !done; miny++) {
745 for (int tx = 0; tx < w; tx++) {
746 if ( jt[miny][tx] != pjt[miny][tx] ) {
747 done = TRUE;
748 break;
749 }
750 }
751 }
752 miny--;
753
754 // Find right edge of change
755 done = FALSE;
756 for (maxy = h-1; maxy >= 0 && !done; maxy--) {
757 for (int tx = 0; tx < w; tx++) {
758 if ( jt[maxy][tx] != pjt[maxy][tx] ) {
759 done = TRUE;
760 break;
761 }
762 }
763 }
764 maxy++;
765
766 if ( minx > maxx ) minx=maxx=0;
767 if ( miny > maxy ) miny=maxy=0;
768
769 if ( alignx > 1 ) {
770 minx -= minx % alignx;
771 maxx = maxx - maxx % alignx + alignx - 1;
772 }
773
774 int dw = maxx-minx+1;
775 int dh = maxy-miny+1;
776
777 QImage diff(dw, dh, 32);
778
779 diff.setAlphaBuffer(TRUE);
780 int x, y;
781 if ( alignx < 1 )
782 alignx = 1;
783 for (y = 0; y < dh; y++) {
784 QRgb* li = (QRgb*)image.scanLine(y+miny)+minx;
785 QRgb* lp = (QRgb*)previous.scanLine(y+miny)+minx;
786 QRgb* ld = (QRgb*)diff.scanLine(y);
787 if ( alignx ) {
788 for (x = 0; x < dw; x+=alignx) {
789 int i;
790 for (i=0; i<alignx; i++) {
791 if ( li[x+i] != lp[x+i] )
792 break;
793 }
794 if ( i == alignx ) {
795 // All the same
796 for (i=0; i<alignx; i++) {
797 ld[x+i] = qRgba(0,0,0,0);
798 }
799 } else {
800 // Some different
801 for (i=0; i<alignx; i++) {
802 ld[x+i] = 0xff000000 | li[x+i];
803 }
804 }
805 }
806 } else {
807 for (x = 0; x < dw; x++) {
808 if ( li[x] != lp[x] )
809 ld[x] = 0xff000000 | li[x];
810 else
811 ld[x] = qRgba(0,0,0,0);
812 }
813 }
814 }
815
816 diff = diff.convertDepth(depth,convflags);
817 if ( !writeImage(diff, minx, miny) )
818 return FALSE;
819 }
820 previous = image;
821 return TRUE;
822}
823
824
825#ifndef QT_NO_ASYNC_IMAGE_IO
826
827class QPNGFormat : public QImageFormat {
828public:
829 QPNGFormat();
830 virtual ~QPNGFormat();
831
832 int decode(QImage& img, QImageConsumer* consumer,
833 const uchar* buffer, int length);
834
835 void info(png_structp png_ptr, png_infop info);
836 void row(png_structp png_ptr, png_bytep new_row,
837 png_uint_32 row_num, int pass);
838 void end(png_structp png_ptr, png_infop info);
839#ifdef PNG_USER_CHUNKS_SUPPORTED
840 int user_chunk(png_structp png_ptr,
841 png_bytep data, png_uint_32 length);
842#endif
843
844private:
845 // Animation-level information
846 enum { MovieStart, FrameStart, Inside, End } state;
847 int first_frame;
848 int base_offx;
849 int base_offy;
850
851 // Image-level information
852 png_structp png_ptr;
853 png_infop info_ptr;
854
855 // Temporary locals during single data-chunk processing
856 QImageConsumer* consumer;
857 QImage* image;
858 int unused_data;
859};
860
861class QPNGFormatType : public QImageFormatType
862{
863 QImageFormat* decoderFor(const uchar* buffer, int length);
864 const char* formatName() const;
865};
866
867
868/*
869 \class QPNGFormat qpngio.h
870 \brief The QPNGFormat class provides an incremental image decoder for PNG
871 image format.
872
873 \ingroup images
874 \ingroup graphics
875
876 This subclass of QImageFormat decodes PNG format images,
877 including animated PNGs.
878
879 Animated PNG images are standard PNG images. The PNG standard
880 defines two extension chunks that are useful for animations:
881
882 \list
883 \i gIFg - GIF-like Graphic Control Extension.
884 This includes frame disposal, user input flag (we ignore this),
885 and inter-frame delay.
886 \i gIFx - GIF-like Application Extension.
887 This is multi-purpose, but we just use the Netscape extension
888 which specifies looping.
889 \endlist
890
891 The subimages usually contain a offset chunk (oFFs) but need not.
892
893 The first image defines the "screen" size. Any subsequent image that
894 doesn't fit is clipped.
895*/
896/* ###TODO: decide on this point. gIFg gives disposal types, so it can be done.
897 All images paste (\e not composite, just place all-channel copying)
898 over the previous image to produce a subsequent frame.
899*/
900
901/*
902 \class QPNGFormatType qasyncimageio.h
903 \brief The QPNGFormatType class provides an incremental image decoder
904 for PNG image format.
905
906 \ingroup images
907 \ingroup graphics
908 \ingroup io
909
910 This subclass of QImageFormatType recognizes PNG format images, creating
911 a QPNGFormat when required. An instance of this class is created
912 automatically before any other factories, so you should have no need for
913 such objects.
914*/
915
916QImageFormat* QPNGFormatType::decoderFor(
917 const uchar* buffer, int length)
918{
919 if (length < 8) return 0;
920 if (buffer[0]==137
921 && buffer[1]=='P'
922 && buffer[2]=='N'
923 && buffer[3]=='G'
924 && buffer[4]==13
925 && buffer[5]==10
926 && buffer[6]==26
927 && buffer[7]==10)
928 return new QPNGFormat;
929 return 0;
930}
931
932const char* QPNGFormatType::formatName() const
933{
934 return "PNG";
935}
936
937#if defined(Q_C_CALLBACKS)
938extern "C" {
939#endif
940
941static void
942CALLBACK_CALL_TYPE info_callback(png_structp png_ptr, png_infop info)
943{
944 QPNGFormat* that = (QPNGFormat*)png_get_progressive_ptr(png_ptr);
945 that->info(png_ptr,info);
946}
947
948static void
949CALLBACK_CALL_TYPE row_callback(png_structp png_ptr, png_bytep new_row,
950 png_uint_32 row_num, int pass)
951{
952 QPNGFormat* that = (QPNGFormat*)png_get_progressive_ptr(png_ptr);
953 that->row(png_ptr,new_row,row_num,pass);
954}
955
956static void
957CALLBACK_CALL_TYPE end_callback(png_structp png_ptr, png_infop info)
958{
959 QPNGFormat* that = (QPNGFormat*)png_get_progressive_ptr(png_ptr);
960 that->end(png_ptr,info);
961}
962
963#if 0
964#ifdef PNG_USER_CHUNKS_SUPPORTED
965static int
966CALLBACK_CALL_TYPE user_chunk_callback(png_structp png_ptr,
967 png_unknown_chunkp chunk)
968{
969 QPNGFormat* that = (QPNGFormat*)png_get_progressive_ptr(png_ptr);
970 return that->user_chunk(png_ptr,chunk->data,chunk->size);
971}
972#endif
973#endif
974
975#if defined(Q_C_CALLBACKS)
976}
977#endif
978
979
980/*!
981 Constructs a QPNGFormat object.
982*/
983QPNGFormat::QPNGFormat()
984{
985 state = MovieStart;
986 first_frame = 1;
987 base_offx = 0;
988 base_offy = 0;
989 png_ptr = 0;
990 info_ptr = 0;
991}
992
993
994/*!
995 Destroys a QPNGFormat object.
996*/
997QPNGFormat::~QPNGFormat()
998{
999 if ( png_ptr )
1000 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
1001}
1002
1003
1004/*!
1005 This function decodes some data into image changes.
1006
1007 Returns the number of bytes consumed.
1008*/
1009int QPNGFormat::decode(QImage& img, QImageConsumer* cons,
1010 const uchar* buffer, int length)
1011{
1012 consumer = cons;
1013 image = &img;
1014
1015 if ( state != Inside ) {
1016 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
1017 if (!png_ptr) {
1018 info_ptr = 0;
1019 image = 0;
1020 return -1;
1021 }
1022
1023 png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
1024 png_set_compression_level(png_ptr, 9);
1025
1026 info_ptr = png_create_info_struct(png_ptr);
1027 if (!info_ptr) {
1028 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
1029 image = 0;
1030 return -1;
1031 }
1032
1033 if (setjmp((png_ptr)->jmpbuf)) {
1034 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
1035 image = 0;
1036 return -1;
1037 }
1038
1039 png_set_progressive_read_fn(png_ptr, (void *)this,
1040 info_callback, row_callback, end_callback);
1041
1042#ifdef PNG_USER_CHUNKS_SUPPORTED
1043 // Can't do this yet. libpng has a crash bug with unknown (user) chunks.
1044 // Warwick has sent them a patch.
1045 // png_set_read_user_chunk_fn(png_ptr, 0, user_chunk_callback);
1046 // png_set_keep_unknown_chunks(png_ptr, 2/*HANDLE_CHUNK_IF_SAFE*/, 0, 0);
1047#endif
1048
1049 if ( state != MovieStart && *buffer != 0211 ) {
1050 // Good, no signature - the preferred way to concat PNG images.
1051 // Skip them.
1052 png_set_sig_bytes(png_ptr, 8);
1053 }
1054
1055 state = Inside;
1056 }
1057
1058 if ( !png_ptr ) return 0;
1059
1060 if (setjmp(png_ptr->jmpbuf)) {
1061 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
1062 image = 0;
1063 state = MovieStart;
1064 return -1;
1065 }
1066 unused_data = 0;
1067 png_process_data(png_ptr, info_ptr, (png_bytep)buffer, length);
1068 int l = length - unused_data;
1069
1070 // TODO: send incremental stuff to consumer (optional)
1071
1072 if ( state != Inside ) {
1073 if ( png_ptr )
1074 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
1075 }
1076
1077 image = 0;
1078 return l;
1079}
1080
1081void QPNGFormat::info(png_structp png, png_infop)
1082{
1083 png_set_interlace_handling(png);
1084 setup_qt(*image, png, info_ptr);
1085}
1086
1087void QPNGFormat::row(png_structp png, png_bytep new_row,
1088 png_uint_32 row_num, int)
1089{
1090 uchar* old_row = image->scanLine(row_num);
1091 png_progressive_combine_row(png, old_row, new_row);
1092}
1093
1094
1095void QPNGFormat::end(png_structp png, png_infop info)
1096{
1097 int offx = png_get_x_offset_pixels(png,info) - base_offx;
1098 int offy = png_get_y_offset_pixels(png,info) - base_offy;
1099 if ( first_frame ) {
1100 base_offx = offx;
1101 base_offy = offy;
1102 first_frame = 0;
1103 }
1104 image->setOffset(QPoint(offx,offy));
1105 image->setDotsPerMeterX(png_get_x_pixels_per_meter(png,info));
1106 image->setDotsPerMeterY(png_get_y_pixels_per_meter(png,info));
1107#ifndef QT_NO_IMAGE_TEXT
1108 png_textp text_ptr;
1109 int num_text=0;
1110 png_get_text(png,info,&text_ptr,&num_text);
1111 while (num_text--) {
1112 image->setText(text_ptr->key,0,text_ptr->text);
1113 text_ptr++;
1114 }
1115#endif
1116 QRect r(0,0,image->width(),image->height());
1117 consumer->frameDone(QPoint(offx,offy),r);
1118 consumer->end();
1119 state = FrameStart;
1120 unused_data = (int)png->buffer_size; // Since libpng doesn't tell us
1121}
1122
1123#ifdef PNG_USER_CHUNKS_SUPPORTED
1124
1125/*
1126#ifndef QT_NO_IMAGE_TEXT
1127static bool skip(png_uint_32& max, png_bytep& data)
1128{
1129 while (*data) {
1130 if ( !max ) return FALSE;
1131 max--;
1132 data++;
1133 }
1134 if ( !max ) return FALSE;
1135 max--;
1136 data++; // skip to after NUL
1137 return TRUE;
1138}
1139#endif
1140*/
1141
1142int QPNGFormat::user_chunk(png_structp png,
1143 png_bytep data, png_uint_32 length)
1144{
1145#if 0 // NOT SUPPORTED: experimental PNG animation.
1146 // qDebug("Got %ld-byte %s chunk", length, png->chunk_name);
1147 if ( 0==qstrcmp((char*)png->chunk_name, "gIFg")
1148 && length == 4 ) {
1149
1150 //QPNGImageWriter::DisposalMethod disposal =
1151 // (QPNGImageWriter::DisposalMethod)data[0];
1152 // ### TODO: use the disposal method
1153 int ms_delay = ((data[2] << 8) | data[3])*10;
1154 consumer->setFramePeriod(ms_delay);
1155 return 1;
1156 } else if ( 0==qstrcmp((char*)png->chunk_name, "gIFx")
1157 && length == 13 ) {
1158 if ( qstrncmp((char*)data,"NETSCAPE2.0",11)==0 ) {
1159 int looping = (data[0xC]<<8)|data[0xB];
1160 consumer->setLooping(looping);
1161 return 1;
1162 }
1163 }
1164#else
1165 Q_UNUSED( png )
1166 Q_UNUSED( data )
1167 Q_UNUSED( length )
1168#endif
1169
1170#ifndef QT_NO_IMAGE_TEXT
1171 /*
1172
1173 libpng now supports this chunk.
1174
1175
1176 if ( 0==qstrcmp((char*)png->chunk_name, "iTXt") && length>=6 ) {
1177 const char* keyword = (const char*)data;
1178 if ( !skip(length,data) ) return 0;
1179 if ( length >= 4 ) {
1180 char compression_flag = *data++;
1181 char compression_method = *data++;
1182 if ( compression_flag == compression_method ) {
1183 // fool the compiler into thinking they're used
1184 }
1185 const char* lang = (const char*)data;
1186 if ( !skip(length,data) ) return 0;
1187 // const char* keyword_utf8 = (const char*)data;
1188 if ( !skip(length,data) ) return 0;
1189 const char* text_utf8 = (const char*)data;
1190 if ( !skip(length,data) ) return 0;
1191 QString text = QString::fromUtf8(text_utf8);
1192 image->setText(keyword,lang[0] ? lang : 0,text);
1193 return 1;
1194 }
1195 }
1196 */
1197#endif
1198
1199 return 0;
1200}
1201#endif
1202
1203
1204static QPNGFormatType* globalPngFormatTypeObject = 0;
1205
1206#endif // QT_NO_ASYNC_IMAGE_IO
1207
1208static bool done = FALSE;
1209void qCleanupPngIO()
1210{
1211#ifndef QT_NO_ASYNC_IMAGE_IO
1212 if ( globalPngFormatTypeObject ) {
1213 delete globalPngFormatTypeObject;
1214 globalPngFormatTypeObject = 0;
1215 }
1216#endif
1217 done = FALSE;
1218}
1219
1220void qInitPngIO()
1221{
1222 if ( !done ) {
1223 done = TRUE;
1224 QImageIO::defineIOHandler( "PNG", "^.PNG\r", 0, read_png_image,
1225 write_png_image);
1226#ifndef QT_NO_ASYNC_IMAGE_IO
1227 globalPngFormatTypeObject = new QPNGFormatType;
1228#endif
1229 qAddPostRoutine( qCleanupPngIO );
1230 }
1231}
1232
1233void qt_zlib_compression_hack()
1234{
1235 compress(0,0,0,0);
1236 uncompress(0,0,0,0);
1237}
1238
1239#endif // QT_NO_IMAGEIO_PNG
Note: See TracBrowser for help on using the repository browser.