Don't convert alpha channel to mask when loading PNG images

This seemed like a good idea many years ago when plenty of code (inside and
outside of wxWidgets) couldn't deal with the real alpha correctly, but this is
not the case since quite some time any more and producing an image without
alpha channel when loading a PNG image with transparency is more surprising
than useful now, so don't do it any more.

Closes #3019.
This commit is contained in:
Vadim Zeitlin 2016-02-08 02:25:20 +01:00
parent 526a627fa8
commit f844b45815

View File

@ -38,40 +38,13 @@
// For memcpy
#include <string.h>
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
// 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
enum Transparency
{
Transparency_None,
Transparency_Mask,
Transparency_Alpha
};
// ----------------------------------------------------------------------------
// local functions
// ----------------------------------------------------------------------------
// return the kind of transparency needed for this image assuming that it does
// have transparent pixels, i.e. either Transparency_Alpha or Transparency_Mask
static Transparency
CheckTransparency(unsigned char **lines,
png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
size_t numColBytes);
// init the alpha channel for the image and fill it with 1s up to (x, y)
static unsigned char *InitAlpha(wxImage *image, png_uint_32 x, png_uint_32 y);
// find a free colour for the mask in the PNG data array
static void
FindMaskColour(unsigned char **lines, png_uint_32 width, png_uint_32 height,
unsigned char& rMask, unsigned char& gMask, unsigned char& bMask);
// is the pixel with this value of alpha a fully opaque one?
static inline
bool IsOpaque(unsigned char a)
@ -179,52 +152,6 @@ PNGLINKAGEMODE wx_PNG_error(png_structp png_ptr, png_const_charp message)
// LoadFile() helpers
// ----------------------------------------------------------------------------
// determine the kind of transparency we need for this image: if the only alpha
// values it has are 0 (transparent) and 0xff (opaque) then we can simply
// create a mask for it, we should be ok with a simple mask but otherwise we
// need a full blown alpha channel in wxImage
//
// parameters:
// lines raw PNG data
// x, y starting position
// w, h size of the image
// numColBytes number of colour bytes (1 for grey scale, 3 for RGB)
// (NB: alpha always follows the colour bytes)
Transparency
CheckTransparency(unsigned char **lines,
png_uint_32 x, png_uint_32 y, png_uint_32 w, png_uint_32 h,
size_t numColBytes)
{
// suppose that a mask will suffice and check all the remaining alpha
// values to see if it does
for ( ; y < h; y++ )
{
// each pixel is numColBytes+1 bytes, offset into the current line by
// the current x position
unsigned const char *ptr = lines[y] + (x * (numColBytes + 1));
for ( png_uint_32 x2 = x; x2 < w; x2++ )
{
// skip the grey or colour byte(s)
ptr += numColBytes;
unsigned char a2 = *ptr++;
if ( !IsTransparent(a2) && !IsOpaque(a2) )
{
// not fully opaque nor fully transparent, hence need alpha
return Transparency_Alpha;
}
}
// during the next loop iteration check all the pixels in the row
x = 0;
}
// mask will be enough
return Transparency_Mask;
}
unsigned char *InitAlpha(wxImage *image, png_uint_32 x, png_uint_32 y)
{
// create alpha channel
@ -243,49 +170,6 @@ unsigned char *InitAlpha(wxImage *image, png_uint_32 x, png_uint_32 y)
return alpha;
}
void
FindMaskColour(unsigned char **lines, png_uint_32 width, png_uint_32 height,
unsigned char& rMask, unsigned char& gMask, unsigned char& bMask)
{
// choosing the colour for the mask is more
// difficult: we need to iterate over the entire
// image for this in order to choose an unused
// colour (this is not very efficient but what else
// can we do?)
wxImageHistogram h;
unsigned nentries = 0;
unsigned char r2, g2, b2;
for ( png_uint_32 y2 = 0; y2 < height; y2++ )
{
const unsigned char *p = lines[y2];
for ( png_uint_32 x2 = 0; x2 < width; x2++ )
{
r2 = *p++;
g2 = *p++;
b2 = *p++;
++p; // jump over alpha
wxImageHistogramEntry&
entry = h[wxImageHistogram:: MakeKey(r2, g2, b2)];
if ( entry.value++ == 0 )
entry.index = nentries++;
}
}
if ( !h.FindFirstUnusedColour(&rMask, &gMask, &bMask) )
{
wxLogWarning(_("Too many colours in PNG, the image may be slightly blurred."));
// use a fixed mask colour and we'll fudge
// the real pixels with this colour (see
// below)
rMask = 0xfe;
gMask = 0;
bMask = 0xff;
}
}
// ----------------------------------------------------------------------------
// reading PNGs
// ----------------------------------------------------------------------------
@ -308,17 +192,9 @@ void CopyDataFromPNG(wxImage *image,
png_uint_32 height,
int color_type)
{
Transparency transparency = Transparency_None;
// only non NULL if transparency == Transparency_Alpha
// allocated on demand if we have any non-opaque pixels
unsigned char *alpha = NULL;
// RGB of the mask colour if transparency == Transparency_Mask
// (but init them anyhow to avoid compiler warnings)
unsigned char rMask = 0,
gMask = 0,
bMask = 0;
unsigned char *ptrDst = image->GetData();
if ( !(color_type & PNG_COLOR_MASK_COLOR) )
{
@ -332,66 +208,16 @@ void CopyDataFromPNG(wxImage *image,
unsigned char a = *ptrSrc++;
// the first time we encounter a transparent pixel we must
// decide about what to do about them
if ( !IsOpaque(a) && transparency == Transparency_None )
{
// we'll need at least the mask for this image and
// maybe even full alpha channel info: the former is
// only enough if we have alpha values of 0 and 0xff
// only, otherwisewe need the latter
transparency = CheckTransparency
(
lines,
x, y,
width, height,
1
);
// allocate alpha channel for the image
if ( !IsOpaque(a) && !alpha )
alpha = InitAlpha(image, x, y);
if ( transparency == Transparency_Mask )
{
// let's choose this colour for the mask: this is
// not a problem here as all the other pixels are
// grey, i.e. R == G == B which is not the case for
// this one so no confusion is possible
rMask = 0xff;
gMask = 0;
bMask = 0xff;
}
else // transparency == Transparency_Alpha
{
alpha = InitAlpha(image, x, y);
}
}
if ( alpha )
*alpha++ = a;
switch ( transparency )
{
case Transparency_Mask:
if ( IsTransparent(a) )
{
*ptrDst++ = rMask;
*ptrDst++ = gMask;
*ptrDst++ = bMask;
break;
}
// else: !transparent
// must be opaque then as otherwise we shouldn't be
// using the mask at all
wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
wxFALLTHROUGH;
case Transparency_Alpha:
if ( alpha )
*alpha++ = a;
wxFALLTHROUGH;
case Transparency_None:
*ptrDst++ = g;
*ptrDst++ = g;
*ptrDst++ = g;
break;
}
*ptrDst++ = g;
*ptrDst++ = g;
*ptrDst++ = g;
}
}
}
@ -407,78 +233,19 @@ void CopyDataFromPNG(wxImage *image,
unsigned char b = *ptrSrc++;
unsigned char a = *ptrSrc++;
// the logic here is the same as for the grey case except
// where noted
if ( !IsOpaque(a) && transparency == Transparency_None )
{
transparency = CheckTransparency
(
lines,
x, y,
width, height,
3
);
// the logic here is the same as for the grey case
if ( !IsOpaque(a) && !alpha )
alpha = InitAlpha(image, x, y);
if ( transparency == Transparency_Mask )
{
FindMaskColour(lines, width, height,
rMask, gMask, bMask);
}
else // transparency == Transparency_Alpha
{
alpha = InitAlpha(image, x, y);
}
if ( alpha )
*alpha++ = a;
}
switch ( transparency )
{
case Transparency_Mask:
if ( IsTransparent(a) )
{
*ptrDst++ = rMask;
*ptrDst++ = gMask;
*ptrDst++ = bMask;
break;
}
else // !transparent
{
// must be opaque then as otherwise we shouldn't be
// using the mask at all
wxASSERT_MSG( IsOpaque(a), wxT("logic error") );
// if we couldn't find a unique colour for the
// mask, we can have real pixels with the same
// value as the mask and it's better to slightly
// change their colour than to make them
// transparent
if ( r == rMask && g == gMask && b == bMask )
{
r++;
}
}
wxFALLTHROUGH;
case Transparency_Alpha:
if ( alpha )
*alpha++ = a;
wxFALLTHROUGH;
case Transparency_None:
*ptrDst++ = r;
*ptrDst++ = g;
*ptrDst++ = b;
break;
}
*ptrDst++ = r;
*ptrDst++ = g;
*ptrDst++ = b;
}
}
}
if ( transparency == Transparency_Mask )
{
image->SetMaskColour(rMask, gMask, bMask);
}
}
// temporarily disable the warning C4611 (interaction between '_setjmp' and