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

    #include "rectangle_abstract.h"
    #include "../algs.h"
    #include <algorithm>
    #include <iostream>
    #include "../serialize.h"
    #include "vector.h"
    #include "../image_processing/generic_image.h"

    namespace dlib
    {

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

        class rectangle
        {
            /*!
                INITIAL VALUE
                    The initial value of this object is defined by its constructor.

                CONVENTION
                    left() == l
                    top() == t
                    right() == r
                    bottom() == b
            !*/

        public:

            rectangle (
                long l_,
                long t_,
                long r_,
                long b_
            ) :
                l(l_),
                t(t_),
                r(r_),
                b(b_)
            {}

            rectangle (
                unsigned long w,
                unsigned long h
            ) :
                l(0),
                t(0),
                r(static_cast<long>(w)-1),
                b(static_cast<long>(h)-1)
            {
                DLIB_ASSERT((w > 0 && h > 0) || (w == 0 && h == 0),
                    "\trectangle(width,height)"
                    << "\n\twidth and height must be > 0 or both == 0"
                    << "\n\twidth:  " << w
                    << "\n\theight: " << h
                    << "\n\tthis: " << this
                    );
            }

            rectangle (
                const point& p
            ) :
                l(p.x()),
                t(p.y()),
                r(p.x()),
                b(p.y())
            {
            }

            rectangle (
                const point& p1,
                const point& p2
            )
            {
                *this = rectangle(p1) + rectangle(p2);
            }

            template <typename T>
            rectangle (
                const vector<T,2>& p1,
                const vector<T,2>& p2
            )
            {
                *this = rectangle(p1) + rectangle(p2);
            }

            rectangle (
            ) :
                l(0),
                t(0),
                r(-1),
                b(-1)
            {}

            long top (
            ) const { return t; }

            long& top (
            ) { return t; }

            void set_top (
                long top_
            ) { t = top_; }

            long left (
            ) const { return l; }

            long& left (
            ) { return l; }

            void set_left (
                long left_
            ) { l = left_; }

            long right (
            ) const { return r; }

            long& right (
            ) { return r; }

            void set_right (
                long right_
            ) { r = right_; }

            long bottom (
            ) const { return b; }

            long& bottom (
            ) { return b; }

            void set_bottom (
                long bottom_
            ) { b = bottom_; }

            const point tl_corner (
            ) const { return point(left(), top()); }

            const point bl_corner (
            ) const { return point(left(), bottom()); }

            const point tr_corner (
            ) const { return point(right(), top()); }

            const point br_corner (
            ) const { return point(right(), bottom()); }

            unsigned long width (
            ) const
            {
                if (is_empty())
                    return 0;
                else
                    return r - l + 1;
            }

            unsigned long height (
            ) const
            {
                if (is_empty())
                    return 0;
                else
                    return b - t + 1;
            }

            unsigned long area (
            ) const
            {
                return width()*height();
            }

            bool is_empty (
            ) const { return (t > b || l > r); }

            rectangle operator + (
                const rectangle& rhs
            ) const
            {
                if (rhs.is_empty())
                    return *this;
                else if (is_empty())
                    return rhs;

                return rectangle (
                    std::min(l,rhs.l),
                    std::min(t,rhs.t),
                    std::max(r,rhs.r),
                    std::max(b,rhs.b)
                    );
            }

            rectangle intersect (
                const rectangle& rhs
            ) const
            {
                return rectangle (
                    std::max(l,rhs.l),
                    std::max(t,rhs.t),
                    std::min(r,rhs.r),
                    std::min(b,rhs.b)
                    );
            }

            bool contains (
                const point& p
            ) const
            {
                if (p.x() < l || p.x() > r || p.y() < t || p.y() > b)
                    return false;
                return true;
            }

            bool contains (
                long x,
                long y
            ) const
            {
                if (x < l || x > r || y < t || y > b)
                    return false;
                return true;
            }

            bool contains (
                const rectangle& rect
            ) const
            {
                return (rect + *this == *this);
            }

            rectangle& operator+= (
                const point& p
            )
            {
                *this = *this + rectangle(p);
                return *this;
            }

            rectangle& operator+= (
                const rectangle& rect
            )
            {
                *this = *this + rect;
                return *this;
            }

            bool operator== (
                const rectangle& rect
            ) const
            {
                return (l == rect.l) && (t == rect.t) && (r == rect.r) && (b == rect.b);
            }

            bool operator!= (
                const rectangle& rect
            ) const
            {
                return !(*this == rect);
            }

            inline bool operator< (const dlib::rectangle& b) const
            {
                if      (left() < b.left()) return true;
                else if (left() > b.left()) return false;
                else if (top() < b.top()) return true;
                else if (top() > b.top()) return false;
                else if (right() < b.right()) return true;
                else if (right() > b.right()) return false;
                else if (bottom() < b.bottom()) return true;
                else if (bottom() > b.bottom()) return false;
                else                    return false;
            }

        private:
            long l;
            long t;
            long r;
            long b;
        };

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

        inline void serialize (
            const rectangle& item,
            std::ostream& out
        )
        {
            try
            {
                serialize(item.left(),out);
                serialize(item.top(),out);
                serialize(item.right(),out);
                serialize(item.bottom(),out);
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while serializing an object of type rectangle");
            }
        }

        inline void deserialize (
            rectangle& item,
            std::istream& in
        )
        {
            try
            {
                deserialize(item.left(),in);
                deserialize(item.top(),in);
                deserialize(item.right(),in);
                deserialize(item.bottom(),in);
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while deserializing an object of type rectangle");
            }
        }

        inline std::ostream& operator<< (
            std::ostream& out,
            const rectangle& item
        )
        {
            out << "[(" << item.left() << ", " << item.top() << ") (" << item.right() << ", " << item.bottom() << ")]";
            return out;
        }

        inline std::istream& operator>>(
            std::istream& in,
            rectangle& item
        )
        {
            // ignore any whitespace
            while (in.peek() == ' ' || in.peek() == '\t' || in.peek() == '\r' || in.peek() == '\n')
                in.get();
            // now eat the leading '[' character
            if (in.get() != '[')
            {
                in.setstate(in.rdstate() | std::ios::failbit);
                return in;
            }

            point p1, p2;
            in >> p1;
            in >> p2;
            item = rectangle(p1) + rectangle(p2);

            // ignore any whitespace
            while (in.peek() == ' ' || in.peek() == '\t' || in.peek() == '\r' || in.peek() == '\n')
                in.get();
            // now eat the trailing ']' character
            if (in.get() != ']')
            {
                in.setstate(in.rdstate() | std::ios::failbit);
            }
            return in;
        }

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

        inline const rectangle centered_rect (
            long x,
            long y,
            unsigned long width,
            unsigned long height
        )
        {
            rectangle result;
            result.set_left ( x - static_cast<long>(width) / 2 );
            result.set_top ( y - static_cast<long>(height) / 2 );
            result.set_right ( result.left() + width - 1 );
            result.set_bottom ( result.top() + height - 1 );
            return result;
        }

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

        inline rectangle intersect (
            const rectangle& a,
            const rectangle& b
        ) { return a.intersect(b); }

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

        inline unsigned long area (
            const rectangle& a
        ) { return a.area(); }

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

        inline point center (
            const dlib::rectangle& rect
        )
        {
            point temp(rect.left() + rect.right() + 1,
                       rect.top() + rect.bottom() + 1);

            if (temp.x() < 0)
                temp.x() -= 1;

            if (temp.y() < 0)
                temp.y() -= 1;

            return temp/2;
        }

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

        inline dlib::vector<double,2> dcenter (
            const dlib::rectangle& rect
        )
        {
            dlib::vector<double,2> temp(rect.left() + rect.right(),
                                        rect.top() + rect.bottom());

            return temp/2.0;
        }

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

        inline long distance_to_rect_edge (
            const rectangle& rect,
            const point& p
        )
        {
            using std::max;
            using std::min;
            using std::abs;

            const long dist_x = min(abs(p.x()-rect.left()), abs(p.x()-rect.right()));
            const long dist_y = min(abs(p.y()-rect.top()),  abs(p.y()-rect.bottom()));

            if (rect.contains(p))
                return min(dist_x,dist_y);
            else if (rect.left() <= p.x() && p.x() <= rect.right())
                return dist_y;
            else if (rect.top() <= p.y() && p.y() <= rect.bottom())
                return dist_x;
            else
                return dist_x + dist_y;
        }

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

        template <typename T>
        inline const dlib::vector<T,2> nearest_point (
            const rectangle& rect,
            const dlib::vector<T,2>& p
        )
        {
            dlib::vector<T,2> temp(p);
            if (temp.x() < rect.left())
                temp.x() = rect.left();
            else if (temp.x() > rect.right())
                temp.x() = rect.right();

            if (temp.y() < rect.top())
                temp.y() = rect.top();
            else if (temp.y() > rect.bottom())
                temp.y() = rect.bottom();

            return temp;
        }

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

        inline size_t nearest_rect (
            const std::vector<rectangle>& rects,
            const point& p
        )
        {
            DLIB_ASSERT(rects.size() > 0);
            size_t idx = 0;
            double best_dist = std::numeric_limits<double>::infinity();

            for (size_t i = 0; i < rects.size(); ++i)
            {
                if (rects[i].contains(p))
                {
                    return i;
                }
                else
                {
                    double dist = (nearest_point(rects[i],p)-p).length();
                    if (dist < best_dist)
                    {
                        best_dist = dist;
                        idx = i;
                    }
                }
            }
            return idx;
        }

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

        template <typename T>
        inline void clip_line_to_rectangle (
            const rectangle& box,
            dlib::vector<T,2>& p1,
            dlib::vector<T,2>& p2
        )
        {
            // Now clip the line segment so it is contained inside box.
            if (p1.x() == p2.x())
            {
                if (!box.contains(p1))
                    p1.y() = box.top();
                if (!box.contains(p2))
                    p2.y() = box.bottom();
            }
            else if (p1.y() == p2.y())
            {
                if (!box.contains(p1))
                    p1.x() = box.left();
                if (!box.contains(p2))
                    p2.x() = box.right();
            }
            else
            {
                // We use these relations to find alpha values.  These values tell us
                // how to generate points intersecting the rectangle boundaries.  We then
                // test the resulting points for ones that are inside the rectangle and output
                // those.
                //box.left()  == alpha1*(p1.x()-p2.x()) + p2.x();
                //box.right() == alpha2*(p1.x()-p2.x()) + p2.x();

                const dlib::vector<T,2> d = p1-p2;
                double alpha1 = (box.left()  -p2.x())/(double)d.x();
                double alpha2 = (box.right() -p2.x())/(double)d.x();
                double alpha3 = (box.top()   -p2.y())/(double)d.y();
                double alpha4 = (box.bottom()-p2.y())/(double)d.y();

                const dlib::vector<T,2> c1 = alpha1*d + p2;
                const dlib::vector<T,2> c2 = alpha2*d + p2;
                const dlib::vector<T,2> c3 = alpha3*d + p2;
                const dlib::vector<T,2> c4 = alpha4*d + p2;

                if (!box.contains(p1))
                    p1 = c1;
                if (!box.contains(p2))
                    p2 = c2;
                if (box.contains(c3))
                {
                    if (!box.contains(p2))
                        p2 = c3;
                    else if (!box.contains(p1))
                        p1 = c3;
                }
                if (box.contains(c4))
                {
                    if (!box.contains(p2))
                        p2 = c4;
                    else if (!box.contains(p1))
                        p1 = c4;
                }
            }

            p1 = nearest_point(box, p1);
            p2 = nearest_point(box, p2);
        }

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

        inline const rectangle centered_rect (
            const point& p,
            unsigned long width,
            unsigned long height
        )
        {
            return centered_rect(p.x(),p.y(),width,height);
        }

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

        inline std::vector<rectangle> centered_rects (
            const std::vector<point>& pts,
            unsigned long width,
            unsigned long height
        )
        {
            std::vector<rectangle> tmp;
            tmp.reserve(pts.size());
            for (auto& p : pts)
                tmp.emplace_back(centered_rect(p, width, height));
            return tmp;
        }

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

        inline const rectangle centered_rect (
            const rectangle& rect,
            unsigned long width,
            unsigned long height
        )
        {
            return centered_rect((rect.left()+rect.right())/2,  (rect.top()+rect.bottom())/2, width, height);
        }

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

        inline const rectangle shrink_rect (
            const rectangle& rect,
            long num
        )
        {
            return rectangle(rect.left()+num, rect.top()+num, rect.right()-num, rect.bottom()-num);
        }

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

        inline const rectangle grow_rect (
            const rectangle& rect,
            long num
        )
        {
            return shrink_rect(rect, -num);
        }

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

        inline const rectangle shrink_rect (
            const rectangle& rect,
            long width,
            long height
        )
        {
            return rectangle(rect.left()+width, rect.top()+height, rect.right()-width, rect.bottom()-height);
        }

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

        inline const rectangle grow_rect (
            const rectangle& rect,
            long width,
            long height
        )
        {
            return shrink_rect(rect, -width, -height);
        }

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

        inline const rectangle scale_rect (
            const rectangle& rect,
            double scale
        )
        {
            DLIB_ASSERT(scale > 0, "scale factor must be > 0");

            const long l = std::lround(rect.left()*scale);
            const long t = std::lround(rect.top()*scale);
            const long r = std::lround(rect.right()*scale);
            const long b = std::lround(rect.bottom()*scale);
            return rectangle(l, t, r, b);
        }

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

        inline const rectangle translate_rect (
            const rectangle& rect,
            const point& p
        )
        {
            rectangle result;
            result.set_top ( rect.top() + p.y() );
            result.set_bottom ( rect.bottom() + p.y() );
            result.set_left ( rect.left() + p.x() );
            result.set_right ( rect.right() + p.x() );
            return result;
        }

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

        inline const rectangle translate_rect (
            const rectangle& rect,
            long x,
            long y
        )
        {
            rectangle result;
            result.set_top ( rect.top() + y );
            result.set_bottom ( rect.bottom() + y );
            result.set_left ( rect.left() + x );
            result.set_right ( rect.right() + x );
            return result;
        }

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

        inline const rectangle resize_rect (
            const rectangle& rect,
            unsigned long width,
            unsigned long height
        )
        {
            return rectangle(rect.left(),rect.top(),
                             rect.left()+width-1,
                             rect.top()+height-1);
        }

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

        inline const rectangle resize_rect_width (
            const rectangle& rect,
            unsigned long width
        )
        {
            return rectangle(rect.left(),rect.top(),
                             rect.left()+width-1,
                             rect.bottom());
        }

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

        inline const rectangle resize_rect_height (
            const rectangle& rect,
            unsigned long height
        )
        {
            return rectangle(rect.left(),rect.top(),
                             rect.right(),
                             rect.top()+height-1);
        }

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

        inline const rectangle move_rect (
            const rectangle& rect,
            const point& p
        )
        {
            return rectangle(p.x(), p.y(), p.x()+rect.width()-1, p.y()+rect.height()-1);
        }

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

        inline const rectangle move_rect (
            const rectangle& rect,
            long x,
            long y
        )
        {
            return rectangle(x, y, x+rect.width()-1, y+rect.height()-1);
        }

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

        // Circumvent what appears to be a bug in Visual Studio 2019's optimizer
        // (see: https://forum.juce.com/t/warning-in-the-lastest-vs2019/38267)
    #if defined (_MSC_VER)
    #pragma warning ( push )
    #pragma warning ( disable: 4723 )
    #endif
        inline rectangle set_rect_area (
            const rectangle& rect,
            unsigned long area
        )
        {
            DLIB_ASSERT(area > 0);

            if (rect.area() == 0)
            {
                // In this case we will make the output rectangle a square with the requested
                // area.
                unsigned long scale = std::round(std::sqrt(area));
                return centered_rect(rect, scale, scale);
            }
            else
            {
                const double scale = std::sqrt(area/static_cast<double>(rect.area()));
                return centered_rect(rect, std::lround(rect.width()*scale), std::lround(rect.height()*scale));
            }
        }
    #if defined (_MSC_VER)
    #pragma warning ( pop )
    #endif

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

        inline rectangle set_aspect_ratio (
            const rectangle& rect,
            double ratio
        )
        {
            DLIB_ASSERT(ratio > 0,
                "\t rectangle set_aspect_ratio()"
                << "\n\t ratio: " << ratio
                );

            // aspect ratio is w/h

            // we need to find the rectangle that is nearest to rect in area but
            // with an aspect ratio of ratio.

            // w/h == ratio
            // w*h == rect.area()

            if (ratio >= 1)
            {
                const long h = static_cast<long>(std::sqrt(rect.area()/ratio) + 0.5);
                const long w = static_cast<long>(h*ratio + 0.5);
                return centered_rect(rect, w, h);
            }
            else
            {
                const long w = static_cast<long>(std::sqrt(rect.area()*ratio) + 0.5);
                const long h = static_cast<long>(w/ratio + 0.5);
                return centered_rect(rect, w, h);
            }
        }

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

        template <
            typename T
            >
        inline const rectangle get_rect (
            const T& m
        )
        {
            return rectangle(0, 0, num_columns(m)-1, num_rows(m)-1);
        }

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

        inline rectangle operator+ (
            const rectangle& r,
            const point& p
        )
        {
            return r + rectangle(p);
        }

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

        inline rectangle operator+ (
            const point& p,
            const rectangle& r
        )
        {
            return r + rectangle(p);
        }

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

    }

    #endif // DLIB_RECTANGLe_