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