1
// this code is copied from Qt, with code added to actually call consumer
2
// methods that inform about the progress of loading
4
/****************************************************************************
7
** Implementation of PNG QImage IOHandler
11
** Copyright (C) 1992-2003 Trolltech AS. All rights reserved.
13
** This file is part of the kernel module of the Qt GUI Toolkit.
15
** This file may be distributed under the terms of the Q Public License
16
** as defined by Trolltech AS of Norway and appearing in the file
17
** LICENSE.QPL included in the packaging of this file.
19
** This file may be distributed and/or modified under the terms of the
20
** GNU General Public License version 2 as published by the Free Software
21
** Foundation and appearing in the file LICENSE.GPL included in the
22
** packaging of this file.
24
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
25
** licenses may use this file in accordance with the Qt Commercial License
26
** Agreement provided with the Software.
28
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
31
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
32
** information about Qt Commercial License Agreements.
33
** See http://www.trolltech.com/qpl/ for QPL licensing information.
34
** See http://www.trolltech.com/gpl/ for GPL licensing information.
36
** Contact info@trolltech.com if any conditions of this licensing are
39
**********************************************************************/
41
#include "gvpngformattype.h"
45
class GVPNGFormat : public QImageFormat {
48
virtual ~GVPNGFormat();
50
int decode(QImage& img, QImageConsumer* consumer,
51
const uchar* buffer, int length);
53
void info(png_structp png_ptr, png_infop info);
54
void row(png_structp png_ptr, png_bytep new_row,
55
png_uint_32 row_num, int pass);
56
void end(png_structp png_ptr, png_infop info);
57
#ifdef PNG_USER_CHUNKS_SUPPORTED
58
int user_chunk(png_structp png_ptr,
59
png_bytep data, png_uint_32 length);
63
// Animation-level information
64
enum { MovieStart, FrameStart, Inside, End } state;
69
// Image-level information
73
// Temporary locals during single data-chunk processing
74
QImageConsumer* consumer;
81
\class QPNGFormat qpngio.h
82
\brief The QPNGFormat class provides an incremental image decoder for PNG
88
This subclass of QImageFormat decodes PNG format images,
89
including animated PNGs.
91
Animated PNG images are standard PNG images. The PNG standard
92
defines two extension chunks that are useful for animations:
95
\i gIFg - GIF-like Graphic Control Extension.
96
This includes frame disposal, user input flag (we ignore this),
97
and inter-frame delay.
98
\i gIFx - GIF-like Application Extension.
99
This is multi-purpose, but we just use the Netscape extension
100
which specifies looping.
103
The subimages usually contain a offset chunk (oFFs) but need not.
105
The first image defines the "screen" size. Any subsequent image that
106
doesn't fit is clipped.
108
/* ###TODO: decide on this point. gIFg gives disposal types, so it can be done.
109
All images paste (\e not composite, just place all-channel copying)
110
over the previous image to produce a subsequent frame.
114
\class QPNGFormatType qasyncimageio.h
115
\brief The QPNGFormatType class provides an incremental image decoder
116
for PNG image format.
122
This subclass of QImageFormatType recognizes PNG format images, creating
123
a QPNGFormat when required. An instance of this class is created
124
automatically before any other factories, so you should have no need for
128
QImageFormat* GVPNGFormatType::decoderFor(
129
const uchar* buffer, int length)
131
if (length < 8) return 0;
140
return new GVPNGFormat;
144
const char* GVPNGFormatType::formatName() const
152
info_callback(png_structp png_ptr, png_infop info)
154
GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
155
that->info(png_ptr,info);
159
row_callback(png_structp png_ptr, png_bytep new_row,
160
png_uint_32 row_num, int pass)
162
GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
163
that->row(png_ptr,new_row,row_num,pass);
167
end_callback(png_structp png_ptr, png_infop info)
169
GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
170
that->end(png_ptr,info);
174
#ifdef PNG_USER_CHUNKS_SUPPORTED
176
CALLBACK_CALL_TYPE user_chunk_callback(png_structp png_ptr,
177
png_unknown_chunkp chunk)
179
GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
180
return that->user_chunk(png_ptr,chunk->data,chunk->size);
185
static void qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
187
qWarning("libpng warning: %s", message);
193
void setup_qt( QImage& image, png_structp png_ptr, png_infop info_ptr, float screen_gamma=0.0 )
195
if ( screen_gamma != 0.0 && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) ) {
197
png_get_gAMA(png_ptr, info_ptr, &file_gamma);
198
png_set_gamma( png_ptr, screen_gamma, file_gamma );
205
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
208
if ( color_type == PNG_COLOR_TYPE_GRAY ) {
209
// Black & White or 8-bit grayscale
210
if ( bit_depth == 1 && info_ptr->channels == 1 ) {
211
png_set_invert_mono( png_ptr );
212
png_read_update_info( png_ptr, info_ptr );
213
if (!image.create( width, height, 1, 2, QImage::BigEndian ))
215
image.setColor( 1, qRgb(0,0,0) );
216
image.setColor( 0, qRgb(255,255,255) );
217
} else if (bit_depth == 16 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
218
png_set_expand(png_ptr);
219
png_set_strip_16(png_ptr);
220
png_set_gray_to_rgb(png_ptr);
222
if (!image.create(width, height, 32))
224
image.setAlphaBuffer(TRUE);
226
if (QImage::systemByteOrder() == QImage::BigEndian)
227
png_set_swap_alpha(png_ptr);
229
png_read_update_info(png_ptr, info_ptr);
231
if ( bit_depth == 16 )
232
png_set_strip_16(png_ptr);
233
else if ( bit_depth < 8 )
234
png_set_packing(png_ptr);
235
int ncols = bit_depth < 8 ? 1 << bit_depth : 256;
236
png_read_update_info(png_ptr, info_ptr);
237
if (!image.create(width, height, 8, ncols))
239
for (int i=0; i<ncols; i++) {
240
int c = i*255/(ncols-1);
241
image.setColor( i, qRgba(c,c,c,0xff) );
243
if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
244
const int g = info_ptr->trans_values.gray;
246
image.setAlphaBuffer(TRUE);
247
image.setColor(g, image.color(g) & RGB_MASK);
251
} else if ( color_type == PNG_COLOR_TYPE_PALETTE
252
&& png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)
253
&& info_ptr->num_palette <= 256 )
255
// 1-bit and 8-bit color
256
if ( bit_depth != 1 )
257
png_set_packing( png_ptr );
258
png_read_update_info( png_ptr, info_ptr );
259
png_get_IHDR(png_ptr, info_ptr,
260
&width, &height, &bit_depth, &color_type, 0, 0, 0);
261
if (!image.create(width, height, bit_depth, info_ptr->num_palette,
265
if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
266
image.setAlphaBuffer( TRUE );
267
while ( i < info_ptr->num_trans ) {
268
image.setColor(i, qRgba(
269
info_ptr->palette[i].red,
270
info_ptr->palette[i].green,
271
info_ptr->palette[i].blue,
278
while ( i < info_ptr->num_palette ) {
279
image.setColor(i, qRgba(
280
info_ptr->palette[i].red,
281
info_ptr->palette[i].green,
282
info_ptr->palette[i].blue,
290
if ( bit_depth == 16 )
291
png_set_strip_16(png_ptr);
293
png_set_expand(png_ptr);
295
if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
296
png_set_gray_to_rgb(png_ptr);
298
if (!image.create(width, height, 32))
301
// Only add filler if no alpha, or we can get 5 channel data.
302
if (!(color_type & PNG_COLOR_MASK_ALPHA)
303
&& !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
304
png_set_filler(png_ptr, 0xff,
305
QImage::systemByteOrder() == QImage::BigEndian ?
306
PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
307
// We want 4 bytes, but it isn't an alpha channel
309
image.setAlphaBuffer(TRUE);
312
if ( QImage::systemByteOrder() == QImage::BigEndian ) {
313
png_set_swap_alpha(png_ptr);
316
png_read_update_info(png_ptr, info_ptr);
319
// Qt==ARGB==Big(ARGB)==Little(BGRA)
320
if ( QImage::systemByteOrder() == QImage::LittleEndian ) {
321
png_set_bgr(png_ptr);
328
Constructs a QPNGFormat object.
330
GVPNGFormat::GVPNGFormat()
342
Destroys a QPNGFormat object.
344
GVPNGFormat::~GVPNGFormat()
347
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
352
This function decodes some data into image changes.
354
Returns the number of bytes consumed.
356
int GVPNGFormat::decode(QImage& img, QImageConsumer* cons,
357
const uchar* buffer, int length)
362
if ( state != Inside ) {
363
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
370
png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
371
png_set_compression_level(png_ptr, 9);
373
info_ptr = png_create_info_struct(png_ptr);
375
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
380
if (setjmp((png_ptr)->jmpbuf)) {
381
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
386
png_set_progressive_read_fn(png_ptr, (void *)this,
387
info_callback, row_callback, end_callback);
389
#ifdef PNG_USER_CHUNKS_SUPPORTED
390
// Can't do this yet. libpng has a crash bug with unknown (user) chunks.
391
// Warwick has sent them a patch.
392
// png_set_read_user_chunk_fn(png_ptr, 0, user_chunk_callback);
393
// png_set_keep_unknown_chunks(png_ptr, 2/*HANDLE_CHUNK_IF_SAFE*/, 0, 0);
396
if ( state != MovieStart && *buffer != 0211 ) {
397
// Good, no signature - the preferred way to concat PNG images.
399
png_set_sig_bytes(png_ptr, 8);
403
changed_rect = QRect();
406
if ( !png_ptr ) return 0;
408
if (setjmp(png_ptr->jmpbuf)) {
409
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
415
png_process_data(png_ptr, info_ptr, (png_bytep)buffer, length);
416
int l = length - unused_data;
418
if( !changed_rect.isNull()) {
419
consumer->changed( changed_rect );
420
changed_rect = QRect();
423
if ( state != Inside ) {
425
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
432
void GVPNGFormat::info(png_structp png, png_infop)
434
png_set_interlace_handling(png);
435
setup_qt(*image, png, info_ptr);
436
consumer->setSize( image->width(), image->height());
439
void GVPNGFormat::row(png_structp png, png_bytep new_row,
440
png_uint_32 row_num, int)
442
uchar* old_row = image->scanLine(row_num);
443
png_progressive_combine_row(png, old_row, new_row);
444
changed_rect |= QRect( 0, row_num, image->width(), 1 );
448
void GVPNGFormat::end(png_structp png, png_infop info)
450
int offx = png_get_x_offset_pixels(png,info) - base_offx;
451
int offy = png_get_y_offset_pixels(png,info) - base_offy;
457
image->setOffset(QPoint(offx,offy));
458
image->setDotsPerMeterX(png_get_x_pixels_per_meter(png,info));
459
image->setDotsPerMeterY(png_get_y_pixels_per_meter(png,info));
462
png_get_text(png,info,&text_ptr,&num_text);
464
image->setText(text_ptr->key,0,text_ptr->text);
467
if( !changed_rect.isNull())
468
consumer->changed( changed_rect );
469
QRect r(0,0,image->width(),image->height());
470
consumer->frameDone(QPoint(offx,offy),r);
473
unused_data = (int)png->buffer_size; // Since libpng doesn't tell us
476
#ifdef PNG_USER_CHUNKS_SUPPORTED
479
#ifndef QT_NO_IMAGE_TEXT
480
static bool skip(png_uint_32& max, png_bytep& data)
483
if ( !max ) return FALSE;
487
if ( !max ) return FALSE;
489
data++; // skip to after NUL
495
int GVPNGFormat::user_chunk(png_structp png,
496
png_bytep data, png_uint_32 length)
498
#if 0 // NOT SUPPORTED: experimental PNG animation.
499
// qDebug("Got %ld-byte %s chunk", length, png->chunk_name);
500
if ( 0==qstrcmp((char*)png->chunk_name, "gIFg")
503
//QPNGImageWriter::DisposalMethod disposal =
504
// (QPNGImageWriter::DisposalMethod)data[0];
505
// ### TODO: use the disposal method
506
int ms_delay = ((data[2] << 8) | data[3])*10;
507
consumer->setFramePeriod(ms_delay);
509
} else if ( 0==qstrcmp((char*)png->chunk_name, "gIFx")
511
if ( qstrncmp((char*)data,"NETSCAPE2.0",11)==0 ) {
512
int looping = (data[0xC]<<8)|data[0xB];
513
consumer->setLooping(looping);
525
libpng now supports this chunk.
528
if ( 0==qstrcmp((char*)png->chunk_name, "iTXt") && length>=6 ) {
529
const char* keyword = (const char*)data;
530
if ( !skip(length,data) ) return 0;
532
char compression_flag = *data++;
533
char compression_method = *data++;
534
if ( compression_flag == compression_method ) {
535
// fool the compiler into thinking they're used
537
const char* lang = (const char*)data;
538
if ( !skip(length,data) ) return 0;
539
// const char* keyword_utf8 = (const char*)data;
540
if ( !skip(length,data) ) return 0;
541
const char* text_utf8 = (const char*)data;
542
if ( !skip(length,data) ) return 0;
543
QString text = QString::fromUtf8(text_utf8);
544
image->setText(keyword,lang[0] ? lang : 0,text);