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