From acf51a5b2f66e576079ba3d6375574a231942b6c Mon Sep 17 00:00:00 2001 From: Gordon Shumway <39967334+oddluck@users.noreply.github.com> Date: Tue, 25 Jun 2019 20:23:40 -0400 Subject: [PATCH] much faster cie2000 function, no dither by default --- ASCII/plugin.py | 134 +++++++++++++++++++++++++++--------------------- 1 file changed, 76 insertions(+), 58 deletions(-) diff --git a/ASCII/plugin.py b/ASCII/plugin.py index 5dacb80..711e117 100644 --- a/ASCII/plugin.py +++ b/ASCII/plugin.py @@ -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): """[]