CIEDE2000 implementation in Fortran

Function version: v1.0.0
Site statistics
Number of visits367
Number of files viewed215 + 321

This page presents a reference implementation of the CIEDE2000 color difference formula in Fortran. If you wish to obtain an exact match with third-party implementations up to 10 decimal places, you may need to make some changes to the source code, including commenting and uncommenting a few lines, which can be applied automatically via the link below.

Diagram of the full-form CIEDE2000 formula with L*a*b* components and adjustments

The ΔE2000 function in Fortran

Let’s consider the more common and academic (Sharma, 2005) of the two formulations.

! This function written in Fortran is not affiliated with the CIE (International Commission on Illumination),
! and is released into the public domain. It is provided "as is" without any warranty, express or implied.

module ciede_2000_module
  use iso_fortran_env, only: real64
  implicit none
  private
  public :: ciede_2000
  real(kind=real64), parameter :: M_PI = 3.14159265358979323846264338328_real64
    ! k_l, k_c, k_h are parametric factors to be adjusted according to
    ! different viewing parameters such as textures, backgrounds...
  real(kind=real64), parameter :: k_l = 1.0_real64, k_c = 1.0_real64, k_h = 1.0_real64
contains
  ! The classic CIE Ξ”E2000 implementation, which operates on two L*a*b* colors, and returns their difference.
  ! "l" ranges from 0 to 100, while "a" and "b" are unbounded and commonly clamped to the range of -128 to 127.
  function ciede_2000(l_1, a_1, b_1, l_2, a_2, b_2) result(delta_e)
    implicit none
    real(kind=real64), intent(in) :: l_1, a_1, b_1, l_2, a_2, b_2
    real(kind=real64) :: n, c_1, c_2, h_1, h_2, h_m, h_d, p, r_t, l, t, h, c, delta_e
    ! Working in Fortran with the CIEDE2000 color-difference formula.
    n = (sqrt(a_1 * a_1 + b_1 * b_1) + sqrt(a_2 * a_2 + b_2 * b_2)) * 0.5_real64
    n = n * n * n * n * n * n * n
    ! A factor involving chroma raised to the power of 7 designed to make
    ! the influence of chroma on the total color difference more accurate.
    n = 1.0_real64 + 0.5_real64 * (1.0_real64 - sqrt(n / (n + 6103515625.0_real64)))
    ! Application of the chroma correction factor.
    c_1 = sqrt(a_1 * a_1 * n * n + b_1 * b_1)
    c_2 = sqrt(a_2 * a_2 * n * n + b_2 * b_2)
    ! atan2 is preferred over atan because it accurately computes the angle of
    ! a point (x, y) in all quadrants, handling the signs of both coordinates.
    h_1 = atan2(b_1, a_1 * n)
    h_2 = atan2(b_2, a_2 * n)
    if (h_1 < 0.0_real64) h_1 = h_1 + 2.0_real64 * M_PI
    if (h_2 < 0.0_real64) h_2 = h_2 + 2.0_real64 * M_PI
    n = abs(h_2 - h_1)
    ! Cross-implementation consistent rounding.
    if (M_PI - 0.00000000000001_real64 < n .and. n < M_PI + 0.00000000000001_real64) n = M_PI
    ! When the hue angles lie in different quadrants, the straightforward
    ! average can produce a mean that incorrectly suggests a hue angle in
    ! the wrong quadrant, the next lines handle this issue.
    h_m = (h_1 + h_2) * 0.5_real64
    h_d = (h_2 - h_1) * 0.5_real64
    if (M_PI < n) then
      h_d = h_d + M_PI
      ! πŸ“œ Sharma’s formulation doesn’t use the next line, but the one after it,
      ! and these two variants differ by Β±0.0003 on the final color differences.
      h_m = h_m + M_PI
      ! h_m = h_m + MERGE(M_PI, -M_PI, h_m < M_PI)
    endif
    p = 36.0_real64 * h_m - 55.0_real64 * M_PI
    n = (c_1 + c_2) * 0.5_real64
    n = n * n * n * n * n * n * n
    ! The hue rotation correction term is designed to account for the
    ! non-linear behavior of hue differences in the blue region.
    r_t = -2.0_real64 * sqrt(n / (n + 6103515625.0_real64)) &
                        * sin(M_PI / 3.0_real64 * exp(p * p / (-25.0_real64 * M_PI * M_PI)))
    n = (l_1 + l_2) * 0.5_real64
    n = (n - 50.0_real64) * (n - 50.0_real64)
    ! Lightness.
    l = (l_2 - l_1) / (k_l * (1.0_real64 + 0.015_real64 * n / sqrt(20.0_real64 + n)))
    ! These coefficients adjust the impact of different harmonic
    ! components on the hue difference calculation.
    t = 1.0_real64   + 0.24_real64 * sin(2.0_real64 * h_m + M_PI / 2.0_real64) &
                     + 0.32_real64 * sin(3.0_real64 * h_m + 8.0_real64 * M_PI / 15.0_real64) &
                     - 0.17_real64 * sin(h_m + M_PI / 3.0_real64) &
                     - 0.20_real64 * sin(4.0_real64 * h_m + 3.0_real64 * M_PI / 20.0_real64)
    n = c_1 + c_2
    ! Hue.
    h = 2.0_real64 * sqrt(c_1 * c_2) * sin(h_d) / (k_h * (1.0_real64 + 0.0075_real64 * n * t))
    ! Chroma.
    c = (c_2 - c_1) / (k_c * (1.0_real64 + 0.0225_real64 * n))
    ! Returning the square root ensures that dE00 accurately reflects the
    ! geometric distance in color space, which can range from 0 to around 185.
    delta_e = sqrt(l * l + h * h + c * c + c * h * r_t)
  end function ciede_2000
end module ciede_2000_module

! GitHub Project : https://github.com/michel-leonard/ciede2000-color-matching
!   Online Tests : https://michel-leonard.github.io/ciede2000-color-matching

! L1 = 68.5   a1 = 18.1   b1 = 3.6
! L2 = 70.2   a2 = 23.8   b2 = -4.0
! CIE Ξ”E00 = 6.1142357448 (Bruce Lindbloom, Netflix’s VMAF, ...)
! CIE Ξ”E00 = 6.1142186799 (Gaurav Sharma, OpenJDK, ...)
! Deviation between implementations β‰ˆ 1.7e-5

! See the source code comments for easy switching between these two widely used Ξ”E*00 implementation variants.

Source code accuracy and reliability

The difference between Sharma and Lindbloom formulations never exceeds Β±0.0003 on the final Ξ”E2000, which corresponds to the usual difference measured between two 32-bit implementations and is imperceptible to the human eye. Our 64-bit implementations, all consistent with each other, guarantee at least 10 correct decimal places, so choosing one formulation over another mainly depends on the desired interoperability. The formulation that appears by default on this page is the most commonly used (its micro-advantage lies in its community anchoring and its greater lightness than its analog when vectorized).

✎ If you find a comment in the source code that does not correspond to another language, please inform the author of the site, who will study your suggestion and incorporate it into the source code.

How do you convert RGB colors to L*a*b*?

Go to the AWK, C, Dart, Java, JavaScript, Kotlin, Lua, PHP, Python, Ruby or Rust page where such a converter (using D65 illuminant) is already implemented in addition to the color comparison function.

CIELAB value ranges and interpretation of the Ξ”E2000

In the CIELAB color space, the L* component represents lightness and typically ranges from 0 (black) to 100 (white). The a* and b* components represent color axes: a* goes from green to red, while b* goes from blue to yellow. In practice, a* and b* values usually fall between -128 and +127, although they can slightly exceed these limits depending on the color conversion.

Example of two colors presenting a just-noticeable difference (JND) according to CIEDE2000
Color 1Color 2Value of Ξ”E2000
1
2
3
Examples of CIEDE2000 values calculated between two distinct colors
Color 1Color 2Value of Ξ”E2000
5
10
15

k_l, k_c and k_h Parameters

The parameters k_l, k_c, and k_h are weighting factors applied to the lightness (Ξ”L*), chroma (Ξ”C*), and hue (Ξ”H*) terms in the CIEDE2000 formula. Their default value is 1, which corresponds to the standard viewing conditions recommended by the International Commission on Illumination. In practice, these coefficients are adjusted to reflect specific conditions: for example, k_l = 2 is sometimes used to give more weight to lightness differences (common in printing), while k_c or k_h may be reduced to increase tolerance to saturation or hue variations depending on quality control requirements. Depending on the context, these coefficients typically range between 0.5 and 2.

Ξ”E2000 (CIEDE2000) measures the perceived difference between two colors: 0 means the colors are identical, and higher values (up to around 185 in extreme cases) indicate a larger difference. For example, a Ξ”E2000 value around 5 means the colors are close, while a value around 15 means they are clearly different.

Example of use in Fortran

! Compute the Delta E (CIEDE2000) color difference between two L*a*b* colors in Fortran

! Color 1: l1 = 89.0   a1 = 33.3   b1 = -1.7
! Color 2: l2 = 89.2   a2 = 38.4   b2 = 2.2

delta_e = ciede_2000(l1, a1, b1, l2, a2, b2)
print '(F0.10)', delta_e

! .................................................. This shows a ΔE2000 of 2.9929564263
! As explained in the comments, compliance with Gaurav Sharma would display 2.9929700654

Test results

The driver, written in the C99 language and featuring 250 precise static tests, has proved that this Fortran function is interoperable with the CIEDE2000 function available in other programming languages.

CIEDE2000 Verification Summary :
  First Verified Line : 24,122.6,117,21,-40.999999999847,-46,81.762663548041459
             Duration : 41.38 s
            Successes : 10000000
               Errors : 0
      Average Delta E : 63.2338
    Average Deviation : 4.6e-15
    Maximum Deviation : 1.1e-13

Files to download

Feel free to use these files provided by Michel, even for commercial purposes.

Site statistics : downloads
FileSizeNumber of clicks
ciede-2000.f905 KB61
ciede-2000-driver.f906 KB55
ciede-2000-random.f907 KB58
test-f90.yml3 KB41
reference-dataset.txt4 KB321
Click on f90.zip to receive all these files in an archive.

Community

If you’d like to leave your opinion on this Fortran source code or CIEDE2000 in general, the guestbook already contains 1 messages in English, and 9 messages in total, so let us know what you think.