// Catalano Imaging Library
// The Catalano Framework
//
// Copyright © Diego Catalano, 2012-2016
// diego.catalano at live.com
//
// Copyright © OpenIMAJ, 2018
// Delta-E is from the project above
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

package Catalano.Imaging.Tools;

/**
 * Calculate color differences.
 * All of them refers to the CIE-L*ab color space.
 * @author Diego Catalano
 */
public class ColorDifference {

    /**
     * Don't let anyone instantiate this class.
     */
    private ColorDifference() {}
    
    /**
     * Delta-C
     * Calculate "distance" between two colors.
     * @param cieLab1 CIE-LAB color space.
     * @param cieLab2 CIE-LAB color space.
     * @return Distance.
     */
    public static double DeltaC(double[] cieLab1, double[] cieLab2){
        double part1 = Math.sqrt(Math.pow(cieLab2[1], 2) + Math.pow(cieLab2[2], 2));
        double part2 = Math.sqrt(Math.pow(cieLab1[1], 2) + Math.pow(cieLab1[2], 2));
        
        return part1 - part2;
    }
    
    /**
     * Delta-H
     * Calculate "distance" between two colors.
     * @param cieLab1 CIE-LAB color space.
     * @param cieLab2 CIE-LAB color space.
     * @return Distance.
     */
    public static double DeltaH(double[] cieLab1, double[] cieLab2){
        double xDE = DeltaC(cieLab1, cieLab2);
        
        double part1 = Math.pow((cieLab2[1] - cieLab1[1]),2);
        double part2 = Math.pow((cieLab2[2] - cieLab1[2]),2);
        
        double result = part1 + part2 - (xDE * xDE);
        return Math.sqrt(result);
    }
    
    /**
     * Delta E.
     * @param cieLab1 CIE-L*A*B* color space.
     * @param cieLab2 CIE-L*A*B* color space.
     * @return Distance.
     */
    public static double DeltaE(double[] cieLab1, double[] cieLab2){
        
        double sum = 0;
        for (int i = 0; i < cieLab1.length; i++) {
            sum += Math.pow(cieLab1[i] - cieLab2[i], 2);
        }
        
        return Math.sqrt(sum);
        
    }
    
    /**
     * CIE DE 2000.
     * Calculate "distance" between two colors.
     * @param cieLab1 CIE-L*A*B* color space.
     * @param cieLab2 CIE-L*A*B* color space.
     * @return Distance.
     */
    public static double CIEDE2000(double[] cieLab1, double[] cieLab2){
        return CIEDE2000(cieLab1[0], cieLab1[1], cieLab1[2], cieLab2[0], cieLab2[1], cieLab2[2]);
    }
    
    /**
     * CIE DE 2000.
     * Calculate "distance" between two colors.
     * @param l1 L* component.
     * @param a1 A* component.
     * @param b1 B* component.
     * @param l2 L* component.
     * @param a2 A* component.
     * @param b2 B* component.
     * @return Distance between the two colors.
     */
    public static double CIEDE2000(double l1, double a1, double b1, double l2, double a2, double b2){
        
        double C1 =  Math.sqrt(a1*a1 + b1*b1);
        double C2 =  Math.sqrt(a2*a2 + b2*b2);
        
        double lMean = (l1 + l2) / 2.0;
        double cMean = (C1 + C2) / 2.0;

        double G =  ( 1 - Math.sqrt( Math.pow(cMean, 7) / (Math.pow(cMean, 7) + Math.pow(25, 7)) ) ) / 2;
        double a1prime = a1 * (1 + G);
        double a2prime = a2 * (1 + G);
        double C1prime =  Math.sqrt(a1prime*a1prime + b1*b1);
        double C2prime =  Math.sqrt(a2prime*a2prime + b2*b2);
        double Cmeanprime = (C1prime + C2prime) / 2;

        double h1prime =  Math.atan2(b1, a1prime) + 2*Math.PI * (Math.atan2(b1, a1prime)<0 ? 1 : 0);
        double h2prime =  Math.atan2(b2, a2prime) + 2*Math.PI * (Math.atan2(b2, a2prime)<0 ? 1 : 0);
        double Hmeanprime =  ((Math.abs(h1prime - h2prime) > Math.PI) ? (h1prime + h2prime + 2*Math.PI) / 2 : (h1prime + h2prime) / 2);

        double T =  1.0 - 0.17 * Math.cos(Hmeanprime - Math.PI/6.0) + 0.24 * Math.cos(2*Hmeanprime) + 0.32 * Math.cos(3*Hmeanprime + Math.PI/30) - 0.2 * Math.cos(4*Hmeanprime - 21*Math.PI/60);

        double deltahprime =  ((Math.abs(h1prime - h2prime) <= Math.PI) ? h2prime - h1prime : (h2prime <= h1prime) ? h2prime - h1prime + 2*Math.PI : h2prime - h1prime - 2*Math.PI); 

        double deltaLprime = l2 - l1;
        double deltaCprime = C2prime - C1prime;
        double deltaHprime =  2.0 * Math.sqrt(C1prime*C2prime) * Math.sin(deltahprime / 2.0);
        double SL =  1.0 + ( (0.015*(lMean - 50)*(lMean - 50)) / (Math.sqrt( 20 + (lMean - 50)*(lMean - 50) )) );
        double SC =  1.0 + 0.045 * Cmeanprime;
        double SH =  1.0 + 0.015 * Cmeanprime * T;

        double deltaTheta =  (30 * Math.PI / 180) * Math.exp(-((180/Math.PI*Hmeanprime-275)/25)*((180/Math.PI*Hmeanprime-275)/25));
        double RC =  (2 * Math.sqrt(Math.pow(Cmeanprime, 7) / (Math.pow(Cmeanprime, 7) + Math.pow(25, 7))));
        double RT =  (-RC * Math.sin(2 * deltaTheta));

        double KL = 1;
        double KC = 1;
        double KH = 1;

        double deltaE = Math.sqrt(
                        ((deltaLprime/(KL*SL)) * (deltaLprime/(KL*SL))) +
                        ((deltaCprime/(KC*SC)) * (deltaCprime/(KC*SC))) +
                        ((deltaHprime/(KH*SH)) * (deltaHprime/(KH*SH))) +
                        (RT * (deltaCprime/(KC*SC)) * (deltaHprime/(KH*SH)))
                        );

        return deltaE;
    }
    
    /**
     * Delta CMC
     * Calculate "distance" between two colors.
     * @param cieLab1 CIE-LAB color space.
     * @param cieLab2 CIE-LAB color space.
     * @param wheightL Wheight L.
     * @param wheightC Wheight C.
     * @return Distance.
     */
    public static double DeltaCMC(double[] cieLab1, double[] cieLab2, double wheightL, double wheightC){
        
        double xc1 = Math.sqrt(cieLab1[1]*cieLab1[1] + cieLab1[2]*cieLab1[2]);
        double xc2 = Math.sqrt(cieLab2[1]*cieLab2[1] + cieLab2[2]*cieLab2[2]);
        
        double xc1_4 = xc1*xc1*xc1*xc1;
        double xff = Math.sqrt(xc1_4 / (xc1_4 + 1900));
        double xh1 = CieLab2Hue(cieLab1[1], cieLab1[2]);
        
        double xTT;
        if ( xh1 < 164 || xh1 > 345 )
            xTT = 0.36D + Math.abs( 0.4 * Math.cos( 35 + xh1 ) );
        else
            xTT = 0.56D + Math.abs( 0.2 * Math.cos( 168 + xh1 ) );
        
        double xSL;
        if ( cieLab1[0] < 16 )
            xSL = 0.511D;
        else
            xSL = ( 0.040975D * cieLab1[0] ) / ( 1.0 + ( 0.01765D * cieLab1[0] ) );
        
        double xSC = ( ( 0.0638D * xc1 ) / ( 1 + ( 0.0131D * xc1 ) ) ) + 0.638D;
        double xSH = ( ( xff * xTT ) + 1 - xff ) * xSC;
        double xDH = Math.sqrt( Math.pow(cieLab2[1] - cieLab1[1], 2) + Math.pow(cieLab2[2] - cieLab1[2], 2) - Math.pow(xc2 - xc1, 2));
        
        xSL = ( cieLab2[0] - cieLab1[0] ) / wheightL * xSL;
        xSC = ( xc2 - xc1 ) / wheightL * xSC;
        xSH = xDH / xSH;
        return Math.sqrt( xSL*xSL + xSC*xSC + xSH*xSH );
        
    }
    
    private static double CieLab2Hue(double a, double b){
        
        if (a >= 0 && b == 0) return 0;
        if (a < 0 && b == 0) return 180;
        if (a == 0 && b > 0) return 90;
        if (a == 0 && b < 0) return 270;
        
        double var = 0;
        if (a > 0 && b > 0) var = 0;
        if (a < 0) var = 180;
        if (a > 0 && b < 0) var = 360;
        
        double atan = Math.atan2(b, a);
        return Math.toRadians(atan) + var;
        
    }
}
 

Ads help maintain this website.