    // Copyright (C) 2006  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_MORPHOLOGICAL_OPERATIONs_
    #define DLIB_MORPHOLOGICAL_OPERATIONs_

    #include "../pixel.h"
    #include "thresholding.h"
    #include "morphological_operations_abstract.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------

        namespace morphological_operations_helpers
        {
            template <typename image_type>
            bool is_binary_image (
                const image_type& img
            )
            /*!
                ensures
                    - returns true if img contains only on_pixel and off_pixel values.
                    - returns false otherwise
            !*/
            {
                for (long r = 0; r < img.nr(); ++r)
                {
                    for (long c = 0; c < img.nc(); ++c)
                    {
                        if (img[r][c] != on_pixel && img[r][c] != off_pixel)
                        {
                            return false;
                        }
                    }
                }
                return true;
            }

            template <
                long M,
                long N
                >
            bool is_binary_image (
                const unsigned char (&structuring_element)[M][N]
            )
            /*!
                ensures
                    - returns true if structuring_element contains only on_pixel and off_pixel values.
                    - returns false otherwise
            !*/
            {
                for (long m = 0; m < M; ++m)
                {
                    for (long n = 0; n < N; ++n)
                    {
                        if (structuring_element[m][n] != on_pixel &&
                            structuring_element[m][n] != off_pixel)
                        {
                            return false;
                        }
                    }
                }
                return true;
            }

        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type,
            typename out_image_type,
            long M,
            long N
            >
        void binary_dilation (
            const in_image_type& in_img,
            out_image_type& out_img,
            const unsigned char (&structuring_element)[M][N]
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(M%2 == 1);
            COMPILE_TIME_ASSERT(N%2 == 1);
            DLIB_ASSERT(is_same_object(in_img,out_img) == false,
                "\tvoid binary_dilation()"
                << "\n\tYou must give two different image objects"
                );
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img) ,
                "\tvoid binary_dilation()"
                << "\n\tin_img must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(structuring_element) ,
                "\tvoid binary_dilation()"
                << "\n\tthe structuring_element must be a binary image"
                );

            // if there isn't any input image then don't do anything
            if (in_img.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img.nr(),in_img.nc());

            // apply the filter to the image
            for (long r = 0; r < in_img.nr(); ++r)
            {
                for (long c = 0; c < in_img.nc(); ++c)
                {
                    unsigned char out_pixel = off_pixel;
                    for (long m = 0; m < M && out_pixel == off_pixel; ++m)
                    {
                        for (long n = 0; n < N && out_pixel == off_pixel; ++n)
                        {
                            if (structuring_element[m][n] == on_pixel)
                            {
                                // if this pixel is inside the image then get it from the image
                                // but if it isn't just pretend it was an off_pixel value
                                if (r+m >= M/2 && c+n >= N/2 &&
                                    r+m-M/2 < in_img.nr() && c+n-N/2 < in_img.nc())
                                {
                                    out_pixel = in_img[r+m-M/2][c+n-N/2];
                                }
                            }
                        }
                    }
                    assign_pixel(out_img[r][c], out_pixel);
                }
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type,
            typename out_image_type,
            long M,
            long N
            >
        void binary_erosion (
            const in_image_type& in_img,
            out_image_type& out_img,
            const unsigned char (&structuring_element)[M][N]
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(M%2 == 1);
            COMPILE_TIME_ASSERT(N%2 == 1);
            DLIB_ASSERT(is_same_object(in_img,out_img) == false,
                "\tvoid binary_erosion()"
                << "\n\tYou must give two different image objects"
                );
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img) ,
                "\tvoid binary_erosion()"
                << "\n\tin_img must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(structuring_element) ,
                "\tvoid binary_erosion()"
                << "\n\tthe structuring_element must be a binary image"
                );

            // if there isn't any input image then don't do anything
            if (in_img.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img.nr(),in_img.nc());

            // apply the filter to the image
            for (long r = 0; r < in_img.nr(); ++r)
            {
                for (long c = 0; c < in_img.nc(); ++c)
                {
                    unsigned char out_pixel = on_pixel;
                    for (long m = 0; m < M && out_pixel == on_pixel; ++m)
                    {
                        for (long n = 0; n < N && out_pixel == on_pixel; ++n)
                        {
                            if (structuring_element[m][n] == on_pixel)
                            {
                                // if this pixel is inside the image then get it from the image
                                // but if it isn't just pretend it was an off_pixel value
                                if (r+m >= M/2 && c+n >= N/2 &&
                                    r+m-M/2 < in_img.nr() && c+n-N/2 < in_img.nc())
                                {
                                    out_pixel = in_img[r+m-M/2][c+n-N/2];
                                }
                                else
                                {
                                    out_pixel = off_pixel;
                                }
                            }
                        }
                    }
                    assign_pixel(out_img[r][c], out_pixel);
                }
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type,
            typename out_image_type,
            long M,
            long N
            >
        void binary_open (
            const in_image_type& in_img,
            out_image_type& out_img,
            const unsigned char (&structuring_element)[M][N],
            const unsigned long iter = 1
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(M%2 == 1);
            COMPILE_TIME_ASSERT(N%2 == 1);
            DLIB_ASSERT(is_same_object(in_img,out_img) == false,
                "\tvoid binary_open()"
                << "\n\tYou must give two different image objects"
                );
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img) ,
                "\tvoid binary_open()"
                << "\n\tin_img must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(structuring_element) ,
                "\tvoid binary_open()"
                << "\n\tthe structuring_element must be a binary image"
                );

            // if there isn't any input image then don't do anything
            if (in_img.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img.nr(),in_img.nc());

            if (iter == 0)
            {
                // just copy the image over
                assign_image(out_img, in_img);
            }
            else if (iter == 1)
            {
                in_image_type temp;
                binary_erosion(in_img,temp,structuring_element);
                binary_dilation(temp,out_img,structuring_element);
            }
            else
            {
                in_image_type temp1, temp2;
                binary_erosion(in_img,temp1,structuring_element);

                // do the extra erosions
                for (unsigned long i = 1; i < iter; ++i)
                {
                    temp1.swap(temp2);
                    binary_erosion(temp2,temp1,structuring_element);
                }

                // do the extra dilations
                for (unsigned long i = 1; i < iter; ++i)
                {
                    temp1.swap(temp2);
                    binary_dilation(temp2,temp1,structuring_element);
                }

                binary_dilation(temp1,out_img,structuring_element);
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type,
            typename out_image_type,
            long M,
            long N
            >
        void binary_close (
            const in_image_type& in_img,
            out_image_type& out_img,
            const unsigned char (&structuring_element)[M][N],
            const unsigned long iter = 1
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(M%2 == 1);
            COMPILE_TIME_ASSERT(N%2 == 1);
            DLIB_ASSERT(is_same_object(in_img,out_img) == false,
                "\tvoid binary_close()"
                << "\n\tYou must give two different image objects"
                );
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img) ,
                "\tvoid binary_close()"
                << "\n\tin_img must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(structuring_element) ,
                "\tvoid binary_close()"
                << "\n\tthe structuring_element must be a binary image"
                );

            // if there isn't any input image then don't do anything
            if (in_img.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img.nr(),in_img.nc());

            if (iter == 0)
            {
                // just copy the image over
                assign_image(out_img, in_img);
            }
            else if (iter == 1)
            {
                in_image_type temp;
                binary_dilation(in_img,temp,structuring_element);
                binary_erosion(temp,out_img,structuring_element);
            }
            else
            {
                in_image_type temp1, temp2;
                binary_dilation(in_img,temp1,structuring_element);

                // do the extra dilations
                for (unsigned long i = 1; i < iter; ++i)
                {
                    temp1.swap(temp2);
                    binary_dilation(temp2,temp1,structuring_element);
                }

                // do the extra erosions
                for (unsigned long i = 1; i < iter; ++i)
                {
                    temp1.swap(temp2);
                    binary_erosion(temp2,temp1,structuring_element);
                }

                binary_erosion(temp1,out_img,structuring_element);
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type1,
            typename in_image_type2,
            typename out_image_type
            >
        void binary_intersection (
            const in_image_type1& in_img1,
            const in_image_type2& in_img2,
            out_image_type& out_img
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type1::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type2::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type1::type>::grayscale);
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type2::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img1) ,
                "\tvoid binary_intersection()"
                << "\n\tin_img1 must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(in_img2) ,
                "\tvoid binary_intersection()"
                << "\n\tin_img2 must be a binary image"
                );
            DLIB_ASSERT(in_img1.nc() == in_img2.nc(),
                "\tvoid binary_intersection()"
                << "\n\tin_img1 and in_img2 must have the same ncs."
                << "\n\tin_img1.nc(): " << in_img1.nc()
                << "\n\tin_img2.nc(): " << in_img2.nc()
                );
            DLIB_ASSERT(in_img1.nr() == in_img2.nr(),
                "\tvoid binary_intersection()"
                << "\n\tin_img1 and in_img2 must have the same nrs."
                << "\n\tin_img1.nr(): " << in_img1.nr()
                << "\n\tin_img2.nr(): " << in_img2.nr()
                );

            // if there isn't any input image then don't do anything
            if (in_img1.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img1.nr(),in_img1.nc());

            for (long r = 0; r < in_img1.nr(); ++r)
            {
                for (long c = 0; c < in_img1.nc(); ++c)
                {
                    if (in_img1[r][c] == on_pixel && in_img2[r][c] == on_pixel)
                        assign_pixel(out_img[r][c], on_pixel);
                    else
                        assign_pixel(out_img[r][c], off_pixel);
                }
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type1,
            typename in_image_type2,
            typename out_image_type
            >
        void binary_union (
            const in_image_type1& in_img1,
            const in_image_type2& in_img2,
            out_image_type& out_img
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type1::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type2::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type1::type>::grayscale);
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type2::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img1) ,
                "\tvoid binary_intersection()"
                << "\n\tin_img1 must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(in_img2) ,
                "\tvoid binary_intersection()"
                << "\n\tin_img2 must be a binary image"
                );
            DLIB_ASSERT(in_img1.nc() == in_img2.nc(),
                "\tvoid binary_intersection()"
                << "\n\tin_img1 and in_img2 must have the same ncs."
                << "\n\tin_img1.nc(): " << in_img1.nc()
                << "\n\tin_img2.nc(): " << in_img2.nc()
                );
            DLIB_ASSERT(in_img1.nr() == in_img2.nr(),
                "\tvoid binary_intersection()"
                << "\n\tin_img1 and in_img2 must have the same nrs."
                << "\n\tin_img1.nr(): " << in_img1.nr()
                << "\n\tin_img2.nr(): " << in_img2.nr()
                );

            // if there isn't any input image then don't do anything
            if (in_img1.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img1.nr(),in_img1.nc());

            for (long r = 0; r < in_img1.nr(); ++r)
            {
                for (long c = 0; c < in_img1.nc(); ++c)
                {
                    if (in_img1[r][c] == on_pixel || in_img2[r][c] == on_pixel)
                        assign_pixel(out_img[r][c], on_pixel);
                    else
                        assign_pixel(out_img[r][c], off_pixel);
                }
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type1,
            typename in_image_type2,
            typename out_image_type
            >
        void binary_difference (
            const in_image_type1& in_img1,
            const in_image_type2& in_img2,
            out_image_type& out_img
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type1::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type2::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type1::type>::grayscale);
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type2::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img1) ,
                "\tvoid binary_difference()"
                << "\n\tin_img1 must be a binary image"
                );
            DLIB_ASSERT(is_binary_image(in_img2) ,
                "\tvoid binary_difference()"
                << "\n\tin_img2 must be a binary image"
                );
            DLIB_ASSERT(in_img1.nc() == in_img2.nc(),
                "\tvoid binary_difference()"
                << "\n\tin_img1 and in_img2 must have the same ncs."
                << "\n\tin_img1.nc(): " << in_img1.nc()
                << "\n\tin_img2.nc(): " << in_img2.nc()
                );
            DLIB_ASSERT(in_img1.nr() == in_img2.nr(),
                "\tvoid binary_difference()"
                << "\n\tin_img1 and in_img2 must have the same nrs."
                << "\n\tin_img1.nr(): " << in_img1.nr()
                << "\n\tin_img2.nr(): " << in_img2.nr()
                );

            // if there isn't any input image then don't do anything
            if (in_img1.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img1.nr(),in_img1.nc());

            for (long r = 0; r < in_img1.nr(); ++r)
            {
                for (long c = 0; c < in_img1.nc(); ++c)
                {
                    if (in_img1[r][c] == on_pixel && in_img2[r][c] == off_pixel)
                        assign_pixel(out_img[r][c], on_pixel);
                    else
                        assign_pixel(out_img[r][c], off_pixel);
                }
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename in_image_type,
            typename out_image_type
            >
        void binary_complement (
            const in_image_type& in_img,
            out_image_type& out_img
        )
        {
            COMPILE_TIME_ASSERT( pixel_traits<typename in_image_type::type>::has_alpha == false );
            COMPILE_TIME_ASSERT( pixel_traits<typename out_image_type::type>::has_alpha == false );

            using namespace morphological_operations_helpers;
            COMPILE_TIME_ASSERT(pixel_traits<typename in_image_type::type>::grayscale);
            DLIB_ASSERT(is_binary_image(in_img) ,
                "\tvoid binary_complement()"
                << "\n\tin_img must be a binary image"
                );

            // if there isn't any input image then don't do anything
            if (in_img.size() == 0)
            {
                out_img.clear();
                return;
            }

            out_img.set_size(in_img.nr(),in_img.nc());

            for (long r = 0; r < in_img.nr(); ++r)
            {
                for (long c = 0; c < in_img.nc(); ++c)
                {
                    if (in_img[r][c] == on_pixel)
                        assign_pixel(out_img[r][c], off_pixel);
                    else
                        assign_pixel(out_img[r][c], on_pixel);
                }
            }
        }

        template <
            typename image_type
            >
        void binary_complement (
            image_type& img
        )
        {
            binary_complement(img,img);
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_MORPHOLOGICAL_OPERATIONs_