]> git.saurik.com Git - wxWidgets.git/blame - src/common/imagpng.cpp
Add wxInputStream::ReadAll() and wxOutputStream::WriteAll().
[wxWidgets.git] / src / common / imagpng.cpp
CommitLineData
e9c4b1a2 1/////////////////////////////////////////////////////////////////////////////
f172cb82 2// Name: src/common/imagpng.cpp
e9c4b1a2
JS
3// Purpose: wxImage PNG handler
4// Author: Robert Roebling
5// RCS-ID: $Id$
6// Copyright: (c) Robert Roebling
65571936 7// Licence: wxWindows licence
e9c4b1a2
JS
8/////////////////////////////////////////////////////////////////////////////
9
27bb2b7c
VZ
10// ============================================================================
11// declarations
12// ============================================================================
13
27bb2b7c
VZ
14// ----------------------------------------------------------------------------
15// headers
16// ----------------------------------------------------------------------------
17
e9c4b1a2
JS
18// For compilers that support precompilation, includes "wx.h".
19#include "wx/wxprec.h"
20
21#ifdef __BORLANDC__
8898456d 22 #pragma hdrstop
ce4169a4
RR
23#endif
24
8898456d
WS
25#if wxUSE_IMAGE && wxUSE_LIBPNG
26
0bca0373 27#include "wx/imagpng.h"
ccec9093 28#include "wx/versioninfo.h"
0bca0373 29
ce4169a4 30#ifndef WX_PRECOMP
8898456d 31 #include "wx/log.h"
c3b10b44
PC
32 #include "wx/intl.h"
33 #include "wx/palette.h"
34 #include "wx/stream.h"
e9c4b1a2
JS
35#endif
36
a37e7424 37#include "png.h"
e9c4b1a2
JS
38
39// For memcpy
40#include <string.h>
41
27bb2b7c
VZ
42// ----------------------------------------------------------------------------
43// constants
44// ----------------------------------------------------------------------------
45
4c51a665 46// image cannot have any transparent pixels at all, have only 100% opaque
27bb2b7c
VZ
47// and/or 100% transparent pixels in which case a simple mask is enough to
48// store this information in wxImage or have a real alpha channel in which case
49// we need to have it in wxImage as well
50enum Transparency
51{
52 Transparency_None,
53 Transparency_Mask,
54 Transparency_Alpha
55};
56
57// ----------------------------------------------------------------------------
58// local functions
59// ----------------------------------------------------------------------------
60
61// return the kind of transparency needed for this image assuming that it does
62// have transparent pixels, i.e. either Transparency_Alpha or Transparency_Mask
63static Transparency
36308e0e 64CheckTransparency(unsigned char **lines,
f7155274
VZ
65 png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
66 size_t numColBytes);
27bb2b7c
VZ
67
68// init the alpha channel for the image and fill it with 1s up to (x, y)
69static unsigned char *InitAlpha(wxImage *image, png_uint_32 x, png_uint_32 y);
70
71// find a free colour for the mask in the PNG data array
72static void
73FindMaskColour(unsigned char **lines, png_uint_32 width, png_uint_32 height,
74 unsigned char& rMask, unsigned char& gMask, unsigned char& bMask);
e9c4b1a2 75
c9fcf581
VZ
76// is the pixel with this value of alpha a fully opaque one?
77static inline
78bool IsOpaque(unsigned char a)
79{
80 return a == 0xff;
81}
82
83// is the pixel with this value of alpha a fully transparent one?
84static inline
85bool IsTransparent(unsigned char a)
86{
87 return !a;
88}
89
27bb2b7c
VZ
90// ============================================================================
91// wxPNGHandler implementation
92// ============================================================================
e9c4b1a2 93
e9c4b1a2 94IMPLEMENT_DYNAMIC_CLASS(wxPNGHandler,wxImageHandler)
e9c4b1a2 95
e30285ab 96#if wxUSE_STREAMS
9ab6ee85 97
0729bd19 98#ifndef PNGLINKAGEMODE
c3b10b44
PC
99 #ifdef PNGAPI
100 #define PNGLINKAGEMODE PNGAPI
101 #elif defined(__WATCOMC__)
30e0e251
VZ
102 // we need an explicit cdecl for Watcom, at least according to
103 //
104 // http://sf.net/tracker/index.php?func=detail&aid=651492&group_id=9863&atid=109863
105 //
106 // more testing is needed for this however, please remove this comment
107 // if you can confirm that my fix works with Watcom 11
108 #define PNGLINKAGEMODE cdecl
109 #else
110 #define PNGLINKAGEMODE LINKAGEMODE
111 #endif
717b9bf2
DW
112#endif
113
bdffd806
VS
114
115// VS: wxPNGInfoStruct declared below is a hack that needs some explanation.
27bb2b7c
VZ
116// First, let me describe what's the problem: libpng uses jmp_buf in
117// its png_struct structure. Unfortunately, this structure is
118// compiler-specific and may vary in size, so if you use libpng compiled
0e1f8ea4
VZ
119// as DLL with another compiler than the main executable, it may not work.
120// Luckily, it is still possible to use setjmp() & longjmp() as long as the
121// structure is not part of png_struct.
bdffd806
VS
122//
123// Sadly, there's no clean way to attach user-defined data to png_struct.
124// There is only one customizable place, png_struct.io_ptr, which is meant
27bb2b7c 125// only for I/O routines and is set with png_set_read_fn or
bdffd806
VS
126// png_set_write_fn. The hacky part is that we use io_ptr to store
127// a pointer to wxPNGInfoStruct that holds I/O structures _and_ jmp_buf.
128
129struct wxPNGInfoStruct
130{
131 jmp_buf jmpbuf;
132 bool verbose;
27bb2b7c 133
bdffd806
VS
134 union
135 {
136 wxInputStream *in;
137 wxOutputStream *out;
138 } stream;
139};
140
141#define WX_PNG_INFO(png_ptr) ((wxPNGInfoStruct*)png_get_io_ptr(png_ptr))
142
27bb2b7c
VZ
143// ----------------------------------------------------------------------------
144// helper functions
145// ----------------------------------------------------------------------------
bdffd806 146
90350682
VZ
147extern "C"
148{
149
7d7b3f69
FM
150static void PNGLINKAGEMODE wx_PNG_stream_reader( png_structp png_ptr, png_bytep data,
151 png_size_t length )
e9c4b1a2 152{
bdffd806 153 WX_PNG_INFO(png_ptr)->stream.in->Read(data, length);
e9c4b1a2
JS
154}
155
7d7b3f69
FM
156static void PNGLINKAGEMODE wx_PNG_stream_writer( png_structp png_ptr, png_bytep data,
157 png_size_t length )
e9c4b1a2 158{
bdffd806 159 WX_PNG_INFO(png_ptr)->stream.out->Write(data, length);
e9c4b1a2
JS
160}
161
7d7b3f69 162static void
bdffd806 163PNGLINKAGEMODE wx_png_warning(png_structp png_ptr, png_const_charp message)
deb2fec0 164{
d10c19d6
VZ
165 wxPNGInfoStruct *info = png_ptr ? WX_PNG_INFO(png_ptr) : NULL;
166 if ( !info || info->verbose )
af588446 167 {
2b5f62a0 168 wxLogWarning( wxString::FromAscii(message) );
af588446 169 }
deb2fec0
SB
170}
171
ce615190
DS
172// from pngerror.c
173// so that the libpng doesn't send anything on stderr
7d7b3f69 174static void
0e0589e8 175PNGLINKAGEMODE wx_png_error(png_structp png_ptr, png_const_charp message)
ce615190 176{
ce615190 177 wx_png_warning(NULL, message);
0e0589e8
VZ
178
179 // we're not using libpng built-in jump buffer (see comment before
180 // wxPNGInfoStruct above) so we have to return ourselves, otherwise libpng
181 // would just abort
182 longjmp(WX_PNG_INFO(png_ptr)->jmpbuf, 1);
ce615190
DS
183}
184
90350682
VZ
185} // extern "C"
186
27bb2b7c
VZ
187// ----------------------------------------------------------------------------
188// LoadFile() helpers
189// ----------------------------------------------------------------------------
190
d26d9754 191// determine the kind of transparency we need for this image: if the only alpha
c9fcf581
VZ
192// values it has are 0 (transparent) and 0xff (opaque) then we can simply
193// create a mask for it, we should be ok with a simple mask but otherwise we
194// need a full blown alpha channel in wxImage
d26d9754
VZ
195//
196// parameters:
36308e0e 197// lines raw PNG data
d26d9754
VZ
198// x, y starting position
199// w, h size of the image
200// numColBytes number of colour bytes (1 for grey scale, 3 for RGB)
201// (NB: alpha always follows the colour bytes)
27bb2b7c 202Transparency
36308e0e 203CheckTransparency(unsigned char **lines,
d26d9754
VZ
204 png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
205 size_t numColBytes)
27bb2b7c 206{
d26d9754
VZ
207 // suppose that a mask will suffice and check all the remaining alpha
208 // values to see if it does
36308e0e 209 for ( ; y < h; y++ )
27bb2b7c 210 {
e45cc235
RD
211 // each pixel is numColBytes+1 bytes, offset into the current line by
212 // the current x position
213 unsigned const char *ptr = lines[y] + (x * (numColBytes + 1));
36308e0e 214
c9fcf581 215 for ( png_uint_32 x2 = x; x2 < w; x2++ )
27bb2b7c 216 {
c9fcf581 217 // skip the grey or colour byte(s)
36308e0e 218 ptr += numColBytes;
27bb2b7c 219
36308e0e 220 unsigned char a2 = *ptr++;
c9fcf581
VZ
221
222 if ( !IsTransparent(a2) && !IsOpaque(a2) )
27bb2b7c 223 {
d26d9754
VZ
224 // not fully opaque nor fully transparent, hence need alpha
225 return Transparency_Alpha;
27bb2b7c 226 }
27bb2b7c 227 }
c9fcf581
VZ
228
229 // during the next loop iteration check all the pixels in the row
230 x = 0;
27bb2b7c
VZ
231 }
232
d26d9754
VZ
233 // mask will be enough
234 return Transparency_Mask;
27bb2b7c
VZ
235}
236
237unsigned char *InitAlpha(wxImage *image, png_uint_32 x, png_uint_32 y)
238{
239 // create alpha channel
240 image->SetAlpha();
241
242 unsigned char *alpha = image->GetAlpha();
243
244 // set alpha for the pixels we had so far
c9fcf581
VZ
245 png_uint_32 end = y * image->GetWidth() + x;
246 for ( png_uint_32 i = 0; i < end; i++ )
27bb2b7c 247 {
c9fcf581
VZ
248 // all the previous pixels were opaque
249 *alpha++ = 0xff;
27bb2b7c
VZ
250 }
251
252 return alpha;
253}
254
255void
256FindMaskColour(unsigned char **lines, png_uint_32 width, png_uint_32 height,
257 unsigned char& rMask, unsigned char& gMask, unsigned char& bMask)
258{
259 // choosing the colour for the mask is more
260 // difficult: we need to iterate over the entire
261 // image for this in order to choose an unused
262 // colour (this is not very efficient but what else
263 // can we do?)
264 wxImageHistogram h;
265 unsigned nentries = 0;
266 unsigned char r2, g2, b2;
267 for ( png_uint_32 y2 = 0; y2 < height; y2++ )
268 {
269 const unsigned char *p = lines[y2];
270 for ( png_uint_32 x2 = 0; x2 < width; x2++ )
271 {
272 r2 = *p++;
273 g2 = *p++;
274 b2 = *p++;
dcc36b34 275 ++p; // jump over alpha
27bb2b7c
VZ
276
277 wxImageHistogramEntry&
278 entry = h[wxImageHistogram:: MakeKey(r2, g2, b2)];
279
280 if ( entry.value++ == 0 )
281 entry.index = nentries++;
282 }
283 }
284
f773e9b0 285 if ( !h.FindFirstUnusedColour(&rMask, &gMask, &bMask) )
27bb2b7c
VZ
286 {
287 wxLogWarning(_("Too many colours in PNG, the image may be slightly blurred."));
288
289 // use a fixed mask colour and we'll fudge
290 // the real pixels with this colour (see
291 // below)
292 rMask = 0xfe;
293 gMask = 0;
294 bMask = 0xff;
295 }
296}
297
298// ----------------------------------------------------------------------------
299// reading PNGs
300// ----------------------------------------------------------------------------
301
302bool wxPNGHandler::DoCanRead( wxInputStream& stream )
303{
304 unsigned char hdr[4];
305
8faef7cc 306 if ( !stream.Read(hdr, WXSIZEOF(hdr)) ) // it's ok to modify the stream position here
7beb59f3 307 return false;
27bb2b7c
VZ
308
309 return memcmp(hdr, "\211PNG", WXSIZEOF(hdr)) == 0;
310}
311
312// convert data from RGB to wxImage format
313static
314void CopyDataFromPNG(wxImage *image,
315 unsigned char **lines,
316 png_uint_32 width,
317 png_uint_32 height,
318 int color_type)
319{
320 Transparency transparency = Transparency_None;
321
322 // only non NULL if transparency == Transparency_Alpha
323 unsigned char *alpha = NULL;
324
325 // RGB of the mask colour if transparency == Transparency_Mask
326 // (but init them anyhow to avoid compiler warnings)
327 unsigned char rMask = 0,
328 gMask = 0,
329 bMask = 0;
330
331 unsigned char *ptrDst = image->GetData();
332 if ( !(color_type & PNG_COLOR_MASK_COLOR) )
333 {
334 // grey image: GAGAGA... where G == grey component and A == alpha
335 for ( png_uint_32 y = 0; y < height; y++ )
336 {
337 const unsigned char *ptrSrc = lines[y];
338 for ( png_uint_32 x = 0; x < width; x++ )
339 {
340 unsigned char g = *ptrSrc++;
341 unsigned char a = *ptrSrc++;
342
343 // the first time we encounter a transparent pixel we must
344 // decide about what to do about them
c9fcf581 345 if ( !IsOpaque(a) && transparency == Transparency_None )
27bb2b7c
VZ
346 {
347 // we'll need at least the mask for this image and
348 // maybe even full alpha channel info: the former is
349 // only enough if we have alpha values of 0 and 0xff
350 // only, otherwisewe need the latter
d26d9754
VZ
351 transparency = CheckTransparency
352 (
36308e0e 353 lines,
d26d9754
VZ
354 x, y,
355 width, height,
356 1
357 );
27bb2b7c
VZ
358
359 if ( transparency == Transparency_Mask )
360 {
361 // let's choose this colour for the mask: this is
362 // not a problem here as all the other pixels are
363 // grey, i.e. R == G == B which is not the case for
364 // this one so no confusion is possible
365 rMask = 0xff;
366 gMask = 0;
367 bMask = 0xff;
368 }
369 else // transparency == Transparency_Alpha
370 {
371 alpha = InitAlpha(image, x, y);
372 }
373 }
374
375 switch ( transparency )
376 {
c9fcf581
VZ
377 case Transparency_Mask:
378 if ( IsTransparent(a) )
379 {
380 *ptrDst++ = rMask;
c9fcf581 381 *ptrDst++ = gMask;
dcc36b34 382 *ptrDst++ = bMask;
c9fcf581
VZ
383 break;
384 }
385 // else: !transparent
386
387 // must be opaque then as otherwise we shouldn't be
388 // using the mask at all
9a83f860 389 wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
c9fcf581
VZ
390
391 // fall through
392
27bb2b7c 393 case Transparency_Alpha:
c9fcf581
VZ
394 if ( alpha )
395 *alpha++ = a;
27bb2b7c
VZ
396 // fall through
397
398 case Transparency_None:
399 *ptrDst++ = g;
400 *ptrDst++ = g;
401 *ptrDst++ = g;
402 break;
27bb2b7c
VZ
403 }
404 }
405 }
406 }
407 else // colour image: RGBRGB...
408 {
409 for ( png_uint_32 y = 0; y < height; y++ )
410 {
411 const unsigned char *ptrSrc = lines[y];
412 for ( png_uint_32 x = 0; x < width; x++ )
413 {
414 unsigned char r = *ptrSrc++;
415 unsigned char g = *ptrSrc++;
416 unsigned char b = *ptrSrc++;
417 unsigned char a = *ptrSrc++;
418
419 // the logic here is the same as for the grey case except
420 // where noted
c9fcf581 421 if ( !IsOpaque(a) && transparency == Transparency_None )
27bb2b7c 422 {
d26d9754
VZ
423 transparency = CheckTransparency
424 (
36308e0e 425 lines,
d26d9754
VZ
426 x, y,
427 width, height,
428 3
429 );
27bb2b7c
VZ
430
431 if ( transparency == Transparency_Mask )
432 {
f773e9b0
RR
433 FindMaskColour(lines, width, height,
434 rMask, gMask, bMask);
27bb2b7c
VZ
435 }
436 else // transparency == Transparency_Alpha
437 {
438 alpha = InitAlpha(image, x, y);
439 }
440
441 }
442
443 switch ( transparency )
444 {
c9fcf581
VZ
445 case Transparency_Mask:
446 if ( IsTransparent(a) )
447 {
c9fcf581 448 *ptrDst++ = rMask;
c9fcf581 449 *ptrDst++ = gMask;
dcc36b34 450 *ptrDst++ = bMask;
c9fcf581
VZ
451 break;
452 }
999836aa
VZ
453 else // !transparent
454 {
455 // must be opaque then as otherwise we shouldn't be
456 // using the mask at all
9a83f860 457 wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
999836aa
VZ
458
459 // if we couldn't find a unique colour for the
460 // mask, we can have real pixels with the same
461 // value as the mask and it's better to slightly
462 // change their colour than to make them
463 // transparent
464 if ( r == rMask && g == gMask && b == bMask )
465 {
466 r++;
467 }
468 }
c9fcf581
VZ
469
470 // fall through
471
27bb2b7c 472 case Transparency_Alpha:
c9fcf581
VZ
473 if ( alpha )
474 *alpha++ = a;
27bb2b7c
VZ
475 // fall through
476
477 case Transparency_None:
478 *ptrDst++ = r;
479 *ptrDst++ = g;
480 *ptrDst++ = b;
481 break;
27bb2b7c
VZ
482 }
483 }
484 }
485 }
486
487 if ( transparency == Transparency_Mask )
488 {
489 image->SetMaskColour(rMask, gMask, bMask);
490 }
491}
492
3ca6a5f0
BP
493// temporarily disable the warning C4611 (interaction between '_setjmp' and
494// C++ object destruction is non-portable) - I don't see any dtors here
495#ifdef __VISUALC__
496 #pragma warning(disable:4611)
497#endif /* VC++ */
498
27bb2b7c
VZ
499bool
500wxPNGHandler::LoadFile(wxImage *image,
501 wxInputStream& stream,
502 bool verbose,
503 int WXUNUSED(index))
e9c4b1a2 504{
7884ab90 505 // VZ: as this function uses setjmp() the only fool-proof error handling
e9c4b1a2 506 // method is to use goto (setjmp is not really C++ dtors friendly...)
717b9bf2 507
27bb2b7c
VZ
508 unsigned char **lines = NULL;
509 png_infop info_ptr = (png_infop) NULL;
bdffd806
VS
510 wxPNGInfoStruct wxinfo;
511
7884ab90
DS
512 png_uint_32 i, width, height = 0;
513 int bit_depth, color_type, interlace_type;
514
bdffd806
VS
515 wxinfo.verbose = verbose;
516 wxinfo.stream.in = &stream;
717b9bf2 517
e9c4b1a2 518 image->Destroy();
717b9bf2 519
d10c19d6
VZ
520 png_structp png_ptr = png_create_read_struct
521 (
522 PNG_LIBPNG_VER_STRING,
c742231d 523 NULL,
d10c19d6
VZ
524 wx_png_error,
525 wx_png_warning
526 );
e9c4b1a2 527 if (!png_ptr)
27bb2b7c 528 goto error;
717b9bf2 529
bdffd806
VS
530 // NB: please see the comment near wxPNGInfoStruct declaration for
531 // explanation why this line is mandatory
e9b964cf 532 png_set_read_fn( png_ptr, &wxinfo, wx_PNG_stream_reader);
deb2fec0 533
e9c4b1a2
JS
534 info_ptr = png_create_info_struct( png_ptr );
535 if (!info_ptr)
27bb2b7c 536 goto error;
717b9bf2 537
bdffd806 538 if (setjmp(wxinfo.jmpbuf))
27bb2b7c 539 goto error;
717b9bf2 540
e9c4b1a2 541 png_read_info( png_ptr, info_ptr );
d3b9f782 542 png_get_IHDR( png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL );
717b9bf2 543
e9c4b1a2
JS
544 if (color_type == PNG_COLOR_TYPE_PALETTE)
545 png_set_expand( png_ptr );
717b9bf2 546
2b5f62a0
VZ
547 // Fix for Bug [ 439207 ] Monochrome PNG images come up black
548 if (bit_depth < 8)
549 png_set_expand( png_ptr );
550
e9c4b1a2
JS
551 png_set_strip_16( png_ptr );
552 png_set_packing( png_ptr );
553 if (png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS))
554 png_set_expand( png_ptr );
555 png_set_filler( png_ptr, 0xff, PNG_FILLER_AFTER );
717b9bf2 556
154e1ca1 557 image->Create((int)width, (int)height, (bool) false /* no need to init pixels */);
717b9bf2 558
a1b806b9 559 if (!image->IsOk())
27bb2b7c 560 goto error;
717b9bf2 561
5550f66b
VZ
562 // initialize all line pointers to NULL to ensure that they can be safely
563 // free()d if an error occurs before all of them could be allocated
564 lines = (unsigned char **)calloc(height, sizeof(unsigned char *));
27bb2b7c
VZ
565 if ( !lines )
566 goto error;
717b9bf2 567
e9c4b1a2
JS
568 for (i = 0; i < height; i++)
569 {
784d9056 570 if ((lines[i] = (unsigned char *)malloc( (size_t)(width * 4))) == NULL)
e9c4b1a2 571 goto error;
e9c4b1a2 572 }
717b9bf2 573
27bb2b7c
VZ
574 png_read_image( png_ptr, lines );
575 png_read_end( png_ptr, info_ptr );
982a6623
RR
576
577#if wxUSE_PALETTE
578 if (color_type == PNG_COLOR_TYPE_PALETTE)
579 {
1de255d6
DS
580 png_colorp palette = NULL;
581 int numPalette = 0;
982a6623 582
1de255d6
DS
583 (void) png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
584
585 unsigned char* r = new unsigned char[numPalette];
586 unsigned char* g = new unsigned char[numPalette];
587 unsigned char* b = new unsigned char[numPalette];
588
589 for (int j = 0; j < numPalette; j++)
982a6623 590 {
1de255d6
DS
591 r[j] = palette[j].red;
592 g[j] = palette[j].green;
593 b[j] = palette[j].blue;
982a6623
RR
594 }
595
1de255d6 596 image->SetPalette(wxPalette(numPalette, r, g, b));
982a6623
RR
597 delete[] r;
598 delete[] g;
599 delete[] b;
600 }
601#endif // wxUSE_PALETTE
602
f2c80791
DS
603
604 // set the image resolution if it's available
605 png_uint_32 resX, resY;
606 int unitType;
607 if (png_get_pHYs(png_ptr, info_ptr, &resX, &resY, &unitType)
608 == PNG_INFO_pHYs)
609 {
610 wxImageResolution res = wxIMAGE_RESOLUTION_CM;
611
612 switch (unitType)
613 {
614 default:
615 wxLogWarning(_("Unknown PNG resolution unit %d"), unitType);
616 // fall through
617
618 case PNG_RESOLUTION_UNKNOWN:
619 image->SetOption(wxIMAGE_OPTION_RESOLUTIONX, resX);
620 image->SetOption(wxIMAGE_OPTION_RESOLUTIONY, resY);
621
622 res = wxIMAGE_RESOLUTION_NONE;
623 break;
624
625 case PNG_RESOLUTION_METER:
626 /*
627 Convert meters to centimeters.
628 Use a string to not lose precision (converting to cm and then
629 to inch would result in integer rounding error).
630 If an app wants an int, GetOptionInt will convert and round
631 down for them.
632 */
633 image->SetOption(wxIMAGE_OPTION_RESOLUTIONX,
634 wxString::FromCDouble((double) resX / 100.0, 2));
635 image->SetOption(wxIMAGE_OPTION_RESOLUTIONY,
636 wxString::FromCDouble((double) resY / 100.0, 2));
637 break;
638 }
639
640 image->SetOption(wxIMAGE_OPTION_RESOLUTIONUNIT, res);
641 }
642
643
27bb2b7c 644 png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp) NULL );
717b9bf2 645
27bb2b7c
VZ
646 // loaded successfully, now init wxImage with this data
647 CopyDataFromPNG(image, lines, width, height, color_type);
717b9bf2 648
27bb2b7c
VZ
649 for ( i = 0; i < height; i++ )
650 free( lines[i] );
651 free( lines );
717b9bf2 652
7beb59f3 653 return true;
95ee0ac8 654
27bb2b7c 655error:
58c837a4 656 if (verbose)
af588446 657 {
58c837a4 658 wxLogError(_("Couldn't load a PNG image - file is corrupted or not enough memory."));
af588446 659 }
717b9bf2 660
a1b806b9 661 if ( image->IsOk() )
e9c4b1a2
JS
662 {
663 image->Destroy();
664 }
717b9bf2 665
e9c4b1a2
JS
666 if ( lines )
667 {
0e0589e8
VZ
668 for ( unsigned int n = 0; n < height; n++ )
669 free( lines[n] );
670
e9c4b1a2
JS
671 free( lines );
672 }
717b9bf2 673
e9c4b1a2
JS
674 if ( png_ptr )
675 {
676 if ( info_ptr )
677 {
678 png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp) NULL );
679 free(info_ptr);
680 }
681 else
682 png_destroy_read_struct( &png_ptr, (png_infopp) NULL, (png_infopp) NULL );
683 }
7beb59f3 684 return false;
e9c4b1a2
JS
685}
686
8ee313d2 687// ----------------------------------------------------------------------------
8529b0b9 688// SaveFile() palette helpers
8ee313d2
DS
689// ----------------------------------------------------------------------------
690
8529b0b9 691typedef wxLongToLongHashMap PaletteMap;
967956dd 692
8529b0b9 693static unsigned long PaletteMakeKey(const png_color_8& clr)
8ee313d2 694{
8529b0b9 695 return (wxImageHistogram::MakeKey(clr.red, clr.green, clr.blue) << 8) | clr.alpha;
8ee313d2
DS
696}
697
8529b0b9
DS
698static long PaletteFind(const PaletteMap& palette, const png_color_8& clr)
699{
700 unsigned long value = PaletteMakeKey(clr);
701 PaletteMap::const_iterator it = palette.find(value);
702
703 return (it != palette.end()) ? it->second : wxNOT_FOUND;
704}
705
706static long PaletteAdd(PaletteMap *palette, const png_color_8& clr)
707{
708 unsigned long value = PaletteMakeKey(clr);
709 PaletteMap::const_iterator it = palette->find(value);
710 size_t index;
711
712 if (it == palette->end())
713 {
714 index = palette->size();
715 (*palette)[value] = index;
716 }
717 else
718 {
719 index = it->second;
720 }
721
722 return index;
723}
967956dd 724
27bb2b7c
VZ
725// ----------------------------------------------------------------------------
726// writing PNGs
727// ----------------------------------------------------------------------------
728
deb2fec0 729bool wxPNGHandler::SaveFile( wxImage *image, wxOutputStream& stream, bool verbose )
e9c4b1a2 730{
bdffd806 731 wxPNGInfoStruct wxinfo;
717b9bf2 732
bdffd806
VS
733 wxinfo.verbose = verbose;
734 wxinfo.stream.out = &stream;
deb2fec0 735
d10c19d6
VZ
736 png_structp png_ptr = png_create_write_struct
737 (
738 PNG_LIBPNG_VER_STRING,
739 NULL,
740 wx_png_error,
741 wx_png_warning
742 );
bdffd806
VS
743 if (!png_ptr)
744 {
745 if (verbose)
af588446 746 {
bdffd806 747 wxLogError(_("Couldn't save PNG image."));
af588446 748 }
7beb59f3 749 return false;
bdffd806 750 }
717b9bf2 751
bdffd806
VS
752 png_infop info_ptr = png_create_info_struct(png_ptr);
753 if (info_ptr == NULL)
754 {
755 png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
756 if (verbose)
af588446 757 {
bdffd806 758 wxLogError(_("Couldn't save PNG image."));
af588446 759 }
7beb59f3 760 return false;
bdffd806 761 }
717b9bf2 762
bdffd806
VS
763 if (setjmp(wxinfo.jmpbuf))
764 {
765 png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
766 if (verbose)
af588446 767 {
bdffd806 768 wxLogError(_("Couldn't save PNG image."));
af588446 769 }
7beb59f3 770 return false;
bdffd806 771 }
717b9bf2 772
bdffd806
VS
773 // NB: please see the comment near wxPNGInfoStruct declaration for
774 // explanation why this line is mandatory
e9b964cf 775 png_set_write_fn( png_ptr, &wxinfo, wx_PNG_stream_writer, NULL);
bdffd806 776
8529b0b9
DS
777 const int iHeight = image->GetHeight();
778 const int iWidth = image->GetWidth();
779
8ee313d2
DS
780 const bool bHasPngFormatOption
781 = image->HasOption(wxIMAGE_OPTION_PNG_FORMAT);
782
783 int iColorType = bHasPngFormatOption
a4efa721
VZ
784 ? image->GetOptionInt(wxIMAGE_OPTION_PNG_FORMAT)
785 : wxPNG_TYPE_COLOUR;
a4efa721 786
a4efa721
VZ
787 bool bHasAlpha = image->HasAlpha();
788 bool bHasMask = image->HasMask();
8ee313d2 789
8529b0b9 790 bool bUsePalette = iColorType == wxPNG_TYPE_PALETTE
8ee313d2 791#if wxUSE_PALETTE
8529b0b9
DS
792 || (!bHasPngFormatOption && image->HasPalette() )
793#endif
794 ;
795
5568067a 796 png_color_8 mask = { 0, 0, 0, 0, 0 };
8529b0b9
DS
797
798 if (bHasMask)
799 {
800 mask.red = image->GetMaskRed();
801 mask.green = image->GetMaskGreen();
802 mask.blue = image->GetMaskBlue();
8529b0b9
DS
803 }
804
805 PaletteMap palette;
806
807 if (bUsePalette)
8ee313d2 808 {
8529b0b9
DS
809 png_color png_rgb [PNG_MAX_PALETTE_LENGTH];
810 png_byte png_trans[PNG_MAX_PALETTE_LENGTH];
811
812 const unsigned char *pColors = image->GetData();
813 const unsigned char* pAlpha = image->GetAlpha();
814
815 if (bHasMask && !pAlpha)
8ee313d2 816 {
8529b0b9
DS
817 // Mask must be first
818 PaletteAdd(&palette, mask);
8ee313d2 819 }
8529b0b9
DS
820
821 for (int y = 0; y < iHeight; y++)
8ee313d2 822 {
8529b0b9
DS
823 for (int x = 0; x < iWidth; x++)
824 {
825 png_color_8 rgba;
826
827 rgba.red = *pColors++;
828 rgba.green = *pColors++;
829 rgba.blue = *pColors++;
830 rgba.gray = 0;
831 rgba.alpha = (pAlpha && !bHasMask) ? *pAlpha++ : 0;
832
833 // save in our palette
834 long index = PaletteAdd(&palette, rgba);
835
836 if (index < PNG_MAX_PALETTE_LENGTH)
837 {
838 // save in libpng's palette
839 png_rgb[index].red = rgba.red;
840 png_rgb[index].green = rgba.green;
841 png_rgb[index].blue = rgba.blue;
842 png_trans[index] = rgba.alpha;
843 }
844 else
845 {
846 bUsePalette = false;
847 break;
848 }
849 }
850 }
851
852 if (bUsePalette)
853 {
854 png_set_PLTE(png_ptr, info_ptr, png_rgb, palette.size());
855
856 if (bHasMask && !pAlpha)
857 {
858 wxASSERT(PaletteFind(palette, mask) == 0);
859 png_trans[0] = 0;
860 png_set_tRNS(png_ptr, info_ptr, png_trans, 1, NULL);
861 }
862 else if (pAlpha && !bHasMask)
863 {
864 png_set_tRNS(png_ptr, info_ptr, png_trans, palette.size(), NULL);
865 }
8ee313d2
DS
866 }
867 }
13c5d825 868
b057ac07
DS
869 /*
870 If saving palettised was requested but it was decided we can't use a
871 palette then reset the colour type to RGB.
872 */
873 if (!bUsePalette && iColorType == wxPNG_TYPE_PALETTE)
13c5d825
DS
874 {
875 iColorType = wxPNG_TYPE_COLOUR;
876 }
8ee313d2
DS
877
878 bool bUseAlpha = !bUsePalette && (bHasAlpha || bHasMask);
879
a4efa721 880 int iPngColorType;
8ee313d2 881
8ee313d2
DS
882 if (bUsePalette)
883 {
884 iPngColorType = PNG_COLOR_TYPE_PALETTE;
885 iColorType = wxPNG_TYPE_PALETTE;
886 }
8529b0b9 887 else if ( iColorType==wxPNG_TYPE_COLOUR )
a4efa721
VZ
888 {
889 iPngColorType = bUseAlpha ? PNG_COLOR_TYPE_RGB_ALPHA
890 : PNG_COLOR_TYPE_RGB;
891 }
892 else
893 {
894 iPngColorType = bUseAlpha ? PNG_COLOR_TYPE_GRAY_ALPHA
895 : PNG_COLOR_TYPE_GRAY;
896 }
bdffd806 897
d19ce8c4
FM
898 if (image->HasOption(wxIMAGE_OPTION_PNG_FILTER))
899 png_set_filter( png_ptr, PNG_FILTER_TYPE_BASE, image->GetOptionInt(wxIMAGE_OPTION_PNG_FILTER) );
900
901 if (image->HasOption(wxIMAGE_OPTION_PNG_COMPRESSION_LEVEL))
902 png_set_compression_level( png_ptr, image->GetOptionInt(wxIMAGE_OPTION_PNG_COMPRESSION_LEVEL) );
903
904 if (image->HasOption(wxIMAGE_OPTION_PNG_COMPRESSION_MEM_LEVEL))
905 png_set_compression_mem_level( png_ptr, image->GetOptionInt(wxIMAGE_OPTION_PNG_COMPRESSION_MEM_LEVEL) );
906
907 if (image->HasOption(wxIMAGE_OPTION_PNG_COMPRESSION_STRATEGY))
908 png_set_compression_strategy( png_ptr, image->GetOptionInt(wxIMAGE_OPTION_PNG_COMPRESSION_STRATEGY) );
909
910 if (image->HasOption(wxIMAGE_OPTION_PNG_COMPRESSION_BUFFER_SIZE))
911 png_set_compression_buffer_size( png_ptr, image->GetOptionInt(wxIMAGE_OPTION_PNG_COMPRESSION_BUFFER_SIZE) );
912
8ee313d2
DS
913 int iBitDepth = !bUsePalette && image->HasOption(wxIMAGE_OPTION_PNG_BITDEPTH)
914 ? image->GetOptionInt(wxIMAGE_OPTION_PNG_BITDEPTH)
915 : 8;
916
a4efa721
VZ
917 png_set_IHDR( png_ptr, info_ptr, image->GetWidth(), image->GetHeight(),
918 iBitDepth, iPngColorType,
919 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
920 PNG_FILTER_TYPE_BASE);
921
922 int iElements;
bdffd806 923 png_color_8 sig_bit;
a4efa721
VZ
924
925 if ( iPngColorType & PNG_COLOR_MASK_COLOR )
926 {
927 sig_bit.red =
928 sig_bit.green =
929 sig_bit.blue = (png_byte)iBitDepth;
930 iElements = 3;
931 }
932 else // grey
933 {
934 sig_bit.gray = (png_byte)iBitDepth;
935 iElements = 1;
936 }
937
8529b0b9 938 if ( bUseAlpha )
a4efa721
VZ
939 {
940 sig_bit.alpha = (png_byte)iBitDepth;
941 iElements++;
942 }
943
dc683654
VZ
944 if ( iBitDepth == 16 )
945 iElements *= 2;
946
361f4288
VZ
947 // save the image resolution if we have it
948 int resX, resY;
949 switch ( GetResolutionFromOptions(*image, &resX, &resY) )
950 {
951 case wxIMAGE_RESOLUTION_INCHES:
095cb950
PC
952 {
953 const double INCHES_IN_METER = 10000.0 / 254;
954 resX = int(resX * INCHES_IN_METER);
955 resY = int(resY * INCHES_IN_METER);
956 }
361f4288
VZ
957 break;
958
959 case wxIMAGE_RESOLUTION_CM:
960 resX *= 100;
961 resY *= 100;
962 break;
963
964 case wxIMAGE_RESOLUTION_NONE:
965 break;
966
967 default:
9a83f860 968 wxFAIL_MSG( wxT("unsupported image resolution units") );
361f4288
VZ
969 }
970
971 if ( resX && resY )
972 png_set_pHYs( png_ptr, info_ptr, resX, resY, PNG_RESOLUTION_METER );
973
bdffd806
VS
974 png_set_sBIT( png_ptr, info_ptr, &sig_bit );
975 png_write_info( png_ptr, info_ptr );
976 png_set_shift( png_ptr, &sig_bit );
977 png_set_packing( png_ptr );
717b9bf2 978
a4efa721
VZ
979 unsigned char *
980 data = (unsigned char *)malloc( image->GetWidth() * iElements );
981 if ( !data )
bdffd806
VS
982 {
983 png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
7beb59f3 984 return false;
bdffd806 985 }
717b9bf2 986
8529b0b9
DS
987 const unsigned char *
988 pAlpha = (const unsigned char *)(bHasAlpha ? image->GetAlpha() : NULL);
a4efa721 989
8529b0b9 990 const unsigned char *pColors = image->GetData();
a4efa721
VZ
991
992 for (int y = 0; y != iHeight; ++y)
bdffd806 993 {
a4efa721
VZ
994 unsigned char *pData = data;
995 for (int x = 0; x != iWidth; x++)
e9c4b1a2 996 {
8529b0b9 997 png_color_8 clr;
8ee313d2
DS
998 clr.red = *pColors++;
999 clr.green = *pColors++;
1000 clr.blue = *pColors++;
8529b0b9
DS
1001 clr.gray = 0;
1002 clr.alpha = (bUsePalette && pAlpha) ? *pAlpha++ : 0; // use with wxPNG_TYPE_PALETTE only
e1e36a3e 1003
a4efa721 1004 switch ( iColorType )
bdffd806 1005 {
a4efa721 1006 default:
9a83f860 1007 wxFAIL_MSG( wxT("unknown wxPNG_TYPE_XXX") );
a4efa721
VZ
1008 // fall through
1009
1010 case wxPNG_TYPE_COLOUR:
8ee313d2 1011 *pData++ = clr.red;
dc683654 1012 if ( iBitDepth == 16 )
a4efa721 1013 *pData++ = 0;
8ee313d2 1014 *pData++ = clr.green;
dc683654 1015 if ( iBitDepth == 16 )
a4efa721 1016 *pData++ = 0;
8ee313d2 1017 *pData++ = clr.blue;
dc683654 1018 if ( iBitDepth == 16 )
a4efa721
VZ
1019 *pData++ = 0;
1020 break;
1021
1022 case wxPNG_TYPE_GREY:
1023 {
dc683654
VZ
1024 // where do these coefficients come from? maybe we
1025 // should have image options for them as well?
a4efa721 1026 unsigned uiColor =
8ee313d2
DS
1027 (unsigned) (76.544*(unsigned)clr.red +
1028 150.272*(unsigned)clr.green +
1029 36.864*(unsigned)clr.blue);
dc683654
VZ
1030
1031 *pData++ = (unsigned char)((uiColor >> 8) & 0xFF);
1032 if ( iBitDepth == 16 )
a4efa721 1033 *pData++ = (unsigned char)(uiColor & 0xFF);
a4efa721
VZ
1034 }
1035 break;
1036
1037 case wxPNG_TYPE_GREY_RED:
8ee313d2 1038 *pData++ = clr.red;
dc683654 1039 if ( iBitDepth == 16 )
a4efa721
VZ
1040 *pData++ = 0;
1041 break;
8ee313d2
DS
1042
1043 case wxPNG_TYPE_PALETTE:
8529b0b9 1044 *pData++ = (unsigned char) PaletteFind(palette, clr);
8ee313d2 1045 break;
a4efa721
VZ
1046 }
1047
1048 if ( bUseAlpha )
1049 {
1050 unsigned char uchAlpha = 255;
1051 if ( bHasAlpha )
1052 uchAlpha = *pAlpha++;
1053
1054 if ( bHasMask )
e1e36a3e 1055 {
8ee313d2
DS
1056 if ( (clr.red == mask.red)
1057 && (clr.green == mask.green)
1058 && (clr.blue == mask.blue) )
a4efa721 1059 uchAlpha = 0;
e1e36a3e 1060 }
a4efa721
VZ
1061
1062 *pData++ = uchAlpha;
dc683654 1063 if ( iBitDepth == 16 )
a4efa721 1064 *pData++ = 0;
e9c4b1a2 1065 }
e9c4b1a2 1066 }
a4efa721 1067
bdffd806
VS
1068 png_bytep row_ptr = data;
1069 png_write_rows( png_ptr, &row_ptr, 1 );
e9c4b1a2 1070 }
bdffd806
VS
1071
1072 free(data);
1073 png_write_end( png_ptr, info_ptr );
1074 png_destroy_write_struct( &png_ptr, (png_infopp)&info_ptr );
1075
7beb59f3 1076 return true;
e9c4b1a2 1077}
e9c4b1a2 1078
3ca6a5f0
BP
1079#ifdef __VISUALC__
1080 #pragma warning(default:4611)
1081#endif /* VC++ */
1082
9ab6ee85 1083#endif // wxUSE_STREAMS
e9c4b1a2 1084
ccec9093
VZ
1085/*static*/ wxVersionInfo wxPNGHandler::GetLibraryVersionInfo()
1086{
1087 // The version string seems to always have a leading space and a trailing
1088 // new line, get rid of them both.
1089 wxString str = png_get_header_version(NULL) + 1;
1090 str.Replace("\n", "");
1091
1092 return wxVersionInfo("libpng",
1093 PNG_LIBPNG_VER_MAJOR,
1094 PNG_LIBPNG_VER_MINOR,
1095 PNG_LIBPNG_VER_RELEASE,
1096 str);
1097}
1098
9ab6ee85 1099#endif // wxUSE_LIBPNG