/***  File Header  ************************************************************/
/**
* cimg_nif.h
*
* Elixir/Erlang extension module: CImg functions
* @author Shozo Fukuda
* @date	  Sun Dec 06 10:10:35 JST 2020
* System  MINGW64/Windows 10, Ubuntu/WSL2<br>
*
**/
/**************************************************************************{{{*/

template <class T>
struct NifCImg {

    typedef CImg<T> CImgT;

    /**********************************************************************}}}*/
    /* Resource handling                                                      */
    /**********************************************************************{{{*/
    static void init_resource_type(ErlNifEnv* env, const char* name)
    {
        Resource<CImgT>::init_resource_type(env, name);
    }

    static int enif_get_image(ErlNifEnv* env, ERL_NIF_TERM term, CImgT** img)
    {
        ERL_NIF_TERM  key;
        ERL_NIF_TERM  handle;
        return enif_make_existing_atom(env, "handle", &key, ERL_NIF_LATIN1)
                && enif_get_map_value(env, term, key, &handle)
                && Resource<CImgT>::get_item(env, handle, img);
    }

    static ERL_NIF_TERM enif_make_image(ErlNifEnv* env, CImgT* img)
    {
        return Resource<CImgT>::make_resource(env, img);
    }

    /**********************************************************************}}}*/
    /* Image creation functions                                               */
    /**********************************************************************{{{*/
    static DECL_NIF(create) {
        unsigned int size_x, size_y, size_z, size_c;
        T value;

        if (argc != 5
        ||  !enif_get_uint(env, argv[0], &size_x)
        ||  !enif_get_uint(env, argv[1], &size_y)
        ||  !enif_get_uint(env, argv[2], &size_z)
        ||  !enif_get_uint(env, argv[3], &size_c)
        ||  !enif_get_value(env, argv[4], &value)) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT(size_x, size_y, size_z, size_c, value);
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, img);
    }

    static DECL_NIF(create_from_bin) {
        std::string dtype;
        unsigned int size_x, size_y, size_z, size_c;
        ErlNifBinary bin;

        if (argc != 6
        ||  !enif_inspect_binary(env, argv[0], &bin)
        ||  !enif_get_uint(env, argv[1], &size_x)
        ||  !enif_get_uint(env, argv[2], &size_y)
        ||  !enif_get_uint(env, argv[3], &size_z)
        ||  !enif_get_uint(env, argv[4], &size_c)
        ||  !enif_get_str(env, argv[5], &dtype)) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT(size_x, size_y, size_z, size_c);
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        if (dtype == "<f4" &&  bin.size == size_x*size_y*size_z*size_c*sizeof(float)) {
			float *p = reinterpret_cast<float*>(bin.data);
			cimg_forXY(*img, x, y) {
			cimg_forC(*img, c) {
				(*img)(x, y, c) = static_cast<T>(256*(*p++)+0.5);
			}}
		}
		else if (dtype == "<u1" &&  bin.size == size_x*size_y*size_z*size_c) {
			unsigned char *p = reinterpret_cast<unsigned char*>(bin.data);
			cimg_forXY(*img, x, y) {
			cimg_forC(*img, c) {
				(*img)(x, y, c) = static_cast<T>(*p++);
			}}
    	}
    	else {
            return enif_make_badarg(env);
    	}

        return enif_make_image(env, img);
    }

    static DECL_NIF(create_list) {
        unsigned int size_x, size_y, size_z, size_c;

        if (argc != 5
        ||  !enif_get_uint(env, argv[0], &size_x)
        ||  !enif_get_uint(env, argv[1], &size_y)
        ||  !enif_get_uint(env, argv[2], &size_z)
        ||  !enif_get_uint(env, argv[3], &size_c)
        ||  !enif_is_list(env, argv[4])) {
            return enif_make_badarg(env);
        }
        
        ERL_NIF_TERM list = argv[4];
        unsigned int len;
        if (!enif_get_list_length(env, list, &len)
        ||  len != size_x*size_y*size_z*size_c) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT(size_x, size_y, size_z, size_c);
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        cimg_for(*img,ptr,T) {
            ERL_NIF_TERM item;
            int val;
            if (!enif_get_list_cell(env, list, &item, &list)
            ||  !enif_get_int(env, item, &val)) {
                return enif_make_badarg(env);
            }
            *ptr = val;
        }

        return enif_make_image(env, img);
    }

    static DECL_NIF(duplicate) {
        CImgT* src;

        if (argc != 1
        ||  !enif_get_image(env, argv[0], &src)) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT(*src);
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, img);
    }

    /**********************************************************************}}}*/
    /* load/save functions                                                    */
    /**********************************************************************{{{*/
    static DECL_NIF(load) {
        std::string fname;

        if (argc != 1
        ||  !enif_get_str(env, argv[0], &fname)) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT(fname.c_str());
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, img);
    }

    static DECL_NIF(save) {
        CImgT* img;
        std::string fname;

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_str(env, argv[1], &fname)) {
            return enif_make_badarg(env);
        }

        img->save(fname.c_str());

        return enif_make_ok(env);
    }
    
    static DECL_NIF(load_from_memory) {
        ErlNifBinary bin;
        if (argc != 1
        ||  !enif_inspect_binary(env, argv[0], &bin)) {
            return enif_make_badarg(env);
        }

        CImgT* img;
        try {
            img = new CImgT();
            img->load_from_memory((const unsigned char*)bin.data, bin.size);
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, img);
    }

    /**********************************************************************}}}*/
    /* PIXEL operations                                                       */
    /**********************************************************************{{{*/
    static MUT DECL_NIF(set) {
        CImgT* img;
        unsigned int x, y, z, c;
        T val;

        if (argc != 6
        ||  !enif_get_value(env, argv[0], &val)
        ||  !enif_get_image(env, argv[1], &img)
        ||  !enif_get_uint(env, argv[2], &x)
        ||  !enif_get_uint(env, argv[3], &y)
        ||  !enif_get_uint(env, argv[4], &z)
        ||  !enif_get_uint(env, argv[5], &c)) {
            return enif_make_badarg(env);
        }

        (*img)(x, y, z, c) = val;

        return argv[0];
    }

    static DECL_NIF(get) {
        CImgT* img;
        unsigned int x, y, z, c;
        T val;

        if (argc != 5
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_uint(env, argv[1], &x)
        ||  !enif_get_uint(env, argv[2], &y)
        ||  !enif_get_uint(env, argv[3], &z)
        ||  !enif_get_uint(env, argv[4], &c)) {
            return enif_make_badarg(env);
        }

        val = (*img)(x, y, z, c);

        return enif_make_value(env, val);
    }

    static DECL_NIF(assign) {
        CImgT* dst;
        CImgT* src;

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &dst)
        ||  !enif_get_image(env, argv[1], &src)){
            return enif_make_badarg(env);
        }

        *dst = *src;

        return argv[0];
    }

    static MUT DECL_NIF(fill) {
        CImgT* img;
        T val;

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_value(env, argv[1], &val)) {
            return enif_make_badarg(env);
        }

        img->fill(val);

        return argv[0];
    }

    static MUT DECL_NIF(clear) {
        CImgT* img;

        if (argc != 1
        ||  !enif_get_image(env, argv[0], &img)) {
            return enif_make_badarg(env);
        }

        img->clear();
        
        return argv[0];
    }

    /**********************************************************************}}}*/
    /* get Image attribute functions                                          */
    /**********************************************************************{{{*/
    static DECL_NIF(shape) {
        CImgT* img;

        if (argc != 1
        ||  !enif_get_image(env, argv[0], &img)) {
            return enif_make_badarg(env);
        }

        return enif_make_tuple4(env,
            enif_make_int(env, img->width()),
            enif_make_int(env, img->height()),
            enif_make_int(env, img->depth()),
            enif_make_int(env, img->spectrum()));
    }

    static DECL_NIF(size) {
        CImgT* img;

        if (argc != 1
        ||  !enif_get_image(env, argv[0], &img)) {
            return enif_make_badarg(env);
        }

        return enif_make_ulong(env, img->size());
    }

    static MUT DECL_NIF(resize) {
        CImgT* img;
        int width, height;

        if (argc != 3
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &width)
        ||  !enif_get_int(env, argv[2], &height)) {
            return enif_make_badarg(env);
        }

        img->resize(width, height, -100, -100, 3);

        return argv[0];
    }

    static DECL_NIF(get_resize) {
        CImgT* img;
        int width, height;
        int align;
        int filling;

        if (argc != 5
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &width)
        ||  !enif_get_int(env, argv[2], &height)
        ||  !enif_get_int(env, argv[3], &align)
        ||  !enif_get_int(env, argv[4], &filling)) {
            return enif_make_badarg(env);
        }

        CImgT* resize;
        try {
            if (align == 0) {
                resize = new CImgT(img->get_resize(width, height, -100, -100, 3));
            }
            else if (align == 1 || align == 2) {
                resize = new CImgT(width, height, img->depth(), img->spectrum(), filling);

                double ratio_w = (double)width/img->width();
                double ratio_h = (double)height/img->height();

                if (ratio_w <= ratio_h) {
                    // there is a gap in the vertical direction.
                    CImgT tmp_img(img->get_resize(width, ratio_w*img->height(), -100, -100, 3));

                    resize->draw_image(0, (align == 1) ? 0 : (height-tmp_img.height()), tmp_img);
                }
                else {
                    // there is a gap in the horizontal direction.
                    CImgT tmp_img(img->get_resize(ratio_h*img->width(), height, -100, -100, 3));

                    resize->draw_image((align == 1) ? 0 : (width-tmp_img.width()), 0, tmp_img);
                }
            }
            else {
                return enif_make_badarg(env);
            }
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, resize);
    }

    static MUT DECL_NIF(mirror) {
        CImgT* img;
        char axis[2];

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_atom(env, argv[1], axis, 2, ERL_NIF_LATIN1)
        ||  (axis[0] != 'x' && axis[0] != 'y')) {
            return enif_make_badarg(env);
        }

        img->mirror(axis[0]);

        return argv[0];
    }

    static MUT DECL_NIF(transpose) {
        CImgT* img;

        if (argc != 1
        ||  !enif_get_image(env, argv[0], &img)) {
            return enif_make_badarg(env);
        }

        img->transpose();

        return argv[0];
    }

    static MUT DECL_NIF(threshold) {
        CImgT* img;
        T     value;
        bool  soft_threshold;
        bool  strict_threshold;

        if (argc != 4
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_value(env, argv[1], &value)
        ||  !enif_get_bool(env, argv[2], &soft_threshold)
        ||  !enif_get_bool(env, argv[3], &strict_threshold)) {
            return enif_make_badarg(env);
        }

        img->threshold(value, soft_threshold, strict_threshold);

        return argv[0];
    }

    static MUT DECL_NIF(get_threshold) {
        CImgT* img;
        T     value;
        bool  soft_threshold;
        bool  strict_threshold;

        if (argc != 4
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_value(env, argv[1], &value)
        ||  !enif_get_bool(env, argv[2], &soft_threshold)
        ||  !enif_get_bool(env, argv[3], &strict_threshold)) {
            return enif_make_badarg(env);
        }

        CImgT* threshold;
        try {
            threshold = new CImgT(img->get_threshold(value, soft_threshold, strict_threshold));
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, threshold);
    }

    static DECL_NIF(get_gray) {
        CImgT* img;
        int opt_pn;

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &opt_pn)) {
            return enif_make_badarg(env);
        }

        CImgT* gray;
        try {
            gray = new CImgT(img->getGRAY(opt_pn));
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, gray);
    }

    static MUT DECL_NIF(blur) {
        CImgT* img;
        double sigma;
        bool   boundary_conditions;
        bool   is_gaussian;

        if (argc != 4
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_double(env, argv[1], &sigma)
        ||  !enif_get_bool(env, argv[2], &boundary_conditions)
        ||  !enif_get_bool(env, argv[3], &is_gaussian)) {
            return enif_make_badarg(env);
        }

        img->blur(sigma, boundary_conditions, is_gaussian);

        return argv[0];
    }

    static DECL_NIF(get_crop) {
        CImgT* img;
        int x0, y0, z0, c0;
        int x1, y1, z1, c1;
        unsigned int boundary_conditions;

        if (argc != 10
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &z0)
        ||  !enif_get_int(env, argv[4], &c0)
        ||  !enif_get_int(env, argv[5], &x1)
        ||  !enif_get_int(env, argv[6], &y1)
        ||  !enif_get_int(env, argv[7], &z1)
        ||  !enif_get_int(env, argv[8], &c1)
        ||  !enif_get_uint(env, argv[9], &boundary_conditions)) {
            return enif_make_badarg(env);
        }

        CImgT* crop;
        try {
            crop = new CImgT(img->get_crop(x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions));
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }

        return enif_make_image(env, crop);
    }

    /**********************************************************************}}}*/
    /* drawing shape functions                                                */
    /**********************************************************************{{{*/
    static MUT DECL_NIF(draw_graph) {
        CImgT* img;
        CImgT* data;
        unsigned char color[3];
        double opacity;
        unsigned int plot_type;
        int           vertex_type;
        double        ymin;
        double        ymax;
        unsigned int pattern;

        if (argc != 9
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_image(env, argv[1], &data)
        ||  !enif_get_color(env, argv[2], color)
        ||  !enif_get_number(env, argv[3], &opacity)
        ||  !enif_get_uint(env, argv[4], &plot_type)
        ||  !enif_get_int(env, argv[5], &vertex_type)
        ||  !enif_get_number(env, argv[6], &ymin)
        ||  !enif_get_number(env, argv[7], &ymax)
        ||  !enif_get_uint(env, argv[8], &pattern)) {
            return enif_make_badarg(env);
        }

        img->draw_graph(*data, color, opacity, plot_type, vertex_type, ymin, ymax, pattern);

        return argv[0];
    }

    static MUT DECL_NIF(draw_circle)
    {
        CImgT* img;
        int x0;
        int y0;
        int radius;
        unsigned char color[3];
        double opacity;
        unsigned int pattern;

        if (argc != 7
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &radius)
        ||  !enif_get_color(env, argv[4], color)
        ||  !enif_get_number(env, argv[5], &opacity)
        ||  !enif_get_uint(env, argv[6], &pattern)) {
            return enif_make_badarg(env);
        }

        img->draw_circle(x0, y0, radius, color, opacity, pattern);

        return argv[0];
    }

    static MUT DECL_NIF(draw_circle_filled) {
        CImgT* img;
        int x0;
        int y0;
        int radius;
        unsigned char color[3];
        double opacity;

        if (argc != 6
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &radius)
        ||  !enif_get_color(env, argv[4], color)
        ||  !enif_get_number(env, argv[5], &opacity)) {
            return enif_make_badarg(env);
        }

        img->draw_circle(x0, y0, radius, color, opacity);

        return argv[0];
    }

    static MUT DECL_NIF(draw_rectangle) {
        CImgT* img;
        int  x0, y0, x1, y1;
        unsigned char color[3];
        double opacity;
        unsigned int pattern;

        if (argc != 8
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &x1)
        ||  !enif_get_int(env, argv[4], &y1)
        ||  !enif_get_color(env, argv[5], color)
        ||  !enif_get_number(env, argv[6], &opacity)
        ||  !enif_get_uint(env, argv[7], &pattern)) {
            return enif_make_badarg(env);
        }

        img->draw_rectangle(x0, y0, x1, y1, color, opacity, pattern);

        return argv[0];
    }

    static MUT DECL_NIF(draw_rectangle_filled) {
        CImgT* img;
        int  x0, y0, x1, y1;
        unsigned char color[3];
        double opacity;

        if (argc != 7
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &x1)
        ||  !enif_get_int(env, argv[4], &y1)
        ||  !enif_get_color(env, argv[5], color)
        ||  !enif_get_number(env, argv[6], &opacity)) {
            return enif_make_badarg(env);
        }

        img->draw_rectangle(x0, y0, x1, y1, color, opacity);

        return argv[0];
    }

    static MUT DECL_NIF(draw_ratio_rectangle) {
        CImgT* img;
        double x0, y0, x1, y1;
        unsigned char color[3];
        double opacity;
        unsigned int pattern;

        if (argc != 8
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_double(env, argv[1], &x0)
        ||  !enif_get_double(env, argv[2], &y0)
        ||  !enif_get_double(env, argv[3], &x1)
        ||  !enif_get_double(env, argv[4], &y1)
        ||  !enif_get_color(env, argv[5], color)
        ||  !enif_get_number(env, argv[6], &opacity)
        ||  !enif_get_uint(env, argv[7], &pattern)) {
            return enif_make_badarg(env);
        }

        int width  = img->width();
        int height = img->height();

        int ix0 = x0*width;
        int iy0 = y0*height;
        int ix1 = x1*width;
        int iy1 = y1*height;

        img->draw_rectangle(ix0, iy0, ix1, iy1, color, opacity, pattern);

        return argv[0];
    }

    static MUT DECL_NIF(draw_triangle) {
        CImgT* img;
        int  x0, y0, x1, y1, x2, y2;
        unsigned char color[3];
        double opacity;
        unsigned int pattern;

        if (argc != 10
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &x1)
        ||  !enif_get_int(env, argv[4], &y1)
        ||  !enif_get_int(env, argv[5], &x2)
        ||  !enif_get_int(env, argv[6], &y2)
        ||  !enif_get_color(env, argv[7], color)
        ||  !enif_get_number(env, argv[8], &opacity)
        ||  !enif_get_uint(env, argv[9], &pattern)) {
            return enif_make_badarg(env);
        }

        img->draw_triangle(x0, y0, x1, y1, x2, y2, color, opacity, pattern);

        return argv[0];
    }

    static MUT DECL_NIF(draw_triangle_filled) {
        CImgT* img;
        int  x0, y0, x1, y1, x2, y2;
        unsigned char color[3];
        double opacity;

        if (argc != 9
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_int(env, argv[1], &x0)
        ||  !enif_get_int(env, argv[2], &y0)
        ||  !enif_get_int(env, argv[3], &x1)
        ||  !enif_get_int(env, argv[4], &y1)
        ||  !enif_get_int(env, argv[5], &x2)
        ||  !enif_get_int(env, argv[6], &y2)
        ||  !enif_get_color(env, argv[7], color)
        ||  !enif_get_number(env, argv[8], &opacity)) {
            return enif_make_badarg(env);
        }

        img->draw_triangle(x0, y0, x1, y1, x2, y2, color, opacity);

        return argv[0];
    }

    static MUT DECL_NIF(draw_image)
    {
        CImgT* img;
        CImgT* mask;
        double opacity;

        if (argc != 3
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_image(env, argv[1], &mask)
        ||  !enif_get_number(env, argv[2], &opacity)) {
            return enif_make_badarg(env);
        }

        img->draw_image(*mask, opacity);

    	return argv[0];
    }

    static DECL_NIF(map_color) {
        CImgT* img;
        std::string lut_name;
        unsigned int boundary_conditions;

        if (argc != 3
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_str(env, argv[1], &lut_name)
        ||  !enif_get_uint(env, argv[2], &boundary_conditions)) {
            return enif_make_badarg(env);
        }

        CImgT lut;
        if (lut_name == "default")     { lut = CImgT::default_LUT256(); }
        else if (lut_name == "lines") { lut = CImgT::lines_LUT256();   }
        else if (lut_name == "hot")   { lut = CImgT::hot_LUT256();     }
        else if (lut_name == "cool")  { lut = CImgT::cool_LUT256();    }
        else if (lut_name == "jet")   { lut = CImgT::jet_LUT256();     }
        else {
        	return enif_make_badarg(env);
        }

        CImgT* map;
        try {
            map = new CImgT(img->map(lut, boundary_conditions));
        }
        catch (CImgException& e) {
            return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, e.what(), ERL_NIF_LATIN1));
        }
        
        return enif_make_image(env, map);
    }

    static DECL_NIF(display) {
#if cimg_display != 0
        CImgT* img;
        CImgDisplay* disp;

        if (argc != 2
        ||  !enif_get_image(env, argv[0], &img)
        ||  !NifCImgDisplay::enif_get_display(env, argv[1], &disp)) {
            return enif_make_badarg(env);
        }

        img->display(*disp);
#endif

        return argv[0];
    }

    static DECL_NIF(to_bin) {
        CImgT*      img;
        std::string dtype;
        double     lo, hi;
        bool        nchw;    // to transpose NCHW
        bool        bgr;     // to convert RGB to BGR

        if (argc != 6
        ||  !enif_get_image(env, argv[0], &img)
        ||  !enif_get_str(env, argv[1], &dtype)
        ||  !enif_get_double(env, argv[2], &lo)
        ||  !enif_get_double(env, argv[3], &hi)
        ||  !enif_get_bool(env, argv[4], &nchw)
        ||  !enif_get_bool(env, argv[5], &bgr)) {
            return enif_make_badarg(env);
        }

        // select BGR convertion
        int color[4] = {0,1,2,3};
        if (bgr && img->spectrum() >= 3) {
            int tmp = color[0]; color[0] = color[2]; color[2] = tmp;
        }

        ERL_NIF_TERM binary;
        if (dtype == "<f4") {
            float* buff = reinterpret_cast<float*>(enif_make_new_binary(env, 4*img->size(), &binary));
            if (buff == NULL) {
                return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, "can't alloc binary", ERL_NIF_LATIN1));
            }
            
            // normalization coefficient
            double a = (hi - lo)/255.0;
            double b = lo;

            if (nchw) {
                cimg_forC(*img, c) cimg_forXY(*img, x, y) {
                    *buff++ = a*((*img)(x, y, color[c])) + b;
                }
            }
            else {
                cimg_forXY(*img, x, y) cimg_forC(*img, c) {
                    *buff++ = a*((*img)(x, y, color[c])) + b;
                }
            }
        }
        else {
            unsigned char* buff = enif_make_new_binary(env, img->size(), &binary);
            if (buff == NULL) {
                return enif_make_tuple2(env, enif_make_error(env), enif_make_string(env, "can't alloc binary", ERL_NIF_LATIN1));
            }

            if (nchw) {
                cimg_forC(*img, c) cimg_forXY(*img, x, y) {
                    *buff++ = (*img)(x, y,  color[c]);
                }
            }
            else {
                cimg_forXY(*img, x, y) cimg_forC(*img, c) {
                    *buff++ = (*img)(x, y,  color[c]);
                }
            }
        }

        return enif_make_tuple2(env, enif_make_ok(env), binary);
    }

    static DECL_NIF(transfer) {
        CImgT* dst;
        CImgT* src;
        int cx, cy, cz;

        if (argc != 6
        ||  !enif_get_image(env, argv[0], &dst)
        ||  !enif_get_image(env, argv[1], &src)
        ||  !enif_is_list(env, argv[2])
        ||  !enif_get_int(env, argv[3], &cx)
        ||  !enif_get_int(env, argv[4], &cy)
        ||  !enif_get_int(env, argv[5], &cz)) {
            return enif_make_badarg(env);
        }

        ERL_NIF_TERM address = argv[2];
        ERL_NIF_TERM head;
        while (enif_get_list_cell(env, address, &head, &address)) {
            int ality;
            const ERL_NIF_TERM* pair;
            int q[3], p[3];
            if (!enif_get_tuple(env, head, &ality, &pair)
            ||  ality != 2
            ||  !enif_get_pos(env, pair[0], q)
            ||  !enif_get_pos(env, pair[1], p)) {
                continue;
            }

            q[0] += cx; q[1] += cy; q[2] += cz;
            p[0] += cx; p[1] += cy; p[2] += cz;

            if (dst->containsXYZC(q[0], q[1], q[2]) && src->containsXYZC(p[0], p[1], p[2])) {
                cimg_forC(*src, c) {
                    (*dst)(q[0], q[1], q[2], c) = (*src)(p[0], p[1], p[2], c);
                }
            }
        }

        return argv[0];
    }

    static MUT DECL_NIF(transfer3) {
        CImgT* dst;
        CImgT* src;
        int p[3];
        CImg<int>* map;

        if (argc != 3
        ||  !enif_get_image(env, argv[0], &dst)
        ||  !enif_get_image(env, argv[1], &src)
        ||  !enif_get_pos(env, argv[2], p)
        ||  !NifCImg<int>::enif_get_image(env, argv[3], &map)){
            return enif_make_badarg(env);
        }

        ERL_NIF_TERM address = argv[3];
        ERL_NIF_TERM head;
        while (enif_get_list_cell(env, address, &head, &address)) {
            int ality;
            const ERL_NIF_TERM* pair;
            if (!enif_get_tuple(env, head, &ality, &pair)
            ||  ality != 2
            ||  !enif_is_list(env, pair[0])
            ||  !enif_is_list(env, pair[1])) {
                continue;
            }

            int q[3], p[3];
            if (!enif_get_pos(env, pair[0], q)
            ||  !enif_get_pos(env, pair[1], p)) {
                continue;
            }

            if (dst->containsXYZC(q[0], q[1], q[2]) && src->containsXYZC(p[0], p[1], p[2])) {
                cimg_forC(*src, c) {
                    (*dst)(q[0], q[1], q[2], c) = (*src)(p[0], p[1], p[2], c);
                }
            }
        }

        return argv[0];
    }

    static DECL_NIF(runit);
};

/*** cimg_nif.h ***********************************************************}}}*/