package com.phobrain.util;

/*
**  Color distance formulae.
**
**  Permission is given to freely copy, modify, and use this software.
*/

public class ColorUtil {

    private static final double CLOSE_TO_ZERO = 0.001; // arbitrary

    /*
    **  Color distance Delta E 1994  http://www.easyrgb.com/
    */

    public static double cdE94(int l1, int a1, int b1,
                               int l2, int a2, int b2) {

        double dL = l1 - l2;
        double dA = a1 - a2;
        double dB = b1 - b2;

        double xC1 = Math.sqrt( a1 * a1 + b1 * b1 );
        double xC2 = Math.sqrt( a2 * a2 + b2 * b2 );
        double xDL = l2 - l1;
        double xDC = xC2 - xC1;
        double xDE = Math.sqrt( xDL * xDL + dA * dA + dB * dB );
        double xDH;
        if (Math.sqrt(xDE) > Math.sqrt(Math.abs(xDL)) +
                             Math.sqrt(Math.abs(xDC))) {
            xDH = Math.sqrt( xDE * xDE - xDL * xDL - xDC * xDC );
        } else {
            xDH = 0.0d;
        }
        double xSC = 1.0d + 0.045 * xC1;
        double xSH = 1.0d + 0.015 * xC1;
        xDC /= xSC;
        xDH /= xSH;

        return Math.sqrt( xDL * xDL +
                          xDC * xDC +
                          xDH * xDH );
    }

    /*
    **  Color distance Delta E 2000 http://www.easyrgb.com/
    */
    private final static double CONST = Math.pow( 25.0, 7.0);

    private final static double WHT_L = 1.0;
    private final static double WHT_C = 1.0;
    private final static double WHT_H = 1.0;

    public static double cdE2k(int l1, int a1, int b1,
                               int l2, int a2, int b2) {

        double xC1 = Math.sqrt( a1 * a1 + b1 * b1 );
        double xC2 = Math.sqrt( a2 * a2 + b2 * b2 );
        double xCX = ( xC1 + xC2 ) / 2.0;
        double xCX7 = Math.pow( xCX, 7.0);

        double xGX = 0.5 * ( 1.0 - Math.sqrt( xCX / ( xCX7 + CONST) ) );

        double xNN = ( 1.0 + xGX ) * a1;
        xC1 = Math.sqrt( xNN * xNN + b1 * b1 );
        double xH1 = cieLab2Hue( xNN, b1 );

        xNN = ( 1.0 + xGX ) * a2;
        xC2 = Math.sqrt( xNN * xNN + b2 * b2 );
        double xH2 = cieLab2Hue( xNN, b2 );

        double xDL = l2 - l1;
        double xDC = xC2 - xC1;
        double xDH = 0.0;
        boolean gtZero = xC1 * xC2 > CLOSE_TO_ZERO;
        if ( gtZero ) { 
            xNN = Math.round( xH2 - xH1 );  // ORIG ROUNDS to 12 -?
        }
        if ( gtZero ) {
            if ( Math.abs( xNN ) <= 180.0 ) {
                xDH = xH2 - xH1;
            } else {
                if ( xNN > 180.0 ) xDH = xH2 - xH1 - 360.0;
                else               xDH = xH2 - xH1 + 360.0;
            }
        }
        xDH = 2.0 * Math.sqrt( xC1 * xC2 ) 
                  * Math.sin( Math.toRadians( xDH / 2.0 ));

        double xLX = ( l1 + l2 ) / 2.0;
        double xCY = ( xC1 + xC2 ) / 2.0;

        double xHX = xH1 + xH2;
        if ( gtZero ) {
            xNN = Math.abs( xNN );
            if ( xNN > 180.0 ) {
                if ( xH2 + xH1 < 360.0 ) xHX = xH1 + xH2 + 360.0;
                else                     xHX = xH1 + xH2 - 360.0;
            } else {
                xHX = xH1 + xH2;
            }
            xHX /= 2.0;
        }

        double xTX = 1.0 - 0.17 * Math.cos( Math.toRadians( xHX - 30.0 ) ) +
                           0.24 * Math.cos( Math.toRadians( 2.0 * xHX ) ) +
                           0.32 * Math.cos( Math.toRadians( 3.0 * xHX + 6.0 ) ) -
                           0.20 * Math.cos( Math.toRadians( 4.0 *xHX - 63.0 ) );

        double t = ( xHX - 275.0 ) / 25.0;
        t *= t * -1.0;
        double xPH = 30.0 * Math.exp( t );

        double xCY7 = Math.pow( xCY, 7.0 );
        double xRC = 2.0 * Math.sqrt( xCY7 / ( xCY7 + CONST ) );

        t = xLX - 50.0;
        t *= t;
        double xSL = 1.0 + ( 0.015 * t ) 
                           / Math.sqrt( 20.0 + t );

        double xSC = 1.0 + 0.045 * xCY;
        double xSH = 1.0 + 0.015 * xCY * xTX;
        double xRT = -1.0 * Math.sin( Math.toRadians( 2.0 * xPH ) ) * xRC;
        
        xDL /= ( WHT_L * xSL );
        xDC /= ( WHT_C * xSC );
        xDH /= ( WHT_H * xSH );

        return Math.sqrt( xDL * xDL + xDH * xDH + xRT * xDC * xDH );
    }

    private static double cieLab2Hue( double a, double b ) {

        if (Math.abs(b) < CLOSE_TO_ZERO) {
            if ( a >= 0.0 ) return 0.0;
            return 180.0;
        }
        if (Math.abs(a) < CLOSE_TO_ZERO) {
            if ( b > 0.0 ) return 90.0;
            return 270.0;
        }

        double bias = 0.0;
        if (a < 0.0) {
            bias = 180.0;
        } else if ( a > 0.0  &&  b < 0.0 ) {
            bias = 360.0;
        }
        return Math.toDegrees( Math.atan ( b / a )) + bias;
     }       
}