Commit dd626b10 authored by Dong-hee Na's avatar Dong-hee Na Committed by Dylan Trotter

Import stdlib colorsys and implement round function for ndigits. (#278)

parent cc56ad21
...@@ -102,6 +102,7 @@ STDLIB_TESTS := \ ...@@ -102,6 +102,7 @@ STDLIB_TESTS := \
test/test_bisect \ test/test_bisect \
test/test_datetime \ test/test_datetime \
test/test_operator \ test/test_operator \
test/test_colorsys \
threading_test \ threading_test \
time_test \ time_test \
types_test \ types_test \
......
...@@ -631,15 +631,27 @@ func builtinRound(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { ...@@ -631,15 +631,27 @@ func builtinRound(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
return nil, raised return nil, raised
} }
} }
// TODO: implement this.
if ndigits != 0 {
return nil, f.RaiseType(NotImplementedErrorType, "round with ndigits is not implemented in grumpy")
}
number, isFloat := floatCoerce(args[0]) number, isFloat := floatCoerce(args[0])
if !isFloat { if !isFloat {
return nil, f.RaiseType(TypeErrorType, "a float is required") return nil, f.RaiseType(TypeErrorType, "a float is required")
} }
return NewFloat(math.Floor(number + 0.5)).ToObject(), nil
if math.IsNaN(number) || math.IsInf(number, 0) || number == 0.0 {
return NewFloat(number).ToObject(), nil
}
neg := false
if number < 0 {
neg = true
number = -number
}
pow := math.Pow(10.0, float64(ndigits))
result := math.Floor(number*pow+0.5) / pow
if neg {
result = -result
}
return NewFloat(result).ToObject(), nil
} }
func builtinSetAttr(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { func builtinSetAttr(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
......
...@@ -293,7 +293,17 @@ func TestBuiltinFuncs(t *testing.T) { ...@@ -293,7 +293,17 @@ func TestBuiltinFuncs(t *testing.T) {
{f: "round", args: wrapArgs(-1234.111), want: NewFloat(-1234).ToObject()}, {f: "round", args: wrapArgs(-1234.111), want: NewFloat(-1234).ToObject()},
{f: "round", args: wrapArgs(1234.567, newTestIndexObject(0)), want: NewFloat(1235).ToObject()}, {f: "round", args: wrapArgs(1234.567, newTestIndexObject(0)), want: NewFloat(1235).ToObject()},
{f: "round", args: wrapArgs("foo"), wantExc: mustCreateException(TypeErrorType, "a float is required")}, {f: "round", args: wrapArgs("foo"), wantExc: mustCreateException(TypeErrorType, "a float is required")},
{f: "round", args: wrapArgs(1234.5, 1), wantExc: mustCreateException(NotImplementedErrorType, "round with ndigits is not implemented in grumpy")}, {f: "round", args: wrapArgs(12.5, 0), want: NewFloat(13.0).ToObject()},
{f: "round", args: wrapArgs(-12.5, 0), want: NewFloat(-13.0).ToObject()},
{f: "round", args: wrapArgs(12.5, 3), want: NewFloat(12.5).ToObject()},
{f: "round", args: wrapArgs(1234.5, 1), want: NewFloat(1234.5).ToObject()},
{f: "round", args: wrapArgs(1234.5, 1), want: NewFloat(1234.5).ToObject()},
{f: "round", args: wrapArgs(1234.56, 1), want: NewFloat(1234.6).ToObject()},
{f: "round", args: wrapArgs(-1234.56, 1), want: NewFloat(-1234.6).ToObject()},
{f: "round", args: wrapArgs(-1234.56, -2), want: NewFloat(-1200.0).ToObject()},
{f: "round", args: wrapArgs(-1234.56, -8), want: NewFloat(0.0).ToObject()},
{f: "round", args: wrapArgs(63.4, -3), want: NewFloat(0.0).ToObject()},
{f: "round", args: wrapArgs(63.4, -2), want: NewFloat(100.0).ToObject()},
{f: "sorted", args: wrapArgs(NewList()), want: NewList().ToObject()}, {f: "sorted", args: wrapArgs(NewList()), want: NewList().ToObject()},
{f: "sorted", args: wrapArgs(newTestList("foo", "bar")), want: newTestList("bar", "foo").ToObject()}, {f: "sorted", args: wrapArgs(newTestList("foo", "bar")), want: newTestList("bar", "foo").ToObject()},
{f: "sorted", args: wrapArgs(newTestList(true, false)), want: newTestList(false, true).ToObject()}, {f: "sorted", args: wrapArgs(newTestList(true, false)), want: newTestList(false, true).ToObject()},
......
"""Conversion functions between RGB and other color systems.
This modules provides two functions for each color system ABC:
rgb_to_abc(r, g, b) --> a, b, c
abc_to_rgb(a, b, c) --> r, g, b
All inputs and outputs are triples of floats in the range [0.0...1.0]
(with the exception of I and Q, which covers a slightly larger range).
Inputs outside the valid range may cause exceptions or invalid outputs.
Supported color systems:
RGB: Red, Green, Blue components
YIQ: Luminance, Chrominance (used by composite video signals)
HLS: Hue, Luminance, Saturation
HSV: Hue, Saturation, Value
"""
# References:
# http://en.wikipedia.org/wiki/YIQ
# http://en.wikipedia.org/wiki/HLS_color_space
# http://en.wikipedia.org/wiki/HSV_color_space
__all__ = ["rgb_to_yiq","yiq_to_rgb","rgb_to_hls","hls_to_rgb",
"rgb_to_hsv","hsv_to_rgb"]
# Some floating point constants
ONE_THIRD = 1.0/3.0
ONE_SIXTH = 1.0/6.0
TWO_THIRD = 2.0/3.0
# YIQ: used by composite video signals (linear combinations of RGB)
# Y: perceived grey level (0.0 == black, 1.0 == white)
# I, Q: color components
#
# There are a great many versions of the constants used in these formulae.
# The ones in this library uses constants from the FCC version of NTSC.
def rgb_to_yiq(r, g, b):
y = 0.30*r + 0.59*g + 0.11*b
i = 0.74*(r-y) - 0.27*(b-y)
q = 0.48*(r-y) + 0.41*(b-y)
return (y, i, q)
def yiq_to_rgb(y, i, q):
# r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48)
# b = y + (0.74*q - 0.48*i) / (0.74*0.41 + 0.27*0.48)
# g = y - (0.30*(r-y) + 0.11*(b-y)) / 0.59
r = y + 0.9468822170900693*i + 0.6235565819861433*q
g = y - 0.27478764629897834*i - 0.6356910791873801*q
b = y - 1.1085450346420322*i + 1.7090069284064666*q
if r < 0.0:
r = 0.0
if g < 0.0:
g = 0.0
if b < 0.0:
b = 0.0
if r > 1.0:
r = 1.0
if g > 1.0:
g = 1.0
if b > 1.0:
b = 1.0
return (r, g, b)
# HLS: Hue, Luminance, Saturation
# H: position in the spectrum
# L: color lightness
# S: color saturation
def rgb_to_hls(r, g, b):
maxc = max(r, g, b)
minc = min(r, g, b)
# XXX Can optimize (maxc+minc) and (maxc-minc)
l = (minc+maxc)/2.0
if minc == maxc:
return 0.0, l, 0.0
if l <= 0.5:
s = (maxc-minc) / (maxc+minc)
else:
s = (maxc-minc) / (2.0-maxc-minc)
rc = (maxc-r) / (maxc-minc)
gc = (maxc-g) / (maxc-minc)
bc = (maxc-b) / (maxc-minc)
if r == maxc:
h = bc-gc
elif g == maxc:
h = 2.0+rc-bc
else:
h = 4.0+gc-rc
h = (h/6.0) % 1.0
return h, l, s
def hls_to_rgb(h, l, s):
if s == 0.0:
return l, l, l
if l <= 0.5:
m2 = l * (1.0+s)
else:
m2 = l+s-(l*s)
m1 = 2.0*l - m2
return (_v(m1, m2, h+ONE_THIRD), _v(m1, m2, h), _v(m1, m2, h-ONE_THIRD))
def _v(m1, m2, hue):
hue = hue % 1.0
if hue < ONE_SIXTH:
return m1 + (m2-m1)*hue*6.0
if hue < 0.5:
return m2
if hue < TWO_THIRD:
return m1 + (m2-m1)*(TWO_THIRD-hue)*6.0
return m1
# HSV: Hue, Saturation, Value
# H: position in the spectrum
# S: color saturation ("purity")
# V: color brightness
def rgb_to_hsv(r, g, b):
maxc = max(r, g, b)
minc = min(r, g, b)
v = maxc
if minc == maxc:
return 0.0, 0.0, v
s = (maxc-minc) / maxc
rc = (maxc-r) / (maxc-minc)
gc = (maxc-g) / (maxc-minc)
bc = (maxc-b) / (maxc-minc)
if r == maxc:
h = bc-gc
elif g == maxc:
h = 2.0+rc-bc
else:
h = 4.0+gc-rc
h = (h/6.0) % 1.0
return h, s, v
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h*6.0) # XXX assume int() truncates!
f = (h*6.0) - i
p = v*(1.0 - s)
q = v*(1.0 - s*f)
t = v*(1.0 - s*(1.0-f))
i = i%6
if i == 0:
return v, t, p
if i == 1:
return q, v, p
if i == 2:
return p, v, t
if i == 3:
return p, q, v
if i == 4:
return t, p, v
if i == 5:
return v, p, q
import unittest
import colorsys
from test import test_support
def frange(start, stop, step):
while start <= stop:
yield start
start += step
class ColorsysTest(unittest.TestCase):
def assertTripleEqual(self, tr1, tr2):
self.assertEqual(len(tr1), 3)
self.assertEqual(len(tr2), 3)
self.assertAlmostEqual(tr1[0], tr2[0])
self.assertAlmostEqual(tr1[1], tr2[1])
self.assertAlmostEqual(tr1[2], tr2[2])
def test_hsv_roundtrip(self):
for r in frange(0.0, 1.0, 0.2):
for g in frange(0.0, 1.0, 0.2):
for b in frange(0.0, 1.0, 0.2):
rgb = (r, g, b)
self.assertTripleEqual(
rgb,
colorsys.hsv_to_rgb(*colorsys.rgb_to_hsv(*rgb))
)
def test_hsv_values(self):
values = [
# rgb, hsv
((0.0, 0.0, 0.0), ( 0 , 0.0, 0.0)), # black
((0.0, 0.0, 1.0), (4./6., 1.0, 1.0)), # blue
((0.0, 1.0, 0.0), (2./6., 1.0, 1.0)), # green
((0.0, 1.0, 1.0), (3./6., 1.0, 1.0)), # cyan
((1.0, 0.0, 0.0), ( 0 , 1.0, 1.0)), # red
((1.0, 0.0, 1.0), (5./6., 1.0, 1.0)), # purple
((1.0, 1.0, 0.0), (1./6., 1.0, 1.0)), # yellow
((1.0, 1.0, 1.0), ( 0 , 0.0, 1.0)), # white
((0.5, 0.5, 0.5), ( 0 , 0.0, 0.5)), # grey
]
for (rgb, hsv) in values:
self.assertTripleEqual(hsv, colorsys.rgb_to_hsv(*rgb))
self.assertTripleEqual(rgb, colorsys.hsv_to_rgb(*hsv))
def test_hls_roundtrip(self):
for r in frange(0.0, 1.0, 0.2):
for g in frange(0.0, 1.0, 0.2):
for b in frange(0.0, 1.0, 0.2):
rgb = (r, g, b)
self.assertTripleEqual(
rgb,
colorsys.hls_to_rgb(*colorsys.rgb_to_hls(*rgb))
)
def test_hls_values(self):
values = [
# rgb, hls
((0.0, 0.0, 0.0), ( 0 , 0.0, 0.0)), # black
((0.0, 0.0, 1.0), (4./6., 0.5, 1.0)), # blue
((0.0, 1.0, 0.0), (2./6., 0.5, 1.0)), # green
((0.0, 1.0, 1.0), (3./6., 0.5, 1.0)), # cyan
((1.0, 0.0, 0.0), ( 0 , 0.5, 1.0)), # red
((1.0, 0.0, 1.0), (5./6., 0.5, 1.0)), # purple
((1.0, 1.0, 0.0), (1./6., 0.5, 1.0)), # yellow
((1.0, 1.0, 1.0), ( 0 , 1.0, 0.0)), # white
((0.5, 0.5, 0.5), ( 0 , 0.5, 0.0)), # grey
]
for (rgb, hls) in values:
self.assertTripleEqual(hls, colorsys.rgb_to_hls(*rgb))
self.assertTripleEqual(rgb, colorsys.hls_to_rgb(*hls))
def test_yiq_roundtrip(self):
for r in frange(0.0, 1.0, 0.2):
for g in frange(0.0, 1.0, 0.2):
for b in frange(0.0, 1.0, 0.2):
rgb = (r, g, b)
self.assertTripleEqual(
rgb,
colorsys.yiq_to_rgb(*colorsys.rgb_to_yiq(*rgb))
)
def test_yiq_values(self):
values = [
# rgb, yiq
((0.0, 0.0, 0.0), (0.0, 0.0, 0.0)), # black
((0.0, 0.0, 1.0), (0.11, -0.3217, 0.3121)), # blue
((0.0, 1.0, 0.0), (0.59, -0.2773, -0.5251)), # green
((0.0, 1.0, 1.0), (0.7, -0.599, -0.213)), # cyan
((1.0, 0.0, 0.0), (0.3, 0.599, 0.213)), # red
((1.0, 0.0, 1.0), (0.41, 0.2773, 0.5251)), # purple
((1.0, 1.0, 0.0), (0.89, 0.3217, -0.3121)), # yellow
((1.0, 1.0, 1.0), (1.0, 0.0, 0.0)), # white
((0.5, 0.5, 0.5), (0.5, 0.0, 0.0)), # grey
]
for (rgb, yiq) in values:
self.assertTripleEqual(yiq, colorsys.rgb_to_yiq(*rgb))
self.assertTripleEqual(rgb, colorsys.yiq_to_rgb(*yiq))
def test_main():
test_support.run_unittest(ColorsysTest)
if __name__ == "__main__":
test_main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment