]> git.saurik.com Git - wxWidgets.git/blob - src/common/imagpng.cpp
Better compatibility with old files when creating an image cache
[wxWidgets.git] / src / common / imagpng.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/imagpng.cpp
3 // Purpose: wxImage PNG handler
4 // Author: Robert Roebling
5 // RCS-ID: $Id$
6 // Copyright: (c) Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #if wxUSE_IMAGE && wxUSE_LIBPNG
26
27 #include "wx/imagpng.h"
28 #include "wx/versioninfo.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/log.h"
32 #include "wx/intl.h"
33 #include "wx/palette.h"
34 #include "wx/stream.h"
35 #endif
36
37 #include "png.h"
38
39 // For memcpy
40 #include <string.h>
41
42 // ----------------------------------------------------------------------------
43 // constants
44 // ----------------------------------------------------------------------------
45
46 // image cannot have any transparent pixels at all, have only 100% opaque
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
50 enum 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
63 static Transparency
64 CheckTransparency(unsigned char **lines,
65 png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
66 size_t numColBytes);
67
68 // init the alpha channel for the image and fill it with 1s up to (x, y)
69 static 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
72 static void
73 FindMaskColour(unsigned char **lines, png_uint_32 width, png_uint_32 height,
74 unsigned char& rMask, unsigned char& gMask, unsigned char& bMask);
75
76 // is the pixel with this value of alpha a fully opaque one?
77 static inline
78 bool IsOpaque(unsigned char a)
79 {
80 return a == 0xff;
81 }
82
83 // is the pixel with this value of alpha a fully transparent one?
84 static inline
85 bool IsTransparent(unsigned char a)
86 {
87 return !a;
88 }
89
90 // ============================================================================
91 // wxPNGHandler implementation
92 // ============================================================================
93
94 IMPLEMENT_DYNAMIC_CLASS(wxPNGHandler,wxImageHandler)
95
96 #if wxUSE_STREAMS
97
98 #ifndef PNGLINKAGEMODE
99 #ifdef PNGAPI
100 #define PNGLINKAGEMODE PNGAPI
101 #elif defined(__WATCOMC__)
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
112 #endif
113
114
115 // VS: wxPNGInfoStruct declared below is a hack that needs some explanation.
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
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.
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
125 // only for I/O routines and is set with png_set_read_fn or
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
129 struct wxPNGInfoStruct
130 {
131 jmp_buf jmpbuf;
132 bool verbose;
133
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
143 // ----------------------------------------------------------------------------
144 // helper functions
145 // ----------------------------------------------------------------------------
146
147 extern "C"
148 {
149
150 static void PNGLINKAGEMODE wx_PNG_stream_reader( png_structp png_ptr, png_bytep data,
151 png_size_t length )
152 {
153 WX_PNG_INFO(png_ptr)->stream.in->Read(data, length);
154 }
155
156 static void PNGLINKAGEMODE wx_PNG_stream_writer( png_structp png_ptr, png_bytep data,
157 png_size_t length )
158 {
159 WX_PNG_INFO(png_ptr)->stream.out->Write(data, length);
160 }
161
162 static void
163 PNGLINKAGEMODE wx_png_warning(png_structp png_ptr, png_const_charp message)
164 {
165 wxPNGInfoStruct *info = png_ptr ? WX_PNG_INFO(png_ptr) : NULL;
166 if ( !info || info->verbose )
167 {
168 wxLogWarning( wxString::FromAscii(message) );
169 }
170 }
171
172 // from pngerror.c
173 // so that the libpng doesn't send anything on stderr
174 static void
175 PNGLINKAGEMODE wx_png_error(png_structp png_ptr, png_const_charp message)
176 {
177 wx_png_warning(NULL, message);
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);
183 }
184
185 } // extern "C"
186
187 // ----------------------------------------------------------------------------
188 // LoadFile() helpers
189 // ----------------------------------------------------------------------------
190
191 // determine the kind of transparency we need for this image: if the only alpha
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
195 //
196 // parameters:
197 // lines raw PNG data
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)
202 Transparency
203 CheckTransparency(unsigned char **lines,
204 png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
205 size_t numColBytes)
206 {
207 // suppose that a mask will suffice and check all the remaining alpha
208 // values to see if it does
209 for ( ; y < h; y++ )
210 {
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));
214
215 for ( png_uint_32 x2 = x; x2 < w; x2++ )
216 {
217 // skip the grey or colour byte(s)
218 ptr += numColBytes;
219
220 unsigned char a2 = *ptr++;
221
222 if ( !IsTransparent(a2) && !IsOpaque(a2) )
223 {
224 // not fully opaque nor fully transparent, hence need alpha
225 return Transparency_Alpha;
226 }
227 }
228
229 // during the next loop iteration check all the pixels in the row
230 x = 0;
231 }
232
233 // mask will be enough
234 return Transparency_Mask;
235 }
236
237 unsigned 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
245 png_uint_32 end = y * image->GetWidth() + x;
246 for ( png_uint_32 i = 0; i < end; i++ )
247 {
248 // all the previous pixels were opaque
249 *alpha++ = 0xff;
250 }
251
252 return alpha;
253 }
254
255 void
256 FindMaskColour(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++;
275 ++p; // jump over alpha
276
277 wxImageHistogramEntry&
278 entry = h[wxImageHistogram:: MakeKey(r2, g2, b2)];
279
280 if ( entry.value++ == 0 )
281 entry.index = nentries++;
282 }
283 }
284
285 if ( !h.FindFirstUnusedColour(&rMask, &gMask, &bMask) )
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
302 bool wxPNGHandler::DoCanRead( wxInputStream& stream )
303 {
304 unsigned char hdr[4];
305
306 if ( !stream.Read(hdr, WXSIZEOF(hdr)) ) // it's ok to modify the stream position here
307 return false;
308
309 return memcmp(hdr, "\211PNG", WXSIZEOF(hdr)) == 0;
310 }
311
312 // convert data from RGB to wxImage format
313 static
314 void 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
345 if ( !IsOpaque(a) && transparency == Transparency_None )
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
351 transparency = CheckTransparency
352 (
353 lines,
354 x, y,
355 width, height,
356 1
357 );
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 {
377 case Transparency_Mask:
378 if ( IsTransparent(a) )
379 {
380 *ptrDst++ = rMask;
381 *ptrDst++ = gMask;
382 *ptrDst++ = bMask;
383 break;
384 }
385 // else: !transparent
386
387 // must be opaque then as otherwise we shouldn't be
388 // using the mask at all
389 wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
390
391 // fall through
392
393 case Transparency_Alpha:
394 if ( alpha )
395 *alpha++ = a;
396 // fall through
397
398 case Transparency_None:
399 *ptrDst++ = g;
400 *ptrDst++ = g;
401 *ptrDst++ = g;
402 break;
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
421 if ( !IsOpaque(a) && transparency == Transparency_None )
422 {
423 transparency = CheckTransparency
424 (
425 lines,
426 x, y,
427 width, height,
428 3
429 );
430
431 if ( transparency == Transparency_Mask )
432 {
433 FindMaskColour(lines, width, height,
434 rMask, gMask, bMask);
435 }
436 else // transparency == Transparency_Alpha
437 {
438 alpha = InitAlpha(image, x, y);
439 }
440
441 }
442
443 switch ( transparency )
444 {
445 case Transparency_Mask:
446 if ( IsTransparent(a) )
447 {
448 *ptrDst++ = rMask;
449 *ptrDst++ = gMask;
450 *ptrDst++ = bMask;
451 break;
452 }
453 else // !transparent
454 {
455 // must be opaque then as otherwise we shouldn't be
456 // using the mask at all
457 wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
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 }
469
470 // fall through
471
472 case Transparency_Alpha:
473 if ( alpha )
474 *alpha++ = a;
475 // fall through
476
477 case Transparency_None:
478 *ptrDst++ = r;
479 *ptrDst++ = g;
480 *ptrDst++ = b;
481 break;
482 }
483 }
484 }
485 }
486
487 if ( transparency == Transparency_Mask )
488 {
489 image->SetMaskColour(rMask, gMask, bMask);
490 }
491 }
492
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
499 bool
500 wxPNGHandler::LoadFile(wxImage *image,
501 wxInputStream& stream,
502 bool verbose,
503 int WXUNUSED(index))
504 {
505 // VZ: as this function uses setjmp() the only fool-proof error handling
506 // method is to use goto (setjmp is not really C++ dtors friendly...)
507
508 unsigned char **lines = NULL;
509 png_infop info_ptr = (png_infop) NULL;
510 wxPNGInfoStruct wxinfo;
511
512 png_uint_32 i, width, height = 0;
513 int bit_depth, color_type, interlace_type;
514
515 wxinfo.verbose = verbose;
516 wxinfo.stream.in = &stream;
517
518 image->Destroy();
519
520 png_structp png_ptr = png_create_read_struct
521 (
522 PNG_LIBPNG_VER_STRING,
523 NULL,
524 wx_png_error,
525 wx_png_warning
526 );
527 if (!png_ptr)
528 goto error;
529
530 // NB: please see the comment near wxPNGInfoStruct declaration for
531 // explanation why this line is mandatory
532 png_set_read_fn( png_ptr, &wxinfo, wx_PNG_stream_reader);
533
534 info_ptr = png_create_info_struct( png_ptr );
535 if (!info_ptr)
536 goto error;
537
538 if (setjmp(wxinfo.jmpbuf))
539 goto error;
540
541 png_read_info( png_ptr, info_ptr );
542 png_get_IHDR( png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL );
543
544 if (color_type == PNG_COLOR_TYPE_PALETTE)
545 png_set_expand( png_ptr );
546
547 // Fix for Bug [ 439207 ] Monochrome PNG images come up black
548 if (bit_depth < 8)
549 png_set_expand( png_ptr );
550
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 );
556
557 image->Create((int)width, (int)height, (bool) false /* no need to init pixels */);
558
559 if (!image->IsOk())
560 goto error;
561
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 *));
565 if ( !lines )
566 goto error;
567
568 for (i = 0; i < height; i++)
569 {
570 if ((lines[i] = (unsigned char *)malloc( (size_t)(width * (sizeof(unsigned char) * 4)))) == NULL)
571 goto error;
572 }
573
574 png_read_image( png_ptr, lines );
575 png_read_end( png_ptr, info_ptr );
576
577 #if wxUSE_PALETTE
578 if (color_type == PNG_COLOR_TYPE_PALETTE)
579 {
580 png_colorp palette = NULL;
581 int numPalette = 0;
582
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++)
590 {
591 r[j] = palette[j].red;
592 g[j] = palette[j].green;
593 b[j] = palette[j].blue;
594 }
595
596 image->SetPalette(wxPalette(numPalette, r, g, b));
597 delete[] r;
598 delete[] g;
599 delete[] b;
600 }
601 #endif // wxUSE_PALETTE
602
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
644 png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp) NULL );
645
646 // loaded successfully, now init wxImage with this data
647 CopyDataFromPNG(image, lines, width, height, color_type);
648
649 for ( i = 0; i < height; i++ )
650 free( lines[i] );
651 free( lines );
652
653 return true;
654
655 error:
656 if (verbose)
657 {
658 wxLogError(_("Couldn't load a PNG image - file is corrupted or not enough memory."));
659 }
660
661 if ( image->IsOk() )
662 {
663 image->Destroy();
664 }
665
666 if ( lines )
667 {
668 for ( unsigned int n = 0; n < height; n++ )
669 free( lines[n] );
670
671 free( lines );
672 }
673
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 }
684 return false;
685 }
686
687 // ----------------------------------------------------------------------------
688 // SaveFile() palette helpers
689 // ----------------------------------------------------------------------------
690
691 typedef wxLongToLongHashMap PaletteMap;
692
693 static unsigned long PaletteMakeKey(const png_color_8& clr)
694 {
695 return (wxImageHistogram::MakeKey(clr.red, clr.green, clr.blue) << 8) | clr.alpha;
696 }
697
698 static 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
706 static 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 }
724
725 // ----------------------------------------------------------------------------
726 // writing PNGs
727 // ----------------------------------------------------------------------------
728
729 bool wxPNGHandler::SaveFile( wxImage *image, wxOutputStream& stream, bool verbose )
730 {
731 wxPNGInfoStruct wxinfo;
732
733 wxinfo.verbose = verbose;
734 wxinfo.stream.out = &stream;
735
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 );
743 if (!png_ptr)
744 {
745 if (verbose)
746 {
747 wxLogError(_("Couldn't save PNG image."));
748 }
749 return false;
750 }
751
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)
757 {
758 wxLogError(_("Couldn't save PNG image."));
759 }
760 return false;
761 }
762
763 if (setjmp(wxinfo.jmpbuf))
764 {
765 png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
766 if (verbose)
767 {
768 wxLogError(_("Couldn't save PNG image."));
769 }
770 return false;
771 }
772
773 // NB: please see the comment near wxPNGInfoStruct declaration for
774 // explanation why this line is mandatory
775 png_set_write_fn( png_ptr, &wxinfo, wx_PNG_stream_writer, NULL);
776
777 const int iHeight = image->GetHeight();
778 const int iWidth = image->GetWidth();
779
780 const bool bHasPngFormatOption
781 = image->HasOption(wxIMAGE_OPTION_PNG_FORMAT);
782
783 int iColorType = bHasPngFormatOption
784 ? image->GetOptionInt(wxIMAGE_OPTION_PNG_FORMAT)
785 : wxPNG_TYPE_COLOUR;
786
787 bool bHasAlpha = image->HasAlpha();
788 bool bHasMask = image->HasMask();
789
790 bool bUsePalette = iColorType == wxPNG_TYPE_PALETTE
791 #if wxUSE_PALETTE
792 || (!bHasPngFormatOption && image->HasPalette() )
793 #endif
794 ;
795
796 png_color_8 mask = { 0, 0, 0, 0, 0 };
797
798 if (bHasMask)
799 {
800 mask.red = image->GetMaskRed();
801 mask.green = image->GetMaskGreen();
802 mask.blue = image->GetMaskBlue();
803 }
804
805 PaletteMap palette;
806
807 if (bUsePalette)
808 {
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)
816 {
817 // Mask must be first
818 PaletteAdd(&palette, mask);
819 }
820
821 for (int y = 0; y < iHeight; y++)
822 {
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 }
866 }
867 }
868
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)
874 {
875 iColorType = wxPNG_TYPE_COLOUR;
876 }
877
878 bool bUseAlpha = !bUsePalette && (bHasAlpha || bHasMask);
879
880 int iPngColorType;
881
882 if (bUsePalette)
883 {
884 iPngColorType = PNG_COLOR_TYPE_PALETTE;
885 iColorType = wxPNG_TYPE_PALETTE;
886 }
887 else if ( iColorType==wxPNG_TYPE_COLOUR )
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 }
897
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
913 int iBitDepth = !bUsePalette && image->HasOption(wxIMAGE_OPTION_PNG_BITDEPTH)
914 ? image->GetOptionInt(wxIMAGE_OPTION_PNG_BITDEPTH)
915 : 8;
916
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;
923 png_color_8 sig_bit;
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
938 if ( bUseAlpha )
939 {
940 sig_bit.alpha = (png_byte)iBitDepth;
941 iElements++;
942 }
943
944 if ( iBitDepth == 16 )
945 iElements *= 2;
946
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:
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 }
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:
968 wxFAIL_MSG( wxT("unsupported image resolution units") );
969 }
970
971 if ( resX && resY )
972 png_set_pHYs( png_ptr, info_ptr, resX, resY, PNG_RESOLUTION_METER );
973
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 );
978
979 unsigned char *
980 data = (unsigned char *)malloc( image->GetWidth() * iElements );
981 if ( !data )
982 {
983 png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
984 return false;
985 }
986
987 const unsigned char *
988 pAlpha = (const unsigned char *)(bHasAlpha ? image->GetAlpha() : NULL);
989
990 const unsigned char *pColors = image->GetData();
991
992 for (int y = 0; y != iHeight; ++y)
993 {
994 unsigned char *pData = data;
995 for (int x = 0; x != iWidth; x++)
996 {
997 png_color_8 clr;
998 clr.red = *pColors++;
999 clr.green = *pColors++;
1000 clr.blue = *pColors++;
1001 clr.gray = 0;
1002 clr.alpha = (bUsePalette && pAlpha) ? *pAlpha++ : 0; // use with wxPNG_TYPE_PALETTE only
1003
1004 switch ( iColorType )
1005 {
1006 default:
1007 wxFAIL_MSG( wxT("unknown wxPNG_TYPE_XXX") );
1008 // fall through
1009
1010 case wxPNG_TYPE_COLOUR:
1011 *pData++ = clr.red;
1012 if ( iBitDepth == 16 )
1013 *pData++ = 0;
1014 *pData++ = clr.green;
1015 if ( iBitDepth == 16 )
1016 *pData++ = 0;
1017 *pData++ = clr.blue;
1018 if ( iBitDepth == 16 )
1019 *pData++ = 0;
1020 break;
1021
1022 case wxPNG_TYPE_GREY:
1023 {
1024 // where do these coefficients come from? maybe we
1025 // should have image options for them as well?
1026 unsigned uiColor =
1027 (unsigned) (76.544*(unsigned)clr.red +
1028 150.272*(unsigned)clr.green +
1029 36.864*(unsigned)clr.blue);
1030
1031 *pData++ = (unsigned char)((uiColor >> 8) & 0xFF);
1032 if ( iBitDepth == 16 )
1033 *pData++ = (unsigned char)(uiColor & 0xFF);
1034 }
1035 break;
1036
1037 case wxPNG_TYPE_GREY_RED:
1038 *pData++ = clr.red;
1039 if ( iBitDepth == 16 )
1040 *pData++ = 0;
1041 break;
1042
1043 case wxPNG_TYPE_PALETTE:
1044 *pData++ = (unsigned char) PaletteFind(palette, clr);
1045 break;
1046 }
1047
1048 if ( bUseAlpha )
1049 {
1050 unsigned char uchAlpha = 255;
1051 if ( bHasAlpha )
1052 uchAlpha = *pAlpha++;
1053
1054 if ( bHasMask )
1055 {
1056 if ( (clr.red == mask.red)
1057 && (clr.green == mask.green)
1058 && (clr.blue == mask.blue) )
1059 uchAlpha = 0;
1060 }
1061
1062 *pData++ = uchAlpha;
1063 if ( iBitDepth == 16 )
1064 *pData++ = 0;
1065 }
1066 }
1067
1068 png_bytep row_ptr = data;
1069 png_write_rows( png_ptr, &row_ptr, 1 );
1070 }
1071
1072 free(data);
1073 png_write_end( png_ptr, info_ptr );
1074 png_destroy_write_struct( &png_ptr, (png_infopp)&info_ptr );
1075
1076 return true;
1077 }
1078
1079 #ifdef __VISUALC__
1080 #pragma warning(default:4611)
1081 #endif /* VC++ */
1082
1083 #endif // wxUSE_STREAMS
1084
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
1099 #endif // wxUSE_LIBPNG