From 82f09db519b10d6265c5ad982d8655801b762a07 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Tue, 29 Dec 2020 16:42:47 +0100 Subject: [PATCH] rework color parsing & dynamic palettes --- render/pattern.go | 100 +++++++++++++++++++++++++++++++++------------- rpc/repl.go | 23 ++++++++--- 2 files changed, 89 insertions(+), 34 deletions(-) diff --git a/render/pattern.go b/render/pattern.go index fb2deca..0622f86 100644 --- a/render/pattern.go +++ b/render/pattern.go @@ -8,27 +8,31 @@ import ( "golang.org/x/image/draw" ) -// pattern provides infinite algorithmically generated patterns and implements +// Pattern provides infinite algorithmically generated Patterns and implements // the image.Image interface. -type pattern struct{} +type Pattern struct { + Size image.Point +} // Image interface -func (c *pattern) ColorModel() color.Model { return color.NRGBAModel } -func (c *pattern) At(x, y int) color.Color { return color.Transparent } -func (c *pattern) Bounds() image.Rectangle { +func (c *Pattern) ColorModel() color.Model { return color.NRGBAModel } +func (c *Pattern) At(x, y int) color.Color { return color.Transparent } +func (c *Pattern) Bounds() image.Rectangle { return image.Rectangle{image.Point{-1e9, -1e9}, image.Point{1e9, 1e9}} } -func (p *pattern) ToFixedImage(bounds image.Rectangle) *image.NRGBA { +func (p *Pattern) ToFixedImage(bounds image.Rectangle) *image.NRGBA { img := image.NewNRGBA(bounds) + // TODO: allow setting a mask? + // override size for pattern? + p.Size = bounds.Size() // override size for this Pattern draw.Draw(img, bounds, p, image.Point{}, draw.Src) return img } type StripePattern struct { - pattern + Pattern Palette color.Palette - Size int } func (c *StripePattern) At(x, y int) color.Color { @@ -37,7 +41,7 @@ func (c *StripePattern) At(x, y int) color.Color { return color.Transparent } - pxPerStripe := c.Size / n + pxPerStripe := c.Size.Y / n if pxPerStripe <= 0 { pxPerStripe = 1 } @@ -46,8 +50,8 @@ func (c *StripePattern) At(x, y int) color.Color { } func NewPrideImage(p color.Palette, bounds image.Rectangle) image.Image { - pattern := StripePattern{Palette: p, Size: bounds.Dy()} - return pattern.ToFixedImage(bounds) + Pattern := StripePattern{Palette: p} + return Pattern.ToFixedImage(bounds) } var PrideFlags = map[string]color.Palette{ @@ -74,27 +78,67 @@ var PrideFlags = map[string]color.Palette{ }, } -type SineColorPattern struct { - pattern - Luma uint8 - Freq float64 - Phase float64 +type DynamicPattern struct { + Pattern + + Color func(x, y int, p *DynamicPattern) (float64, float64, float64) // should return rgb in [0,1] range + + Luma float64 // color parameter + Freq float64 // frequency parameter + Phase float64 // phase parameter + + Brightness float64 // additive base brightness + ColFactor float64 // Pattern intensity } -func (c *SineColorPattern) At(x, y int) color.Color { - TAU := 6.28318 - offset := 0.5 - scale := 0.5 - v := math.Atan2(float64(x), float64(y)) - +func (c *DynamicPattern) At(x, y int) color.Color { col := color.NRGBA{} - col.R = uint8((offset+scale*math.Sin(v/c.Freq*TAU))*128 + 128) - col.G = uint8((offset+scale*math.Sin(v/c.Freq*TAU+TAU/3))*128 + 128) - col.B = uint8((offset+scale*math.Sin(v/c.Freq*TAU+TAU/1.5))*128 + 128) + + if c.ColFactor == 0.0 && c.Brightness == 0.0 { + c.ColFactor = 1.0 + } + if c.Color != nil { + r, g, b := c.Color(x, y, c) + col.R = uint8(r * c.ColFactor * 255) + col.G = uint8(g * c.ColFactor * 255) + col.B = uint8(b * c.ColFactor * 255) + } + if c.Freq == 0.0 { + c.Freq = 1.0 + } + + col.R = col.R + uint8(255*c.Brightness) + col.G = col.G + uint8(255*c.Brightness) + col.B = col.B + uint8(255*c.Brightness) col.A = 0xff - // col.R = c.Luma - // col.G = uint8(math.Sin(float64(x)/c.Freq+c.Phase) * 128 + 128) - // col.B = uint8(math.Cos(float64(y)/c.Freq+c.Phase) * 128 + 128) return col } + +var DynPatterns = map[string]image.Image{ + "rbow": &DynamicPattern{ + Color: func(x, y int, p *DynamicPattern) (float64, float64, float64) { + xx := float64(x) / 26.0 // TODO: dynamic sizing + yy := float64(y) / 13.0 + r := 0.5 + 0.5*math.Cos(xx+p.Phase) + g := 0.5 + 0.5*math.Sin(yy+p.Phase) + b := 0.5 + 0.5*math.Sin(xx+p.Phase)*math.Cos(yy+p.Phase) + return r, g, b + }, + }, + + "pastel": &DynamicPattern{ + Brightness: 0.5, // TODO: expose these params? + ColFactor: 0.5, + Freq: 5.0, + + Color: func(x, y int, p *DynamicPattern) (float64, float64, float64) { + offset := 0.0 + v := math.Atan2(float64(x)-offset, float64(y)-offset) + p.Phase + r := 0.5 + 0.5*math.Sin(v*p.Freq) + g := 0.5 + 0.5*math.Cos(v*p.Freq) + b := 0.5 + 0.5*math.Sin(v*p.Freq+6.28318/p.Luma) + return r, g, b + }, + }, +} diff --git a/rpc/repl.go b/rpc/repl.go index 1450536..cc95b2e 100644 --- a/rpc/repl.go +++ b/rpc/repl.go @@ -128,16 +128,27 @@ func RunREPL(f Fluter) { // alternatively treat it as palette name. If both fail, // give image.Transparent func parseColorOrPalette(input string) image.Image { - if col, err := hex.DecodeString(input); err == nil { + if input == "w" { + return image.NewUniform(color.White) + } else if input == "b" { + return image.NewUniform(color.Black) + } else if input == "t" { + return image.Transparent + } else if col, err := hex.DecodeString(input); err == nil && len(col) >= 3 { var alpha byte = 0xff if len(col) == 4 { alpha = col[3] } return image.NewUniform(color.NRGBA{col[0], col[1], col[2], alpha}) - } else if pal := render.PrideFlags[input]; len(pal) != 0 { - return &render.StripePattern{Palette: pal, Size: 13} - } else { - return &render.SineColorPattern{Luma: 0xf0, Freq: 1} - return image.Transparent } + + if pal := render.PrideFlags[input]; len(pal) != 0 { + return &render.StripePattern{Palette: pal} + } + + if p, ok := render.DynPatterns[input]; ok { + return p + } + + return image.Transparent }