wxQuantize workaround

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
Troels
Experienced Solver
Experienced Solver
Posts: 79
Joined: Fri Jan 07, 2005 12:02 pm
Location: Denmark

wxQuantize workaround

Post by Troels »

The docs doesn't mention it, but wxQuantize works well only with photo-like images (jpeg). You may feel tempted to call wxQuantize with simple computer-made images (png,xpm), to obtain the palettes from such images. Unfortunately, this will typically produce
- inaccurate palettes, holding slighty changed RGB-values
- uncalled-for dithering in the returned image, rendering it useless (unless a photo)

- wxImage_Quantize() below gives you a much better chance of receiving sensible palettes and images back from different types of images, as it will bypass wxQuantize unless really needed.
- The implementation is somewhat hampered by wx not providing the wxColourArray class, sorely missed (together with wxBitmapArray and wxIconArray - wxImageArray exists but is somewhat hidden away). Also, the helper function wxColourArray_ToPalette may hurt your eyes (the newnewnew + delete[]delete[]delete[] construct is tremendously ugly)
- Compiles with VC/gcc/MinGW, wxMSW/wxGTK, 2.8.9/2.9
- More info:
http://trac.wxwidgets.org/ticket/4539
http://trac.wxwidgets.org/ticket/9765

Code: Select all

class wxImage;
extern bool wxImage_Quantize(wxImage* image, size_t desiredNoColours, 
                             unsigned char** eightBitData = NULL, size_t colorcount = 0);

//////////////////////////////////////////////////

#include <wx/wx.h>
#include <wx/quantize.h>
#include <wx/arrimpl.cpp>

#if wxUSE_STL
#include <vector>
#include <algorithm>
class wxColourArray : public std::vector<wxColour>
{
public:
   size_t index(const wxColour& color)
   {
      return std::distance(begin(), std::find(begin(), end(), color));
   }
};
#else
#include <wx/dynarray.h>
WX_DECLARE_OBJARRAY(wxColour, wxColourArrayBase);
class wxColourArray : public wxColourArrayBase
{
public:
   void clear() { Clear(); }
   const wxColour& at(size_t index) const { return Item(index); }

// According to docs wxObjArray.Index() is not working  -
// and indeed it isn't. Roll our own...
   size_t index(const wxColour& color)
   {
      size_t i;
      for (i = 0; i < GetCount(); i++)
      {
         if (at(i) == color) break;
      }
      return i;
   }
};
WX_DEFINE_OBJARRAY(wxColourArrayBase)
#endif

#if wxUSE_PALETTE
size_t wxColourArray_ToPalette(const wxColourArray& array, wxPalette* pal)
{
   const size_t count = array.size();
   unsigned char* r = new unsigned char[count];
   unsigned char* g = new unsigned char[count];
   unsigned char* b = new unsigned char[count];

   for (size_t i = 0; i < count; i++)
   {
      const wxColour& col = array.at(i);
      r[i] = col.Red();
      g[i] = col.Green();
      b[i] = col.Blue();
   }

   pal->operator=(wxPalette(count, r, g, b));
   wxDELETEA(r);
   wxDELETEA(g);
   wxDELETEA(b);
   return count;
}
#endif

bool wxImage_Quantize(wxImage* image, size_t desiredNoColours, 
                      unsigned char** eightBitData, size_t colorcount)
{
   bool ok = true;
   wxPalette* pal = NULL;

   if (0 == colorcount) colorcount = image->CountColours(); // expensive

   // Only do quantizing if absolutely required -
   // wxQuantize works well for jpeg-type photo quality images
   // but is known to ruin images of other types
   if (colorcount > desiredNoColours)
   {
      // reduce BPP
      wxImage temp;
      ok = wxQuantize::Quantize(*image, temp, &pal, desiredNoColours,
           eightBitData, wxQUANTIZE_FILL_DESTINATION_IMAGE
           | (eightBitData ? wxQUANTIZE_RETURN_8BIT_DATA : 0)
           );
      if (ok)
      {
         image->operator=(temp);
      }
   }
   else if (colorcount <= 256)
   {
      // Quantizing not required -
      // just create and attach corresponding palette
      wxColourArray array;

      const int width = image->GetWidth();
      const int height = image->GetHeight();

      if (eightBitData) *eightBitData = new wxByte[width*height];

      wxByte* bits = image->GetData();
      for (int j = 0; j < height; j++)
      {
         for (int i = 0; i < width; i++)
         {
            const int index     = j*width + i;
            const int index_rgb = index*3;
            const wxColour color(
               bits[index_rgb + 0],
               bits[index_rgb + 1],


               bits[index_rgb + 2]
               );
            const size_t pal_index = array.index(color);
            if (pal_index == array.size())
            {
               array.push_back(color);
            }
            if (eightBitData) (*eightBitData)[index] = (wxByte)pal_index;
         }
      }
      wxASSERT(array.size() == colorcount);
#if wxUSE_PALETTE
      pal = new wxPalette;
      wxColourArray_ToPalette(array, pal);
#endif // wxUSE_PALETTE/!wxUSE_PALETTE
   }
#if wxUSE_PALETTE
   if (pal)
   {
      if (ok) image->SetPalette(*pal);
      wxDELETE(pal);
   }
#endif // wxUSE_PALETTE/!wxUSE_PALETTE
   return ok;
}
Post Reply