much faster cie2000 function, no dither by default

This commit is contained in:
Gordon Shumway 2019-06-25 20:23:40 -04:00 committed by GitHub
parent 42afb01ec5
commit acf51a5b2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -368,56 +368,79 @@ class ASCII(callbacks.Plugin):
Lab [ 2 ] = round( b, 4 )
return Lab
def cie_de_2000(self, lab1,lab2,k_L=1,k_C=1,k_H=1):
L_1_star,a_1_star,b_1_star=lab1
L_2_star,a_2_star,b_2_star=lab2
C_1_star=np.sqrt(np.power(a_1_star,2)+np.power(b_1_star,2))
C_2_star=np.sqrt(np.power(a_2_star,2)+np.power(b_2_star,2))
C_bar_star=np.average([C_1_star,C_2_star])
G=0.5*(1-np.sqrt(np.power(C_bar_star,7)/(np.power(C_bar_star,7)+np.power(25,7))))
a_1_dash=(1+G)*a_1_star
a_2_dash=(1+G)*a_2_star
C_1_dash=np.sqrt(np.power(a_1_dash,2)+np.power(b_1_star,2))
C_2_dash=np.sqrt(np.power(a_2_dash,2)+np.power(b_2_star,2))
h_1_dash=np.degrees(np.arctan2(b_1_star,a_1_dash))
h_1_dash += (h_1_dash < 0) * 360
h_2_dash=np.degrees(np.arctan2(b_2_star,a_2_dash))
h_2_dash += (h_2_dash < 0) * 360
delta_L_dash=L_2_star-L_1_star
delta_C_dash=C_2_dash-C_1_dash
delta_h_dash=0.0
if(C_1_dash*C_2_dash):
if(np.fabs(h_2_dash-h_1_dash)<=180):
delta_h_dash=h_2_dash-h_1_dash
elif(h_2_dash-h_1_dash>180):
delta_h_dash=(h_2_dash-h_1_dash)-360
elif(h_2_dash-h_1_dash)<-180:
delta_h_dash=(h_2_dash-h_1_dash)+360
delta_H_dash=2*np.sqrt(C_1_dash*C_2_dash)*np.sin(np.radians(delta_h_dash)/2.0)
L_bar_dash=np.average([L_1_star,L_2_star])
C_bar_dash=np.average([C_1_dash,C_2_dash])
h_bar_dash=h_1_dash+h_2_dash
if(C_1_dash*C_2_dash):
if(np.fabs(h_1_dash-h_2_dash)<=180):
h_bar_dash=np.average([h_1_dash,h_2_dash])
def ciede2000(self, color1, color2):
"""
Calculates color difference according to the `CIEDE 2000`_ formula. This is
the most accurate algorithm currently implemented but also the most complex
and slowest. Like CIE1994 it is largely based in CIE L*C*h* space, but with
several modifications to account for perceptual uniformity flaws.
.. _CIEDE 2000: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
"""
# See WP article and Sharma 2005 for important implementation notes:
# http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
#
# Yes, there's lots of locals; but this is easiest to understand as it's a
# near straight translation of the math
# pylint: disable=too-many-locals
C_ = (
math.sqrt(color1[1] ** 2 + color1[2] ** 2) +
math.sqrt(color2[1] ** 2 + color2[2] ** 2)
) / 2
G = (1 - math.sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7))) / 2
a1_prime = (1 + G) * color1[1]
a2_prime = (1 + G) * color2[1]
C1_prime = math.sqrt(a1_prime ** 2 + color1[2] ** 2)
C2_prime = math.sqrt(a2_prime ** 2 + color2[2] ** 2)
L_ = (color1[0] + color2[0]) / 2
C_ = (C1_prime + C2_prime) / 2
h1 = (
0.0 if color1[2] == a1_prime == 0 else
math.degrees(math.atan2(color1[2], a1_prime)) % 360
)
h2 = (
0.0 if color2[2] == a2_prime == 0 else
math.degrees(math.atan2(color2[2], a2_prime)) % 360
)
if C1_prime * C2_prime == 0.0:
dh = 0.0
h_ = h1 + h2
elif abs(h1 - h2) <= 180:
dh = h2 - h1
h_ = (h1 + h2) / 2
else:
if h2 > h1:
dh = h2 - h1 - 360
else:
if(h_1_dash+h_2_dash)<360:
h_bar_dash=(h_1_dash+h_2_dash+360)/2
else:
h_bar_dash=(h_1_dash+h_2_dash-360)/2
T=1-0.17*np.cos(np.radians(h_bar_dash-30))+0.24*np.cos(np.radians(2*h_bar_dash))\
+0.32*np.cos(np.radians(3*h_bar_dash+6))-0.20*np.cos(np.radians(4*h_bar_dash-63))
delta_theta=30 * np.exp(- np.power( (h_bar_dash-275) / 25, 2))
R_c=2*np.sqrt( np.power(C_bar_dash,7) / (np.power(C_bar_dash,7)+np.power(25,7)) )
S_L=1+((0.015*np.power(L_bar_dash-50,2))/np.sqrt(20+np.power(L_bar_dash-50,2)))
S_C=1+0.045*C_bar_dash
S_H=1+0.015*C_bar_dash*T
R_T=-R_c * np.sin(2*np.radians(delta_theta))
delta_e=np.sqrt(np.power(delta_L_dash/(k_L*S_L),2)+\
np.power(delta_C_dash/(k_C*S_C),2)+\
np.power(delta_H_dash/(k_H*S_H),2)+\
R_T*(delta_C_dash/(k_C*S_C))*(delta_H_dash/(k_H*S_H))\
)
dh = h2 - h1 + 360
if h1 + h2 >= 360:
h_ = (h1 + h2 - 360) / 2
else:
h_ = (h1 + h2 + 360) / 2
dL = color2[0] - color1[0]
dC = C2_prime - C1_prime
dH = 2 * math.sqrt(C1_prime * C2_prime) * math.sin(math.radians(dh / 2))
T = (
1 -
0.17 * math.cos(math.radians(h_ - 30)) +
0.24 * math.cos(math.radians(2 * h_)) +
0.32 * math.cos(math.radians(3 * h_ + 6)) -
0.20 * math.cos(math.radians(4 * h_ - 63))
)
SL = 1 + (0.015 * (L_ - 50) ** 2) / math.sqrt(20 + (L_ - 50) ** 2)
SC = 1 + 0.045 * C_
SH = 1 + 0.015 * C_ * T
RT = (
-2 * math.sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7)) *
math.sin(math.radians(60 * math.exp(-(((h_ - 275) / 25) ** 2))))
)
delta_e = math.sqrt(
(dL / SL) ** 2 +
(dC / SC) ** 2 +
(dH / SH) ** 2 +
RT * (dC / SC) * (dH / SH)
)
return delta_e
def distance(self, c1, c2, speed):
@ -426,7 +449,7 @@ class ASCII(callbacks.Plugin):
(r2,g2,b2) = (c2[0], c2[1], c2[2])
delta_e = math.sqrt((r1 - r2)**2 + (g1 - g2) ** 2 + (b1 - b2) **2)
elif speed == 'slow':
delta_e = self.cie_de_2000(c1, c2)
delta_e = self.ciede2000(c1, c2)
return delta_e
def png(self, irc, msg, args, optlist, url):
@ -556,8 +579,6 @@ class ASCII(callbacks.Plugin):
speed = 'fast'
elif 'slow' in optlist:
speed = 'slow'
elif 'insane' in optlist:
speed = 'slow'
else:
speed = 'slow'
if 'chars' in optlist:
@ -644,11 +665,8 @@ class ASCII(callbacks.Plugin):
image2 = image.resize((cols, rows), Image.LANCZOS)
if 's' in optlist:
image2 = ImageEnhance.Color(image2).enhance(s)
if 'dither' in optlist and 'insane' not in optlist:
image2 = image2.convert('P', dither=Image.FLOYDSTEINBERG, palette=Image.ADAPTIVE)
image2 = image2.convert('RGB')
elif 'insane' not in optlist:
image2 = image2.convert('P', dither=None, palette=Image.ADAPTIVE)
if 'dither' in optlist:
image2 = image2.convert('P', palette=Image.ADAPTIVE)
image2 = image2.convert('RGB')
colormap = np.array(image2)
self.matches = {}
@ -904,7 +922,7 @@ class ASCII(callbacks.Plugin):
irc.reply(line, prefixNick=False, noLengthCheck=True, private=False, notice=False, to=channel)
if self.registryValue('pasteEnable', msg.args[0]):
irc.reply(self.doPaste(url, paste), private=False, notice=False, to=channel)
img = wrap(img,[optional('channel'), getopts({'w':'int', 'invert':'', 'fast':'', 'slow':'', 'insane':'', '16':'', 'delay':'float', 'dither':'', 'chars':'text', 'bg':'int', 'fg':'int', 'ramp':'text', 'nocolor':'', 'block':'', 'ascii':'', '1/4':'', 's':'float', 'tops':''}), ('text')])
img = wrap(img,[optional('channel'), getopts({'w':'int', 'invert':'', 'fast':'', 'slow':'', '16':'', 'delay':'float', 'dither':'', 'chars':'text', 'bg':'int', 'fg':'int', 'ramp':'text', 'nocolor':'', 'block':'', 'ascii':'', '1/4':'', 's':'float', 'tops':''}), ('text')])
def scroll(self, irc, msg, args, channel, optlist, url):
"""[<channel>] <url>