VBA / ciede-2000-driver.bas 💾

This is a 6,171-byte text/plain file, placed in the public domain, which has been consulted 131 times since the year 2026.

' Limited Use License – March 1, 2025

' This source code is provided for public use under the following conditions :
' It may be downloaded, compiled, and executed, including in publicly accessible environments.
' Modification is strictly prohibited without the express written permission of the author.

' © Michel Leonard 2025

' 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.
Public Function ciede_2000(l_1 As Double, a_1 As Double, b_1 As Double, l_2 As Double, a_2 As Double, b_2 As Double) As Double
	' Working in VBA with the CIEDE2000 color-difference formula.
	' k_l, k_c, k_h are parametric factors to be adjusted according to
	' different viewing parameters such as textures, backgrounds...
	Const M_PI = 3.14159265358979323846264338328, k_l = 1.0, k_c = 1.0, k_h = 1.0
	Dim n As Double, c_1 As Double, c_2 As Double, h_1 As Double, h_2 As Double
	Dim h_m As Double, h_d As Double, p As Double, r_t As Double, l As Double
	Dim t As Double, h As Double, c As Double
	n = (Sqr(a_1 * a_1 + b_1 * b_1) + Sqr(a_2 * a_2 + b_2 * b_2)) * 0.5
	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 + 0.5 * (1.0 - Sqr(n / (n + 6103515625.0)))
	' Application of the chroma correction factor.
	c_1 = Sqr(a_1 * a_1 * n * n + b_1 * b_1)
	c_2 = Sqr(a_2 * a_2 * n * n + b_2 * b_2)
	' Using 14 lines to simulate atan2, as VBA does not have this built-in.
	If 0.0 < a_1 Then
		h_1 = Atn(b_1 / (a_1 * n)) - (b_1 < 0.0) * 2.0 * M_PI
	ElseIf a_1 < 0.0 Then
		h_1 = Atn(b_1 / (a_1 * n)) + M_PI
	Else
		h_1 = M_PI + ((0.0 < b_1) - (b_1 < 0.0)) * 0.5 * M_PI
	End If
	If 0.0 < a_2 Then
		h_2 = Atn(b_2 / (a_2 * n)) - (b_2 < 0.0) * 2.0 * M_PI
	ElseIf a_2 < 0.0 Then
		h_2 = Atn(b_2 / (a_2 * n)) + M_PI
	Else
		h_2 = M_PI + ((0.0 < b_2) - (b_2 < 0.0)) * 0.5 * M_PI
	End If
	' The atan2 polyfill (customized) is complete.
	n = Abs(h_2 - h_1)
	' Cross-implementation consistent rounding.
	If M_PI - 1E-14 < n And n < M_PI + 1E-14 Then 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
	h_d = (h_2 - h_1) * 0.5
	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
		' If h_m < M_PI Then h_m = h_m + M_PI Else h_m = h_m - M_PI
	End If
	p = 36.0 * h_m - 55.0 * M_PI
	n = (c_1 + c_2) * 0.5
	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 * Sqr(n / (n + 6103515625.0)) _
			* Sin(M_PI / 3.0 * Exp(p * p / (-25.0 * M_PI * M_PI)))
	n = (l_1 + l_2) * 0.5
	n = (n - 50.0) * (n - 50.0)
	' Lightness.
	l = (l_2 - l_1) / (k_l * (1.0 + 0.015 * n / Sqr(20.0 + n)))
	' These coefficients adjust the impact of different harmonic
	' components on the hue difference calculation.
	t = 1.0		+ 0.24 * Sin(2.0 * h_m + M_PI * 0.5) _
			+ 0.32 * Sin(3.0 * h_m + 8.0 * M_PI / 15.0) _
			- 0.17 * Sin(h_m + M_PI / 3.0) _
			- 0.2 * Sin(4.0 * h_m + 3.0 * M_PI / 20.0)
	n = c_1 + c_2
	' Hue.
	h = 2.0 * Sqr(c_1 * c_2) * Sin(h_d) / (k_h * (1.0 + 0.0075 * n * t))
	' Chroma.
	c = (c_2 - c_1) / (k_c * (1.0 + 0.0225 * n))
	' Returning the square root ensures that dE00 accurately reflects the
	' geometric distance in color space, which can range from 0 to around 185.
	ciede_2000 = Sqr(l * l + h * h + c * c + c * h * r_t)
End Function

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

' L1 = 30.5   a1 = 43.0   b1 = 2.1
' L2 = 31.6   a2 = 37.8   b2 = -1.8
' CIE ΔE00 = 2.9669616372 (Bruce Lindbloom, Netflix’s VMAF, ...)
' CIE ΔE00 = 2.9669747208 (Gaurav Sharma, OpenJDK, ...)
' Deviation between implementations ≈ 1.3e-5

' See the source code comments for easy switching between these two widely used ΔE*00 implementation variants.

'''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''                         ''''''''''''
''''''''''''    CIEDE2000 Driver     ''''''''''''
''''''''''''                         ''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''

' Reads a CSV file specified as the first command-line argument. For each line, this program
' in VBA displays the original line with the computed Delta E 2000 color difference appended.
' The C driver can offer CSV files to process and programmatically check the calculations performed there.

'  Example of a CSV input line : 20.9,100,-64,22,92,12.7
'    Corresponding output line : 20.9,100,-64,22,92,12.7,22.549861408469072907235901140461

Sub split(Byref s1 As String, Byref s2 As String, splits(Any) As String)
	Dim As Integer n, n0 = 1.0
	Do
		Redim Preserve splits(Ubound(splits) + 1)
		n = Instr(n0, s1, s2)
		If n > 0 Then
			splits(Ubound(splits)) = Mid(s1, n0, n - n0)
			n0 = n + Len(s2)
		Else
			splits(Ubound(splits)) = Mid(s1, n0)
			Exit Do
		End If
	Loop
End Sub

Sub Main(filename As String)
	Dim f As Integer = FreeFile()
	Dim buffer As String
	If Open(filename For Input As #f) <> 0 Then
		Print "Can't open file: " & filename
		End 1
	End If
	While Not Eof(f)
		Line Input #f, buffer
		Dim As String parts(Any)
		split(buffer, ",", parts())
		Dim L1 As Double = Val(parts(0))
		Dim a1 As Double = Val(parts(1))
		Dim b1 As Double = Val(parts(2))
		Dim L2 As Double = Val(parts(3))
		Dim a2 As Double = Val(parts(4))
		Dim b2 As Double = Val(parts(5))
		Dim deltaE As Double = ciede_2000(L1, a1, b1, L2, a2, b2)
		Print buffer & "," & Str(deltaE)
	Wend
	Close #f
End Sub

If Command(1) <> "" Then
	Main(Command(1))
End If