Files
wxWidgets/src/common/image.cpp
Paul Cornett 632b58cdce Rewrite ConvertToGreyscale() and ConvertToDisabled()
They do almost the same thing, so use almost the same code.
Fixes ConvertToDisabled() modifying its own data.
Closes #15389


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@74625 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2013-08-06 06:05:38 +00:00

3660 lines
105 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/common/image.cpp
// Purpose: wxImage
// Author: Robert Roebling
// Copyright: (c) Robert Roebling
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_IMAGE
#include "wx/image.h"
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/hash.h"
#include "wx/utils.h"
#include "wx/math.h"
#include "wx/module.h"
#include "wx/palette.h"
#include "wx/intl.h"
#include "wx/colour.h"
#endif
#include "wx/wfstream.h"
#include "wx/xpmdecod.h"
// For memcpy
#include <string.h>
// make the code compile with either wxFile*Stream or wxFFile*Stream:
#define HAS_FILE_STREAMS (wxUSE_STREAMS && (wxUSE_FILE || wxUSE_FFILE))
#if HAS_FILE_STREAMS
#if wxUSE_FFILE
typedef wxFFileInputStream wxImageFileInputStream;
typedef wxFFileOutputStream wxImageFileOutputStream;
#elif wxUSE_FILE
typedef wxFileInputStream wxImageFileInputStream;
typedef wxFileOutputStream wxImageFileOutputStream;
#endif // wxUSE_FILE/wxUSE_FFILE
#endif // HAS_FILE_STREAMS
#if wxUSE_VARIANT
IMPLEMENT_VARIANT_OBJECT_EXPORTED_SHALLOWCMP(wxImage,WXDLLEXPORT)
#endif
//-----------------------------------------------------------------------------
// global data
//-----------------------------------------------------------------------------
wxList wxImage::sm_handlers;
wxImage wxNullImage;
//-----------------------------------------------------------------------------
// wxImageRefData
//-----------------------------------------------------------------------------
class wxImageRefData: public wxObjectRefData
{
public:
wxImageRefData();
virtual ~wxImageRefData();
int m_width;
int m_height;
wxBitmapType m_type;
unsigned char *m_data;
bool m_hasMask;
unsigned char m_maskRed,m_maskGreen,m_maskBlue;
// alpha channel data, may be NULL for the formats without alpha support
unsigned char *m_alpha;
bool m_ok;
// if true, m_data is pointer to static data and shouldn't be freed
bool m_static;
// same as m_static but for m_alpha
bool m_staticAlpha;
#if wxUSE_PALETTE
wxPalette m_palette;
#endif // wxUSE_PALETTE
wxArrayString m_optionNames;
wxArrayString m_optionValues;
wxDECLARE_NO_COPY_CLASS(wxImageRefData);
};
wxImageRefData::wxImageRefData()
{
m_width = 0;
m_height = 0;
m_type = wxBITMAP_TYPE_INVALID;
m_data =
m_alpha = (unsigned char *) NULL;
m_maskRed = 0;
m_maskGreen = 0;
m_maskBlue = 0;
m_hasMask = false;
m_ok = false;
m_static =
m_staticAlpha = false;
}
wxImageRefData::~wxImageRefData()
{
if ( !m_static )
free( m_data );
if ( !m_staticAlpha )
free( m_alpha );
}
//-----------------------------------------------------------------------------
// wxImage
//-----------------------------------------------------------------------------
#define M_IMGDATA static_cast<wxImageRefData*>(m_refData)
IMPLEMENT_DYNAMIC_CLASS(wxImage, wxObject)
bool wxImage::Create(const char* const* xpmData)
{
#if wxUSE_XPM
UnRef();
wxXPMDecoder decoder;
(*this) = decoder.ReadData(xpmData);
return IsOk();
#else
wxUnusedVar(xpmData);
return false;
#endif
}
bool wxImage::Create( int width, int height, bool clear )
{
UnRef();
m_refData = new wxImageRefData();
M_IMGDATA->m_data = (unsigned char *) malloc( width*height*3 );
if (!M_IMGDATA->m_data)
{
UnRef();
return false;
}
M_IMGDATA->m_width = width;
M_IMGDATA->m_height = height;
M_IMGDATA->m_ok = true;
if (clear)
{
Clear();
}
return true;
}
bool wxImage::Create( int width, int height, unsigned char* data, bool static_data )
{
UnRef();
wxCHECK_MSG( data, false, wxT("NULL data in wxImage::Create") );
m_refData = new wxImageRefData();
M_IMGDATA->m_data = data;
M_IMGDATA->m_width = width;
M_IMGDATA->m_height = height;
M_IMGDATA->m_ok = true;
M_IMGDATA->m_static = static_data;
return true;
}
bool wxImage::Create( int width, int height, unsigned char* data, unsigned char* alpha, bool static_data )
{
UnRef();
wxCHECK_MSG( data, false, wxT("NULL data in wxImage::Create") );
m_refData = new wxImageRefData();
M_IMGDATA->m_data = data;
M_IMGDATA->m_alpha = alpha;
M_IMGDATA->m_width = width;
M_IMGDATA->m_height = height;
M_IMGDATA->m_ok = true;
M_IMGDATA->m_static = static_data;
M_IMGDATA->m_staticAlpha = static_data;
return true;
}
void wxImage::Destroy()
{
UnRef();
}
void wxImage::Clear(unsigned char value)
{
memset(M_IMGDATA->m_data, value, M_IMGDATA->m_width*M_IMGDATA->m_height*3);
}
wxObjectRefData* wxImage::CreateRefData() const
{
return new wxImageRefData;
}
wxObjectRefData* wxImage::CloneRefData(const wxObjectRefData* that) const
{
const wxImageRefData* refData = static_cast<const wxImageRefData*>(that);
wxCHECK_MSG(refData->m_ok, NULL, wxT("invalid image") );
wxImageRefData* refData_new = new wxImageRefData;
refData_new->m_width = refData->m_width;
refData_new->m_height = refData->m_height;
refData_new->m_maskRed = refData->m_maskRed;
refData_new->m_maskGreen = refData->m_maskGreen;
refData_new->m_maskBlue = refData->m_maskBlue;
refData_new->m_hasMask = refData->m_hasMask;
refData_new->m_ok = true;
unsigned size = unsigned(refData->m_width) * unsigned(refData->m_height);
if (refData->m_alpha != NULL)
{
refData_new->m_alpha = (unsigned char*)malloc(size);
memcpy(refData_new->m_alpha, refData->m_alpha, size);
}
size *= 3;
refData_new->m_data = (unsigned char*)malloc(size);
memcpy(refData_new->m_data, refData->m_data, size);
#if wxUSE_PALETTE
refData_new->m_palette = refData->m_palette;
#endif
refData_new->m_optionNames = refData->m_optionNames;
refData_new->m_optionValues = refData->m_optionValues;
return refData_new;
}
// returns a new image with the same dimensions, alpha, and mask as *this
// if on_its_side is true, width and height are swapped
wxImage wxImage::MakeEmptyClone(int flags) const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxS("invalid image") );
long height = M_IMGDATA->m_height;
long width = M_IMGDATA->m_width;
if ( flags & Clone_SwapOrientation )
wxSwap( width, height );
if ( !image.Create( width, height, false ) )
{
wxFAIL_MSG( wxS("unable to create image") );
return image;
}
if ( M_IMGDATA->m_alpha )
{
image.SetAlpha();
wxCHECK2_MSG( image.GetAlpha(), return wxImage(),
wxS("unable to create alpha channel") );
}
if ( M_IMGDATA->m_hasMask )
{
image.SetMaskColour( M_IMGDATA->m_maskRed,
M_IMGDATA->m_maskGreen,
M_IMGDATA->m_maskBlue );
}
return image;
}
wxImage wxImage::Copy() const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
image.m_refData = CloneRefData(m_refData);
return image;
}
wxImage wxImage::ShrinkBy( int xFactor , int yFactor ) const
{
if( xFactor == 1 && yFactor == 1 )
return *this;
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
// can't scale to/from 0 size
wxCHECK_MSG( (xFactor > 0) && (yFactor > 0), image,
wxT("invalid new image size") );
long old_height = M_IMGDATA->m_height,
old_width = M_IMGDATA->m_width;
wxCHECK_MSG( (old_height > 0) && (old_width > 0), image,
wxT("invalid old image size") );
long width = old_width / xFactor ;
long height = old_height / yFactor ;
image.Create( width, height, false );
char unsigned *data = image.GetData();
wxCHECK_MSG( data, image, wxT("unable to create image") );
bool hasMask = false ;
unsigned char maskRed = 0;
unsigned char maskGreen = 0;
unsigned char maskBlue = 0 ;
const unsigned char *source_data = M_IMGDATA->m_data;
unsigned char *target_data = data;
const unsigned char *source_alpha = 0 ;
unsigned char *target_alpha = 0 ;
if (M_IMGDATA->m_hasMask)
{
hasMask = true ;
maskRed = M_IMGDATA->m_maskRed;
maskGreen = M_IMGDATA->m_maskGreen;
maskBlue =M_IMGDATA->m_maskBlue ;
image.SetMaskColour( M_IMGDATA->m_maskRed,
M_IMGDATA->m_maskGreen,
M_IMGDATA->m_maskBlue );
}
else
{
source_alpha = M_IMGDATA->m_alpha ;
if ( source_alpha )
{
image.SetAlpha() ;
target_alpha = image.GetAlpha() ;
}
}
for (long y = 0; y < height; y++)
{
for (long x = 0; x < width; x++)
{
unsigned long avgRed = 0 ;
unsigned long avgGreen = 0;
unsigned long avgBlue = 0;
unsigned long avgAlpha = 0 ;
unsigned long counter = 0 ;
// determine average
for ( int y1 = 0 ; y1 < yFactor ; ++y1 )
{
long y_offset = (y * yFactor + y1) * old_width;
for ( int x1 = 0 ; x1 < xFactor ; ++x1 )
{
const unsigned char *pixel = source_data + 3 * ( y_offset + x * xFactor + x1 ) ;
unsigned char red = pixel[0] ;
unsigned char green = pixel[1] ;
unsigned char blue = pixel[2] ;
unsigned char alpha = 255 ;
if ( source_alpha )
alpha = *(source_alpha + y_offset + x * xFactor + x1) ;
if ( !hasMask || red != maskRed || green != maskGreen || blue != maskBlue )
{
if ( alpha > 0 )
{
avgRed += red ;
avgGreen += green ;
avgBlue += blue ;
}
avgAlpha += alpha ;
counter++ ;
}
}
}
if ( counter == 0 )
{
*(target_data++) = M_IMGDATA->m_maskRed ;
*(target_data++) = M_IMGDATA->m_maskGreen ;
*(target_data++) = M_IMGDATA->m_maskBlue ;
}
else
{
if ( source_alpha )
*(target_alpha++) = (unsigned char)(avgAlpha / counter ) ;
*(target_data++) = (unsigned char)(avgRed / counter);
*(target_data++) = (unsigned char)(avgGreen / counter);
*(target_data++) = (unsigned char)(avgBlue / counter);
}
}
}
// In case this is a cursor, make sure the hotspot is scaled accordingly:
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_X) )
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X,
(GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_X))/xFactor);
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y) )
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y,
(GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_Y))/yFactor);
return image;
}
wxImage
wxImage::Scale( int width, int height, wxImageResizeQuality quality ) const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
// can't scale to/from 0 size
wxCHECK_MSG( (width > 0) && (height > 0), image,
wxT("invalid new image size") );
long old_height = M_IMGDATA->m_height,
old_width = M_IMGDATA->m_width;
wxCHECK_MSG( (old_height > 0) && (old_width > 0), image,
wxT("invalid old image size") );
// If the image's new width and height are the same as the original, no
// need to waste time or CPU cycles
if ( old_width == width && old_height == height )
return *this;
// Resample the image using the method as specified.
switch ( quality )
{
case wxIMAGE_QUALITY_NEAREST:
if ( old_width % width == 0 && old_width >= width &&
old_height % height == 0 && old_height >= height )
{
return ShrinkBy( old_width / width , old_height / height );
}
image = ResampleNearest(width, height);
break;
case wxIMAGE_QUALITY_BILINEAR:
image = ResampleBilinear(width, height);
break;
case wxIMAGE_QUALITY_BICUBIC:
image = ResampleBicubic(width, height);
break;
case wxIMAGE_QUALITY_BOX_AVERAGE:
image = ResampleBox(width, height);
break;
case wxIMAGE_QUALITY_HIGH:
image = width < old_width && height < old_height
? ResampleBox(width, height)
: ResampleBicubic(width, height);
break;
}
// If the original image has a mask, apply the mask to the new image
if (M_IMGDATA->m_hasMask)
{
image.SetMaskColour( M_IMGDATA->m_maskRed,
M_IMGDATA->m_maskGreen,
M_IMGDATA->m_maskBlue );
}
// In case this is a cursor, make sure the hotspot is scaled accordingly:
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_X) )
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X,
(GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_X)*width)/old_width);
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y) )
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y,
(GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_Y)*height)/old_height);
return image;
}
wxImage wxImage::ResampleNearest(int width, int height) const
{
wxImage image;
image.Create( width, height, false );
unsigned char *data = image.GetData();
wxCHECK_MSG( data, image, wxT("unable to create image") );
const unsigned char *source_data = M_IMGDATA->m_data;
unsigned char *target_data = data;
const unsigned char *source_alpha = 0 ;
unsigned char *target_alpha = 0 ;
if ( !M_IMGDATA->m_hasMask )
{
source_alpha = M_IMGDATA->m_alpha ;
if ( source_alpha )
{
image.SetAlpha() ;
target_alpha = image.GetAlpha() ;
}
}
long old_height = M_IMGDATA->m_height,
old_width = M_IMGDATA->m_width;
long x_delta = (old_width<<16) / width;
long y_delta = (old_height<<16) / height;
unsigned char* dest_pixel = target_data;
long y = 0;
for ( long j = 0; j < height; j++ )
{
const unsigned char* src_line = &source_data[(y>>16)*old_width*3];
const unsigned char* src_alpha_line = source_alpha ? &source_alpha[(y>>16)*old_width] : 0 ;
long x = 0;
for ( long i = 0; i < width; i++ )
{
const unsigned char* src_pixel = &src_line[(x>>16)*3];
const unsigned char* src_alpha_pixel = source_alpha ? &src_alpha_line[(x>>16)] : 0 ;
dest_pixel[0] = src_pixel[0];
dest_pixel[1] = src_pixel[1];
dest_pixel[2] = src_pixel[2];
dest_pixel += 3;
if ( source_alpha )
*(target_alpha++) = *src_alpha_pixel ;
x += x_delta;
}
y += y_delta;
}
return image;
}
namespace
{
struct BoxPrecalc
{
int boxStart;
int boxEnd;
};
inline int BoxBetween(int value, int low, int high)
{
return wxMax(wxMin(value, high), low);
}
void ResampleBoxPrecalc(wxVector<BoxPrecalc>& boxes, int oldDim)
{
const int newDim = boxes.size();
const double scale_factor_1 = double(oldDim) / newDim;
const int scale_factor_2 = (int)(scale_factor_1 / 2);
for ( int dst = 0; dst < newDim; ++dst )
{
// Source pixel in the Y direction
const int src_p = int(dst * scale_factor_1);
BoxPrecalc& precalc = boxes[dst];
precalc.boxStart = BoxBetween(int(src_p - scale_factor_1/2.0 + 1),
0, oldDim - 1);
precalc.boxEnd = BoxBetween(wxMax(precalc.boxStart + 1,
int(src_p + scale_factor_2)),
0, oldDim - 1);
}
}
} // anonymous namespace
wxImage wxImage::ResampleBox(int width, int height) const
{
// This function implements a simple pre-blur/box averaging method for
// downsampling that gives reasonably smooth results To scale the image
// down we will need to gather a grid of pixels of the size of the scale
// factor in each direction and then do an averaging of the pixels.
wxImage ret_image(width, height, false);
wxVector<BoxPrecalc> vPrecalcs(height);
wxVector<BoxPrecalc> hPrecalcs(width);
ResampleBoxPrecalc(vPrecalcs, M_IMGDATA->m_height);
ResampleBoxPrecalc(hPrecalcs, M_IMGDATA->m_width);
const unsigned char* src_data = M_IMGDATA->m_data;
const unsigned char* src_alpha = M_IMGDATA->m_alpha;
unsigned char* dst_data = ret_image.GetData();
unsigned char* dst_alpha = NULL;
if ( src_alpha )
{
ret_image.SetAlpha();
dst_alpha = ret_image.GetAlpha();
}
int averaged_pixels, src_pixel_index;
double sum_r, sum_g, sum_b, sum_a;
for ( int y = 0; y < height; y++ ) // Destination image - Y direction
{
// Source pixel in the Y direction
const BoxPrecalc& vPrecalc = vPrecalcs[y];
for ( int x = 0; x < width; x++ ) // Destination image - X direction
{
// Source pixel in the X direction
const BoxPrecalc& hPrecalc = hPrecalcs[x];
// Box of pixels to average
averaged_pixels = 0;
sum_r = sum_g = sum_b = sum_a = 0.0;
for ( int j = vPrecalc.boxStart; j <= vPrecalc.boxEnd; ++j )
{
for ( int i = hPrecalc.boxStart; i <= hPrecalc.boxEnd; ++i )
{
// Calculate the actual index in our source pixels
src_pixel_index = j * M_IMGDATA->m_width + i;
sum_r += src_data[src_pixel_index * 3 + 0];
sum_g += src_data[src_pixel_index * 3 + 1];
sum_b += src_data[src_pixel_index * 3 + 2];
if ( src_alpha )
sum_a += src_alpha[src_pixel_index];
averaged_pixels++;
}
}
// Calculate the average from the sum and number of averaged pixels
dst_data[0] = (unsigned char)(sum_r / averaged_pixels);
dst_data[1] = (unsigned char)(sum_g / averaged_pixels);
dst_data[2] = (unsigned char)(sum_b / averaged_pixels);
dst_data += 3;
if ( src_alpha )
*dst_alpha++ = (unsigned char)(sum_a / averaged_pixels);
}
}
return ret_image;
}
namespace
{
struct BilinearPrecalc
{
int offset1;
int offset2;
double dd;
double dd1;
};
void ResampleBilinearPrecalc(wxVector<BilinearPrecalc>& precalcs, int oldDim)
{
const int newDim = precalcs.size();
const double scale_factor = double(oldDim) / newDim;
const int srcpixmax = oldDim - 1;
for ( int dsty = 0; dsty < newDim; dsty++ )
{
// We need to calculate the source pixel to interpolate from - Y-axis
double srcpix = double(dsty) * scale_factor;
double srcpix1 = int(srcpix);
double srcpix2 = srcpix1 == srcpixmax ? srcpix1 : srcpix1 + 1.0;
BilinearPrecalc& precalc = precalcs[dsty];
precalc.dd = srcpix - (int)srcpix;
precalc.dd1 = 1.0 - precalc.dd;
precalc.offset1 = srcpix1 < 0.0
? 0
: srcpix1 > srcpixmax
? srcpixmax
: (int)srcpix1;
precalc.offset2 = srcpix2 < 0.0
? 0
: srcpix2 > srcpixmax
? srcpixmax
: (int)srcpix2;
}
}
} // anonymous namespace
wxImage wxImage::ResampleBilinear(int width, int height) const
{
// This function implements a Bilinear algorithm for resampling.
wxImage ret_image(width, height, false);
const unsigned char* src_data = M_IMGDATA->m_data;
const unsigned char* src_alpha = M_IMGDATA->m_alpha;
unsigned char* dst_data = ret_image.GetData();
unsigned char* dst_alpha = NULL;
if ( src_alpha )
{
ret_image.SetAlpha();
dst_alpha = ret_image.GetAlpha();
}
wxVector<BilinearPrecalc> vPrecalcs(height);
wxVector<BilinearPrecalc> hPrecalcs(width);
ResampleBilinearPrecalc(vPrecalcs, M_IMGDATA->m_height);
ResampleBilinearPrecalc(hPrecalcs, M_IMGDATA->m_width);
// initialize alpha values to avoid g++ warnings about possibly
// uninitialized variables
double r1, g1, b1, a1 = 0;
double r2, g2, b2, a2 = 0;
for ( int dsty = 0; dsty < height; dsty++ )
{
// We need to calculate the source pixel to interpolate from - Y-axis
const BilinearPrecalc& vPrecalc = vPrecalcs[dsty];
const int y_offset1 = vPrecalc.offset1;
const int y_offset2 = vPrecalc.offset2;
const double dy = vPrecalc.dd;
const double dy1 = vPrecalc.dd1;
for ( int dstx = 0; dstx < width; dstx++ )
{
// X-axis of pixel to interpolate from
const BilinearPrecalc& hPrecalc = hPrecalcs[dstx];
const int x_offset1 = hPrecalc.offset1;
const int x_offset2 = hPrecalc.offset2;
const double dx = hPrecalc.dd;
const double dx1 = hPrecalc.dd1;
int src_pixel_index00 = y_offset1 * M_IMGDATA->m_width + x_offset1;
int src_pixel_index01 = y_offset1 * M_IMGDATA->m_width + x_offset2;
int src_pixel_index10 = y_offset2 * M_IMGDATA->m_width + x_offset1;
int src_pixel_index11 = y_offset2 * M_IMGDATA->m_width + x_offset2;
// first line
r1 = src_data[src_pixel_index00 * 3 + 0] * dx1 + src_data[src_pixel_index01 * 3 + 0] * dx;
g1 = src_data[src_pixel_index00 * 3 + 1] * dx1 + src_data[src_pixel_index01 * 3 + 1] * dx;
b1 = src_data[src_pixel_index00 * 3 + 2] * dx1 + src_data[src_pixel_index01 * 3 + 2] * dx;
if ( src_alpha )
a1 = src_alpha[src_pixel_index00] * dx1 + src_alpha[src_pixel_index01] * dx;
// second line
r2 = src_data[src_pixel_index10 * 3 + 0] * dx1 + src_data[src_pixel_index11 * 3 + 0] * dx;
g2 = src_data[src_pixel_index10 * 3 + 1] * dx1 + src_data[src_pixel_index11 * 3 + 1] * dx;
b2 = src_data[src_pixel_index10 * 3 + 2] * dx1 + src_data[src_pixel_index11 * 3 + 2] * dx;
if ( src_alpha )
a2 = src_alpha[src_pixel_index10] * dx1 + src_alpha[src_pixel_index11] * dx;
// result lines
dst_data[0] = static_cast<unsigned char>(r1 * dy1 + r2 * dy);
dst_data[1] = static_cast<unsigned char>(g1 * dy1 + g2 * dy);
dst_data[2] = static_cast<unsigned char>(b1 * dy1 + b2 * dy);
dst_data += 3;
if ( src_alpha )
*dst_alpha++ = static_cast<unsigned char>(a1 * dy1 + a2 * dy);
}
}
return ret_image;
}
// The following two local functions are for the B-spline weighting of the
// bicubic sampling algorithm
static inline double spline_cube(double value)
{
return value <= 0.0 ? 0.0 : value * value * value;
}
static inline double spline_weight(double value)
{
return (spline_cube(value + 2) -
4 * spline_cube(value + 1) +
6 * spline_cube(value) -
4 * spline_cube(value - 1)) / 6;
}
namespace
{
struct BicubicPrecalc
{
double weight[4];
int offset[4];
};
void ResampleBicubicPrecalc(wxVector<BicubicPrecalc> &aWeight, int oldDim)
{
const int newDim = aWeight.size();
for ( int dstd = 0; dstd < newDim; dstd++ )
{
// We need to calculate the source pixel to interpolate from - Y-axis
const double srcpixd = static_cast<double>(dstd * oldDim) / newDim;
const double dd = srcpixd - static_cast<int>(srcpixd);
BicubicPrecalc &precalc = aWeight[dstd];
for ( int k = -1; k <= 2; k++ )
{
precalc.offset[k + 1] = srcpixd + k < 0.0
? 0
: srcpixd + k >= oldDim
? oldDim - 1
: static_cast<int>(srcpixd + k);
precalc.weight[k + 1] = spline_weight(k - dd);
}
}
}
} // anonymous namespace
// This is the bicubic resampling algorithm
wxImage wxImage::ResampleBicubic(int width, int height) const
{
// This function implements a Bicubic B-Spline algorithm for resampling.
// This method is certainly a little slower than wxImage's default pixel
// replication method, however for most reasonably sized images not being
// upsampled too much on a fairly average CPU this difference is hardly
// noticeable and the results are far more pleasing to look at.
//
// This particular bicubic algorithm does pixel weighting according to a
// B-Spline that basically implements a Gaussian bell-like weighting
// kernel. Because of this method the results may appear a bit blurry when
// upsampling by large factors. This is basically because a slight
// gaussian blur is being performed to get the smooth look of the upsampled
// image.
// Edge pixels: 3-4 possible solutions
// - (Wrap/tile) Wrap the image, take the color value from the opposite
// side of the image.
// - (Mirror) Duplicate edge pixels, so that pixel at coordinate (2, n),
// where n is nonpositive, will have the value of (2, 1).
// - (Ignore) Simply ignore the edge pixels and apply the kernel only to
// pixels which do have all neighbours.
// - (Clamp) Choose the nearest pixel along the border. This takes the
// border pixels and extends them out to infinity.
//
// NOTE: below the y_offset and x_offset variables are being set for edge
// pixels using the "Mirror" method mentioned above
wxImage ret_image;
ret_image.Create(width, height, false);
const unsigned char* src_data = M_IMGDATA->m_data;
const unsigned char* src_alpha = M_IMGDATA->m_alpha;
unsigned char* dst_data = ret_image.GetData();
unsigned char* dst_alpha = NULL;
if ( src_alpha )
{
ret_image.SetAlpha();
dst_alpha = ret_image.GetAlpha();
}
// Precalculate weights
wxVector<BicubicPrecalc> vPrecalcs(height);
wxVector<BicubicPrecalc> hPrecalcs(width);
ResampleBicubicPrecalc(vPrecalcs, M_IMGDATA->m_height);
ResampleBicubicPrecalc(hPrecalcs, M_IMGDATA->m_width);
for ( int dsty = 0; dsty < height; dsty++ )
{
// We need to calculate the source pixel to interpolate from - Y-axis
const BicubicPrecalc& vPrecalc = vPrecalcs[dsty];
for ( int dstx = 0; dstx < width; dstx++ )
{
// X-axis of pixel to interpolate from
const BicubicPrecalc& hPrecalc = hPrecalcs[dstx];
// Sums for each color channel
double sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
// Here we actually determine the RGBA values for the destination pixel
for ( int k = -1; k <= 2; k++ )
{
// Y offset
const int y_offset = vPrecalc.offset[k + 1];
// Loop across the X axis
for ( int i = -1; i <= 2; i++ )
{
// X offset
const int x_offset = hPrecalc.offset[i + 1];
// Calculate the exact position where the source data
// should be pulled from based on the x_offset and y_offset
int src_pixel_index = y_offset*M_IMGDATA->m_width + x_offset;
// Calculate the weight for the specified pixel according
// to the bicubic b-spline kernel we're using for
// interpolation
const double
pixel_weight = vPrecalc.weight[k + 1] * hPrecalc.weight[i + 1];
// Create a sum of all velues for each color channel
// adjusted for the pixel's calculated weight
sum_r += src_data[src_pixel_index * 3 + 0] * pixel_weight;
sum_g += src_data[src_pixel_index * 3 + 1] * pixel_weight;
sum_b += src_data[src_pixel_index * 3 + 2] * pixel_weight;
if ( src_alpha )
sum_a += src_alpha[src_pixel_index] * pixel_weight;
}
}
// Put the data into the destination image. The summed values are
// of double data type and are rounded here for accuracy
dst_data[0] = (unsigned char)(sum_r + 0.5);
dst_data[1] = (unsigned char)(sum_g + 0.5);
dst_data[2] = (unsigned char)(sum_b + 0.5);
dst_data += 3;
if ( src_alpha )
*dst_alpha++ = (unsigned char)sum_a;
}
}
return ret_image;
}
// Blur in the horizontal direction
wxImage wxImage::BlurHorizontal(int blurRadius) const
{
wxImage ret_image(MakeEmptyClone());
wxCHECK( ret_image.IsOk(), ret_image );
const unsigned char* src_data = M_IMGDATA->m_data;
unsigned char* dst_data = ret_image.GetData();
const unsigned char* src_alpha = M_IMGDATA->m_alpha;
unsigned char* dst_alpha = ret_image.GetAlpha();
// number of pixels we average over
const int blurArea = blurRadius*2 + 1;
// Horizontal blurring algorithm - average all pixels in the specified blur
// radius in the X or horizontal direction
for ( int y = 0; y < M_IMGDATA->m_height; y++ )
{
// Variables used in the blurring algorithm
long sum_r = 0,
sum_g = 0,
sum_b = 0,
sum_a = 0;
long pixel_idx;
const unsigned char *src;
unsigned char *dst;
// Calculate the average of all pixels in the blur radius for the first
// pixel of the row
for ( int kernel_x = -blurRadius; kernel_x <= blurRadius; kernel_x++ )
{
// To deal with the pixels at the start of a row so it's not
// grabbing GOK values from memory at negative indices of the
// image's data or grabbing from the previous row
if ( kernel_x < 0 )
pixel_idx = y * M_IMGDATA->m_width;
else
pixel_idx = kernel_x + y * M_IMGDATA->m_width;
src = src_data + pixel_idx*3;
sum_r += src[0];
sum_g += src[1];
sum_b += src[2];
if ( src_alpha )
sum_a += src_alpha[pixel_idx];
}
dst = dst_data + y * M_IMGDATA->m_width*3;
dst[0] = (unsigned char)(sum_r / blurArea);
dst[1] = (unsigned char)(sum_g / blurArea);
dst[2] = (unsigned char)(sum_b / blurArea);
if ( src_alpha )
dst_alpha[y * M_IMGDATA->m_width] = (unsigned char)(sum_a / blurArea);
// Now average the values of the rest of the pixels by just moving the
// blur radius box along the row
for ( int x = 1; x < M_IMGDATA->m_width; x++ )
{
// Take care of edge pixels on the left edge by essentially
// duplicating the edge pixel
if ( x - blurRadius - 1 < 0 )
pixel_idx = y * M_IMGDATA->m_width;
else
pixel_idx = (x - blurRadius - 1) + y * M_IMGDATA->m_width;
// Subtract the value of the pixel at the left side of the blur
// radius box
src = src_data + pixel_idx*3;
sum_r -= src[0];
sum_g -= src[1];
sum_b -= src[2];
if ( src_alpha )
sum_a -= src_alpha[pixel_idx];
// Take care of edge pixels on the right edge
if ( x + blurRadius > M_IMGDATA->m_width - 1 )
pixel_idx = M_IMGDATA->m_width - 1 + y * M_IMGDATA->m_width;
else
pixel_idx = x + blurRadius + y * M_IMGDATA->m_width;
// Add the value of the pixel being added to the end of our box
src = src_data + pixel_idx*3;
sum_r += src[0];
sum_g += src[1];
sum_b += src[2];
if ( src_alpha )
sum_a += src_alpha[pixel_idx];
// Save off the averaged data
dst = dst_data + x*3 + y*M_IMGDATA->m_width*3;
dst[0] = (unsigned char)(sum_r / blurArea);
dst[1] = (unsigned char)(sum_g / blurArea);
dst[2] = (unsigned char)(sum_b / blurArea);
if ( src_alpha )
dst_alpha[x + y * M_IMGDATA->m_width] = (unsigned char)(sum_a / blurArea);
}
}
return ret_image;
}
// Blur in the vertical direction
wxImage wxImage::BlurVertical(int blurRadius) const
{
wxImage ret_image(MakeEmptyClone());
wxCHECK( ret_image.IsOk(), ret_image );
const unsigned char* src_data = M_IMGDATA->m_data;
unsigned char* dst_data = ret_image.GetData();
const unsigned char* src_alpha = M_IMGDATA->m_alpha;
unsigned char* dst_alpha = ret_image.GetAlpha();
// number of pixels we average over
const int blurArea = blurRadius*2 + 1;
// Vertical blurring algorithm - same as horizontal but switched the
// opposite direction
for ( int x = 0; x < M_IMGDATA->m_width; x++ )
{
// Variables used in the blurring algorithm
long sum_r = 0,
sum_g = 0,
sum_b = 0,
sum_a = 0;
long pixel_idx;
const unsigned char *src;
unsigned char *dst;
// Calculate the average of all pixels in our blur radius box for the
// first pixel of the column
for ( int kernel_y = -blurRadius; kernel_y <= blurRadius; kernel_y++ )
{
// To deal with the pixels at the start of a column so it's not
// grabbing GOK values from memory at negative indices of the
// image's data or grabbing from the previous column
if ( kernel_y < 0 )
pixel_idx = x;
else
pixel_idx = x + kernel_y * M_IMGDATA->m_width;
src = src_data + pixel_idx*3;
sum_r += src[0];
sum_g += src[1];
sum_b += src[2];
if ( src_alpha )
sum_a += src_alpha[pixel_idx];
}
dst = dst_data + x*3;
dst[0] = (unsigned char)(sum_r / blurArea);
dst[1] = (unsigned char)(sum_g / blurArea);
dst[2] = (unsigned char)(sum_b / blurArea);
if ( src_alpha )
dst_alpha[x] = (unsigned char)(sum_a / blurArea);
// Now average the values of the rest of the pixels by just moving the
// box along the column from top to bottom
for ( int y = 1; y < M_IMGDATA->m_height; y++ )
{
// Take care of pixels that would be beyond the top edge by
// duplicating the top edge pixel for the column
if ( y - blurRadius - 1 < 0 )
pixel_idx = x;
else
pixel_idx = x + (y - blurRadius - 1) * M_IMGDATA->m_width;
// Subtract the value of the pixel at the top of our blur radius box
src = src_data + pixel_idx*3;
sum_r -= src[0];
sum_g -= src[1];
sum_b -= src[2];
if ( src_alpha )
sum_a -= src_alpha[pixel_idx];
// Take care of the pixels that would be beyond the bottom edge of
// the image similar to the top edge
if ( y + blurRadius > M_IMGDATA->m_height - 1 )
pixel_idx = x + (M_IMGDATA->m_height - 1) * M_IMGDATA->m_width;
else
pixel_idx = x + (blurRadius + y) * M_IMGDATA->m_width;
// Add the value of the pixel being added to the end of our box
src = src_data + pixel_idx*3;
sum_r += src[0];
sum_g += src[1];
sum_b += src[2];
if ( src_alpha )
sum_a += src_alpha[pixel_idx];
// Save off the averaged data
dst = dst_data + (x + y * M_IMGDATA->m_width) * 3;
dst[0] = (unsigned char)(sum_r / blurArea);
dst[1] = (unsigned char)(sum_g / blurArea);
dst[2] = (unsigned char)(sum_b / blurArea);
if ( src_alpha )
dst_alpha[x + y * M_IMGDATA->m_width] = (unsigned char)(sum_a / blurArea);
}
}
return ret_image;
}
// The new blur function
wxImage wxImage::Blur(int blurRadius) const
{
wxImage ret_image;
ret_image.Create(M_IMGDATA->m_width, M_IMGDATA->m_height, false);
// Blur the image in each direction
ret_image = BlurHorizontal(blurRadius);
ret_image = ret_image.BlurVertical(blurRadius);
return ret_image;
}
wxImage wxImage::Rotate90( bool clockwise ) const
{
wxImage image(MakeEmptyClone(Clone_SwapOrientation));
wxCHECK( image.IsOk(), image );
long height = M_IMGDATA->m_height;
long width = M_IMGDATA->m_width;
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_X) )
{
int hot_x = GetOptionInt( wxIMAGE_OPTION_CUR_HOTSPOT_X );
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y,
clockwise ? hot_x : width - 1 - hot_x);
}
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y) )
{
int hot_y = GetOptionInt( wxIMAGE_OPTION_CUR_HOTSPOT_Y );
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X,
clockwise ? height - 1 - hot_y : hot_y);
}
unsigned char *data = image.GetData();
unsigned char *target_data;
// we rotate the image in 21-pixel (63-byte) wide strips
// to make better use of cpu cache - memory transfers
// (note: while much better than single-pixel "strips",
// our vertical strips will still generally straddle 64-byte cachelines)
for (long ii = 0; ii < width; )
{
long next_ii = wxMin(ii + 21, width);
for (long j = 0; j < height; j++)
{
const unsigned char *source_data
= M_IMGDATA->m_data + (j*width + ii)*3;
for (long i = ii; i < next_ii; i++)
{
if ( clockwise )
{
target_data = data + ((i + 1)*height - j - 1)*3;
}
else
{
target_data = data + (height*(width - 1 - i) + j)*3;
}
memcpy( target_data, source_data, 3 );
source_data += 3;
}
}
ii = next_ii;
}
const unsigned char *source_alpha = M_IMGDATA->m_alpha;
if ( source_alpha )
{
unsigned char *alpha_data = image.GetAlpha();
unsigned char *target_alpha = 0 ;
for (long ii = 0; ii < width; )
{
long next_ii = wxMin(ii + 64, width);
for (long j = 0; j < height; j++)
{
source_alpha = M_IMGDATA->m_alpha + j*width + ii;
for (long i = ii; i < next_ii; i++)
{
if ( clockwise )
{
target_alpha = alpha_data + (i+1)*height - j - 1;
}
else
{
target_alpha = alpha_data + height*(width - i - 1) + j;
}
*target_alpha = *source_alpha++;
}
}
ii = next_ii;
}
}
return image;
}
wxImage wxImage::Rotate180() const
{
wxImage image(MakeEmptyClone());
wxCHECK( image.IsOk(), image );
long height = M_IMGDATA->m_height;
long width = M_IMGDATA->m_width;
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_X) )
{
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X,
width - 1 - GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_X));
}
if ( HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y) )
{
image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y,
height - 1 - GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_Y));
}
unsigned char *data = image.GetData();
unsigned char *alpha = image.GetAlpha();
const unsigned char *source_data = M_IMGDATA->m_data;
unsigned char *target_data = data + width * height * 3;
for (long j = 0; j < height; j++)
{
for (long i = 0; i < width; i++)
{
target_data -= 3;
memcpy( target_data, source_data, 3 );
source_data += 3;
}
}
if ( alpha )
{
const unsigned char *src_alpha = M_IMGDATA->m_alpha;
unsigned char *dest_alpha = alpha + width * height;
for (long j = 0; j < height; ++j)
{
for (long i = 0; i < width; ++i)
{
*(--dest_alpha) = *(src_alpha++);
}
}
}
return image;
}
wxImage wxImage::Mirror( bool horizontally ) const
{
wxImage image(MakeEmptyClone());
wxCHECK( image.IsOk(), image );
long height = M_IMGDATA->m_height;
long width = M_IMGDATA->m_width;
unsigned char *data = image.GetData();
unsigned char *alpha = image.GetAlpha();
const unsigned char *source_data = M_IMGDATA->m_data;
unsigned char *target_data;
if (horizontally)
{
for (long j = 0; j < height; j++)
{
data += width*3;
target_data = data-3;
for (long i = 0; i < width; i++)
{
memcpy( target_data, source_data, 3 );
source_data += 3;
target_data -= 3;
}
}
if (alpha != NULL)
{
// src_alpha starts at the first pixel and increases by 1 after each step
// (a step here is the copy of the alpha value of one pixel)
const unsigned char *src_alpha = M_IMGDATA->m_alpha;
// dest_alpha starts just beyond the first line, decreases before each step,
// and after each line is finished, increases by 2 widths (skipping the line
// just copied and the line that will be copied next)
unsigned char *dest_alpha = alpha + width;
for (long jj = 0; jj < height; ++jj)
{
for (long i = 0; i < width; ++i) {
*(--dest_alpha) = *(src_alpha++); // copy one pixel
}
dest_alpha += 2 * width; // advance beyond the end of the next line
}
}
}
else
{
for (long i = 0; i < height; i++)
{
target_data = data + 3*width*(height-1-i);
memcpy( target_data, source_data, (size_t)3*width );
source_data += 3*width;
}
if ( alpha )
{
// src_alpha starts at the first pixel and increases by 1 width after each step
// (a step here is the copy of the alpha channel of an entire line)
const unsigned char *src_alpha = M_IMGDATA->m_alpha;
// dest_alpha starts just beyond the last line (beyond the whole image)
// and decreases by 1 width before each step
unsigned char *dest_alpha = alpha + width * height;
for (long jj = 0; jj < height; ++jj)
{
dest_alpha -= width;
memcpy( dest_alpha, src_alpha, (size_t)width );
src_alpha += width;
}
}
}
return image;
}
wxImage wxImage::GetSubImage( const wxRect &rect ) const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
wxCHECK_MSG( (rect.GetLeft()>=0) && (rect.GetTop()>=0) &&
(rect.GetRight()<=GetWidth()) && (rect.GetBottom()<=GetHeight()),
image, wxT("invalid subimage size") );
const int subwidth = rect.GetWidth();
const int subheight = rect.GetHeight();
image.Create( subwidth, subheight, false );
const unsigned char *src_data = GetData();
const unsigned char *src_alpha = M_IMGDATA->m_alpha;
unsigned char *subdata = image.GetData();
unsigned char *subalpha = NULL;
wxCHECK_MSG( subdata, image, wxT("unable to create image") );
if ( src_alpha ) {
image.SetAlpha();
subalpha = image.GetAlpha();
wxCHECK_MSG( subalpha, image, wxT("unable to create alpha channel"));
}
if (M_IMGDATA->m_hasMask)
image.SetMaskColour( M_IMGDATA->m_maskRed, M_IMGDATA->m_maskGreen, M_IMGDATA->m_maskBlue );
const int width = GetWidth();
const int pixsoff = rect.GetLeft() + width * rect.GetTop();
src_data += 3 * pixsoff;
src_alpha += pixsoff; // won't be used if was NULL, so this is ok
for (long j = 0; j < subheight; ++j)
{
memcpy( subdata, src_data, 3 * subwidth );
subdata += 3 * subwidth;
src_data += 3 * width;
if (subalpha != NULL) {
memcpy( subalpha, src_alpha, subwidth );
subalpha += subwidth;
src_alpha += width;
}
}
return image;
}
wxImage wxImage::Size( const wxSize& size, const wxPoint& pos,
int r_, int g_, int b_ ) const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
wxCHECK_MSG( (size.GetWidth() > 0) && (size.GetHeight() > 0), image, wxT("invalid size") );
int width = GetWidth(), height = GetHeight();
image.Create(size.GetWidth(), size.GetHeight(), false);
unsigned char r = (unsigned char)r_;
unsigned char g = (unsigned char)g_;
unsigned char b = (unsigned char)b_;
if ((r_ == -1) && (g_ == -1) && (b_ == -1))
{
GetOrFindMaskColour( &r, &g, &b );
image.SetMaskColour(r, g, b);
}
image.SetRGB(wxRect(), r, g, b);
// we have two coordinate systems:
// source: starting at 0,0 of source image
// destination starting at 0,0 of destination image
// Documentation says:
// "The image is pasted into a new image [...] at the position pos relative
// to the upper left of the new image." this means the transition rule is:
// "dest coord" = "source coord" + pos;
// calculate the intersection using source coordinates:
wxRect srcRect(0, 0, width, height);
wxRect dstRect(-pos, size);
srcRect.Intersect(dstRect);
if (!srcRect.IsEmpty())
{
// insertion point is needed in destination coordinates.
// NB: it is not always "pos"!
wxPoint ptInsert = srcRect.GetTopLeft() + pos;
if ((srcRect.GetWidth() == width) && (srcRect.GetHeight() == height))
image.Paste(*this, ptInsert.x, ptInsert.y);
else
image.Paste(GetSubImage(srcRect), ptInsert.x, ptInsert.y);
}
return image;
}
void wxImage::Paste( const wxImage &image, int x, int y )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
wxCHECK_RET( image.IsOk(), wxT("invalid image") );
AllocExclusive();
int xx = 0;
int yy = 0;
int width = image.GetWidth();
int height = image.GetHeight();
if (x < 0)
{
xx = -x;
width += x;
}
if (y < 0)
{
yy = -y;
height += y;
}
if ((x+xx)+width > M_IMGDATA->m_width)
width = M_IMGDATA->m_width - (x+xx);
if ((y+yy)+height > M_IMGDATA->m_height)
height = M_IMGDATA->m_height - (y+yy);
if (width < 1) return;
if (height < 1) return;
// If we can, copy the data using memcpy() as this is the fastest way. But
// for this the image being pasted must have "compatible" mask with this
// one meaning that either it must not have one at all or it must use the
// same masked colour.
if ( !image.HasMask() ||
((HasMask() &&
(GetMaskRed()==image.GetMaskRed()) &&
(GetMaskGreen()==image.GetMaskGreen()) &&
(GetMaskBlue()==image.GetMaskBlue()))) )
{
const unsigned char* source_data = image.GetData() + 3*(xx + yy*image.GetWidth());
int source_step = image.GetWidth()*3;
unsigned char* target_data = GetData() + 3*((x+xx) + (y+yy)*M_IMGDATA->m_width);
int target_step = M_IMGDATA->m_width*3;
for (int j = 0; j < height; j++)
{
memcpy( target_data, source_data, width*3 );
source_data += source_step;
target_data += target_step;
}
}
// Copy over the alpha channel from the original image
if ( image.HasAlpha() )
{
if ( !HasAlpha() )
InitAlpha();
const unsigned char* source_data = image.GetAlpha() + xx + yy*image.GetWidth();
int source_step = image.GetWidth();
unsigned char* target_data = GetAlpha() + (x+xx) + (y+yy)*M_IMGDATA->m_width;
int target_step = M_IMGDATA->m_width;
for (int j = 0; j < height; j++,
source_data += source_step,
target_data += target_step)
{
memcpy( target_data, source_data, width );
}
}
if (!HasMask() && image.HasMask())
{
unsigned char r = image.GetMaskRed();
unsigned char g = image.GetMaskGreen();
unsigned char b = image.GetMaskBlue();
const unsigned char* source_data = image.GetData() + 3*(xx + yy*image.GetWidth());
int source_step = image.GetWidth()*3;
unsigned char* target_data = GetData() + 3*((x+xx) + (y+yy)*M_IMGDATA->m_width);
int target_step = M_IMGDATA->m_width*3;
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width*3; i+=3)
{
if ((source_data[i] != r) ||
(source_data[i+1] != g) ||
(source_data[i+2] != b))
{
memcpy( target_data+i, source_data+i, 3 );
}
}
source_data += source_step;
target_data += target_step;
}
}
}
void wxImage::Replace( unsigned char r1, unsigned char g1, unsigned char b1,
unsigned char r2, unsigned char g2, unsigned char b2 )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
unsigned char *data = GetData();
const int w = GetWidth();
const int h = GetHeight();
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++)
{
if ((data[0] == r1) && (data[1] == g1) && (data[2] == b1))
{
data[0] = r2;
data[1] = g2;
data[2] = b2;
}
data += 3;
}
}
wxImage wxImage::ConvertToGreyscale(void) const
{
return ConvertToGreyscale(0.299, 0.587, 0.114);
}
wxImage wxImage::ConvertToGreyscale(double weight_r, double weight_g, double weight_b) const
{
wxImage image;
wxCHECK_MSG(IsOk(), image, "invalid image");
const int w = M_IMGDATA->m_width;
const int h = M_IMGDATA->m_height;
size_t size = size_t(w) * h;
image.Create(w, h, false);
const unsigned char* alpha = M_IMGDATA->m_alpha;
if (alpha)
{
image.SetAlpha();
memcpy(image.GetAlpha(), alpha, size);
}
const unsigned char mask_r = M_IMGDATA->m_maskRed;
const unsigned char mask_g = M_IMGDATA->m_maskGreen;
const unsigned char mask_b = M_IMGDATA->m_maskBlue;
const bool hasMask = M_IMGDATA->m_hasMask;
if (hasMask)
image.SetMaskColour(mask_r, mask_g, mask_b);
const unsigned char* src = M_IMGDATA->m_data;
unsigned char* dst = image.GetData();
while (size--)
{
unsigned char r = *src++;
unsigned char g = *src++;
unsigned char b = *src++;
if (!hasMask || r != mask_r || g != mask_g || b != mask_b)
wxColour::MakeGrey(&r, &g, &b, weight_r, weight_g, weight_b);
*dst++ = r;
*dst++ = g;
*dst++ = b;
}
return image;
}
wxImage wxImage::ConvertToMono( unsigned char r, unsigned char g, unsigned char b ) const
{
wxImage image;
wxCHECK_MSG( IsOk(), image, wxT("invalid image") );
image.Create( M_IMGDATA->m_width, M_IMGDATA->m_height, false );
unsigned char *data = image.GetData();
wxCHECK_MSG( data, image, wxT("unable to create image") );
if (M_IMGDATA->m_hasMask)
{
if (M_IMGDATA->m_maskRed == r && M_IMGDATA->m_maskGreen == g &&
M_IMGDATA->m_maskBlue == b)
image.SetMaskColour( 255, 255, 255 );
else
image.SetMaskColour( 0, 0, 0 );
}
long size = M_IMGDATA->m_height * M_IMGDATA->m_width;
unsigned char *srcd = M_IMGDATA->m_data;
unsigned char *tard = image.GetData();
for ( long i = 0; i < size; i++, srcd += 3, tard += 3 )
{
bool on = (srcd[0] == r) && (srcd[1] == g) && (srcd[2] == b);
wxColourBase::MakeMono(tard + 0, tard + 1, tard + 2, on);
}
return image;
}
wxImage wxImage::ConvertToDisabled(unsigned char brightness) const
{
wxImage image;
wxCHECK_MSG(IsOk(), image, "invalid image");
const int w = M_IMGDATA->m_width;
const int h = M_IMGDATA->m_height;
size_t size = size_t(w) * h;
image.Create(w, h, false);
const unsigned char* alpha = M_IMGDATA->m_alpha;
if (alpha)
{
image.SetAlpha();
memcpy(image.GetAlpha(), alpha, size);
}
const unsigned char mask_r = M_IMGDATA->m_maskRed;
const unsigned char mask_g = M_IMGDATA->m_maskGreen;
const unsigned char mask_b = M_IMGDATA->m_maskBlue;
const bool hasMask = M_IMGDATA->m_hasMask;
if (hasMask)
image.SetMaskColour(mask_r, mask_g, mask_b);
const unsigned char* src = M_IMGDATA->m_data;
unsigned char* dst = image.GetData();
while (size--)
{
unsigned char r = *src++;
unsigned char g = *src++;
unsigned char b = *src++;
if (!hasMask || r != mask_r || g != mask_g || b != mask_b)
wxColour::MakeDisabled(&r, &g, &b, brightness);
*dst++ = r;
*dst++ = g;
*dst++ = b;
}
return image;
}
int wxImage::GetWidth() const
{
wxCHECK_MSG( IsOk(), 0, wxT("invalid image") );
return M_IMGDATA->m_width;
}
int wxImage::GetHeight() const
{
wxCHECK_MSG( IsOk(), 0, wxT("invalid image") );
return M_IMGDATA->m_height;
}
wxBitmapType wxImage::GetType() const
{
wxCHECK_MSG( IsOk(), wxBITMAP_TYPE_INVALID, wxT("invalid image") );
return M_IMGDATA->m_type;
}
void wxImage::SetType(wxBitmapType type)
{
wxCHECK_RET( IsOk(), "must create the image before setting its type");
// type can be wxBITMAP_TYPE_INVALID to reset the image type to default
wxASSERT_MSG( type != wxBITMAP_TYPE_MAX, "invalid bitmap type" );
M_IMGDATA->m_type = type;
}
long wxImage::XYToIndex(int x, int y) const
{
if ( IsOk() &&
x >= 0 && y >= 0 &&
x < M_IMGDATA->m_width && y < M_IMGDATA->m_height )
{
return y*M_IMGDATA->m_width + x;
}
return -1;
}
void wxImage::SetRGB( int x, int y, unsigned char r, unsigned char g, unsigned char b )
{
long pos = XYToIndex(x, y);
wxCHECK_RET( pos != -1, wxT("invalid image coordinates") );
AllocExclusive();
pos *= 3;
M_IMGDATA->m_data[ pos ] = r;
M_IMGDATA->m_data[ pos+1 ] = g;
M_IMGDATA->m_data[ pos+2 ] = b;
}
void wxImage::SetRGB( const wxRect& rect_, unsigned char r, unsigned char g, unsigned char b )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
wxRect rect(rect_);
wxRect imageRect(0, 0, GetWidth(), GetHeight());
if ( rect == wxRect() )
{
rect = imageRect;
}
else
{
wxCHECK_RET( imageRect.Contains(rect.GetTopLeft()) &&
imageRect.Contains(rect.GetBottomRight()),
wxT("invalid bounding rectangle") );
}
int x1 = rect.GetLeft(),
y1 = rect.GetTop(),
x2 = rect.GetRight() + 1,
y2 = rect.GetBottom() + 1;
unsigned char *data wxDUMMY_INITIALIZE(NULL);
int x, y, width = GetWidth();
for (y = y1; y < y2; y++)
{
data = M_IMGDATA->m_data + (y*width + x1)*3;
for (x = x1; x < x2; x++)
{
*data++ = r;
*data++ = g;
*data++ = b;
}
}
}
unsigned char wxImage::GetRed( int x, int y ) const
{
long pos = XYToIndex(x, y);
wxCHECK_MSG( pos != -1, 0, wxT("invalid image coordinates") );
pos *= 3;
return M_IMGDATA->m_data[pos];
}
unsigned char wxImage::GetGreen( int x, int y ) const
{
long pos = XYToIndex(x, y);
wxCHECK_MSG( pos != -1, 0, wxT("invalid image coordinates") );
pos *= 3;
return M_IMGDATA->m_data[pos+1];
}
unsigned char wxImage::GetBlue( int x, int y ) const
{
long pos = XYToIndex(x, y);
wxCHECK_MSG( pos != -1, 0, wxT("invalid image coordinates") );
pos *= 3;
return M_IMGDATA->m_data[pos+2];
}
bool wxImage::IsOk() const
{
// image of 0 width or height can't be considered ok - at least because it
// causes crashes in ConvertToBitmap() if we don't catch it in time
wxImageRefData *data = M_IMGDATA;
return data && data->m_ok && data->m_width && data->m_height;
}
unsigned char *wxImage::GetData() const
{
wxCHECK_MSG( IsOk(), (unsigned char *)NULL, wxT("invalid image") );
return M_IMGDATA->m_data;
}
void wxImage::SetData( unsigned char *data, bool static_data )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
wxImageRefData *newRefData = new wxImageRefData();
newRefData->m_width = M_IMGDATA->m_width;
newRefData->m_height = M_IMGDATA->m_height;
newRefData->m_data = data;
newRefData->m_ok = true;
newRefData->m_maskRed = M_IMGDATA->m_maskRed;
newRefData->m_maskGreen = M_IMGDATA->m_maskGreen;
newRefData->m_maskBlue = M_IMGDATA->m_maskBlue;
newRefData->m_hasMask = M_IMGDATA->m_hasMask;
newRefData->m_static = static_data;
UnRef();
m_refData = newRefData;
}
void wxImage::SetData( unsigned char *data, int new_width, int new_height, bool static_data )
{
wxImageRefData *newRefData = new wxImageRefData();
if (m_refData)
{
newRefData->m_width = new_width;
newRefData->m_height = new_height;
newRefData->m_data = data;
newRefData->m_ok = true;
newRefData->m_maskRed = M_IMGDATA->m_maskRed;
newRefData->m_maskGreen = M_IMGDATA->m_maskGreen;
newRefData->m_maskBlue = M_IMGDATA->m_maskBlue;
newRefData->m_hasMask = M_IMGDATA->m_hasMask;
}
else
{
newRefData->m_width = new_width;
newRefData->m_height = new_height;
newRefData->m_data = data;
newRefData->m_ok = true;
}
newRefData->m_static = static_data;
UnRef();
m_refData = newRefData;
}
// ----------------------------------------------------------------------------
// alpha channel support
// ----------------------------------------------------------------------------
void wxImage::SetAlpha(int x, int y, unsigned char alpha)
{
wxCHECK_RET( HasAlpha(), wxT("no alpha channel") );
long pos = XYToIndex(x, y);
wxCHECK_RET( pos != -1, wxT("invalid image coordinates") );
AllocExclusive();
M_IMGDATA->m_alpha[pos] = alpha;
}
unsigned char wxImage::GetAlpha(int x, int y) const
{
wxCHECK_MSG( HasAlpha(), 0, wxT("no alpha channel") );
long pos = XYToIndex(x, y);
wxCHECK_MSG( pos != -1, 0, wxT("invalid image coordinates") );
return M_IMGDATA->m_alpha[pos];
}
bool
wxImage::ConvertColourToAlpha(unsigned char r, unsigned char g, unsigned char b)
{
SetAlpha(NULL);
const int w = M_IMGDATA->m_width;
const int h = M_IMGDATA->m_height;
unsigned char *alpha = GetAlpha();
unsigned char *data = GetData();
for ( int y = 0; y < h; y++ )
{
for ( int x = 0; x < w; x++ )
{
*alpha++ = *data;
*data++ = r;
*data++ = g;
*data++ = b;
}
}
return true;
}
void wxImage::SetAlpha( unsigned char *alpha, bool static_data )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
if ( !alpha )
{
alpha = (unsigned char *)malloc(M_IMGDATA->m_width*M_IMGDATA->m_height);
}
if( !M_IMGDATA->m_staticAlpha )
free(M_IMGDATA->m_alpha);
M_IMGDATA->m_alpha = alpha;
M_IMGDATA->m_staticAlpha = static_data;
}
unsigned char *wxImage::GetAlpha() const
{
wxCHECK_MSG( IsOk(), (unsigned char *)NULL, wxT("invalid image") );
return M_IMGDATA->m_alpha;
}
void wxImage::InitAlpha()
{
wxCHECK_RET( !HasAlpha(), wxT("image already has an alpha channel") );
// initialize memory for alpha channel
SetAlpha();
unsigned char *alpha = M_IMGDATA->m_alpha;
const size_t lenAlpha = M_IMGDATA->m_width * M_IMGDATA->m_height;
if ( HasMask() )
{
// use the mask to initialize the alpha channel.
const unsigned char * const alphaEnd = alpha + lenAlpha;
const unsigned char mr = M_IMGDATA->m_maskRed;
const unsigned char mg = M_IMGDATA->m_maskGreen;
const unsigned char mb = M_IMGDATA->m_maskBlue;
for ( unsigned char *src = M_IMGDATA->m_data;
alpha < alphaEnd;
src += 3, alpha++ )
{
*alpha = (src[0] == mr && src[1] == mg && src[2] == mb)
? wxIMAGE_ALPHA_TRANSPARENT
: wxIMAGE_ALPHA_OPAQUE;
}
M_IMGDATA->m_hasMask = false;
}
else // no mask
{
// make the image fully opaque
memset(alpha, wxIMAGE_ALPHA_OPAQUE, lenAlpha);
}
}
void wxImage::ClearAlpha()
{
wxCHECK_RET( HasAlpha(), wxT("image already doesn't have an alpha channel") );
if ( !M_IMGDATA->m_staticAlpha )
free( M_IMGDATA->m_alpha );
M_IMGDATA->m_alpha = NULL;
}
// ----------------------------------------------------------------------------
// mask support
// ----------------------------------------------------------------------------
void wxImage::SetMaskColour( unsigned char r, unsigned char g, unsigned char b )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
M_IMGDATA->m_maskRed = r;
M_IMGDATA->m_maskGreen = g;
M_IMGDATA->m_maskBlue = b;
M_IMGDATA->m_hasMask = true;
}
bool wxImage::GetOrFindMaskColour( unsigned char *r, unsigned char *g, unsigned char *b ) const
{
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
if (M_IMGDATA->m_hasMask)
{
if (r) *r = M_IMGDATA->m_maskRed;
if (g) *g = M_IMGDATA->m_maskGreen;
if (b) *b = M_IMGDATA->m_maskBlue;
return true;
}
else
{
FindFirstUnusedColour(r, g, b);
return false;
}
}
unsigned char wxImage::GetMaskRed() const
{
wxCHECK_MSG( IsOk(), 0, wxT("invalid image") );
return M_IMGDATA->m_maskRed;
}
unsigned char wxImage::GetMaskGreen() const
{
wxCHECK_MSG( IsOk(), 0, wxT("invalid image") );
return M_IMGDATA->m_maskGreen;
}
unsigned char wxImage::GetMaskBlue() const
{
wxCHECK_MSG( IsOk(), 0, wxT("invalid image") );
return M_IMGDATA->m_maskBlue;
}
void wxImage::SetMask( bool mask )
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
M_IMGDATA->m_hasMask = mask;
}
bool wxImage::HasMask() const
{
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
return M_IMGDATA->m_hasMask;
}
bool wxImage::IsTransparent(int x, int y, unsigned char threshold) const
{
long pos = XYToIndex(x, y);
wxCHECK_MSG( pos != -1, false, wxT("invalid image coordinates") );
// check mask
if ( M_IMGDATA->m_hasMask )
{
const unsigned char *p = M_IMGDATA->m_data + 3*pos;
if ( p[0] == M_IMGDATA->m_maskRed &&
p[1] == M_IMGDATA->m_maskGreen &&
p[2] == M_IMGDATA->m_maskBlue )
{
return true;
}
}
// then check alpha
if ( M_IMGDATA->m_alpha )
{
if ( M_IMGDATA->m_alpha[pos] < threshold )
{
// transparent enough
return true;
}
}
// not transparent
return false;
}
bool wxImage::SetMaskFromImage(const wxImage& mask,
unsigned char mr, unsigned char mg, unsigned char mb)
{
// check that the images are the same size
if ( (M_IMGDATA->m_height != mask.GetHeight() ) || (M_IMGDATA->m_width != mask.GetWidth () ) )
{
wxLogError( _("Image and mask have different sizes.") );
return false;
}
// find unused colour
unsigned char r,g,b ;
if (!FindFirstUnusedColour(&r, &g, &b))
{
wxLogError( _("No unused colour in image being masked.") );
return false ;
}
AllocExclusive();
unsigned char *imgdata = GetData();
unsigned char *maskdata = mask.GetData();
const int w = GetWidth();
const int h = GetHeight();
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
if ((maskdata[0] == mr) && (maskdata[1] == mg) && (maskdata[2] == mb))
{
imgdata[0] = r;
imgdata[1] = g;
imgdata[2] = b;
}
imgdata += 3;
maskdata += 3;
}
}
SetMaskColour(r, g, b);
SetMask(true);
return true;
}
bool wxImage::ConvertAlphaToMask(unsigned char threshold)
{
if ( !HasAlpha() )
return false;
unsigned char mr, mg, mb;
if ( !FindFirstUnusedColour(&mr, &mg, &mb) )
{
wxLogError( _("No unused colour in image being masked.") );
return false;
}
return ConvertAlphaToMask(mr, mg, mb, threshold);
}
bool wxImage::ConvertAlphaToMask(unsigned char mr,
unsigned char mg,
unsigned char mb,
unsigned char threshold)
{
if ( !HasAlpha() )
return false;
AllocExclusive();
SetMask(true);
SetMaskColour(mr, mg, mb);
unsigned char *imgdata = GetData();
unsigned char *alphadata = GetAlpha();
int w = GetWidth();
int h = GetHeight();
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++, imgdata += 3, alphadata++)
{
if (*alphadata < threshold)
{
imgdata[0] = mr;
imgdata[1] = mg;
imgdata[2] = mb;
}
}
}
if ( !M_IMGDATA->m_staticAlpha )
free(M_IMGDATA->m_alpha);
M_IMGDATA->m_alpha = NULL;
M_IMGDATA->m_staticAlpha = false;
return true;
}
// ----------------------------------------------------------------------------
// Palette functions
// ----------------------------------------------------------------------------
#if wxUSE_PALETTE
bool wxImage::HasPalette() const
{
if (!IsOk())
return false;
return M_IMGDATA->m_palette.IsOk();
}
const wxPalette& wxImage::GetPalette() const
{
wxCHECK_MSG( IsOk(), wxNullPalette, wxT("invalid image") );
return M_IMGDATA->m_palette;
}
void wxImage::SetPalette(const wxPalette& palette)
{
wxCHECK_RET( IsOk(), wxT("invalid image") );
AllocExclusive();
M_IMGDATA->m_palette = palette;
}
#endif // wxUSE_PALETTE
// ----------------------------------------------------------------------------
// Option functions (arbitrary name/value mapping)
// ----------------------------------------------------------------------------
void wxImage::SetOption(const wxString& name, const wxString& value)
{
AllocExclusive();
int idx = M_IMGDATA->m_optionNames.Index(name, false);
if ( idx == wxNOT_FOUND )
{
M_IMGDATA->m_optionNames.Add(name);
M_IMGDATA->m_optionValues.Add(value);
}
else
{
M_IMGDATA->m_optionNames[idx] = name;
M_IMGDATA->m_optionValues[idx] = value;
}
}
void wxImage::SetOption(const wxString& name, int value)
{
wxString valStr;
valStr.Printf(wxT("%d"), value);
SetOption(name, valStr);
}
wxString wxImage::GetOption(const wxString& name) const
{
if ( !M_IMGDATA )
return wxEmptyString;
int idx = M_IMGDATA->m_optionNames.Index(name, false);
if ( idx == wxNOT_FOUND )
return wxEmptyString;
else
return M_IMGDATA->m_optionValues[idx];
}
int wxImage::GetOptionInt(const wxString& name) const
{
return wxAtoi(GetOption(name));
}
bool wxImage::HasOption(const wxString& name) const
{
return M_IMGDATA ? M_IMGDATA->m_optionNames.Index(name, false) != wxNOT_FOUND
: false;
}
// ----------------------------------------------------------------------------
// image I/O
// ----------------------------------------------------------------------------
// Under Windows we can load wxImage not only from files but also from
// resources.
#if defined(__WINDOWS__) && wxUSE_WXDIB && wxUSE_IMAGE
#define HAS_LOAD_FROM_RESOURCE
#endif
#ifdef HAS_LOAD_FROM_RESOURCE
#include "wx/msw/dib.h"
#include "wx/msw/private.h"
static wxImage LoadImageFromResource(const wxString &name, wxBitmapType type)
{
AutoHBITMAP
hBitmap,
hMask;
if ( type == wxBITMAP_TYPE_BMP_RESOURCE )
{
hBitmap.Init( ::LoadBitmap(wxGetInstance(), name.t_str()) );
if ( !hBitmap )
{
wxLogError(_("Failed to load bitmap \"%s\" from resources."), name);
}
}
else if ( type == wxBITMAP_TYPE_ICO_RESOURCE )
{
const HICON hIcon = ::LoadIcon(wxGetInstance(), name.t_str());
if ( !hIcon )
{
wxLogError(_("Failed to load icon \"%s\" from resources."), name);
}
else
{
ICONINFO info;
if ( !::GetIconInfo(hIcon, &info) )
{
wxLogLastError(wxT("GetIconInfo"));
return wxImage();
}
hBitmap.Init(info.hbmColor);
hMask.Init(info.hbmMask);
}
}
else if ( type == wxBITMAP_TYPE_CUR_RESOURCE )
{
wxLogDebug(wxS("Loading cursors from resources is not implemented."));
}
else
{
wxFAIL_MSG(wxS("Invalid bitmap resource type."));
}
if ( !hBitmap )
return wxImage();
wxImage image = wxDIB(hBitmap).ConvertToImage();
if ( hMask )
{
const wxImage mask = wxDIB(hMask).ConvertToImage();
image.SetMaskFromImage(mask, 255, 255, 255);
}
else
{
// Light gray colour is a default mask
image.SetMaskColour(0xc0, 0xc0, 0xc0);
}
// We could have already loaded alpha from the resources, but if not,
// initialize it now using the mask.
if ( !image.HasAlpha() )
image.InitAlpha();
return image;
}
#endif // HAS_LOAD_FROM_RESOURCE
bool wxImage::LoadFile( const wxString& filename,
wxBitmapType type,
int WXUNUSED_UNLESS_STREAMS(index) )
{
#ifdef HAS_LOAD_FROM_RESOURCE
if ( type == wxBITMAP_TYPE_BMP_RESOURCE
|| type == wxBITMAP_TYPE_ICO_RESOURCE
|| type == wxBITMAP_TYPE_CUR_RESOURCE)
{
const wxImage image = ::LoadImageFromResource(filename, type);
if ( image.IsOk() )
{
*this = image;
return true;
}
}
#endif // HAS_LOAD_FROM_RESOURCE
#if HAS_FILE_STREAMS
wxImageFileInputStream stream(filename);
if ( stream.IsOk() )
{
wxBufferedInputStream bstream( stream );
if ( LoadFile(bstream, type, index) )
return true;
}
wxLogError(_("Failed to load image from file \"%s\"."), filename);
#endif // HAS_FILE_STREAMS
return false;
}
bool wxImage::LoadFile( const wxString& WXUNUSED_UNLESS_STREAMS(filename),
const wxString& WXUNUSED_UNLESS_STREAMS(mimetype),
int WXUNUSED_UNLESS_STREAMS(index) )
{
#if HAS_FILE_STREAMS
wxImageFileInputStream stream(filename);
if ( stream.IsOk() )
{
wxBufferedInputStream bstream( stream );
if ( LoadFile(bstream, mimetype, index) )
return true;
}
wxLogError(_("Failed to load image from file \"%s\"."), filename);
#endif // HAS_FILE_STREAMS
return false;
}
bool wxImage::SaveFile( const wxString& filename ) const
{
wxString ext = filename.AfterLast('.').Lower();
wxImageHandler *handler = FindHandler(ext, wxBITMAP_TYPE_ANY);
if ( !handler)
{
wxLogError(_("Can't save image to file '%s': unknown extension."),
filename);
return false;
}
return SaveFile(filename, handler->GetType());
}
bool wxImage::SaveFile( const wxString& WXUNUSED_UNLESS_STREAMS(filename),
wxBitmapType WXUNUSED_UNLESS_STREAMS(type) ) const
{
#if HAS_FILE_STREAMS
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
((wxImage*)this)->SetOption(wxIMAGE_OPTION_FILENAME, filename);
wxImageFileOutputStream stream(filename);
if ( stream.IsOk() )
{
wxBufferedOutputStream bstream( stream );
return SaveFile(bstream, type);
}
#endif // HAS_FILE_STREAMS
return false;
}
bool wxImage::SaveFile( const wxString& WXUNUSED_UNLESS_STREAMS(filename),
const wxString& WXUNUSED_UNLESS_STREAMS(mimetype) ) const
{
#if HAS_FILE_STREAMS
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
((wxImage*)this)->SetOption(wxIMAGE_OPTION_FILENAME, filename);
wxImageFileOutputStream stream(filename);
if ( stream.IsOk() )
{
wxBufferedOutputStream bstream( stream );
return SaveFile(bstream, mimetype);
}
#endif // HAS_FILE_STREAMS
return false;
}
bool wxImage::CanRead( const wxString& WXUNUSED_UNLESS_STREAMS(name) )
{
#if HAS_FILE_STREAMS
wxImageFileInputStream stream(name);
return CanRead(stream);
#else
return false;
#endif
}
int wxImage::GetImageCount( const wxString& WXUNUSED_UNLESS_STREAMS(name),
wxBitmapType WXUNUSED_UNLESS_STREAMS(type) )
{
#if HAS_FILE_STREAMS
wxImageFileInputStream stream(name);
if (stream.IsOk())
return GetImageCount(stream, type);
#endif
return 0;
}
#if wxUSE_STREAMS
bool wxImage::CanRead( wxInputStream &stream )
{
const wxList& list = GetHandlers();
for ( wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext() )
{
wxImageHandler *handler=(wxImageHandler*)node->GetData();
if (handler->CanRead( stream ))
return true;
}
return false;
}
int wxImage::GetImageCount( wxInputStream &stream, wxBitmapType type )
{
wxImageHandler *handler;
if ( type == wxBITMAP_TYPE_ANY )
{
const wxList& list = GetHandlers();
for ( wxList::compatibility_iterator node = list.GetFirst();
node;
node = node->GetNext() )
{
handler = (wxImageHandler*)node->GetData();
if ( handler->CanRead(stream) )
{
const int count = handler->GetImageCount(stream);
if ( count >= 0 )
return count;
}
}
wxLogWarning(_("No handler found for image type."));
return 0;
}
handler = FindHandler(type);
if ( !handler )
{
wxLogWarning(_("No image handler for type %d defined."), type);
return false;
}
if ( handler->CanRead(stream) )
{
return handler->GetImageCount(stream);
}
else
{
wxLogError(_("Image file is not of type %d."), type);
return 0;
}
}
bool wxImage::DoLoad(wxImageHandler& handler, wxInputStream& stream, int index)
{
// save the options values which can be clobbered by the handler (e.g. many
// of them call Destroy() before trying to load the file)
const unsigned maxWidth = GetOptionInt(wxIMAGE_OPTION_MAX_WIDTH),
maxHeight = GetOptionInt(wxIMAGE_OPTION_MAX_HEIGHT);
// Preserve the original stream position if possible to rewind back to it
// if we failed to load the file -- maybe the next handler that we try can
// succeed after us then.
wxFileOffset posOld = wxInvalidOffset;
if ( stream.IsSeekable() )
posOld = stream.TellI();
if ( !handler.LoadFile(this, stream, true/*verbose*/, index) )
{
if ( posOld != wxInvalidOffset )
stream.SeekI(posOld);
return false;
}
// rescale the image to the specified size if needed
if ( maxWidth || maxHeight )
{
const unsigned widthOrig = GetWidth(),
heightOrig = GetHeight();
// this uses the same (trivial) algorithm as the JPEG handler
unsigned width = widthOrig,
height = heightOrig;
while ( (maxWidth && width > maxWidth) ||
(maxHeight && height > maxHeight) )
{
width /= 2;
height /= 2;
}
if ( width != widthOrig || height != heightOrig )
{
// get the original size if it was set by the image handler
// but also in order to restore it after Rescale
int widthOrigOption = GetOptionInt(wxIMAGE_OPTION_ORIGINAL_WIDTH),
heightOrigOption = GetOptionInt(wxIMAGE_OPTION_ORIGINAL_HEIGHT);
Rescale(width, height, wxIMAGE_QUALITY_HIGH);
SetOption(wxIMAGE_OPTION_ORIGINAL_WIDTH, widthOrigOption ? widthOrigOption : widthOrig);
SetOption(wxIMAGE_OPTION_ORIGINAL_HEIGHT, heightOrigOption ? heightOrigOption : heightOrig);
}
}
// Set this after Rescale, which currently does not preserve it
M_IMGDATA->m_type = handler.GetType();
return true;
}
bool wxImage::LoadFile( wxInputStream& stream, wxBitmapType type, int index )
{
AllocExclusive();
wxImageHandler *handler;
if ( type == wxBITMAP_TYPE_ANY )
{
if ( !stream.IsSeekable() )
{
// The error message about image data format being unknown below
// would be misleading in this case as we are not even going to try
// any handlers because CanRead() never does anything for not
// seekable stream, so try to be more precise here.
wxLogError(_("Can't automatically determine the image format "
"for non-seekable input."));
return false;
}
const wxList& list = GetHandlers();
for ( wxList::compatibility_iterator node = list.GetFirst();
node;
node = node->GetNext() )
{
handler = (wxImageHandler*)node->GetData();
if ( handler->CanRead(stream) && DoLoad(*handler, stream, index) )
return true;
}
wxLogWarning( _("Unknown image data format.") );
return false;
}
//else: have specific type
handler = FindHandler(type);
if ( !handler )
{
wxLogWarning( _("No image handler for type %d defined."), type );
return false;
}
if ( stream.IsSeekable() && !handler->CanRead(stream) )
{
wxLogError(_("This is not a %s."), handler->GetName());
return false;
}
return DoLoad(*handler, stream, index);
}
bool wxImage::LoadFile( wxInputStream& stream, const wxString& mimetype, int index )
{
UnRef();
m_refData = new wxImageRefData;
wxImageHandler *handler = FindHandlerMime(mimetype);
if ( !handler )
{
wxLogWarning( _("No image handler for type %s defined."), mimetype.GetData() );
return false;
}
if ( stream.IsSeekable() && !handler->CanRead(stream) )
{
wxLogError(_("Image is not of type %s."), mimetype);
return false;
}
return DoLoad(*handler, stream, index);
}
bool wxImage::DoSave(wxImageHandler& handler, wxOutputStream& stream) const
{
wxImage * const self = const_cast<wxImage *>(this);
if ( !handler.SaveFile(self, stream) )
return false;
M_IMGDATA->m_type = handler.GetType();
return true;
}
bool wxImage::SaveFile( wxOutputStream& stream, wxBitmapType type ) const
{
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
wxImageHandler *handler = FindHandler(type);
if ( !handler )
{
wxLogWarning( _("No image handler for type %d defined."), type );
return false;
}
return DoSave(*handler, stream);
}
bool wxImage::SaveFile( wxOutputStream& stream, const wxString& mimetype ) const
{
wxCHECK_MSG( IsOk(), false, wxT("invalid image") );
wxImageHandler *handler = FindHandlerMime(mimetype);
if ( !handler )
{
wxLogWarning( _("No image handler for type %s defined."), mimetype.GetData() );
return false;
}
return DoSave(*handler, stream);
}
#endif // wxUSE_STREAMS
// ----------------------------------------------------------------------------
// image I/O handlers
// ----------------------------------------------------------------------------
void wxImage::AddHandler( wxImageHandler *handler )
{
// Check for an existing handler of the type being added.
if (FindHandler( handler->GetType() ) == 0)
{
sm_handlers.Append( handler );
}
else
{
// This is not documented behaviour, merely the simplest 'fix'
// for preventing duplicate additions. If someone ever has
// a good reason to add and remove duplicate handlers (and they
// may) we should probably refcount the duplicates.
// also an issue in InsertHandler below.
wxLogDebug( wxT("Adding duplicate image handler for '%s'"),
handler->GetName().c_str() );
delete handler;
}
}
void wxImage::InsertHandler( wxImageHandler *handler )
{
// Check for an existing handler of the type being added.
if (FindHandler( handler->GetType() ) == 0)
{
sm_handlers.Insert( handler );
}
else
{
// see AddHandler for additional comments.
wxLogDebug( wxT("Inserting duplicate image handler for '%s'"),
handler->GetName().c_str() );
delete handler;
}
}
bool wxImage::RemoveHandler( const wxString& name )
{
wxImageHandler *handler = FindHandler(name);
if (handler)
{
sm_handlers.DeleteObject(handler);
delete handler;
return true;
}
else
return false;
}
wxImageHandler *wxImage::FindHandler( const wxString& name )
{
wxList::compatibility_iterator node = sm_handlers.GetFirst();
while (node)
{
wxImageHandler *handler = (wxImageHandler*)node->GetData();
if (handler->GetName().Cmp(name) == 0) return handler;
node = node->GetNext();
}
return NULL;
}
wxImageHandler *wxImage::FindHandler( const wxString& extension, wxBitmapType bitmapType )
{
wxList::compatibility_iterator node = sm_handlers.GetFirst();
while (node)
{
wxImageHandler *handler = (wxImageHandler*)node->GetData();
if ((bitmapType == wxBITMAP_TYPE_ANY) || (handler->GetType() == bitmapType))
{
if (handler->GetExtension() == extension)
return handler;
if (handler->GetAltExtensions().Index(extension, false) != wxNOT_FOUND)
return handler;
}
node = node->GetNext();
}
return NULL;
}
wxImageHandler *wxImage::FindHandler(wxBitmapType bitmapType )
{
wxList::compatibility_iterator node = sm_handlers.GetFirst();
while (node)
{
wxImageHandler *handler = (wxImageHandler *)node->GetData();
if (handler->GetType() == bitmapType) return handler;
node = node->GetNext();
}
return NULL;
}
wxImageHandler *wxImage::FindHandlerMime( const wxString& mimetype )
{
wxList::compatibility_iterator node = sm_handlers.GetFirst();
while (node)
{
wxImageHandler *handler = (wxImageHandler *)node->GetData();
if (handler->GetMimeType().IsSameAs(mimetype, false)) return handler;
node = node->GetNext();
}
return NULL;
}
void wxImage::InitStandardHandlers()
{
#if wxUSE_STREAMS
AddHandler(new wxBMPHandler);
#endif // wxUSE_STREAMS
}
void wxImage::CleanUpHandlers()
{
wxList::compatibility_iterator node = sm_handlers.GetFirst();
while (node)
{
wxImageHandler *handler = (wxImageHandler *)node->GetData();
wxList::compatibility_iterator next = node->GetNext();
delete handler;
node = next;
}
sm_handlers.Clear();
}
wxString wxImage::GetImageExtWildcard()
{
wxString fmts;
wxList& Handlers = wxImage::GetHandlers();
wxList::compatibility_iterator Node = Handlers.GetFirst();
while ( Node )
{
wxImageHandler* Handler = (wxImageHandler*)Node->GetData();
fmts += wxT("*.") + Handler->GetExtension();
for (size_t i = 0; i < Handler->GetAltExtensions().size(); i++)
fmts += wxT(";*.") + Handler->GetAltExtensions()[i];
Node = Node->GetNext();
if ( Node ) fmts += wxT(";");
}
return wxT("(") + fmts + wxT(")|") + fmts;
}
wxImage::HSVValue wxImage::RGBtoHSV(const RGBValue& rgb)
{
const double red = rgb.red / 255.0,
green = rgb.green / 255.0,
blue = rgb.blue / 255.0;
// find the min and max intensity (and remember which one was it for the
// latter)
double minimumRGB = red;
if ( green < minimumRGB )
minimumRGB = green;
if ( blue < minimumRGB )
minimumRGB = blue;
enum { RED, GREEN, BLUE } chMax = RED;
double maximumRGB = red;
if ( green > maximumRGB )
{
chMax = GREEN;
maximumRGB = green;
}
if ( blue > maximumRGB )
{
chMax = BLUE;
maximumRGB = blue;
}
const double value = maximumRGB;
double hue = 0.0, saturation;
const double deltaRGB = maximumRGB - minimumRGB;
if ( wxIsNullDouble(deltaRGB) )
{
// Gray has no color
hue = 0.0;
saturation = 0.0;
}
else
{
switch ( chMax )
{
case RED:
hue = (green - blue) / deltaRGB;
break;
case GREEN:
hue = 2.0 + (blue - red) / deltaRGB;
break;
case BLUE:
hue = 4.0 + (red - green) / deltaRGB;
break;
}
hue /= 6.0;
if ( hue < 0.0 )
hue += 1.0;
saturation = deltaRGB / maximumRGB;
}
return HSVValue(hue, saturation, value);
}
wxImage::RGBValue wxImage::HSVtoRGB(const HSVValue& hsv)
{
double red, green, blue;
if ( wxIsNullDouble(hsv.saturation) )
{
// Grey
red = hsv.value;
green = hsv.value;
blue = hsv.value;
}
else // not grey
{
double hue = hsv.hue * 6.0; // sector 0 to 5
int i = (int)floor(hue);
double f = hue - i; // fractional part of h
double p = hsv.value * (1.0 - hsv.saturation);
switch (i)
{
case 0:
red = hsv.value;
green = hsv.value * (1.0 - hsv.saturation * (1.0 - f));
blue = p;
break;
case 1:
red = hsv.value * (1.0 - hsv.saturation * f);
green = hsv.value;
blue = p;
break;
case 2:
red = p;
green = hsv.value;
blue = hsv.value * (1.0 - hsv.saturation * (1.0 - f));
break;
case 3:
red = p;
green = hsv.value * (1.0 - hsv.saturation * f);
blue = hsv.value;
break;
case 4:
red = hsv.value * (1.0 - hsv.saturation * (1.0 - f));
green = p;
blue = hsv.value;
break;
default: // case 5:
red = hsv.value;
green = p;
blue = hsv.value * (1.0 - hsv.saturation * f);
break;
}
}
return RGBValue((unsigned char)(red * 255.0),
(unsigned char)(green * 255.0),
(unsigned char)(blue * 255.0));
}
/*
* Rotates the hue of each pixel of the image. angle is a double in the range
* -1.0..1.0 where -1.0 is -360 degrees and 1.0 is 360 degrees
*/
void wxImage::RotateHue(double angle)
{
AllocExclusive();
unsigned char *srcBytePtr;
unsigned char *dstBytePtr;
unsigned long count;
wxImage::HSVValue hsv;
wxImage::RGBValue rgb;
wxASSERT (angle >= -1.0 && angle <= 1.0);
count = M_IMGDATA->m_width * M_IMGDATA->m_height;
if ( count > 0 && !wxIsNullDouble(angle) )
{
srcBytePtr = M_IMGDATA->m_data;
dstBytePtr = srcBytePtr;
do
{
rgb.red = *srcBytePtr++;
rgb.green = *srcBytePtr++;
rgb.blue = *srcBytePtr++;
hsv = RGBtoHSV(rgb);
hsv.hue = hsv.hue + angle;
if (hsv.hue > 1.0)
hsv.hue = hsv.hue - 1.0;
else if (hsv.hue < 0.0)
hsv.hue = hsv.hue + 1.0;
rgb = HSVtoRGB(hsv);
*dstBytePtr++ = rgb.red;
*dstBytePtr++ = rgb.green;
*dstBytePtr++ = rgb.blue;
} while (--count != 0);
}
}
//-----------------------------------------------------------------------------
// wxImageHandler
//-----------------------------------------------------------------------------
IMPLEMENT_ABSTRACT_CLASS(wxImageHandler,wxObject)
#if wxUSE_STREAMS
int wxImageHandler::GetImageCount( wxInputStream& stream )
{
// NOTE: this code is the same of wxAnimationDecoder::CanRead and
// wxImageHandler::CallDoCanRead
if ( !stream.IsSeekable() )
return false; // can't test unseekable stream
wxFileOffset posOld = stream.TellI();
int n = DoGetImageCount(stream);
// restore the old position to be able to test other formats and so on
if ( stream.SeekI(posOld) == wxInvalidOffset )
{
wxLogDebug(wxT("Failed to rewind the stream in wxImageHandler!"));
// reading would fail anyhow as we're not at the right position
return false;
}
return n;
}
bool wxImageHandler::CanRead( const wxString& name )
{
wxImageFileInputStream stream(name);
if ( !stream.IsOk() )
{
wxLogError(_("Failed to check format of image file \"%s\"."), name);
return false;
}
return CanRead(stream);
}
bool wxImageHandler::CallDoCanRead(wxInputStream& stream)
{
// NOTE: this code is the same of wxAnimationDecoder::CanRead and
// wxImageHandler::GetImageCount
if ( !stream.IsSeekable() )
return false; // can't test unseekable stream
wxFileOffset posOld = stream.TellI();
bool ok = DoCanRead(stream);
// restore the old position to be able to test other formats and so on
if ( stream.SeekI(posOld) == wxInvalidOffset )
{
wxLogDebug(wxT("Failed to rewind the stream in wxImageHandler!"));
// reading would fail anyhow as we're not at the right position
return false;
}
return ok;
}
#endif // wxUSE_STREAMS
/* static */
wxImageResolution
wxImageHandler::GetResolutionFromOptions(const wxImage& image, int *x, int *y)
{
wxCHECK_MSG( x && y, wxIMAGE_RESOLUTION_NONE, wxT("NULL pointer") );
if ( image.HasOption(wxIMAGE_OPTION_RESOLUTIONX) &&
image.HasOption(wxIMAGE_OPTION_RESOLUTIONY) )
{
*x = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONX);
*y = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONY);
}
else if ( image.HasOption(wxIMAGE_OPTION_RESOLUTION) )
{
*x =
*y = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTION);
}
else // no resolution options specified
{
*x =
*y = 0;
return wxIMAGE_RESOLUTION_NONE;
}
// get the resolution unit too
int resUnit = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONUNIT);
if ( !resUnit )
{
// this is the default
resUnit = wxIMAGE_RESOLUTION_INCHES;
}
return (wxImageResolution)resUnit;
}
// ----------------------------------------------------------------------------
// image histogram stuff
// ----------------------------------------------------------------------------
bool
wxImageHistogram::FindFirstUnusedColour(unsigned char *r,
unsigned char *g,
unsigned char *b,
unsigned char r2,
unsigned char b2,
unsigned char g2) const
{
unsigned long key = MakeKey(r2, g2, b2);
while ( find(key) != end() )
{
// color already used
r2++;
if ( r2 >= 255 )
{
r2 = 0;
g2++;
if ( g2 >= 255 )
{
g2 = 0;
b2++;
if ( b2 >= 255 )
{
wxLogError(_("No unused colour in image.") );
return false;
}
}
}
key = MakeKey(r2, g2, b2);
}
if ( r )
*r = r2;
if ( g )
*g = g2;
if ( b )
*b = b2;
return true;
}
bool
wxImage::FindFirstUnusedColour(unsigned char *r,
unsigned char *g,
unsigned char *b,
unsigned char r2,
unsigned char b2,
unsigned char g2) const
{
wxImageHistogram histogram;
ComputeHistogram(histogram);
return histogram.FindFirstUnusedColour(r, g, b, r2, g2, b2);
}
// GRG, Dic/99
// Counts and returns the number of different colours. Optionally stops
// when it exceeds 'stopafter' different colours. This is useful, for
// example, to see if the image can be saved as 8-bit (256 colour or
// less, in this case it would be invoked as CountColours(256)). Default
// value for stopafter is -1 (don't care).
//
unsigned long wxImage::CountColours( unsigned long stopafter ) const
{
wxHashTable h;
wxObject dummy;
unsigned char r, g, b;
unsigned char *p;
unsigned long size, nentries, key;
p = GetData();
size = GetWidth() * GetHeight();
nentries = 0;
for (unsigned long j = 0; (j < size) && (nentries <= stopafter) ; j++)
{
r = *(p++);
g = *(p++);
b = *(p++);
key = wxImageHistogram::MakeKey(r, g, b);
if (h.Get(key) == NULL)
{
h.Put(key, &dummy);
nentries++;
}
}
return nentries;
}
unsigned long wxImage::ComputeHistogram( wxImageHistogram &h ) const
{
unsigned char *p = GetData();
unsigned long nentries = 0;
h.clear();
const unsigned long size = GetWidth() * GetHeight();
unsigned char r, g, b;
for ( unsigned long n = 0; n < size; n++ )
{
r = *p++;
g = *p++;
b = *p++;
wxImageHistogramEntry& entry = h[wxImageHistogram::MakeKey(r, g, b)];
if ( entry.value++ == 0 )
entry.index = nentries++;
}
return nentries;
}
/*
* Rotation code by Carlos Moreno
*/
static const double wxROTATE_EPSILON = 1e-10;
// Auxiliary function to rotate a point (x,y) with respect to point p0
// make it inline and use a straight return to facilitate optimization
// also, the function receives the sine and cosine of the angle to avoid
// repeating the time-consuming calls to these functions -- sin/cos can
// be computed and stored in the calling function.
static inline wxRealPoint
wxRotatePoint(const wxRealPoint& p, double cos_angle, double sin_angle,
const wxRealPoint& p0)
{
return wxRealPoint(p0.x + (p.x - p0.x) * cos_angle - (p.y - p0.y) * sin_angle,
p0.y + (p.y - p0.y) * cos_angle + (p.x - p0.x) * sin_angle);
}
static inline wxRealPoint
wxRotatePoint(double x, double y, double cos_angle, double sin_angle,
const wxRealPoint & p0)
{
return wxRotatePoint (wxRealPoint(x,y), cos_angle, sin_angle, p0);
}
wxImage wxImage::Rotate(double angle,
const wxPoint& centre_of_rotation,
bool interpolating,
wxPoint *offset_after_rotation) const
{
// screen coordinates are a mirror image of "real" coordinates
angle = -angle;
const bool has_alpha = HasAlpha();
const int w = GetWidth();
const int h = GetHeight();
int i;
// Create pointer-based array to accelerate access to wxImage's data
unsigned char ** data = new unsigned char * [h];
data[0] = GetData();
for (i = 1; i < h; i++)
data[i] = data[i - 1] + (3 * w);
// Same for alpha channel
unsigned char ** alpha = NULL;
if (has_alpha)
{
alpha = new unsigned char * [h];
alpha[0] = GetAlpha();
for (i = 1; i < h; i++)
alpha[i] = alpha[i - 1] + w;
}
// precompute coefficients for rotation formula
const double cos_angle = cos(angle);
const double sin_angle = sin(angle);
// Create new Image to store the result
// First, find rectangle that covers the rotated image; to do that,
// rotate the four corners
const wxRealPoint p0(centre_of_rotation.x, centre_of_rotation.y);
wxRealPoint p1 = wxRotatePoint (0, 0, cos_angle, sin_angle, p0);
wxRealPoint p2 = wxRotatePoint (0, h, cos_angle, sin_angle, p0);
wxRealPoint p3 = wxRotatePoint (w, 0, cos_angle, sin_angle, p0);
wxRealPoint p4 = wxRotatePoint (w, h, cos_angle, sin_angle, p0);
int x1a = (int) floor (wxMin (wxMin(p1.x, p2.x), wxMin(p3.x, p4.x)));
int y1a = (int) floor (wxMin (wxMin(p1.y, p2.y), wxMin(p3.y, p4.y)));
int x2a = (int) ceil (wxMax (wxMax(p1.x, p2.x), wxMax(p3.x, p4.x)));
int y2a = (int) ceil (wxMax (wxMax(p1.y, p2.y), wxMax(p3.y, p4.y)));
// Create rotated image
wxImage rotated (x2a - x1a + 1, y2a - y1a + 1, false);
// With alpha channel
if (has_alpha)
rotated.SetAlpha();
if (offset_after_rotation != NULL)
{
*offset_after_rotation = wxPoint (x1a, y1a);
}
// the rotated (destination) image is always accessed sequentially via this
// pointer, there is no need for pointer-based arrays here
unsigned char *dst = rotated.GetData();
unsigned char *alpha_dst = has_alpha ? rotated.GetAlpha() : NULL;
// if the original image has a mask, use its RGB values as the blank pixel,
// else, fall back to default (black).
unsigned char blank_r = 0;
unsigned char blank_g = 0;
unsigned char blank_b = 0;
if (HasMask())
{
blank_r = GetMaskRed();
blank_g = GetMaskGreen();
blank_b = GetMaskBlue();
rotated.SetMaskColour( blank_r, blank_g, blank_b );
}
// Now, for each point of the rotated image, find where it came from, by
// performing an inverse rotation (a rotation of -angle) and getting the
// pixel at those coordinates
const int rH = rotated.GetHeight();
const int rW = rotated.GetWidth();
// do the (interpolating) test outside of the loops, so that it is done
// only once, instead of repeating it for each pixel.
if (interpolating)
{
for (int y = 0; y < rH; y++)
{
for (int x = 0; x < rW; x++)
{
wxRealPoint src = wxRotatePoint (x + x1a, y + y1a, cos_angle, -sin_angle, p0);
if (-0.25 < src.x && src.x < w - 0.75 &&
-0.25 < src.y && src.y < h - 0.75)
{
// interpolate using the 4 enclosing grid-points. Those
// points can be obtained using floor and ceiling of the
// exact coordinates of the point
int x1, y1, x2, y2;
if (0 < src.x && src.x < w - 1)
{
x1 = wxRound(floor(src.x));
x2 = wxRound(ceil(src.x));
}
else // else means that x is near one of the borders (0 or width-1)
{
x1 = x2 = wxRound (src.x);
}
if (0 < src.y && src.y < h - 1)
{
y1 = wxRound(floor(src.y));
y2 = wxRound(ceil(src.y));
}
else
{
y1 = y2 = wxRound (src.y);
}
// get four points and the distances (square of the distance,
// for efficiency reasons) for the interpolation formula
// GRG: Do not calculate the points until they are
// really needed -- this way we can calculate
// just one, instead of four, if d1, d2, d3
// or d4 are < wxROTATE_EPSILON
const double d1 = (src.x - x1) * (src.x - x1) + (src.y - y1) * (src.y - y1);
const double d2 = (src.x - x2) * (src.x - x2) + (src.y - y1) * (src.y - y1);
const double d3 = (src.x - x2) * (src.x - x2) + (src.y - y2) * (src.y - y2);
const double d4 = (src.x - x1) * (src.x - x1) + (src.y - y2) * (src.y - y2);
// Now interpolate as a weighted average of the four surrounding
// points, where the weights are the distances to each of those points
// If the point is exactly at one point of the grid of the source
// image, then don't interpolate -- just assign the pixel
// d1,d2,d3,d4 are positive -- no need for abs()
if (d1 < wxROTATE_EPSILON)
{
unsigned char *p = data[y1] + (3 * x1);
*(dst++) = *(p++);
*(dst++) = *(p++);
*(dst++) = *p;
if (has_alpha)
*(alpha_dst++) = *(alpha[y1] + x1);
}
else if (d2 < wxROTATE_EPSILON)
{
unsigned char *p = data[y1] + (3 * x2);
*(dst++) = *(p++);
*(dst++) = *(p++);
*(dst++) = *p;
if (has_alpha)
*(alpha_dst++) = *(alpha[y1] + x2);
}
else if (d3 < wxROTATE_EPSILON)
{
unsigned char *p = data[y2] + (3 * x2);
*(dst++) = *(p++);
*(dst++) = *(p++);
*(dst++) = *p;
if (has_alpha)
*(alpha_dst++) = *(alpha[y2] + x2);
}
else if (d4 < wxROTATE_EPSILON)
{
unsigned char *p = data[y2] + (3 * x1);
*(dst++) = *(p++);
*(dst++) = *(p++);
*(dst++) = *p;
if (has_alpha)
*(alpha_dst++) = *(alpha[y2] + x1);
}
else
{
// weights for the weighted average are proportional to the inverse of the distance
unsigned char *v1 = data[y1] + (3 * x1);
unsigned char *v2 = data[y1] + (3 * x2);
unsigned char *v3 = data[y2] + (3 * x2);
unsigned char *v4 = data[y2] + (3 * x1);
const double w1 = 1/d1, w2 = 1/d2, w3 = 1/d3, w4 = 1/d4;
// GRG: Unrolled.
*(dst++) = (unsigned char)
( (w1 * *(v1++) + w2 * *(v2++) +
w3 * *(v3++) + w4 * *(v4++)) /
(w1 + w2 + w3 + w4) );
*(dst++) = (unsigned char)
( (w1 * *(v1++) + w2 * *(v2++) +
w3 * *(v3++) + w4 * *(v4++)) /
(w1 + w2 + w3 + w4) );
*(dst++) = (unsigned char)
( (w1 * *v1 + w2 * *v2 +
w3 * *v3 + w4 * *v4) /
(w1 + w2 + w3 + w4) );
if (has_alpha)
{
v1 = alpha[y1] + (x1);
v2 = alpha[y1] + (x2);
v3 = alpha[y2] + (x2);
v4 = alpha[y2] + (x1);
*(alpha_dst++) = (unsigned char)
( (w1 * *v1 + w2 * *v2 +
w3 * *v3 + w4 * *v4) /
(w1 + w2 + w3 + w4) );
}
}
}
else
{
*(dst++) = blank_r;
*(dst++) = blank_g;
*(dst++) = blank_b;
if (has_alpha)
*(alpha_dst++) = 0;
}
}
}
}
else // not interpolating
{
for (int y = 0; y < rH; y++)
{
for (int x = 0; x < rW; x++)
{
wxRealPoint src = wxRotatePoint (x + x1a, y + y1a, cos_angle, -sin_angle, p0);
const int xs = wxRound (src.x); // wxRound rounds to the
const int ys = wxRound (src.y); // closest integer
if (0 <= xs && xs < w && 0 <= ys && ys < h)
{
unsigned char *p = data[ys] + (3 * xs);
*(dst++) = *(p++);
*(dst++) = *(p++);
*(dst++) = *p;
if (has_alpha)
*(alpha_dst++) = *(alpha[ys] + (xs));
}
else
{
*(dst++) = blank_r;
*(dst++) = blank_g;
*(dst++) = blank_b;
if (has_alpha)
*(alpha_dst++) = 255;
}
}
}
}
delete [] data;
delete [] alpha;
return rotated;
}
// A module to allow wxImage initialization/cleanup
// without calling these functions from app.cpp or from
// the user's application.
class wxImageModule: public wxModule
{
DECLARE_DYNAMIC_CLASS(wxImageModule)
public:
wxImageModule() {}
bool OnInit() { wxImage::InitStandardHandlers(); return true; }
void OnExit() { wxImage::CleanUpHandlers(); }
};
IMPLEMENT_DYNAMIC_CLASS(wxImageModule, wxModule)
#endif // wxUSE_IMAGE