~ubuntu-branches/ubuntu/intrepid/gwenview/intrepid

« back to all changes in this revision

Viewing changes to src/gvpngformattype.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Christopher Martin
  • Date: 2004-06-13 18:55:04 UTC
  • mfrom: (1.1.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040613185504-net8ekxoswwvyxs9
Tags: 1.1.3-1
New upstream release. Translations now included.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// this code is copied from Qt, with code added to actually call consumer
 
2
// methods that inform about the progress of loading
 
3
 
 
4
/****************************************************************************
 
5
** 
 
6
**
 
7
** Implementation of PNG QImage IOHandler
 
8
**
 
9
** Created : 970521
 
10
**
 
11
** Copyright (C) 1992-2003 Trolltech AS.  All rights reserved.
 
12
**
 
13
** This file is part of the kernel module of the Qt GUI Toolkit.
 
14
**
 
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.
 
18
**
 
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.
 
23
**
 
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.
 
27
**
 
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.
 
30
**
 
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.
 
35
**
 
36
** Contact info@trolltech.com if any conditions of this licensing are
 
37
** not clear to you.
 
38
**
 
39
**********************************************************************/
 
40
 
 
41
#include "gvpngformattype.h"
 
42
 
 
43
#include <png.h>
 
44
 
 
45
class GVPNGFormat : public QImageFormat {
 
46
public:
 
47
    GVPNGFormat();
 
48
    virtual ~GVPNGFormat();
 
49
 
 
50
    int decode(QImage& img, QImageConsumer* consumer,
 
51
            const uchar* buffer, int length);
 
52
 
 
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);
 
60
#endif
 
61
 
 
62
private:
 
63
    // Animation-level information
 
64
    enum { MovieStart, FrameStart, Inside, End } state;
 
65
    int first_frame;
 
66
    int base_offx;
 
67
    int base_offy;
 
68
 
 
69
    // Image-level information
 
70
    png_structp png_ptr;
 
71
    png_infop info_ptr;
 
72
 
 
73
    // Temporary locals during single data-chunk processing
 
74
    QImageConsumer* consumer;
 
75
    QImage* image;
 
76
    int unused_data;
 
77
    QRect changed_rect;
 
78
};
 
79
 
 
80
/*
 
81
  \class QPNGFormat qpngio.h
 
82
  \brief The QPNGFormat class provides an incremental image decoder for PNG
 
83
  image format.
 
84
 
 
85
  \ingroup images
 
86
  \ingroup graphics
 
87
 
 
88
  This subclass of QImageFormat decodes PNG format images,
 
89
  including animated PNGs.
 
90
 
 
91
  Animated PNG images are standard PNG images. The PNG standard
 
92
  defines two extension chunks that are useful for animations:
 
93
 
 
94
  \list
 
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.
 
101
  \endlist
 
102
 
 
103
  The subimages usually contain a offset chunk (oFFs) but need not.
 
104
 
 
105
  The first image defines the "screen" size. Any subsequent image that
 
106
  doesn't fit is clipped.
 
107
*/
 
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.
 
111
*/
 
112
 
 
113
/*
 
114
  \class QPNGFormatType qasyncimageio.h
 
115
  \brief The QPNGFormatType class provides an incremental image decoder
 
116
  for PNG image format.
 
117
 
 
118
  \ingroup images
 
119
  \ingroup graphics
 
120
  \ingroup io
 
121
 
 
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
 
125
  such objects.
 
126
*/
 
127
 
 
128
QImageFormat* GVPNGFormatType::decoderFor(
 
129
    const uchar* buffer, int length)
 
130
{
 
131
    if (length < 8) return 0;
 
132
    if (buffer[0]==137
 
133
     && buffer[1]=='P'
 
134
     && buffer[2]=='N'
 
135
     && buffer[3]=='G'
 
136
     && buffer[4]==13
 
137
     && buffer[5]==10
 
138
     && buffer[6]==26
 
139
     && buffer[7]==10)
 
140
        return new GVPNGFormat;
 
141
    return 0;
 
142
}
 
143
 
 
144
const char* GVPNGFormatType::formatName() const
 
145
{
 
146
    return "PNG";
 
147
}
 
148
 
 
149
extern "C" {
 
150
 
 
151
static void
 
152
info_callback(png_structp png_ptr, png_infop info)
 
153
{
 
154
    GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
 
155
    that->info(png_ptr,info);
 
156
}
 
157
 
 
158
static void
 
159
row_callback(png_structp png_ptr, png_bytep new_row,
 
160
   png_uint_32 row_num, int pass)
 
161
{
 
162
    GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
 
163
    that->row(png_ptr,new_row,row_num,pass);
 
164
}
 
165
 
 
166
static void
 
167
end_callback(png_structp png_ptr, png_infop info)
 
168
{
 
169
    GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
 
170
    that->end(png_ptr,info);
 
171
}
 
172
 
 
173
#if 0
 
174
#ifdef PNG_USER_CHUNKS_SUPPORTED
 
175
static int
 
176
CALLBACK_CALL_TYPE user_chunk_callback(png_structp png_ptr,
 
177
         png_unknown_chunkp chunk)
 
178
{
 
179
    GVPNGFormat* that = (GVPNGFormat*)png_get_progressive_ptr(png_ptr);
 
180
    return that->user_chunk(png_ptr,chunk->data,chunk->size);
 
181
}
 
182
#endif
 
183
#endif
 
184
 
 
185
static void qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
 
186
{
 
187
    qWarning("libpng warning: %s", message);
 
188
}
 
189
 
 
190
}
 
191
 
 
192
static
 
193
void setup_qt( QImage& image, png_structp png_ptr, png_infop info_ptr, float screen_gamma=0.0 )
 
194
{
 
195
    if ( screen_gamma != 0.0 && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) ) {
 
196
        double file_gamma;
 
197
        png_get_gAMA(png_ptr, info_ptr, &file_gamma);
 
198
        png_set_gamma( png_ptr, screen_gamma, file_gamma );
 
199
    }
 
200
 
 
201
    png_uint_32 width;
 
202
    png_uint_32 height;
 
203
    int bit_depth;
 
204
    int color_type;
 
205
    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
 
206
        0, 0, 0);
 
207
 
 
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 ))
 
214
                return;
 
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);
 
221
 
 
222
            if (!image.create(width, height, 32))
 
223
                return;
 
224
            image.setAlphaBuffer(TRUE);
 
225
 
 
226
            if (QImage::systemByteOrder() == QImage::BigEndian)
 
227
                png_set_swap_alpha(png_ptr);
 
228
 
 
229
            png_read_update_info(png_ptr, info_ptr);
 
230
        } else {
 
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))
 
238
                return;
 
239
            for (int i=0; i<ncols; i++) {
 
240
                int c = i*255/(ncols-1);
 
241
                image.setColor( i, qRgba(c,c,c,0xff) );
 
242
            }
 
243
            if ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ) {
 
244
                const int g = info_ptr->trans_values.gray;
 
245
                if (g < ncols) {
 
246
                    image.setAlphaBuffer(TRUE);
 
247
                    image.setColor(g, image.color(g) & RGB_MASK);
 
248
                }
 
249
            }
 
250
        }
 
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 )
 
254
    {
 
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,
 
262
            QImage::BigEndian))
 
263
            return;
 
264
        int i = 0;
 
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,
 
272
                    info_ptr->trans[i]
 
273
                    )
 
274
                );
 
275
                i++;
 
276
            }
 
277
        }
 
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,
 
283
                0xff
 
284
                )
 
285
            );
 
286
            i++;
 
287
        }
 
288
    } else {
 
289
        // 32-bit
 
290
        if ( bit_depth == 16 )
 
291
            png_set_strip_16(png_ptr);
 
292
 
 
293
        png_set_expand(png_ptr);
 
294
 
 
295
        if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
 
296
            png_set_gray_to_rgb(png_ptr);
 
297
 
 
298
        if (!image.create(width, height, 32))
 
299
            return;
 
300
 
 
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
 
308
        } else {
 
309
            image.setAlphaBuffer(TRUE);
 
310
        }
 
311
 
 
312
        if ( QImage::systemByteOrder() == QImage::BigEndian ) {
 
313
            png_set_swap_alpha(png_ptr);
 
314
        }
 
315
 
 
316
        png_read_update_info(png_ptr, info_ptr);
 
317
    }
 
318
 
 
319
    // Qt==ARGB==Big(ARGB)==Little(BGRA)
 
320
    if ( QImage::systemByteOrder() == QImage::LittleEndian ) {
 
321
        png_set_bgr(png_ptr);
 
322
    }
 
323
}
 
324
 
 
325
 
 
326
 
 
327
/*!
 
328
    Constructs a QPNGFormat object.
 
329
*/
 
330
GVPNGFormat::GVPNGFormat()
 
331
{
 
332
    state = MovieStart;
 
333
    first_frame = 1;
 
334
    base_offx = 0;
 
335
    base_offy = 0;
 
336
    png_ptr = 0;
 
337
    info_ptr = 0;
 
338
}
 
339
 
 
340
 
 
341
/*!
 
342
    Destroys a QPNGFormat object.
 
343
*/
 
344
GVPNGFormat::~GVPNGFormat()
 
345
{
 
346
    if ( png_ptr )
 
347
        png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
348
}
 
349
 
 
350
 
 
351
/*!
 
352
    This function decodes some data into image changes.
 
353
 
 
354
    Returns the number of bytes consumed.
 
355
*/
 
356
int GVPNGFormat::decode(QImage& img, QImageConsumer* cons,
 
357
        const uchar* buffer, int length)
 
358
{
 
359
    consumer = cons;
 
360
    image = &img;
 
361
 
 
362
    if ( state != Inside ) {
 
363
        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
 
364
        if (!png_ptr) {
 
365
            info_ptr = 0;
 
366
            image = 0;
 
367
                return -1;
 
368
        }
 
369
 
 
370
        png_set_error_fn(png_ptr, 0, 0, qt_png_warning);
 
371
        png_set_compression_level(png_ptr, 9);
 
372
 
 
373
        info_ptr = png_create_info_struct(png_ptr);
 
374
        if (!info_ptr) {
 
375
            png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
376
            image = 0;
 
377
            return -1;
 
378
        }
 
379
 
 
380
        if (setjmp((png_ptr)->jmpbuf)) {
 
381
            png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
382
            image = 0;
 
383
            return -1;
 
384
        }
 
385
 
 
386
        png_set_progressive_read_fn(png_ptr, (void *)this,
 
387
                                    info_callback, row_callback, end_callback);
 
388
 
 
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);
 
394
#endif
 
395
 
 
396
        if ( state != MovieStart && *buffer != 0211 ) {
 
397
            // Good, no signature - the preferred way to concat PNG images.
 
398
            // Skip them.
 
399
            png_set_sig_bytes(png_ptr, 8);
 
400
        }
 
401
 
 
402
        state = Inside;
 
403
        changed_rect = QRect();
 
404
    }
 
405
 
 
406
    if ( !png_ptr ) return 0;
 
407
 
 
408
    if (setjmp(png_ptr->jmpbuf)) {
 
409
        png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
410
        image = 0;
 
411
        state = MovieStart;
 
412
        return -1;
 
413
    }
 
414
    unused_data = 0;
 
415
    png_process_data(png_ptr, info_ptr, (png_bytep)buffer, length);
 
416
    int l = length - unused_data;
 
417
 
 
418
    if( !changed_rect.isNull()) {
 
419
        consumer->changed( changed_rect );
 
420
        changed_rect = QRect();
 
421
    }
 
422
 
 
423
    if ( state != Inside ) {
 
424
        if ( png_ptr )
 
425
            png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 
426
    }
 
427
 
 
428
    image = 0;
 
429
    return l;
 
430
}
 
431
 
 
432
void GVPNGFormat::info(png_structp png, png_infop)
 
433
{
 
434
    png_set_interlace_handling(png);
 
435
    setup_qt(*image, png, info_ptr);
 
436
    consumer->setSize( image->width(), image->height());
 
437
}
 
438
 
 
439
void GVPNGFormat::row(png_structp png, png_bytep new_row,
 
440
    png_uint_32 row_num, int)
 
441
{
 
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 );
 
445
}
 
446
 
 
447
 
 
448
void GVPNGFormat::end(png_structp png, png_infop info)
 
449
{
 
450
    int offx = png_get_x_offset_pixels(png,info) - base_offx;
 
451
    int offy = png_get_y_offset_pixels(png,info) - base_offy;
 
452
    if ( first_frame ) {
 
453
        base_offx = offx;
 
454
        base_offy = offy;
 
455
        first_frame = 0;
 
456
    }
 
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));
 
460
    png_textp text_ptr;
 
461
    int num_text=0;
 
462
    png_get_text(png,info,&text_ptr,&num_text);
 
463
    while (num_text--) {
 
464
        image->setText(text_ptr->key,0,text_ptr->text);
 
465
        text_ptr++;
 
466
    }
 
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);
 
471
    consumer->end();
 
472
    state = FrameStart;
 
473
    unused_data = (int)png->buffer_size; // Since libpng doesn't tell us
 
474
}
 
475
 
 
476
#ifdef PNG_USER_CHUNKS_SUPPORTED
 
477
 
 
478
/*
 
479
#ifndef QT_NO_IMAGE_TEXT
 
480
static bool skip(png_uint_32& max, png_bytep& data)
 
481
{
 
482
    while (*data) {
 
483
        if ( !max ) return FALSE;
 
484
        max--;
 
485
        data++;
 
486
    }
 
487
    if ( !max ) return FALSE;
 
488
    max--;
 
489
    data++; // skip to after NUL
 
490
    return TRUE;
 
491
}
 
492
#endif
 
493
*/
 
494
 
 
495
int GVPNGFormat::user_chunk(png_structp png,
 
496
            png_bytep data, png_uint_32 length)
 
497
{
 
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")
 
501
            && length == 4 ) {
 
502
 
 
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);
 
508
        return 1;
 
509
    } else if ( 0==qstrcmp((char*)png->chunk_name, "gIFx")
 
510
            && length == 13 ) {
 
511
        if ( qstrncmp((char*)data,"NETSCAPE2.0",11)==0 ) {
 
512
            int looping = (data[0xC]<<8)|data[0xB];
 
513
            consumer->setLooping(looping);
 
514
            return 1;
 
515
        }
 
516
    }
 
517
#else
 
518
    Q_UNUSED( png )
 
519
    Q_UNUSED( data )
 
520
    Q_UNUSED( length )
 
521
#endif
 
522
 
 
523
    /*
 
524
 
 
525
    libpng now supports this chunk.
 
526
 
 
527
 
 
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;
 
531
        if ( length >= 4 ) {
 
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
 
536
            }
 
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);
 
545
            return 1;
 
546
        }
 
547
    }
 
548
    */
 
549
 
 
550
    return 0;
 
551
}
 
552
#endif