// Name: src/common/imagpng.cpp
// Purpose: wxImage PNG handler
// Author: Robert Roebling
-// RCS-ID: $Id$
// Copyright: (c) Robert Roebling
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// constants
// ----------------------------------------------------------------------------
-// image can not have any transparent pixels at all, have only 100% opaque
+// image cannot have any transparent pixels at all, have only 100% opaque
// and/or 100% transparent pixels in which case a simple mask is enough to
// store this information in wxImage or have a real alpha channel in which case
// we need to have it in wxImage as well
// First, let me describe what's the problem: libpng uses jmp_buf in
// its png_struct structure. Unfortunately, this structure is
// compiler-specific and may vary in size, so if you use libpng compiled
-// as DLL with another compiler than the main executable, it may not work
-// (this is for example the case with wxMGL port and SciTech MGL library
-// that provides custom runtime-loadable libpng implementation with jmpbuf
-// disabled altogether). Luckily, it is still possible to use setjmp() &
-// longjmp() as long as the structure is not part of png_struct.
+// as DLL with another compiler than the main executable, it may not work.
+// Luckily, it is still possible to use setjmp() & longjmp() as long as the
+// structure is not part of png_struct.
//
// Sadly, there's no clean way to attach user-defined data to png_struct.
// There is only one customizable place, png_struct.io_ptr, which is meant
}
static void
-PNGLINKAGEMODE wx_png_warning(png_structp png_ptr, png_const_charp message)
+PNGLINKAGEMODE wx_PNG_warning(png_structp png_ptr, png_const_charp message)
{
wxPNGInfoStruct *info = png_ptr ? WX_PNG_INFO(png_ptr) : NULL;
if ( !info || info->verbose )
// from pngerror.c
// so that the libpng doesn't send anything on stderr
static void
-PNGLINKAGEMODE wx_png_error(png_structp png_ptr, png_const_charp message)
+PNGLINKAGEMODE wx_PNG_error(png_structp png_ptr, png_const_charp message)
{
- wx_png_warning(NULL, message);
+ wx_PNG_warning(NULL, message);
// we're not using libpng built-in jump buffer (see comment before
// wxPNGInfoStruct above) so we have to return ourselves, otherwise libpng
png_structp png_ptr = png_create_read_struct
(
PNG_LIBPNG_VER_STRING,
- (voidp) NULL,
- wx_png_error,
- wx_png_warning
+ NULL,
+ wx_PNG_error,
+ wx_PNG_warning
);
if (!png_ptr)
goto error;
image->Create((int)width, (int)height, (bool) false /* no need to init pixels */);
- if (!image->Ok())
+ if (!image->IsOk())
goto error;
// initialize all line pointers to NULL to ensure that they can be safely
for (i = 0; i < height; i++)
{
- if ((lines[i] = (unsigned char *)malloc( (size_t)(width * (sizeof(unsigned char) * 4)))) == NULL)
+ if ((lines[i] = (unsigned char *)malloc( (size_t)(width * 4))) == NULL)
goto error;
}
}
#endif // wxUSE_PALETTE
+
+ // set the image resolution if it's available
+ png_uint_32 resX, resY;
+ int unitType;
+ if (png_get_pHYs(png_ptr, info_ptr, &resX, &resY, &unitType)
+ == PNG_INFO_pHYs)
+ {
+ wxImageResolution res = wxIMAGE_RESOLUTION_CM;
+
+ switch (unitType)
+ {
+ default:
+ wxLogWarning(_("Unknown PNG resolution unit %d"), unitType);
+ // fall through
+
+ case PNG_RESOLUTION_UNKNOWN:
+ image->SetOption(wxIMAGE_OPTION_RESOLUTIONX, resX);
+ image->SetOption(wxIMAGE_OPTION_RESOLUTIONY, resY);
+
+ res = wxIMAGE_RESOLUTION_NONE;
+ break;
+
+ case PNG_RESOLUTION_METER:
+ /*
+ Convert meters to centimeters.
+ Use a string to not lose precision (converting to cm and then
+ to inch would result in integer rounding error).
+ If an app wants an int, GetOptionInt will convert and round
+ down for them.
+ */
+ image->SetOption(wxIMAGE_OPTION_RESOLUTIONX,
+ wxString::FromCDouble((double) resX / 100.0, 2));
+ image->SetOption(wxIMAGE_OPTION_RESOLUTIONY,
+ wxString::FromCDouble((double) resY / 100.0, 2));
+ break;
+ }
+
+ image->SetOption(wxIMAGE_OPTION_RESOLUTIONUNIT, res);
+ }
+
+
png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp) NULL );
// loaded successfully, now init wxImage with this data
wxLogError(_("Couldn't load a PNG image - file is corrupted or not enough memory."));
}
- if ( image->Ok() )
+ if ( image->IsOk() )
{
image->Destroy();
}
}
// ----------------------------------------------------------------------------
-// SaveFile() helpers
+// SaveFile() palette helpers
// ----------------------------------------------------------------------------
-static int PaletteFind(const png_color& clr,
- const png_color *pal, int palCount)
+typedef wxLongToLongHashMap PaletteMap;
+
+static unsigned long PaletteMakeKey(const png_color_8& clr)
+{
+ return (wxImageHistogram::MakeKey(clr.red, clr.green, clr.blue) << 8) | clr.alpha;
+}
+
+static long PaletteFind(const PaletteMap& palette, const png_color_8& clr)
{
- for (int i = 0; i < palCount; ++i)
- {
- if ( (clr.red == pal[i].red)
- && (clr.green == pal[i].green)
- && (clr.blue == pal[i].blue))
- {
- return i;
- }
- }
-
- return wxNOT_FOUND;
+ unsigned long value = PaletteMakeKey(clr);
+ PaletteMap::const_iterator it = palette.find(value);
+
+ return (it != palette.end()) ? it->second : wxNOT_FOUND;
+}
+
+static long PaletteAdd(PaletteMap *palette, const png_color_8& clr)
+{
+ unsigned long value = PaletteMakeKey(clr);
+ PaletteMap::const_iterator it = palette->find(value);
+ size_t index;
+
+ if (it == palette->end())
+ {
+ index = palette->size();
+ (*palette)[value] = index;
+ }
+ else
+ {
+ index = it->second;
+ }
+
+ return index;
}
// ----------------------------------------------------------------------------
(
PNG_LIBPNG_VER_STRING,
NULL,
- wx_png_error,
- wx_png_warning
+ wx_PNG_error,
+ wx_PNG_warning
);
if (!png_ptr)
{
// explanation why this line is mandatory
png_set_write_fn( png_ptr, &wxinfo, wx_PNG_stream_writer, NULL);
+ const int iHeight = image->GetHeight();
+ const int iWidth = image->GetWidth();
+
const bool bHasPngFormatOption
= image->HasOption(wxIMAGE_OPTION_PNG_FORMAT);
bool bHasAlpha = image->HasAlpha();
bool bHasMask = image->HasMask();
+ bool bUsePalette = iColorType == wxPNG_TYPE_PALETTE
#if wxUSE_PALETTE
- /*
- Only save as an indexed image if the number of palette entries does not
- exceed libpng's limit (256).
- We assume here that we will need an extra palette entry if there's an
- alpha or mask, regardless of whether a possibly needed conversion from
- alpha to a mask fails (unlikely), or whether the mask colour already
- can be found in the palette (more likely). In the latter case an extra
- palette entry would not be required later on and the image could actually
- be saved as a palettised PNG (instead now it will be saved as true colour).
- A little bit of precision is lost, but at the benefit of a lot more
- simplified code.
- */
- bool bUsePalette =
- (!bHasPngFormatOption || iColorType == wxPNG_TYPE_PALETTE)
- && image->HasPalette()
- && image->GetPalette().GetColoursCount()
- + ((bHasAlpha || bHasMask) ? 1 : 0) <= PNG_MAX_PALETTE_LENGTH;
-
- wxImage temp_image(*image);
- if (bUsePalette && image->HasAlpha() && !bHasMask)
+ || (!bHasPngFormatOption && image->HasPalette() )
+#endif
+ ;
+
+ png_color_8 mask = { 0, 0, 0, 0, 0 };
+
+ if (bHasMask)
+ {
+ mask.red = image->GetMaskRed();
+ mask.green = image->GetMaskGreen();
+ mask.blue = image->GetMaskBlue();
+ }
+
+ PaletteMap palette;
+
+ if (bUsePalette)
{
- /*
- Only convert alpha to mask if saving as a palettised image was
- explicitly requested. We don't want to lose alpha's precision
- by converting to a mask just to be able to save palettised.
- */
- if (iColorType == wxPNG_TYPE_PALETTE
- && temp_image.ConvertAlphaToMask())
+ png_color png_rgb [PNG_MAX_PALETTE_LENGTH];
+ png_byte png_trans[PNG_MAX_PALETTE_LENGTH];
+
+ const unsigned char *pColors = image->GetData();
+ const unsigned char* pAlpha = image->GetAlpha();
+
+ if (bHasMask && !pAlpha)
{
- image = &temp_image;
- bHasMask = true;
- bHasAlpha = image->HasAlpha();
+ // Mask must be first
+ PaletteAdd(&palette, mask);
}
- else
+
+ for (int y = 0; y < iHeight; y++)
+ {
+ for (int x = 0; x < iWidth; x++)
+ {
+ png_color_8 rgba;
+
+ rgba.red = *pColors++;
+ rgba.green = *pColors++;
+ rgba.blue = *pColors++;
+ rgba.gray = 0;
+ rgba.alpha = (pAlpha && !bHasMask) ? *pAlpha++ : 0;
+
+ // save in our palette
+ long index = PaletteAdd(&palette, rgba);
+
+ if (index < PNG_MAX_PALETTE_LENGTH)
+ {
+ // save in libpng's palette
+ png_rgb[index].red = rgba.red;
+ png_rgb[index].green = rgba.green;
+ png_rgb[index].blue = rgba.blue;
+ png_trans[index] = rgba.alpha;
+ }
+ else
+ {
+ bUsePalette = false;
+ break;
+ }
+ }
+ }
+
+ if (bUsePalette)
{
- bUsePalette = false;
- iColorType = wxPNG_TYPE_COLOUR;
+ png_set_PLTE(png_ptr, info_ptr, png_rgb, palette.size());
+
+ if (bHasMask && !pAlpha)
+ {
+ wxASSERT(PaletteFind(palette, mask) == 0);
+ png_trans[0] = 0;
+ png_set_tRNS(png_ptr, info_ptr, png_trans, 1, NULL);
+ }
+ else if (pAlpha && !bHasMask)
+ {
+ png_set_tRNS(png_ptr, info_ptr, png_trans, palette.size(), NULL);
+ }
}
}
-#else
- bool bUsePalette = false;
-#endif // wxUSE_PALETTE
-
- bool bUseAlpha = !bUsePalette && (bHasAlpha || bHasMask);
- png_color mask;
- if (bHasMask)
+ /*
+ If saving palettised was requested but it was decided we can't use a
+ palette then reset the colour type to RGB.
+ */
+ if (!bUsePalette && iColorType == wxPNG_TYPE_PALETTE)
{
- mask.red = image->GetMaskRed();
- mask.green = image->GetMaskGreen();
- mask.blue = image->GetMaskBlue();
+ iColorType = wxPNG_TYPE_COLOUR;
}
+ bool bUseAlpha = !bUsePalette && (bHasAlpha || bHasMask);
int iPngColorType;
-#if wxUSE_PALETTE
if (bUsePalette)
{
iPngColorType = PNG_COLOR_TYPE_PALETTE;
iColorType = wxPNG_TYPE_PALETTE;
}
- else
-#endif // wxUSE_PALETTE
- if ( iColorType==wxPNG_TYPE_COLOUR )
+ else if ( iColorType==wxPNG_TYPE_COLOUR )
{
iPngColorType = bUseAlpha ? PNG_COLOR_TYPE_RGB_ALPHA
: PNG_COLOR_TYPE_RGB;
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
-#if wxUSE_PALETTE
- png_colorp palette = NULL;
- int numPalette = 0;
-
- if (bUsePalette)
- {
- const wxPalette& pal = image->GetPalette();
- const int palCount = pal.GetColoursCount();
- palette = (png_colorp) malloc(
- (palCount + 1 /*headroom for trans */) * sizeof(png_color));
-
- if (!palette)
- {
- png_destroy_write_struct( &png_ptr, (png_infopp)NULL );
- if (verbose)
- {
- wxLogError(_("Couldn't save PNG image."));
- }
- return false;
- }
-
- for (int i = 0; i < palCount; ++i)
- {
- pal.GetRGB(i, &palette[i].red, &palette[i].green, &palette[i].blue);
- }
-
- numPalette = palCount;
- if (bHasMask)
- {
- int index = PaletteFind(mask, palette, numPalette);
-
- if (index)
- {
- if (index == wxNOT_FOUND)
- {
- numPalette++;
- index = palCount;
- palette[index] = mask;
- }
-
- wxSwap(palette[0], palette[index]);
- }
-
- png_byte trans = 0;
- png_set_tRNS(png_ptr, info_ptr, &trans, 1, NULL);
- }
-
- png_set_PLTE(png_ptr, info_ptr, palette, numPalette);
- free (palette);
- palette = NULL;
-
- // Let palette point to libpng's copy of the palette.
- (void) png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
- }
-#endif // wxUSE_PALETTE
-
int iElements;
png_color_8 sig_bit;
iElements = 1;
}
- if ( iPngColorType & PNG_COLOR_MASK_ALPHA )
+ if ( bUseAlpha )
{
sig_bit.alpha = (png_byte)iBitDepth;
iElements++;
return false;
}
- unsigned char *
- pAlpha = (unsigned char *)(bHasAlpha ? image->GetAlpha() : NULL);
- int iHeight = image->GetHeight();
- int iWidth = image->GetWidth();
+ const unsigned char *
+ pAlpha = (const unsigned char *)(bHasAlpha ? image->GetAlpha() : NULL);
- unsigned char *pColors = image->GetData();
+ const unsigned char *pColors = image->GetData();
for (int y = 0; y != iHeight; ++y)
{
unsigned char *pData = data;
for (int x = 0; x != iWidth; x++)
{
- png_color clr;
+ png_color_8 clr;
clr.red = *pColors++;
clr.green = *pColors++;
clr.blue = *pColors++;
+ clr.gray = 0;
+ clr.alpha = (bUsePalette && pAlpha) ? *pAlpha++ : 0; // use with wxPNG_TYPE_PALETTE only
switch ( iColorType )
{
break;
case wxPNG_TYPE_PALETTE:
- *pData++ = (unsigned char) PaletteFind(clr,
- palette, numPalette);
+ *pData++ = (unsigned char) PaletteFind(palette, clr);
break;
}