/*******************************************************************************
* Copyright 2005-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#include "base_image.h"

#include <fstream>
#include <math.h>
#include <limits>

struct BMPInfoHeader;
Status BmpReadHeader(File &file, BMPInfoHeader *pHeader, Image &image);
Status BmpReadData(File &file, Image &image);
Status BmpWriteData(Image &image, File &file);

double sfwSaturate(double val, SampleFormat dstFormat, double fMin = 0, double fMax = 1)
{
    double min;
    double max;

    switch(dstFormat)
    {
    case ST_8U:
        min = MIN_UCHAR;
        max = MAX_UCHAR;
        break;
    case ST_8S:
        min = MIN_CHAR;
        max = MAX_CHAR;
        break;
    case ST_16U:
        min = MIN_USHORT;
        max = MAX_USHORT;
        break;
    case ST_16S:
        min = MIN_SHORT;
        max = MAX_SHORT;
        break;
    case ST_32U:
        min = MIN_UINT;
        max = MAX_UINT;
        break;
    case ST_32S:
        min = MIN_INT;
        max = MAX_INT;
        break;
    case ST_32F:
        min = fMin;
        max = fMax;
        break;
    case ST_64F:
        min = fMin;
        max = fMax;
        break;
    default:
        return val;
    }

    return MIN(MAX(val, min), max);
}

template<typename SRC>
void compareGetMaxDiff(Image &in1, Image &in2, double *pRes)
{
    for(long long h = 0; h < in1.m_size.height; h++)
    {
        SRC* Row1 = (SRC*)in1.ptr(h);
        SRC* Row2 = (SRC*)in2.ptr(h);
        for(long long w = 0; w < in1.m_size.width; w++)
        {
            for(long long c = 0; c < in1.m_samples; c++)
            {
                double absVal = ABS((double)(Row1[w+c]-Row2[w+c]));
                pRes[0] = (absVal > pRes[0])?absVal:pRes[0];
                pRes[c+1] = (absVal > pRes[c+1])?absVal:pRes[c+1];
            }
        }
    }
}

unsigned int GetSampleSize(SampleFormat sampleFormat)
{
    if(sampleFormat == ST_8U || sampleFormat == ST_8S)
        return 1;
    else if(sampleFormat == ST_16U || sampleFormat == ST_16S)
        return 2;
    else if(sampleFormat == ST_32U || sampleFormat == ST_32S || sampleFormat == ST_32F)
        return 4;
    else if(sampleFormat == ST_64F)
        return 8;

    return 0;
}
unsigned int GetSamplesNum(ColorFormat colorFormat)
{
    if(colorFormat == CF_GRAY)
        return 1;
    else if(colorFormat == CF_RGB || colorFormat == CF_BGR)
        return 3;
    else if(colorFormat == CF_RGBA || colorFormat == CF_BGRA)
        return 4;

    return 0;
}
bool HasAlpha(ColorFormat colorFormat)
{
    if(colorFormat == CF_RGBA || colorFormat == CF_BGRA)
        return true;
    else
        return false;
}

template<class T>
static double findMinValue(void *pSrc, size_t srcStep, Size size)
{
    double val = ((T*)pSrc)[0];
    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + srcStep*i);
        for(long long j = 0; j < size.width; j++)
        {
            if(val > pSrcRow[j])
                val = pSrcRow[j];
        }
    }
    return val;
}
template<class T>
static double findMaxValue(void *pSrc, size_t srcStep, Size size)
{
    double val = ((T*)pSrc)[0];
    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + srcStep*i);
        for(long long j = 0; j < size.width; j++)
        {
            if(val < pSrcRow[j])
                val = pSrcRow[j];
        }
    }
    return val;
}
static double getRangeMin(SampleFormat format)
{
    if(format == ST_8S)
        return -128;
    else if(format == ST_16S)
        return -32768;
    else if(format == ST_32S)
        return (-2147483647 - 1);
    else 
        return 0;
}
static double getRangeMax(SampleFormat format)
{
    if(format == ST_8U)
        return 0xFF;
    else if(format == ST_8S)
        return 127;
    else if(format == ST_16U)
        return 0xFFFF;
    else if(format == ST_16S)
        return 32767;
    else if(format == ST_32U)
        return 0xFFFFFFFF;
    else if(format == ST_32S)
        return 2147483647;
    else if(format == ST_32F || format == ST_64F)
        return 1;
    else
        return 0;
}

template <class SRC, class DST>
IPP_NOINLINE
static Status cppiConvertSamples(void *pSrc, size_t srcStep, SampleFormat,  void *pDst, size_t dstStep,
    SampleFormat dstFormat, Size size, bool rescale = true, double min = 0, double max = 0)
{
    if(rescale)
    {
        double srcMin  = (min == max)?findMinValue<SRC>(pSrc, srcStep, size):min;
        double srcMax  = (min == max)?findMaxValue<SRC>(pSrc, srcStep, size):max;
        double dstMin  = getRangeMin(dstFormat);
        double dstMax  = getRangeMax(dstFormat);
        double srcRange = srcMax - srcMin;
        double dstRange = dstMax - dstMin;

        if(srcRange <= 0 || dstRange <= 0)
            return STS_ERR_FAILED;

        double mul = dstRange/srcRange;

        for(long long i = 0; i < size.height; i++)
        {
            SRC *pSrcRow = (SRC*)((char*)pSrc + i*srcStep);
            DST *pDstRow = (DST*)((char*)pDst + i*dstStep);

            for(long long j = 0; j < size.width; j++)
            {
                double res = (((pSrcRow[j] - srcMin)*mul + dstMin));
                if(res < dstMin)
                    res = dstMin;
                else if(res > dstMax)
                    res = dstMax;

                pDstRow[j] = (DST)res;
            }
        }
    }
    else // saturate
    {
        for(long long i = 0; i < size.height; i++)
        {
            SRC *pSrcRow = (SRC*)((char*)pSrc + i*srcStep);
            DST *pDstRow = (DST*)((char*)pDst + i*dstStep);

            for(long long j = 0; j < size.width; j++)
            {
                pDstRow[j] = (DST)sfwSaturate(pSrcRow[j], dstFormat, min, max);
            }
        }
    }

    return STS_OK;
}

template <class T>
static Status cppiGrayToRGB_P1C3R(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size)
{
    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, k = 0; j < size.width; j++, k += 3)
        {
            pDstRow[k] = pDstRow[k + 1] = pDstRow[k + 2] = pSrcRow[j];
        }
    }
    return STS_OK;
}

template <class T>
static Status cppiGrayToRGBA_P1C4R(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size, double alphaVal)
{
    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, k = 0; j < size.width; j++, k += 4)
        {
            pDstRow[k] = pDstRow[k + 1] = pDstRow[k + 2] = pSrcRow[j];
            pDstRow[k + 3] = (T)alphaVal;
        }
    }
    return STS_OK;
}

template <class T>
static Status cppiRGBToGray_C3P1R(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size, bool bBGR)
{
    int ind1 = (bBGR)?2:0;
    int ind3 = (bBGR)?0:2;

    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, k = 0; j < size.width; j++, k += 3)
        {
            pDstRow[j] = (T)((double)pSrcRow[k + ind1]*0.299 + (double)pSrcRow[k + 1]*0.587 + (double)pSrcRow[k + ind3]*0.114);
        }
    }

    return STS_OK;
}

template <class T>
static Status cppiCopy_C3C4R(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size, double alphaVal = 0)
{
    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, src = 0, dst = 0; j < size.width; j++, src += 3, dst += 4)
        {
            pDstRow[dst]   = pSrcRow[src];
            pDstRow[dst+1] = pSrcRow[src+1];
            pDstRow[dst+2] = pSrcRow[src+2];
            pDstRow[dst+3] = (T)alphaVal;
        }
    }
    return STS_OK;
}

template <class T>
static Status cppiSwapChannes(void *pSrc, size_t srcStep, unsigned int srcChannels, void *pDst, size_t dstStep, unsigned int dstChannels, Size size, double alphaVal = 0)
{
    T iTemp;

    if((srcChannels != 3 && srcChannels != 4) || (dstChannels != 3 && dstChannels != 4))
        return STS_ERR_UNSUPPORTED;

    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, src = 0, dst = 0; j < size.width; j++, src += srcChannels, dst += dstChannels)
        {
            iTemp = pSrcRow[src];
            pDstRow[dst] = pSrcRow[src + 2];
            pDstRow[dst + 1] = pSrcRow[src + 1];
            pDstRow[dst + 2] = iTemp;
            if(dstChannels == 4)
                pDstRow[dst + 3] = (T)alphaVal;
        }
    }

    return STS_OK;
}

Status sfwCopy(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size, unsigned int sampleSize, unsigned int channels)
{
    for(long long i = 0; i < size.height; i++)
        vm_memcpy((char*)pDst + i*dstStep, (char*)pSrc + i*srcStep, (size_t)(size.width*sampleSize*channels));
    return STS_OK;
}

template<typename T>
Status cppiSetT(Image val, Image &dst, int dstChannel, int srcChannel)
{
    if(dstChannel >= 0)
    {
        for(long long h = 0; h < dst.m_size.height; h++)
        {
            for(long long w = 0; w < dst.m_size.width; w++)
            {
                *((T*)dst.ptr(h, w, dstChannel)) = *((T*)val.ptr(h, w, srcChannel, MP_WRAP));
            }
        }
    }
    else
    {
        for(long long h = 0; h < dst.m_size.height; h++)
        {
            for(long long w = 0; w < dst.m_size.width; w++)
            {
                for(unsigned int c = 0; c < dst.m_samples; c++)
                {
                    *((T*)dst.ptr(h, w, c)) = *((T*)val.ptr(h, w, c, MP_WRAP));
                }
            }
        }
    }
    return STS_OK;
}

Status cppiSet(Image val, Image &dst, int dstChannel = - 1, int srcChannel = 0)
{
    if(dstChannel >= (int)dst.m_samples)
        return STS_OK;

    val.ConvertSamples(dst.m_sampleFormat, NULL, false);

    switch(dst.m_sampleFormat)
    {
    case ST_8U:     return cppiSetT<unsigned char>  (val, dst, dstChannel, srcChannel);
    case ST_8S:     return cppiSetT<char>           (val, dst, dstChannel, srcChannel);
    case ST_16U:    return cppiSetT<unsigned short> (val, dst, dstChannel, srcChannel);
    case ST_16S:    return cppiSetT<short>          (val, dst, dstChannel, srcChannel);
    case ST_32U:    return cppiSetT<unsigned int>   (val, dst, dstChannel, srcChannel);
    case ST_32S:    return cppiSetT<int>            (val, dst, dstChannel, srcChannel);
    case ST_32F:    return cppiSetT<float>          (val, dst, dstChannel, srcChannel);
    case ST_64F:    return cppiSetT<double>         (val, dst, dstChannel, srcChannel);
    default:        return STS_ERR_UNSUPPORTED;
    }
}

template<typename T>
Status cppiSetChannelT(double val, void *pDst, size_t dstStep, Size size, unsigned int channels, unsigned int channel)
{
    for(long long h = 0; h < size.height; h++)
    {
        for(long long w = 0; w < size.width; w++)
        {
            ((T*)((char*)pDst + h*dstStep + w*sizeof(T)*channels))[channel] = (T)val;
        }
    }
    return STS_OK;
}

Status cppiSetChannel(double val, void *pDst, size_t dstStep, Size size, SampleFormat sampleFormat, unsigned int channels, unsigned int channel)
{
    if(channel >= channels)
        return STS_ERR_INVALID_PARAMS;

    switch(sampleFormat)
    {
    case ST_8U:     return cppiSetChannelT<unsigned char>  (val, pDst, dstStep, size, channels, channel);
    case ST_8S:     return cppiSetChannelT<char>           (val, pDst, dstStep, size, channels, channel);
    case ST_16U:    return cppiSetChannelT<unsigned short> (val, pDst, dstStep, size, channels, channel);
    case ST_16S:    return cppiSetChannelT<short>          (val, pDst, dstStep, size, channels, channel);
    case ST_32U:    return cppiSetChannelT<unsigned int>   (val, pDst, dstStep, size, channels, channel);
    case ST_32S:    return cppiSetChannelT<int>            (val, pDst, dstStep, size, channels, channel);
    case ST_32F:    return cppiSetChannelT<float>          (val, pDst, dstStep, size, channels, channel);
    case ST_64F:    return cppiSetChannelT<double>         (val, pDst, dstStep, size, channels, channel);
    default:        return STS_ERR_UNSUPPORTED;
    }
}

static Status CArrayWrite(Image &image, const char *fileName)
{
    long long bufferWidth = image.m_step/image.m_sampleSize;
    std::ofstream file(fileName);
    file << "// Auto-generated image array for C\n\n";
    file << "const ";
    if(image.m_sampleFormat == ST_8U)
        file << "unsigned char ";
    else if(image.m_sampleFormat == ST_8S)
        file << "char ";
    else if(image.m_sampleFormat == ST_16U)
        file << "unsigned short ";
    else if(image.m_sampleFormat == ST_16S)
        file << "short ";
    else if(image.m_sampleFormat == ST_32U)
        file << "unsigned int ";
    else if(image.m_sampleFormat == ST_32S)
        file << "int ";
    else if(image.m_sampleFormat == ST_32F)
        file << "float ";
    else if(image.m_sampleFormat == ST_64F)
        file << "double ";
    else
        return STS_ERR_FAILED;
    file << "image_" << sampleFormatName[image.m_sampleFormat] << "_C" << image.m_samples << "_" << image.m_size.width << "x" << image.m_size.height << "_S" << image.m_step << "[" << image.m_size.height << "][" << bufferWidth << "] = {\n";
    for(long long h = 0; h < image.m_size.height; h++)
    {
        file << "{ ";
        for(long long w = 0; w < bufferWidth/image.m_samples; w++)
        {
            for(long long c = 0; c < image.m_samples; c++)
            {
                if(image.m_sampleFormat == ST_8U)
                    file << DString::Format("%d", *(((unsigned char*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_8S)
                    file << DString::Format("%d", *(((char*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_16U)
                    file << DString::Format("%d", *(((unsigned short*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_16S)
                    file << DString::Format("%d", *(((short*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_32U)
                    file << DString::Format("%d", *(((unsigned int*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_32S)
                    file << DString::Format("%d", *(((int*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_32F)
                    file << DString::Format("%.8gf", *(((float*)image.ptr(h, w)) + c)).c_str();
                else if(image.m_sampleFormat == ST_64F)
                    file << DString::Format("%.16g", *(((double*)image.ptr(h, w)) + c)).c_str();

                if(w < image.m_size.width - 1 || c < image.m_samples - 1)
                    file << ", ";
            }
        }
        if(h < image.m_size.height - 1)
            file << "},\n";
        else
            file << "}\n";
    }
    file << "};\n";

    return STS_OK;
}

static FileFormat GetImageType(File &file)
{
    unsigned char buf[4];

    file.Seek(0, File::ORIGIN_BEGIN);
    file.Read(&buf[0], 1, 4);
    file.Seek(0, File::ORIGIN_BEGIN);

    // check BMP
    if((buf[0] == 'B') && (buf[1] == 'M'))
        return FF_BMP;
    else
        return FF_RAW;
}

Image::Image()
{
    m_bufferAlignment  = 64;
    m_stepAlignment    = 64;
    Reset();
}

Image::Image(Size size, ColorFormat color, SampleFormat sampleFormat)
{
    m_bufferAlignment  = 64;
    m_stepAlignment    = 64;

    Reset();

    m_size         = size;
    m_sampleFormat = sampleFormat;
    m_sampleSize   = GetSampleSize(sampleFormat);
    m_color        = color;
    m_samples      = GetSamplesNum(m_color);
}

Image::Image(Size size, unsigned int samples, SampleFormat sampleFormat)
{
    m_bufferAlignment  = 64;
    m_stepAlignment    = 64;

    Reset();

    m_size         = size;
    m_sampleFormat = sampleFormat;
    m_sampleSize   = GetSampleSize(sampleFormat);
    m_samples      = samples;
}

Image::Image(const char *pFileName, ColorFormat dstColor, SampleFormat dstFormat)
{
    m_bufferAlignment  = 64;
    m_stepAlignment    = 64;

    Reset();

    Read(pFileName, dstColor, dstFormat);
}

Image::~Image()
{
    Release();
}

Status Image::Reset()
{
    Release();

    m_pPointer         = 0;
    m_imageSize        = 0;
    m_bufferSize       = 0;
    m_step             = 0;
    m_samples          = 0;
    m_size.width       = 0;
    m_size.height      = 0;
    m_sampleSize       = 0;
    m_color            = CF_UNKNOWN;
    m_sampleFormat     = ST_UNKNOWN;
    m_border           = BorderSize();

    return STS_OK;
}

Status Image::Alloc(Size size, unsigned int samples, SampleFormat sampleFormat)
{
    if(size.width < 0 || size.height < 0 || !GetSampleSize(sampleFormat) || !m_stepAlignment)
        return STS_ERR_INVALID_PARAMS;

    if(m_buffer.get() && m_buffer.getCounter() == 1 &&
        m_size.width == size.width && m_size.height == size.height && m_samples == samples && m_sampleFormat == sampleFormat)
        return STS_OK;

    Release();

    m_samples       = samples;
    m_sampleSize    = GetSampleSize(sampleFormat);
    m_size          = size;
    m_sampleFormat  = sampleFormat;

    m_step = (size_t)((m_size.width+m_border.left+m_border.right)*m_samples*m_sampleSize);
    m_step = alignValue<size_t>(m_step, m_stepAlignment);

    m_bufferSize = (size_t)(m_step*(m_size.height+m_border.top+m_border.bottom));
    if(!m_bufferSize)
        return STS_OK;

    m_buffer.attach((unsigned char*)vm_malloc(m_bufferSize, m_bufferAlignment));
    if(!m_buffer.get())
        return STS_ERR_ALLOC;

    m_pPointer  = (unsigned char*)m_buffer.get();
    m_pPointer  = (unsigned char*)ptr(m_border.top, m_border.left);
    m_imageSize = (size_t)(m_step*m_size.height);

    return STS_OK;
}

Status Image::Alloc(Size size, ColorFormat color, SampleFormat sampleFormat)
{
    m_color = color;
    return Alloc(size, GetSamplesNum(m_color), sampleFormat);
}

Status Image::Alloc()
{
    return Alloc(m_size, m_samples, m_sampleFormat);
}

Status Image::Release()
{
    void *pBuffer = m_buffer.deattach();
    if(pBuffer)
        vm_free(pBuffer);

    m_pPointer    = NULL;
    m_imageSize   = 0;
    m_bufferSize  = 0;
    m_border      = BorderSize();

    return STS_OK;
}

Status Image::AttachBuffer(Size size, ColorFormat color, SampleFormat sampleFormat, void *pBuffer, size_t step)
{
    m_color = color;
    return AttachBuffer(size, GetSamplesNum(m_color), sampleFormat, pBuffer, step);
}

Status Image::AttachBuffer(Size size, unsigned int samples, SampleFormat sampleFormat, void *pBuffer, size_t step)
{
    Release();

    m_samples    = samples;
    m_sampleSize = GetSampleSize(sampleFormat);

    if(size.width < 0 || size.height < 0 || !m_sampleSize || !m_stepAlignment)
        return STS_ERR_INVALID_PARAMS;

    m_size          = size;
    m_sampleFormat  = sampleFormat;
    m_step          = step;
    m_bufferSize    = 0;

    m_pPointer  = (unsigned char*)pBuffer;
    m_imageSize = (size_t)(m_step*m_size.height);

    return STS_OK;
}

Status Image::ReadHeader(const char *pFileName)
{
    Status status = STS_OK;
    File   file(pFileName, "rb");
    if(!file.IsOpened())
        return STS_ERR_FILE_OPEN;

    FileFormat type = GetImageType(file);

    // read image to buffer
    if(type == FF_BMP)
        status = BmpReadHeader(file, NULL, *this);
    else
        status = STS_ERR_UNSUPPORTED;

    if(status != STS_OK)
        return status;

    return status;
}

Status Image::Read(const char *pFileName, ColorFormat dstColor, SampleFormat dstFormat)
{
    Status status = STS_OK;
    File   file(pFileName, "rb");
    if(!file.IsOpened())
        return STS_ERR_FILE_OPEN;

    FileFormat type = GetImageType(file);

    // read image to buffer
    if(type == FF_BMP)
        status = BmpReadData(file, *this);
    else if(type == FF_RAW && ptr()) // Image must be preallocated to read RAW files
    {
        size_t count;
        for(long long row = 0; row < m_size.height; row++)
        {
            count = file.Read(ptr(row), m_sampleSize*m_samples, (size_t)m_size.width);
            if(count != (size_t)m_size.width)
            {
                status = STS_ERR_NOT_ENOUGH_DATA;
                break;
            }
        }
    }
    else
        status = STS_ERR_UNSUPPORTED;

    if(status != STS_OK)
        return status;

    if(dstColor != CF_UNKNOWN)
    {
        status = ConvertColor(dstColor);
        if(status != STS_OK)
            return status;
    }

    if(dstFormat != ST_UNKNOWN)
    {
        status = ConvertSamples(dstFormat);
        if(status != STS_OK)
            return status;
    }

    return status;
}

Status Image::Write(const char *pFileName, FileFormat fileFormat)
{
    Status status = STS_OK;
    File   file;

    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    // Detect file format
    if(fileFormat == FF_UNKNOWN)
    {
        DString fileName = pFileName;
        std::vector<DString> fileVec;
        DString::Split(fileName, '\\', fileVec, false, 1); // trim slashes
        DString::Split(fileVec[0], '/', fileVec, false, 1);
        DString::Split(fileVec[0], '.', fileVec, false, 2);
        if(fileVec.size() > 1)
        {
            fileName = fileVec[0];

            if(!fileName.Compare("bmp", false))
                fileFormat = FF_BMP;
            else if(!fileName.Compare("raw", false))
                fileFormat = FF_RAW;
            else if(!fileName.Compare("c", false))
                fileFormat = FF_C;
            else
            {
                PRINT_MESSAGE(DString::Format("Unsupported file extension: .%s", fileName.c_str()).c_str());
                status = STS_ERR_UNSUPPORTED;
            }
        }
        else
            fileFormat = FF_RAW; // Treat no extension as raw
    }

    if(fileFormat == FF_RAW)
    {
        status = file.Open(pFileName, "wb");
        CHECK_STATUS_PRINT(status, "File::Open()", GetBaseStatusString(status));

        size_t count;
        for(long long i = 0; i < m_size.height; i++)
        {
            count = file.Write(ptr(i), m_samples*m_sampleSize, (size_t)m_size.width);
            if(count != (size_t)m_size.width)
            {
                status = STS_ERR_FAILED;
                break;
            }
        }
    }
    else if(fileFormat == FF_BMP)
    {
        status = file.Open(pFileName, "wb");
        CHECK_STATUS_PRINT(status, "File::Open()", GetBaseStatusString(status));

        status = BmpWriteData(*this, file);
    }
    else if(fileFormat == FF_C)
        status = CArrayWrite(*this, pFileName);
    else
        status = STS_ERR_UNSUPPORTED;

    return status;
}

Status Image::ConvertColor(ColorFormat dstColor, Image *pDst, double alphaVal)
{
    Status  status;

    Image  *pSrc = this;
    Image   tmpImage;

    if(m_sampleFormat != ST_8U)
        return STS_ERR_UNSUPPORTED;

    if(dstColor == CF_UNKNOWN)
        return STS_ERR_INVALID_PARAMS;

    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    if(pDst && pSrc != pDst)
    {
        status = pDst->Alloc(pSrc->m_size, dstColor, pSrc->m_sampleFormat);
        if(status < 0)
            return status;

        if(pSrc->m_color == pDst->m_color)
            return CopyTo(*pDst);
    }
    else
    {
        if(pSrc->m_color == dstColor)
            return STS_OK;

        tmpImage = *pSrc;
        pDst     = pSrc;
        pSrc     = &tmpImage;

        // Inplace
        if(pSrc->m_samples == GetSamplesNum(dstColor))
            pDst->m_color = dstColor;
        else
        {
            status = pDst->Alloc(pSrc->m_size, dstColor, pSrc->m_sampleFormat);
            if(status < 0)
                return status;
        }
    }

    alphaVal = sfwSaturate(alphaVal, pSrc->m_sampleFormat);
    switch(pSrc->m_color)
    {
    case CF_GRAY:
        switch(pDst->m_color)
        {
        case CF_RGB:
        case CF_BGR:  status = cppiGrayToRGB_P1C3R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size); break;
        case CF_RGBA:
        case CF_BGRA: status = cppiGrayToRGBA_P1C4R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size, alphaVal); break;
        default:      status = STS_ERR_UNSUPPORTED; break;
        }
        break;
    case CF_RGB:
        switch(pDst->m_color)
        {
        case CF_GRAY: status = cppiRGBToGray_C3P1R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size, false); break;
        case CF_RGBA: status = cppiCopy_C3C4R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size, alphaVal); break;
        case CF_BGR:
        case CF_BGRA: status = cppiSwapChannes<unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_samples, pDst->ptr(), pDst->m_step, pDst->m_samples, m_size, alphaVal); break;
        default:      status = STS_ERR_UNSUPPORTED; break;
        }
        break;
    case CF_BGR:
        switch(pDst->m_color)
        {
        case CF_GRAY: status = cppiRGBToGray_C3P1R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size, true); break;
        case CF_BGRA: status = cppiCopy_C3C4R<unsigned char>(pSrc->ptr(), pSrc->m_step, pDst->ptr(), pDst->m_step, m_size, alphaVal); break;
        case CF_RGB:
        case CF_RGBA: status = cppiSwapChannes<unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_samples, pDst->ptr(), pDst->m_step, pDst->m_samples, m_size, alphaVal); break;
        default:      status = STS_ERR_UNSUPPORTED; break;
        }
        break;
    default:
        status = STS_ERR_UNSUPPORTED; break;
    }

    return status;
}

Status Image::ConvertSamples(SampleFormat dstFormat, Image *pDst, bool bRescale, bool bDetectMinMax, double min, double max)
{
    Status  status;

    Image  *pSrc = this;
    Image   tmpImage;

    if(dstFormat == ST_UNKNOWN)
        return STS_ERR_INVALID_PARAMS;

    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    if(pDst && pSrc != pDst)
    {
        if(pSrc->m_color==CF_UNKNOWN)
            status = pDst->Alloc(pSrc->m_size, pSrc->m_samples, dstFormat);
        else
            status = pDst->Alloc(pSrc->m_size, pSrc->m_color, dstFormat);
        if(status < 0)
            return status;

        if(pSrc->m_sampleFormat == pDst->m_sampleFormat && (pSrc->m_sampleFormat != ST_32F && pSrc->m_sampleFormat != ST_64F))
            return CopyTo(*pDst);
    }
    else
    {
        if(pSrc->m_sampleFormat == dstFormat && (pSrc->m_sampleFormat != ST_32F && pSrc->m_sampleFormat != ST_64F))
            return STS_OK;

        tmpImage = *pSrc;
        pDst     = pSrc;
        pSrc     = &tmpImage;

        // Inplace
        if(pSrc->m_sampleSize == GetSampleSize(dstFormat))
            pDst->m_sampleFormat = dstFormat;
        else
        {
            if(pSrc->m_color==CF_UNKNOWN)
                status = pDst->Alloc(pSrc->m_size, pSrc->m_samples, dstFormat);
            else
                status = pDst->Alloc(pSrc->m_size, pSrc->m_color, dstFormat);

            if(status < 0)
                return status;
        }
    }

    if(!bDetectMinMax && min == max)
    {
        min = getRangeMin(pSrc->m_sampleFormat);
        max = getRangeMax(pSrc->m_sampleFormat);
    }
    Size size(m_size.width*m_samples, m_size.height);
    if(pSrc->m_sampleFormat == ST_8U)
    {
        if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<unsigned char, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<unsigned char, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<unsigned char, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<unsigned char, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<unsigned char, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<unsigned char, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<unsigned char, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_8S)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<char, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<char, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<char, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<char, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<char, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<char, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<char, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_16U)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<unsigned short, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<unsigned short, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<unsigned short, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<unsigned short, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<unsigned short, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<unsigned short, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<unsigned short, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_16S)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<short, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<short, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<short, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<short, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<short, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<short, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<short, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_32U)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<unsigned int, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<unsigned int, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<unsigned int, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<unsigned int, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<unsigned int, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<unsigned int, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<unsigned int, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_32S)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<int, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<int, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<int, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<int, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<int, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<int, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<int, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_32F)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<float, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<float, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<float, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<float, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<float, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<float, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<float, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<float, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else if(pSrc->m_sampleFormat == ST_64F)
    {
        if(pDst->m_sampleFormat == ST_8U)
            status = cppiConvertSamples<double, unsigned char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_8S)
            status = cppiConvertSamples<double, char>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16U)
            status = cppiConvertSamples<double, unsigned short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_16S)
            status = cppiConvertSamples<double, short>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32U)
            status = cppiConvertSamples<double, unsigned int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32S)
            status = cppiConvertSamples<double, int>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_32F)
            status = cppiConvertSamples<double, float>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else if(pDst->m_sampleFormat == ST_64F)
            status = cppiConvertSamples<double, double>(pSrc->ptr(), pSrc->m_step, pSrc->m_sampleFormat, pDst->ptr(), pDst->m_step, pDst->m_sampleFormat, size, bRescale, min, max);
        else
            status = STS_ERR_UNSUPPORTED;
    }
    else
        status = STS_ERR_UNSUPPORTED;

    return status;
}

Status Image::CopyTo(Image &dst, BorderSize borderSize)
{
    if(!m_buffer.get())
        return STS_ERR_NULL_PTR;

    if(m_size.width < 0 || m_size.height < 0)
        return STS_ERR_INVALID_PARAMS;

    if(!dst.m_pPointer || m_size.width != dst.m_size.width || m_size.height != dst.m_size.height || m_samples != dst.m_samples || m_sampleFormat != dst.m_sampleFormat ||
        borderSize.left > dst.m_border.left || borderSize.top > dst.m_border.top || borderSize.right > dst.m_border.right || borderSize.bottom > dst.m_border.bottom)
    {
        dst.Alloc(Size(m_size.width+borderSize.left+borderSize.right, m_size.height+borderSize.top+borderSize.bottom), m_color, m_sampleFormat);
        dst.BorderSet(borderSize);
    }
    if(this->m_pPointer == dst.m_pPointer)
        return STS_OK;

    for(long long i = 0; i < m_size.height; i++)
    {
        vm_memcpy(dst.ptr(i), ptr(i), (size_t)(m_size.width*m_samples*m_sampleSize));
    }

    return STS_OK;
}

Status Image::MapTo(Image &dst, Point dstOffset, Point srcOffset)
{
    if(!m_buffer.get())
        return STS_ERR_NULL_PTR;

    if(m_size.width < 0 || m_size.height < 0 || dst.m_size.width < 0 || dst.m_size.height < 0)
        return STS_ERR_INVALID_PARAMS;

    if(dstOffset.x < 0 || dstOffset.y < 0 || srcOffset.x < 0 || srcOffset.y < 0 || m_samples != dst.m_samples || m_sampleSize != dst.m_sampleSize)
        return STS_ERR_INVALID_PARAMS;

    if(dstOffset.x > dst.m_size.width)
        dstOffset.x = dst.m_size.width;
    if(dstOffset.y > dst.m_size.height)
        dstOffset.y = dst.m_size.height;

    if(srcOffset.x > this->m_size.width)
        srcOffset.x = this->m_size.width;
    if(srcOffset.y > this->m_size.height)
        srcOffset.y = this->m_size.height;

    Size minSize(MIN(dst.m_size.width - dstOffset.x, this->m_size.width - srcOffset.x), MIN(dst.m_size.height - dstOffset.y, this->m_size.height - srcOffset.y));
    for(long long i = 0; i < minSize.height; i++)
    {
        vm_memcpy(dst.ptr(i+dstOffset.y, dstOffset.x), ptr(i+srcOffset.y, srcOffset.x), (size_t)(minSize.width*m_samples*m_sampleSize));
    }

    return STS_OK;
}

Status Image::Set(double val, int channel)
{
    Image temp;
    temp.AttachBuffer(Size(1,1), 1, ST_64F, &val, 8);
    return cppiSet(temp, *this, channel);
}

Status Image::Set(Image val, int dstChannel, int srcChannel)
{
    return cppiSet(val, *this, dstChannel, srcChannel);
}

Status Image::BorderAdd(BorderSize borderSize)
{
    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    if((int)(borderSize.left + borderSize.right) >= this->m_size.width)
        return STS_ERR_FAILED;
    if((int)(borderSize.top + borderSize.bottom) >= this->m_size.height)
        return STS_ERR_FAILED;

    this->m_pPointer    = (unsigned char*)this->ptr(borderSize.top, borderSize.left);
    this->m_size.width  = this->m_size.width  - borderSize.left - borderSize.right;
    this->m_size.height = this->m_size.height - borderSize.top  - borderSize.bottom;
    this->m_border.left   += borderSize.left;
    this->m_border.top    += borderSize.left;
    this->m_border.right  += borderSize.right;
    this->m_border.bottom += borderSize.bottom;

    return STS_OK;
}

Status Image::BorderSub(BorderSize borderSize)
{
    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    if(borderSize.left > this->m_border.left)
        return STS_ERR_FAILED;
    if(borderSize.top > this->m_border.top)
        return STS_ERR_FAILED;
    if(borderSize.right > this->m_border.right)
        return STS_ERR_FAILED;
    if(borderSize.bottom > this->m_border.bottom)
        return STS_ERR_FAILED;

    this->m_pPointer    = (unsigned char*)this->ptr(-((int)borderSize.top), -((int)borderSize.left));
    this->m_size.width  = this->m_size.width  + borderSize.left + borderSize.right;
    this->m_size.height = this->m_size.height + borderSize.top  + borderSize.bottom;
    this->m_border.left   -= borderSize.left;
    this->m_border.top    -= borderSize.top;
    this->m_border.right  -= borderSize.right;
    this->m_border.bottom -= borderSize.bottom;

    return STS_OK;
}

Status Image::BorderSet(BorderSize borderSize)
{
    long long diffLeft, diffTop, diffRight, diffBottom;

    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    diffLeft    = borderSize.left    - this->m_border.left;
    diffTop     = borderSize.top     - this->m_border.top;
    diffRight   = borderSize.right   - this->m_border.right;
    diffBottom  = borderSize.bottom  - this->m_border.bottom;

    if(diffLeft+diffRight >= this->m_size.width)
        return STS_ERR_FAILED;
    if(diffTop+diffBottom >= this->m_size.height)
        return STS_ERR_FAILED;

    this->m_pPointer    = (unsigned char*)this->ptr(diffTop, diffLeft, 0);
    this->m_size.width  = this->m_size.width  - (diffLeft + diffRight);
    this->m_size.height = this->m_size.height - (diffTop + diffBottom);
    this->m_border   = borderSize;

    return STS_OK;
}

Status Image::RoiSet(Rect roi)
{
    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    // Unroll border
    this->m_pPointer    = (unsigned char*)this->ptr(-(int)this->m_border.top, -(int)this->m_border.left);
    this->m_size.width  = this->m_size.width  + this->m_border.left + this->m_border.right;
    this->m_size.height = this->m_size.height + this->m_border.top  + this->m_border.bottom;
    roi.x += this->m_border.left;
    roi.y += this->m_border.top;
    if(roi.x < 0 || roi.y < 0)
        return STS_ERR_FAILED;
    if(roi.width <= 0 || roi.height <= 0)
        return STS_ERR_FAILED;
    if(roi.x > this->m_size.width || roi.y > this->m_size.height)
        return STS_ERR_FAILED;
    if(roi.x + roi.width > this->m_size.width)
        roi.width   = this->m_size.width - roi.x;
    if(roi.y + roi.height > this->m_size.height)
        roi.height = this->m_size.height - roi.y;

    // Rebuild border
    this->m_border.left   = roi.x;
    this->m_border.top    = roi.y;
    this->m_border.right  = (this->m_size.width  - roi.x - roi.width);
    this->m_border.bottom = (this->m_size.height - roi.y - roi.height);
    this->m_size.width    = roi.width;
    this->m_size.height   = roi.height;
    this->m_pPointer      = (unsigned char*)this->ptr(this->m_border.top, this->m_border.left);

    return STS_OK;
}

Image Image::GetRoiImage(Rect roi)
{
    if(!IsInitialized())
        return Image();

    Image image = *this;
    image.RoiSet(roi);
    return image;
}

bool Image::Compare(const Image &image)
{
    if(m_sampleSize     != image.m_sampleSize ||
        m_sampleFormat  != image.m_sampleFormat ||
        m_size.width    != image.m_size.width ||
        m_size.height   != image.m_size.height ||
        m_samples       != image.m_samples)
        return false;

    if(m_color != image.m_color && m_color != CF_UNKNOWN && image.m_color != CF_UNKNOWN)
        return false;

    return true;
}

bool Image::operator==(const Image& image)
{
    return Compare(image);
}

bool Image::operator!=(const Image& image)
{
    return !Compare(image);
}

Image::Image(const Image& ref)
{
    *this = ref;
}

Image& Image::operator=(const Image &image)
{
    Release();

    m_buffer            = image.m_buffer;
    m_pPointer          = image.m_pPointer;
    m_imageSize         = image.m_imageSize;
    m_bufferSize        = image.m_bufferSize;
    m_step              = image.m_step;
    m_sampleFormat      = image.m_sampleFormat;
    m_sampleSize        = image.m_sampleSize;
    m_samples           = image.m_samples;
    m_size.width        = image.m_size.width;
    m_size.height       = image.m_size.height;
    m_color             = image.m_color;
    m_border            = image.m_border;

    if(m_pPointer)
    {
        m_stepAlignment     = image.m_stepAlignment;
        m_bufferAlignment   = image.m_bufferAlignment;
    }

    return *this;
}

std::vector<double> Image::FindMaxDiff(Image &ref)
{
    if(*this != ref)
        return std::vector<double>();

    std::vector<double> diff(this->m_samples+1);

    if(this->m_sampleFormat == ST_8U)
        compareGetMaxDiff<unsigned char>    (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_8S)
        compareGetMaxDiff<char>             (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_16U)
        compareGetMaxDiff<unsigned short>   (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_16S)
        compareGetMaxDiff<short>            (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_32U)
        compareGetMaxDiff<unsigned int>     (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_32S)
        compareGetMaxDiff<int>              (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_32F)
        compareGetMaxDiff<float>            (*this, ref, &diff[0]);
    else if(this->m_sampleFormat == ST_64F)
        compareGetMaxDiff<double>           (*this, ref, &diff[0]);
    else
        return std::vector<double>();

    return diff;
}

Status Image::GenerateSin(double frequency, double amplitude, double shiftX, double shiftY)
{
    if(!IsInitialized())
        return STS_ERR_NOT_INITIALIZED;

    Image pattern;
    pattern.Alloc(Size(m_size.width, 1), this->m_samples, (this->m_sampleFormat == ST_32F)?ST_32F:ST_64F);
    if(!this->IsFloat()) // shift values for integer dst to the middle of the range [0;1]
        shiftY += 0.5;

    // Initialize pattern
    double step = (double)(2*PI)/m_size.width*frequency;
    for(long long i = 0; i < pattern.m_size.width; i++)
    {
        pattern.SetPixel(i, 0, 0, ((sin(step*i + shiftX))*amplitude+shiftY));

        for(unsigned int c = 1; c < pattern.m_samples; c++)
            pattern.SetPixel(i, 0, c, pattern.GetPixel(i, 0, 0));
    }
    if(!this->IsFloat())
        pattern.ConvertSamples(this->m_sampleFormat, NULL, true, false);

    for(long long i = 0; i < this->m_size.height; i++)
        vm_memcpy(this->ptr(i), pattern.ptr(), this->m_step);

    return STS_OK;
}

void Image::SetPixel(long long x, long long y, unsigned int c, double value)
{
    switch(this->m_sampleFormat)
    {
    case ST_8U:     ((unsigned char*)this->ptr(y, x))[c]    = (unsigned char)value;     return;
    case ST_8S:     ((char*)this->ptr(y, x))[c]             = (char)value;              return;
    case ST_16U:    ((unsigned short*)this->ptr(y, x))[c]   = (unsigned short)value;    return;
    case ST_16S:    ((short*)this->ptr(y, x))[c]            = (short)value;             return;
    case ST_32U:    ((unsigned int*)this->ptr(y, x))[c]     = (unsigned int)value;      return;
    case ST_32S:    ((int*)this->ptr(y, x))[c]              = (int)value;               return;
    case ST_32F:    ((float*)this->ptr(y, x))[c]            = (float)value;             return;
    case ST_64F:    ((double*)this->ptr(y, x))[c]           = (double)value;            return;
    default:        return;
    }
}

double Image::GetPixel(long long x, long long y, unsigned int c)
{
    switch(this->m_sampleFormat)
    {
    case ST_8U:     return (unsigned char)((unsigned char*)this->ptr(y, x))[c];
    case ST_8S:     return (char)((char*)this->ptr(y, x))[c];
    case ST_16U:    return (unsigned short)((unsigned short*)this->ptr(y, x))[c];
    case ST_16S:    return (short)((short*)this->ptr(y, x))[c];
    case ST_32U:    return (unsigned int)((unsigned int*)this->ptr(y, x))[c];
    case ST_32S:    return (int)((int*)this->ptr(y, x))[c];
    case ST_32F:    return (float)((float*)this->ptr(y, x))[c];
    case ST_64F:    return (double)((double*)this->ptr(y, x))[c];
    default:        return std::numeric_limits<double>::quiet_NaN();
    }
}

Status Image::DrawPixel(long long x, long long y, unsigned char color[4])
{
    if(!m_pPointer)
        return STS_ERR_NOT_INITIALIZED;

    if(m_samples > 4)
        return STS_ERR_UNSUPPORTED;

    if(m_sampleFormat != ST_8U)
        return STS_ERR_UNSUPPORTED;

    if(x >= m_size.width || y >= m_size.height)
        return STS_OK;

    for(unsigned int k = 0; k < m_samples; k++)
        ((unsigned char*)ptr(y, x))[k] = (unsigned char)color[k];

    return STS_OK;
}

Status Image::DrawRect(Rect rect, unsigned char color[4], unsigned int iThickness)
{
    if(!m_pPointer)
        return STS_ERR_NOT_INITIALIZED;

    if(m_samples > 4)
        return STS_ERR_UNSUPPORTED;

    if(m_sampleFormat != ST_8U)
        return STS_ERR_UNSUPPORTED;

    for(long long i = rect.y; (i < (rect.y + rect.height)) && (i < m_size.height); i++)
    {
        for(long long j = rect.x; (j < (rect.x + rect.width)) && (j < m_size.width); j++)
        {
            if((i < rect.y + iThickness) || (j < rect.x + iThickness) || (i >= rect.y + rect.height - iThickness) || 
                        (j >= rect.x + rect.width - iThickness))
                DrawPixel(j, i, color);
        }
    }

    return STS_OK;
}

// Bresenham line algorithm
Status Image::DrawLine(Vect vect, unsigned char color[4])
{
    if(!m_pPointer)
        return STS_ERR_NOT_INITIALIZED;

    if(m_samples > 4)
        return STS_ERR_UNSUPPORTED;

    int i;
    int x, y;
    int xl, yl;

    int dx = vect.x2 - vect.x1;
    int dy = vect.y2 - vect.y1;

    int dxa = abs(dx);
    int dya = abs(dy);

    int px  = 2*dya - dxa;
    int py  = 2*dxa - dya;

    if(dya <= dxa)
    {
        if(dx >= 0)
        {
            x  = vect.x1;
            y  = vect.y1;
            xl = vect.x2;
        }
        else
        {
            x  = vect.x2;
            y  = vect.y2;
            xl = vect.x1;
        }
        DrawPixel(x, y, color);

        for(i = 0; x < xl; i++)
        {
            x = x + 1;
            if(px < 0)
                px = px + 2*dya;
            else
            {
                if((dx < 0 && dy < 0) || (dx > 0 && dy > 0))
                    y = y + 1;
                else
                    y = y - 1;

                px = px + 2*(dya - dxa);
            }
            DrawPixel(x, y, color);
        }
    }
    else
    {
        if(dy >= 0)
        {
            x  = vect.x1;
            y  = vect.y1;
            yl = vect.y2;
        }
        else
        {
            x  = vect.x2;
            y  = vect.y2;
            yl = vect.y1;
        }
        DrawPixel(x, y, color);

        for(i = 0; y < yl; i++)
        {
            y = y + 1;
            if(py <= 0)
                py = py + 2*dxa;
            else
            {
                if((dx < 0 && dy < 0) || (dx > 0 && dy > 0))
                    x = x + 1;
                else
                    x = x - 1;

                py = py + 2*(dxa - dya);
            }
            DrawPixel(x, y, color);
        }
    }

    return STS_OK;
}
