/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES   *
 *   thierry@les-charles.net   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "tbitmap.h"
#include <wx/image.h>

// 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;
}

/** crée une copie en niveaux de gris de l'image (penser à libérer la mémoire) */
TBitmap * TBitmap::getGreyedCopy() const
{
    wxImage copy = this->ConvertToImage();
    int iNbPixels = copy.GetWidth() * copy.GetHeight();
    int iCurrentPixel = 0;
    unsigned char * data = copy.GetData();
    unsigned char * px;
    unsigned char value;

    while(iCurrentPixel < iNbPixels)
    {
        px = data + (iCurrentPixel*3);

        // on saute tout ce qui correspond à la couleur du masque pour que ca reste transparent
        if(px[0] != copy.GetMaskRed()
            || px[1] != copy.GetMaskGreen()
            || px[2] != copy.GetMaskBlue())
        {
            value = px[0];
            if(px[1] > value)
                value = px[1];
            if(px[2] > value)
                value = px[2];

            px[0] = value;
            px[1] = value;
            px[2] = value;
        }

        iCurrentPixel++;
    }

    return new TBitmap(copy);
}

/**
 * crée une copie redimensionnée de l'image (penser à libérer la mémoire)
 * copie conforme de ce qui est dans le CVS de wxImage
 */
TBitmap * TBitmap::getResampledCopy(int width, int height) const
{
    // We need to check whether we are downsampling or upsampling the image
    if ( width < this->GetWidth() && height < this->GetHeight() )
    {
        // Downsample the image using the box averaging method for best results
        return new TBitmap(this->ResampleBox(width, height));
    }
    else
    {
        // For upsampling or other random/wierd image dimensions we'll use
        // a bicubic b-spline scaling method
        return new TBitmap(this->ResampleBicubic(width, height));
    }
}

// This is the bicubic resampling algorithm
wxImage TBitmap::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);

    wxImage src_image(this->ConvertToImage());
    unsigned char* src_data = src_image.GetData();
    unsigned char* src_alpha = src_image.GetAlpha();
    unsigned char* dst_data = ret_image.GetData();
    unsigned char* dst_alpha = NULL;

    if ( src_alpha )
    {
        ret_image.SetAlpha();
        dst_alpha = ret_image.GetAlpha();
    }

    for ( int dsty = 0; dsty < height; dsty++ )
    {
        // We need to calculate the source pixel to interpolate from - Y-axis
        double srcpixy = double(dsty * this->GetHeight()) / height;
        double dy = srcpixy - (int)srcpixy;

        for ( int dstx = 0; dstx < width; dstx++ )
        {
            // X-axis of pixel to interpolate from
            double srcpixx = double(dstx * this->GetWidth()) / width;
            double dx = srcpixx - (int)srcpixx;

            // 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
                int y_offset = srcpixy + k < 0.0
                        ? 0
                    : srcpixy + k >= this->GetHeight()
                        ? this->GetHeight() - 1
                    : (int)(srcpixy + k);

                // Loop across the X axis
                for ( int i = -1; i <= 2; i++ )
                {
                    // X offset
                    int x_offset = srcpixx + i < 0.0
                            ? 0
                        : srcpixx + i >= this->GetWidth()
                            ? this->GetWidth() - 1
                        : (int)(srcpixx + i);

                    // 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*this->GetWidth() + x_offset;

                    // Calculate the weight for the specified pixel according
                    // to the bicubic b-spline kernel we're using for
                    // interpolation
                    double pixel_weight = spline_weight(i - dx)*spline_weight(k - dy);

                    // 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;
}


wxImage TBitmap::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);

    const double scale_factor_x = double(this->GetWidth()) / width;
    const double scale_factor_y = double(this->GetHeight()) / height;

    const int scale_factor_x_2 = (int)(scale_factor_x / 2);
    const int scale_factor_y_2 = (int)(scale_factor_y / 2);

    // If we want good-looking results we need to pre-blur the image a bit first
    wxImage src_image(this->ConvertToImage());
    src_image = this->BlurHorizontal(src_image,scale_factor_x_2);
    src_image = this->BlurVertical(src_image, scale_factor_y_2);

    unsigned char* src_data = src_image.GetData();
    unsigned char* src_alpha = src_image.GetAlpha();
    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
        int src_y = (int)(y * scale_factor_y);

        for ( int x = 0; x < width; x++ )      // Destination image - X direction
        {
            // Source pixel in the X direction
            int src_x = (int)(x * scale_factor_x);

            // Box of pixels to average
            averaged_pixels = 0;
            sum_r = sum_g = sum_b = sum_a = 0.0;

            for ( int j = int(src_y - scale_factor_y/2.0 + 1);
                  j <= int(src_y + scale_factor_y_2);
                  j++ )
            {
                // We don't care to average pixels that don't exist (edges)
                if ( j < 0 || j > this->GetHeight() )
                    continue;

                for ( int i = int(src_x - scale_factor_x/2.0 + 1);
                      i <= src_x + scale_factor_x_2;
                      i++ )
                {
                    // Don't average edge pixels
                    if ( i < 0 || i > this->GetWidth() )
                        continue;

                    // Calculate the actual index in our source pixels
                    src_pixel_index = src_y * this->GetWidth() + src_x;

                    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;
}



// Blur in the horizontal direction
wxImage TBitmap::BlurHorizontal(const wxImage & src_img, int blurRadius) const
{
    wxImage ret_image;
    ret_image.Create(src_img.GetWidth(), src_img.GetHeight(), false);

    unsigned char* src_data = src_img.GetData();
    unsigned char* dst_data = ret_image.GetData();
    unsigned char* src_alpha = src_img.GetAlpha();
    unsigned char* dst_alpha = NULL;

    // Check for a mask or alpha
    if ( src_img.HasMask() )
    {
        ret_image.SetMaskColour(src_img.GetMaskRed(),
                                src_img.GetMaskGreen(),
                                src_img.GetMaskBlue());
    }
    else
    {
        if ( src_alpha )
        {
            ret_image.SetAlpha();
            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 < src_img.GetHeight(); 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 * src_img.GetWidth();
            else
                pixel_idx = kernel_x + y * src_img.GetWidth();

            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 * src_img.GetWidth()*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 * src_img.GetWidth()] = (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 < src_img.GetWidth(); x++ )
        {
            // Take care of edge pixels on the left edge by essentially
            // duplicating the edge pixel
            if ( x - blurRadius - 1 < 0 )
                pixel_idx = y * src_img.GetWidth();
            else
                pixel_idx = (x - blurRadius - 1) + (y * src_img.GetWidth());

            // 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 > src_img.GetWidth() - 1 )
                pixel_idx = src_img.GetWidth() - 1 + (y * src_img.GetWidth());
            else
                pixel_idx = x + blurRadius + (y * src_img.GetWidth());

            // 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*src_img.GetWidth()*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 * src_img.GetWidth())] = (unsigned char)(sum_a / blurArea);
        }
    }

    return ret_image;
}

// Blur in the vertical direction
wxImage TBitmap::BlurVertical(const wxImage & src_img, int blurRadius) const
{
    wxImage ret_image;
    ret_image.Create(src_img.GetWidth(), src_img.GetHeight(), false);

    unsigned char* src_data = src_img.GetData();
    unsigned char* dst_data = ret_image.GetData();
    unsigned char* src_alpha = src_img.GetAlpha();
    unsigned char* dst_alpha = NULL;

    // Check for a mask or alpha
    if ( src_img.HasMask() )
    {
        ret_image.SetMaskColour(src_img.GetMaskRed(),
                                src_img.GetMaskGreen(),
                                src_img.GetMaskBlue());
    }
    else
    {
        if ( src_alpha )
        {
            ret_image.SetAlpha();
            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 < src_img.GetWidth(); 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 * src_img.GetWidth();

            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 < src_img.GetHeight(); 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) * src_img.GetWidth();

            // 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 > src_img.GetHeight() - 1 )
                pixel_idx = x + (src_img.GetHeight() - 1) * src_img.GetWidth();
            else
                pixel_idx = x + (blurRadius + y) * src_img.GetWidth();

            // 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 * src_img.GetWidth()) * 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 * src_img.GetWidth()] = (unsigned char)(sum_a / blurArea);
        }
    }

    return ret_image;
}

