#include <amscppimglib4/amscppimglib4.hpp>
#include <amscppimglib4/amscppimglib4_intlutil.hpp>
#include <amscimglib4/amscimglib4.h>

namespace ams
{

    amsimage::amsimage() : Nx(0), Ny(0),data(NULL),width(Nx),height(Ny)
    {
        return;
    }

    amsimage::~amsimage()
    {
        Nx = 0;
        Ny = 0;
        if(data!=NULL) {delete[] data; data=NULL;}
        return;
    }

    int amsimage::resize(int _Nx, int _Ny)
    {
        int ret = amsimage_success;
        
        uint8_t *newdata = NULL;

        _Nx = (_Nx<0) ? 0 : _Nx;
        _Ny = (_Ny<0) ? 0 : _Ny;

        if(_Nx == Nx && _Ny == Ny)
        {
            return ret; //no resize necessary
        }

        if(_Nx==0 || _Ny == 0)
        {
            //zero size image
            if(data!=NULL) {delete[] data; data=NULL;}
            Nx = 0;
            Ny = 0;
            return ret;
        }
        
        newdata = new(std::nothrow) uint8_t[4*_Nx*_Ny];
        if(newdata==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        imglib4::amsimage_region_set(
            newdata,
            _Nx,_Ny,
            0,0,_Nx,_Ny,
            amspixel(0,0,0,0)
        );

        if(data!=NULL)
        {
            imglib4::amsimage_region_copy(
                newdata,
                _Nx,_Ny,
                data,
                Nx,Ny,
                0,0
            );
        }

        if(data!=NULL) {delete[] data; data=NULL;}
        data = newdata;
        Nx = _Nx;
        Ny = _Ny;

        return ret;
    }

    amsimage::amsimage(const amsimage& other) : 
        Nx(0), Ny(0),data(NULL),width(Nx),height(Ny)
    {
        int res;
        // Nx = 0;
        // Ny = 0;
        // data = NULL;

        if(this!=&other)
        {
            res = this->resize(other.Nx,other.Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_region_copy(
                    data, Nx, Ny,
                    other.data, other.Nx, other.Ny,
                    0,0
                );
            }
        }
    }

    amsimage::amsimage(amsimage&& other) noexcept :
        Nx(0), Ny(0),data(NULL),width(Nx),height(Ny)
    {
        int res;
        // Nx = 0;
        // Ny = 0;
        // data = NULL;

        if(this!=&other)
        {
            this->Nx = other.Nx;
            this->Ny = other.Ny;
            this->data = other.data;

            other.Nx = 0;
            other.Ny = 0;
            other.data = NULL;
        }

        return;
    }

    amsimage& amsimage::operator=(const amsimage& other)
    {
        int res;

        if(this!=&other)
        {
            res = this->resize(other.Nx,other.Ny);
            if(res==amsimage_success)
            {
                imglib4::amsimage_region_copy(
                    data, Nx, Ny,
                    other.data, other.Nx, other.Ny,
                    0,0
                );
            }
        }

        return *this;
    }
    
    amsimage& amsimage::operator=(amsimage&& other) noexcept
    {
        if(this!=&other)
        {
            if(this->data!=NULL) {delete[] this->data; this->data=NULL;}
            this->Nx = 0;
            this->Ny = 0;

            this->Nx = other.Nx;
            this->Ny = other.Ny;
            this->data = other.data;

            other.Nx = 0;
            other.Ny = 0;
            other.data = NULL;
        }

        return *this;
    }

    amsimage amsimage::subimage(int I0, int J0, int I1, int J1) const
    {
        amsimage ret;
        int _Nx,_Ny;
        _Nx = ((I1-I0) < 0) ? 0 : I1-I0;
        _Ny = ((J1-J0) < 0) ? 0 : J1-J0;

        ret.resize(_Nx,_Ny);

        imglib4::amsimage_region_copy(
            ret.data, ret.Nx, ret.Ny,
            this->data, Nx,Ny,
            I0,J0
        );

        return ret;
    }

    void amsimage_transpose_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = Ix+Nx*Iy;

            datato[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::transpose() const
    {
        amsimage ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsimage_transpose_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    void amsimage_rotcw_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = Ix+Nx*(Ny-Iy-1);

            datato[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::rotcw() const
    {
        amsimage ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsimage_rotcw_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    void amsimage_rotccw_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Iy + Ny*Ix;
            Ib = (Nx-Ix-1)+Nx*Iy;

            datato[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::rotccw() const
    {
        amsimage ret;
        int64_t N;

        ret.resize(Ny,Nx);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsimage_rotccw_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    void amsimage_flipx_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Ix + Nx*Iy;
            Ib = (Nx-Ix-1)+Nx*Iy;

            datato[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::flipx() const
    {
        amsimage ret;
        int64_t N;

        ret.resize(Nx,Ny);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsimage_flipx_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }

    void amsimage_flipy_tf(
        int threadnum,
        int nthreads,
        uint8_t *datato,
        const uint8_t *datafrom,
        int Nx, int Ny
    )
    {
        int64_t I,I0,I1,Is,N,Ia,Ib,Ix,Iy;
        N = Nx*Ny;
        Is = N/nthreads;
        I0 = (threadnum)*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;
            Ia = Ix + Nx*Iy;
            Ib = Ix+Nx*(Ny-Iy-1);

            datato[0 + 4*Ia] = datafrom[0 + 4*Ib];
            datato[1 + 4*Ia] = datafrom[1 + 4*Ib];
            datato[2 + 4*Ia] = datafrom[2 + 4*Ib];
            datato[3 + 4*Ia] = datafrom[3 + 4*Ib];
        }
        return;
    }

    amsimage amsimage::flipy() const
    {
        amsimage ret;
        int64_t N;

        ret.resize(Nx,Ny);
        N = Nx*Ny;

        imglib4::threaded_execute(
            amsimage_flipy_tf,
            N,
            ret.data,this->data,Nx,Ny
        );

        return ret;
    }


    amspixel amsimage::get_pixel(int I,int J) const
    {
        amspixel ret;
        if(I>=0 && I<Nx && J>=0 && J<Ny)
        {
            ret.R = data[0 + 4*(I + Nx*J)];
            ret.G = data[1 + 4*(I + Nx*J)];
            ret.B = data[2 + 4*(I + Nx*J)];
            ret.A = data[3 + 4*(I + Nx*J)];
        }
        return ret;
    }

    int amsimage::set_pixel(int I,int J,const amspixel pix)
    {
        int ret = amsimage_failure;
        if(I>=0 && I<Nx && J>=0 && J<Ny)
        {
            ret = amsimage_success;
            data[0 + 4*(I + Nx*J)] = pix.R;
            data[1 + 4*(I + Nx*J)] = pix.G;
            data[2 + 4*(I + Nx*J)] = pix.B;
            data[3 + 4*(I + Nx*J)] = pix.A;
        }
        return ret;
    }

    int amsimage::set_pixel(int I,int J,uint8_t R, uint8_t G, uint8_t B, uint8_t A)
    {  
        return this->set_pixel(I,J,amspixel(R,G,B,A));
    }

    uint8_t& amsimage::operator[](int ind)
    {
        return data[ind];
    }

    const uint8_t& amsimage::operator[](int ind) const
    {
        return data[ind];
    }

    uint8_t& amsimage::operator()(int Nc, int I, int J)
    {
        return data[Nc + 4*(I + Nx*J)];
    }

    const uint8_t& amsimage::operator()(int Nc, int I, int J) const
    {
        return data[Nc + 4*(I + Nx*J)];
    }


    int read_image(const char *fname, amsimage* image)
    {
        int ret = amsimage_success;
        int res;
        amscimglib4_image *im2 = NULL;

        if(image==NULL)
        {
            ret = amsimage_failure;
            printf("read_image: Error: image pointer is null.\n");
            return ret;
        }

        res = amscimglib4_image_new(&im2,1,1);
        if(res!=amscimglib4_success)
        {
            ret = amsimage_failure;
            printf("read_image: Error: c image struct failed to allocate.\n");
            return ret;
        }

        amscimglib4_readimage(fname,im2);
        //you can NOT move buffers. The im2 buffers are created with malloc, not new. No pseudo-move-semantics for you!

        //copy buffers
        res =  image->resize(im2->sizex,im2->sizey);
        if(res!=amsimage_success)
        {
            ret = amsimage_failure;
            amscimglib4_image_delete(&im2);
            printf("read_image: Error: c++ image failed to allocate.\n");
            return ret;
        }

        //both structures have the same memory layout, so the internal copy function works
        imglib4::amsimage_region_copy(
            image->data,image->Nx,image->Ny,
            im2->data,im2->sizex,im2->sizey,
            0,0
        );


        amscimglib4_image_delete(&im2);

        return ret;
    }

    int write_image(const char *fname, amsimage* image)
    {
        int ret = amsimage_success;
        int res;
        amscimglib4_image *im2 = NULL;

        if(image==NULL)
        {
            ret = amsimage_failure;
            printf("write_image: Error: image pointer is null.\n");
            return ret;
        }

        res = amscimglib4_image_new(&im2,image->Nx,image->Ny);
        if(res!=amscimglib4_success)
        {
            ret = amsimage_failure;
            printf("write_image: Error: c image struct failed to allocate.\n");
            return ret;
        }

        //both structures have the same memory layout, so the internal copy function works
        imglib4::amsimage_region_copy(
            im2->data,im2->sizex,im2->sizey,
            image->data,image->Nx,image->Ny,
            0,0
        );

        amscimglib4_writeimage(fname,im2);

        amscimglib4_image_delete(&im2);
        return ret;
    }

    void amsimage::clear()
    {
        this->setall(amspixel(0,0,0,0));
    }

    void amsimage_setall_tf(
        int threadnum,
        int nthreads,
        amsimage *img,
        const amspixel color
    )
    {
        int64_t I,I0,I1,Is,N,Ix,Iy;

        N = img->Nx*img->Ny;
        Is = N/nthreads; Is = (Is<1) ? 1 : Is;
        I0 = Is*threadnum;
        I1 = (threadnum<(nthreads-1)) ? Is*(threadnum+1) : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%img->Nx;
            Iy = I/img->Nx;

            img->data[0 + 4*(Ix + img->Nx*Iy)] = color.R;
            img->data[1 + 4*(Ix + img->Nx*Iy)] = color.G;
            img->data[2 + 4*(Ix + img->Nx*Iy)] = color.B;
            img->data[3 + 4*(Ix + img->Nx*Iy)] = color.A;
        }
    }

    void amsimage::setall(amspixel color)
    {
        imglib4::threaded_execute(
            amsimage_setall_tf,
            this->Nx*this->Ny,
            this,
            color
        );
        return;
    }

    amsbitplane amsimage::get_colorplane(int Ncp)
    {
        amsbitplane ret;
        int res;

        res = ret.resize(this->Nx,this->Ny);
        if(res!=amsimage_success)
        {
            printf("amsimage::get_colorplane: error: bitplane failed to resize.\n");
            return ret;
        }

        if(Ncp<0 || Ncp>=4)
        {
            printf("amsimage::get_colorplane: error: %d is outside indices of valid colorplanes [0,4)\n",Ncp);
            return ret;
        }

        imglib4::amsimage_plane_copy(
            ret.data,
            0,1,ret.Nx,ret.Ny,
            this->data,Ncp,4,this->Nx,this->Ny,
            0,0
        );

        return ret;
    }

    int amsimage::set_colorplane(int Ncp, const amsbitplane *bp)
    {
        int ret = amsimage_success;
        int res;

        if(Ncp<0 || Ncp>=4)
        {
            ret = amsimage_failure;
            printf("amsimage::set_colorplane: error: %d is outside indices of valid colorplanes [0,4)\n",Ncp);
            return ret;
        }

        imglib4::amsimage_plane_copy(
            this->data, Ncp, 4, this->Nx, this->Ny,
            bp->data, 0, 1, bp->Nx, bp->Ny,
            0,0
        );

        return ret;
    }

    void amsimage_apply_image_tf(
        int threadnum,
        int nthreads,
        amsimage *imgto,
        const amsimage *imgfrom,
        int x0, int y0
    )
    {
        int dx,dy;
        int64_t N;
        int64_t I,I0,I1,Is,Ix,Iy,Ia,Ib;
        double r1,g1,b1,a1;
        double r2,g2,b2,a2;
        double r3,g3,b3,a3;
        int r3i,g3i,b3i,a3i;

        dx = (imgfrom->Nx < (imgto->Nx-x0)) ? imgfrom->Nx : imgto->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (imgfrom->Ny < (imgto->Ny-y0)) ? imgfrom->Ny : imgto->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

        Is = N/nthreads;
        I0 = threadnum*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%dx;
            Iy = I/dx;

            Ia = (Ix + x0) + (Iy + y0)*imgto->Ny;
            Ib = Ix + Iy*imgfrom->Ny;

            r1 = imgto->data[0 + 4*Ia]/255.0;
            g1 = imgto->data[1 + 4*Ia]/255.0;
            b1 = imgto->data[2 + 4*Ia]/255.0;
            a1 = imgto->data[3 + 4*Ia]/255.0;
            
            r2 = imgfrom->data[0 + 4*Ib]/255.0;
            g2 = imgfrom->data[1 + 4*Ib]/255.0;
            b2 = imgfrom->data[2 + 4*Ib]/255.0;
            a2 = imgfrom->data[3 + 4*Ib]/255.0;

            r3 = r1 + r2*(1.0-a2);
            g3 = g1 + g2*(1.0-a2);
            b3 = b1 + b2*(1.0-a2);
            a3 = 1.0-(1.0-a1)*(1.0-a2);

            r3i = (int)(r3*255.0);
            g3i = (int)(g3*255.0);
            b3i = (int)(b3*255.0);
            a3i = (int)(a3*255.0);
            
            r3i = (r3i>255) ? 255 : r3i;
            g3i = (g3i>255) ? 255 : g3i;
            b3i = (b3i>255) ? 255 : b3i;
            a3i = (a3i>255) ? 255 : a3i;
            

            imgto->data[0 + 4*Ia] = r3i;
            imgto->data[1 + 4*Ia] = g3i;
            imgto->data[2 + 4*Ia] = b3i;
            imgto->data[3 + 4*Ia] = a3i;
        }

        return;
    }

    int amsimage::apply_image(int x0, int y0, const amsimage *img)
    {
        int ret = amsimage_success;

        int dx,dy;
        int64_t N;

        if(img==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        dx = (img->Nx < (this->Nx-x0)) ? img->Nx : this->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (img->Ny < (this->Ny-y0)) ? img->Ny : this->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

        imglib4::threaded_execute(
            amsimage_apply_image_tf, N,
            this,img,x0,y0
        );

        return ret;
    }

    void amsimage_apply_bitplanemask_tf(
        int threadnum,
        int nthreads,
        amsimage *imgto,
        const amsbitplane *bpfrom,
        amspixel color,
        int x0, int y0,
        uint8_t thresh
    )
    {
        int dx,dy;
        int64_t N;
        int64_t I,I0,I1,Is,Ix,Iy,Ia,Ib;


        dx = (bpfrom->Nx < (imgto->Nx-x0)) ? bpfrom->Nx : imgto->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (bpfrom->Ny < (imgto->Ny-y0)) ? bpfrom->Ny : imgto->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

        Is = N/nthreads;
        I0 = threadnum*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%dx;
            Iy = I/dx;

            Ia = (Ix+x0) + imgto->Nx*(Iy+y0);
            Ib = Ix + bpfrom->Nx*Iy;

            if(bpfrom->data[Ib]>=thresh)
            {
                imgto->data[0 + 4*Ia] = color.R;
                imgto->data[1 + 4*Ia] = color.G;
                imgto->data[2 + 4*Ia] = color.B;
                imgto->data[3 + 4*Ia] = color.A;
            }
        }
    }

    //applied color to any pixels where the bitplane's value is >= thresh
    int amsimage::apply_bitplane_mask(int x0, int y0, const amsbitplane* bp, amspixel color, uint8_t thresh)
    {
        int ret = amsimage_success;

        int dx,dy;
        int64_t N;

        if(bp==NULL)
        {
            ret = amsimage_failure;
            return ret;
        }

        dx = (bp->Nx < (this->Nx-x0)) ? bp->Nx : this->Nx-x0;
        dx = (dx<0) ? 0 : dx;
        dy = (bp->Ny < (this->Ny-y0)) ? bp->Ny : this->Ny-y0;
        dy = (dy<0) ? 0 : dy;
        N = dx*dy;

        imglib4::threaded_execute(
            amsimage_apply_bitplanemask_tf, N,
            this,bp,color,x0,y0,thresh
        );

        return ret;
    }


    amsfloatpixel amsimage::get_fpixel(int x, int y) const
    {
        amsfloatpixel p;
        amspixel pi;

        pi = get_pixel(x,y);
        p.R = (float)pi.R/(255.0);
        p.G = (float)pi.G/(255.0);
        p.B = (float)pi.B/(255.0);
        p.A = (float)pi.A/(255.0);
        
        return p;
    }

    int amsimage::set_fpixel(int x, int y, amsfloatpixel p)
    {
        int ret = amsimage_failure;
        amspixel pi;

        if(x>=0 && x<Nx && y>=0 && y<Ny)
        {
            ret = amsimage_success;
            if(p.R<0.0f) p.R = 0.0f;
            if(p.R>1.0f) p.R = 1.0f;
            if(p.G<0.0f) p.G = 0.0f;
            if(p.G>1.0f) p.G = 1.0f;
            if(p.B<0.0f) p.B = 0.0f;
            if(p.B>1.0f) p.B = 1.0f;
            if(p.A<0.0f) p.A = 0.0f;
            if(p.A>1.0f) p.A = 1.0f;

            pi.R = (int)(p.R*255.0f);
            pi.G = (int)(p.G*255.0f);
            pi.B = (int)(p.B*255.0f);
            pi.A = (int)(p.A*255.0f);
            
            set_pixel(x,y,pi);
        }

        return ret;
    }

    amsfloatpixel amsimage::interpolate(float x, float y) const
    {
        amsfloatpixel ret = amsfloatpixel(0.0,0.0,0.0,0.0);

        float Nxf,Nyf;
        //just do simple (0,N-1) interpolation for now
        int xi,yi,xip,yip;
        float xif,yif,xr,yr;
        float w00,w01,w10,w11;
        amsfloatpixel p00,p01,p10,p11;

        Nxf = (float)Nx;
        Nyf = (float)Ny;

        if(x>-0.5 && x<Nxf-0.5 && y>-0.5 && y<Nyf-0.5)
        {
            xr = imglib4::mod(x,1.0f);
            yr = imglib4::mod(y,1.0f);
            xif = x - xr;
            yif = y - yr;
            if(xif<0.0f) {xr = 1.0f;}
            if(xif>=(Nxf-1.0f)) {xr = 0.0f;}
            if(yif<0.0f) {yr = 1.0;}
            if(yif>=(Nyf-1.0f)) {yr = 0.0f;}
            xi = (int)xif;
            yi = (int)yif;
            w00 = (1.0-xr)*(1.0-yr);
            w10 = (xr)*(1.0-yr);
            w01 = (1.0-xr)*(yr);
            w11 = (xr)*(yr);

            xip = xi + 1;
            yip = yi + 1;
            if(xi<0) xi = 0;
            if(xi>Nx-1) xi = Nx-1;
            if(yi<0) yi = 0;
            if(yi>Ny-1) yi = Ny-1;
            if(xip>Nx-1) xip = Nx-1;
            if(yip>Ny-1) yip = Ny-1;

            p00 = get_fpixel(xi,yi);
            p10 = get_fpixel(xip,yi);
            p01 = get_fpixel(xi,yip);
            p11 = get_fpixel(xip,yip);

            ret = p00*w00 + p10*w10 + p01*w01 + p11*w11;
        }

        return ret;
    }

    void amsimage_rescale_tf(
        int threadnum,
        int nthreads,
        amsimage *imgto,
        const amsimage *imgfrom
    )
    {
        int Nx = imgto->Nx;
        int Ny = imgto->Ny;
        int64_t I,I0,I1,Is,N,Ix,Iy;
        amsfloatpixel p;

        int xs,ys;

        N = Nx*Ny;
        Is = N/nthreads; if(Is<1) Is = 1;
        I0 = threadnum*Is;
        I1 = (threadnum<nthreads-1) ? (threadnum+1)*Is : N;

        for(I=I0;I<I1;I++)
        {
            Ix = I%Nx;
            Iy = I/Nx;

            xs = (float)Ix*((float)(imgfrom->Nx-1))/((float)(imgto->Nx-1));
            ys = (float)Iy*((float)(imgfrom->Ny-1))/((float)(imgto->Ny-1));

            p = imgfrom->interpolate(xs,ys);
            imgto->set_fpixel(Ix,Iy,p);
        }
    }

    amsimage amsimage::rescale(int nnx, int nny)
    {
        amsimage ret;
        int res;

        res = ret.resize(nnx,nny);
        if(res!=amsimage_success)
        {
            printf("amsimage::rescale: error, could not allocate return image.\n");
            return ret;
        }

        imglib4::threaded_execute(
            amsimage_rescale_tf, nnx*nny,
            &ret,this
        );

        return ret;
    }

};