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