From 180f3c7461bf2a8ed136967f45be880a38cfc0b8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 30 Aug 2009 21:11:37 +0000 Subject: [PATCH] Add support for bilinear resize algorithm to wxImage. Add wxIMAGE_QUALITY_BILINEAR in addition to the existing wxIMAGE_QUALITY_BICUBIC, it is supposed to be much faster yet yield almost the same results. Closes #11034. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61791 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + include/wx/image.h | 23 ++++- interface/wx/image.h | 35 +++++-- src/common/image.cpp | 235 ++++++++++++++++++++++++++++++------------- 4 files changed, 211 insertions(+), 83 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 83c8386534..98749bb661 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -372,6 +372,7 @@ All: (file, line and function name) and id of the thread which generated it. * SetThreadActiveTarget() allows to set up thread-specific log targets. - Fix output buffer overflow in wxBase64Decode() (Eric W. Savage). +- Added bilinear image resizing algorithm to wxImage (bishop). All (GUI): diff --git a/include/wx/image.h b/include/wx/image.h index 03c1ea2210..eb0d19b7db 100644 --- a/include/wx/image.h +++ b/include/wx/image.h @@ -56,10 +56,18 @@ enum wxImageResolution }; // Constants for wxImage::Scale() for determining the level of quality -enum +enum wxImageResizeQuality { - wxIMAGE_QUALITY_NORMAL = 0, - wxIMAGE_QUALITY_HIGH = 1 + // different image resizing algorithms used by Scale() and Rescale() + wxIMAGE_QUALITY_NEAREST = 0, + wxIMAGE_QUALITY_BILINEAR = 1, + wxIMAGE_QUALITY_BICUBIC = 2, + + // default quality is low (but fast) + wxIMAGE_QUALITY_NORMAL = wxIMAGE_QUALITY_NEAREST, + + // highest (but best) quality + wxIMAGE_QUALITY_HIGH = wxIMAGE_QUALITY_BICUBIC }; // alpha channel values: fully transparent, default threshold separating @@ -308,10 +316,13 @@ public: void Paste( const wxImage &image, int x, int y ); // return the new image with size width*height - wxImage Scale( int width, int height, int quality = wxIMAGE_QUALITY_NORMAL ) const; + wxImage Scale( int width, int height, + wxImageResizeQuality quality = wxIMAGE_QUALITY_NORMAL ) const; // box averager and bicubic filters for up/down sampling + wxImage ResampleNearest(int width, int height) const; wxImage ResampleBox(int width, int height) const; + wxImage ResampleBilinear(int width, int height) const; wxImage ResampleBicubic(int width, int height) const; // blur the image according to the specified pixel radius @@ -322,7 +333,9 @@ public: wxImage ShrinkBy( int xFactor , int yFactor ) const ; // rescales the image in place - wxImage& Rescale( int width, int height, int quality = wxIMAGE_QUALITY_NORMAL ) { return *this = Scale(width, height, quality); } + wxImage& Rescale( int width, int height, + wxImageResizeQuality quality = wxIMAGE_QUALITY_NORMAL ) + { return *this = Scale(width, height, quality); } // resizes the image in place wxImage& Resize( const wxSize& size, const wxPoint& pos, diff --git a/interface/wx/image.h b/interface/wx/image.h index 4a9f0fd13f..1b44387d2a 100644 --- a/interface/wx/image.h +++ b/interface/wx/image.h @@ -23,6 +23,29 @@ enum wxImageResolution wxIMAGE_RESOLUTION_CM = 2 }; +/** + Image resize algorithm. + + This is used with wxImage::Scale() and wxImage::Rescale(). + */ +enum wxImageResizeQuality +{ + /// Simplest and fastest algorithm. + wxIMAGE_QUALITY_NEAREST, + + /// Compromise between wxIMAGE_QUALITY_NEAREST and wxIMAGE_QUALITY_BICUBIC. + wxIMAGE_QUALITY_BILINEAR, + + /// Highest quality but slowest execution time. + wxIMAGE_QUALITY_BICUBIC, + + /// Default image resizing algorithm used by wxImage::Scale(). + wxIMAGE_QUALITY_NORMAL, + + /// Best image resizing algorithm, currently same as wxIMAGE_QUALITY_BICUBIC. + wxIMAGE_QUALITY_HIGH +}; + /** Possible values for PNG image type option. @@ -661,7 +684,7 @@ public: @see Scale() */ wxImage& Rescale(int width, int height, - int quality = wxIMAGE_QUALITY_NORMAL); + wxImageResizeQuality quality = wxIMAGE_QUALITY_NORMAL); /** Changes the size of the image in-place without scaling it by adding either a @@ -714,12 +737,8 @@ public: This is also useful for scaling bitmaps in general as the only other way to scale bitmaps is to blit a wxMemoryDC into another wxMemoryDC. - The parameter @a quality determines what method to use for resampling the image. - Can be one of the following: - - wxIMAGE_QUALITY_NORMAL: Uses the normal default scaling method of pixel - replication - - wxIMAGE_QUALITY_HIGH: Uses bicubic and box averaging resampling methods - for upsampling and downsampling respectively + The parameter @a quality determines what method to use for resampling + the image, see wxImageResizeQuality documentation. It should be noted that although using @c wxIMAGE_QUALITY_HIGH produces much nicer looking results it is a slower method. Downsampling will use the box averaging @@ -753,7 +772,7 @@ public: @see Rescale() */ wxImage Scale(int width, int height, - int quality = wxIMAGE_QUALITY_NORMAL) const; + wxImageResizeQuality quality = wxIMAGE_QUALITY_NORMAL) const; /** Returns a resized version of this image without scaling it by adding either a diff --git a/src/common/image.cpp b/src/common/image.cpp index 70fe52e707..1c527ca0d7 100644 --- a/src/common/image.cpp +++ b/src/common/image.cpp @@ -384,7 +384,8 @@ wxImage wxImage::ShrinkBy( int xFactor , int yFactor ) const return image; } -wxImage wxImage::Scale( int width, int height, int quality ) const +wxImage +wxImage::Scale( int width, int height, wxImageResizeQuality quality ) const { wxImage image; @@ -404,78 +405,32 @@ wxImage wxImage::Scale( int width, int height, int quality ) const if ( old_width == width && old_height == height ) return *this; - // Scale the image (...or more appropriately, resample the image) using - // either the high-quality or normal method as specified - if ( quality == wxIMAGE_QUALITY_HIGH ) - { - // We need to check whether we are downsampling or upsampling the image - if ( width < old_width && height < old_height ) - { - // Downsample the image using the box averaging method for best results - image = ResampleBox(width, height); - } - else - { - // For upsampling or other random/wierd image dimensions we'll use - // a bicubic b-spline scaling method - image = ResampleBicubic(width, height); - } - } - else // Default scaling method == simple pixel replication - { - if ( old_width % width == 0 && old_width >= width && - old_height % height == 0 && old_height >= height ) - { - return ShrinkBy( old_width / width , old_height / height ) ; - } - image.Create( width, height, false ); - - unsigned char *data = image.GetData(); - - wxCHECK_MSG( data, image, wxT("unable to create image") ); - - unsigned char *source_data = M_IMGDATA->m_data; - unsigned char *target_data = data; - 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 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++ ) - { - unsigned char* src_line = &source_data[(y>>16)*old_width*3]; - unsigned char* src_alpha_line = source_alpha ? &source_alpha[(y>>16)*old_width] : 0 ; - - long x = 0; - for ( long i = 0; i < width; i++ ) + // resample the image using either the nearest neighbourhood, bilinear or + // bicubic method as specified + switch ( quality ) + { + case wxIMAGE_QUALITY_BICUBIC: + case wxIMAGE_QUALITY_BILINEAR: + // both of these algorithms should be used for up-sampling the + // image only, when down-sampling always use box averaging for best + // results + if ( width < old_width && height < old_height ) + image = ResampleBox(width, height); + else if ( quality == wxIMAGE_QUALITY_BILINEAR ) + image = ResampleBilinear(width, height); + else if ( quality == wxIMAGE_QUALITY_BICUBIC ) + image = ResampleBicubic(width, height); + break; + + case wxIMAGE_QUALITY_NEAREST: + if ( old_width % width == 0 && old_width >= width && + old_height % height == 0 && old_height >= height ) { - unsigned char* src_pixel = &src_line[(x>>16)*3]; - 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; + return ShrinkBy( old_width / width , old_height / height ); } - y += y_delta; - } + image = ResampleNearest(width, height); + break; } // If the original image has a mask, apply the mask to the new image @@ -497,6 +452,63 @@ wxImage wxImage::Scale( int width, int height, int quality ) const 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") ); + + unsigned char *source_data = M_IMGDATA->m_data; + unsigned char *target_data = data; + 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++ ) + { + unsigned char* src_line = &source_data[(y>>16)*old_width*3]; + unsigned char* src_alpha_line = source_alpha ? &source_alpha[(y>>16)*old_width] : 0 ; + + long x = 0; + for ( long i = 0; i < width; i++ ) + { + unsigned char* src_pixel = &src_line[(x>>16)*3]; + 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; +} + wxImage wxImage::ResampleBox(int width, int height) const { // This function implements a simple pre-blur/box averaging method for @@ -582,6 +594,89 @@ wxImage wxImage::ResampleBox(int width, int height) const return ret_image; } +wxImage wxImage::ResampleBilinear(int width, int height) const +{ + // This function implements a Bilinear algorithm for resampling. + wxImage ret_image(width, height, false); + unsigned char* src_data = M_IMGDATA->m_data; + 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(); + } + double HFactor = double(M_IMGDATA->m_height) / height; + double WFactor = double(M_IMGDATA->m_width) / width; + + int srcpixymax = M_IMGDATA->m_height - 1; + int srcpixxmax = M_IMGDATA->m_width - 1; + + double srcpixy, srcpixy1, srcpixy2, dy, dy1; + double srcpixx, srcpixx1, srcpixx2, dx, dx1; + double r1, g1, b1, a1; + double r2, g2, b2, a2; + + for ( int dsty = 0; dsty < height; dsty++ ) + { + // We need to calculate the source pixel to interpolate from - Y-axis + srcpixy = double(dsty) * HFactor; + srcpixy1 = int(srcpixy); + srcpixy2 = ( srcpixy1 == srcpixymax ) ? srcpixy1 : srcpixy1 + 1.0; + dy = srcpixy - (int)srcpixy; + dy1 = 1.0 - dy; + + + for ( int dstx = 0; dstx < width; dstx++ ) + { + // X-axis of pixel to interpolate from + srcpixx = double(dstx) * WFactor; + srcpixx1 = int(srcpixx); + srcpixx2 = ( srcpixx1 == srcpixxmax ) ? srcpixx1 : srcpixx1 + 1.0; + dx = srcpixx - (int)srcpixx; + dx1 = 1.0 - dx; + + int x_offset1 = srcpixx1 < 0.0 ? 0 : srcpixx1 > srcpixxmax ? srcpixxmax : (int)srcpixx1; + int x_offset2 = srcpixx2 < 0.0 ? 0 : srcpixx2 > srcpixxmax ? srcpixxmax : (int)srcpixx2; + int y_offset1 = srcpixy1 < 0.0 ? 0 : srcpixy1 > srcpixymax ? srcpixymax : (int)srcpixy1; + int y_offset2 = srcpixy2 < 0.0 ? 0 : srcpixy2 > srcpixymax ? srcpixymax : (int)srcpixy2; + + 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] = r1 * dy1 + r2 * dy; + dst_data[1] = g1 * dy1 + g2 * dy; + dst_data[2] = b1 * dy1 + b2 * dy; + dst_data += 3; + + if ( src_alpha ) + *dst_alpha++ = 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) -- 2.45.2