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

    #include "random_color_transform_abstract.h"
    #include "../image_processing/generic_image.h"
    #include "../pixel.h"
    #include "../rand.h"
    #include "../matrix.h"

    namespace dlib
    {

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

        class color_transform
        {
        public:
            color_transform(
                const double gamma_ = 1,
                const double red_scale_ = 1,
                const double green_scale_ = 1,
                const double blue_scale_ = 1
            ) : gamma(gamma_), red_scale(red_scale_), green_scale(green_scale_), blue_scale(blue_scale_)
            {
                DLIB_CASSERT(gamma_ >= 0)
                DLIB_CASSERT(0 <= red_scale_ && red_scale_ <= 1)
                DLIB_CASSERT(0 <= green_scale_ && green_scale_ <= 1)
                DLIB_CASSERT(0 <= blue_scale_ && blue_scale_ <= 1)
                const double m = 255 * std::max({red_scale_, green_scale_, blue_scale_});
                red_scale /= m;
                green_scale /= m;
                blue_scale /= m;
                // Now compute a lookup table for all the color channels.  The table tells us
                // what the transform does.
                table.resize(256 * 3);
                unsigned long i = 0;
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(255 * std::pow(k * red_scale, gamma) + 0.5);
                }
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(255 * std::pow(k * green_scale, gamma) + 0.5);
                }
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(255 * std::pow(k * blue_scale, gamma) + 0.5);
                }
            }

            rgb_pixel operator()(rgb_pixel p) const
            {
                p.red = table[static_cast<unsigned int>(p.red)];
                p.green = table[static_cast<unsigned int>(p.green + 256)];
                p.blue = table[static_cast<unsigned int>(p.blue + 512)];
                return p;
            }

            double get_gamma() const { return gamma; }
            double get_red_scale() const { return red_scale; }
            double get_green_scale() const { return green_scale; }
            double get_blue_scale() const { return blue_scale; }

        private:
            std::vector<unsigned char> table;
            double gamma;
            double red_scale;
            double green_scale;
            double blue_scale;
        };

        class inv_color_transform
        {
        public:
            inv_color_transform(
                const color_transform& tform
            )
            {
                const auto gamma = tform.get_gamma();
                const auto red_scale = tform.get_red_scale();
                const auto green_scale = tform.get_green_scale();
                const auto blue_scale = tform.get_blue_scale();

                // Now compute a lookup table for all the color channels.  The table tells us
                // what the transform does.
                table.resize(256 * 3);
                unsigned long i = 0;
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(std::pow(k / 255.0, 1 / gamma) / red_scale + 0.5);
                }
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(std::pow(k / 255.0, 1 / gamma) / green_scale + 0.5);
                }
                for (int k = 0; k < 256; ++k)
                {
                    table[i++] = static_cast<unsigned char>(std::pow(k / 255.0, 1 / gamma) / blue_scale + 0.5);
                }
            }

            rgb_pixel operator()(rgb_pixel p) const
            {
                p.red = table[static_cast<unsigned int>(p.red)];
                p.green = table[static_cast<unsigned int>(p.green + 256)];
                p.blue = table[static_cast<unsigned int>(p.blue + 512)];
                return p;
            }

        private:
            std::vector<unsigned char> table;
        };

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

        inline color_transform random_color_transform (
                dlib::rand& rnd,
                const double gamma_magnitude = 0.5,
                const double color_magnitude = 0.2
        )
        {
            // pick a random gamma correction factor.
            const double gamma = std::max(0.0, 1 + gamma_magnitude * (rnd.get_random_double() - 0.5));
            // pick a random color balancing scheme.
            const double red_scale = 1 - rnd.get_random_double() * color_magnitude;
            const double green_scale = 1 - rnd.get_random_double() * color_magnitude;
            const double blue_scale = 1 - rnd.get_random_double() * color_magnitude;
            return color_transform(gamma, red_scale, green_scale, blue_scale);
        }

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

        template <typename image_type>
        color_transform disturb_colors (
            image_type& img_,
            dlib::rand& rnd,
            const double gamma_magnitude = 0.5,
            const double color_magnitude = 0.2
        )
        {
            if (gamma_magnitude == 0 && color_magnitude == 0)
                return {};

            image_view<image_type> img(img_);
            const auto tform = random_color_transform(rnd, gamma_magnitude, color_magnitude);
            for (long r = 0; r < img.nr(); ++r)
            {
                for (long c = 0; c < img.nc(); ++c)
                {
                    rgb_pixel temp;
                    assign_pixel(temp, img[r][c]);
                    temp = tform(temp);
                    assign_pixel(img[r][c], temp);
                }
            }
            return tform;
        }

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

        template <typename image_type>
        void apply_random_color_offset (
            image_type& img_,
            dlib::rand& rnd
        )
        {
            // Make a random color offset.  This tform matrix came from looking at the
            // covariance matrix of RGB values in a bunch of images.  In particular, if you
            // multiply Gaussian random vectors by tform it will result in vectors with the
            // same covariance matrix as the original RGB data.  Also, this color transform is
            // what is suggested by the paper:
            //  Krizhevsky, Alex, Ilya Sutskever, and Geoffrey E. Hinton. "Imagenet
            //  classification with deep convolutional neural networks." Advances in neural
            //  information processing systems. 2012.
            // Except that we used the square root of the eigenvalues (which I'm pretty sure is
            // what the authors intended).
            matrix<double,3,3> tform;
            tform = -66.379,    25.094,   6.79698,
                    -68.0492, -0.302309,  -13.9539,
                    -68.4907,  -24.0199,   7.27653;
            matrix<double,3,1> v;
            v = rnd.get_random_gaussian(),rnd.get_random_gaussian(),rnd.get_random_gaussian();
            v = round(tform*0.1*v);
            const int roffset = v(0);
            const int goffset = v(1);
            const int boffset = v(2);

            // Make up lookup tables that apply the color mapping so we don't have to put a
            // bunch of complicated conditional branches in the loop below.
            unsigned char rtable[256];
            unsigned char gtable[256];
            unsigned char btable[256];
            for (int i = 0; i < 256; ++i)
            {
                rtable[i] = put_in_range(0, 255, i+roffset);
                gtable[i] = put_in_range(0, 255, i+goffset);
                btable[i] = put_in_range(0, 255, i+boffset);
            }

            // now transform the image.
            image_view<image_type> img(img_);
            for (long r = 0; r < img.nr(); ++r)
            {
                for (long c = 0; c < img.nc(); ++c)
                {
                    rgb_pixel temp;
                    assign_pixel(temp, img[r][c]);
                    temp.red   = rtable[temp.red];
                    temp.green = gtable[temp.green];
                    temp.blue  = btable[temp.blue];
                    assign_pixel(img[r][c], temp);
                }
            }
        }

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

    }

    #endif // DLIB_RANDOM_cOLOR_TRANSFORM_Hh_