Class: AutoColors::ColorScheme

Inherits:
Object
  • Object
show all
Defined in:
lib/autocolors/colorscheme.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil) ⇒ ColorScheme

Returns a new instance of ColorScheme.



6
7
8
9
10
11
12
13
14
15
# File 'lib/autocolors/colorscheme.rb', line 6

def initialize(name=nil)
  srand rand(0xffffffff)
  name ||= Webster.new.random_word
  @name = name
  @seed = @name.hash
  srand @seed
  @dark = {}
  @light = {}
  generate
end

Instance Attribute Details

#contrastObject

Returns the value of attribute contrast.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def contrast
  @contrast
end

#darkObject

Returns the value of attribute dark.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def dark
  @dark
end

#lightObject

Returns the value of attribute light.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def light
  @light
end

#nameObject

Returns the value of attribute name.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def name
  @name
end

#saturationObject

Returns the value of attribute saturation.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def saturation
  @saturation
end

#seedObject

Returns the value of attribute seed.



5
6
7
# File 'lib/autocolors/colorscheme.rb', line 5

def seed
  @seed
end

Instance Method Details

#concrete_index(entry, k) ⇒ Object (protected)



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/autocolors/colorscheme.rb', line 72

def concrete_index(entry, k)
  return if entry.data[k].is_a? Integer
  primes = entry.data[k].count("'")
  entry.data[k].gsub! "'",''
  if entry.data[k] =~ /^(\d+)$/ # Direct index
    if primes > 0
      entry.data[k] = new_color(Integer($1), primes, entry.depth)
    else # Otherwise good to go
      entry.data[k] = Integer($1)
    end
  elsif entry.data[k] =~ /^<$/ # Inherit from parent
    if entry.parent.nil?
      $stderr.puts "Mapping refers to parent without having a parent"
      exit(2)
    else
      concrete_index(entry.parent, k)
      if primes == 0 # Directly inherit
        entry.data[k] = entry.parent.data[k]
      else # Modify a bit
        entry.data[k] = new_color(entry.parent.data[k], primes, entry.depth)
      end
    end
  end
end

#concrete_lvl(entry, k) ⇒ Object (protected)



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/autocolors/colorscheme.rb', line 97

def concrete_lvl(entry, k)
  return if entry.data[k].is_a? Integer
  c_parent = entry.data[k].count('<')
  c_plus   = entry.data[k].count('+')
  c_minus  = entry.data[k].count('-')
  c_neut   = entry.data[k].count('~')
  val = 0
  if c_parent == 0
    val = 3 - c_minus + c_plus
  else
    concrete_lvl(entry.parent, k)
    offset = entry.parent.data[k]
    val = offset + c_plus - c_minus
  end
  entry.data[k] = [[val,7].min,0].max
end

#concrete_style(entry) ⇒ Object (protected)



114
115
116
# File 'lib/autocolors/colorscheme.rb', line 114

def concrete_style(entry)
  entry.data[:styles] = 'NONE'
end

#dark_i(idx) ⇒ Object (protected)



118
# File 'lib/autocolors/colorscheme.rb', line 118

def dark_i(idx)  @intensity[idx]     end

#dark_s(idx) ⇒ Object (protected)



120
# File 'lib/autocolors/colorscheme.rb', line 120

def dark_s(idx)  @fcolor[idx]        end

#do_concrete_mappingObject (protected)



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/autocolors/colorscheme.rb', line 50

def do_concrete_mapping
  @mapping = Marshal.load(Marshal.dump(MAPPING))
  @mapping.entries.each do |name, entry|
    [:fg_idx, :bg_idx].each{|k| concrete_index(entry,k)}
    [:fg_intensity, :fg_saturation, :bg_intensity, :bg_saturation].each{|k| concrete_lvl(entry,k)}
    concrete_style(entry)
  end

  @mapping.entries.each do |name, entry|
    ldat = entry.data.dup
    ddat = entry.data.dup
    fg_a, fg_b, _ = @base_colors[ldat[:fg_idx]]
    bg_a, bg_b, _ = @base_colors[ldat[:bg_idx]]
    ldat[:fg] = lab(light_i(ldat[:fg_intensity]-1), light_s(ldat[:fg_saturation]+1), fg_a, fg_b)
    ldat[:bg] = lab(light_i(ldat[:bg_intensity]), light_s(ldat[:bg_saturation]), bg_a, bg_b, false)
    ddat[:fg] = lab( dark_i(ldat[:fg_intensity]),  dark_s(ldat[:fg_saturation]), fg_a, fg_b)
    ddat[:bg] = lab( dark_i(ldat[:bg_intensity]),  dark_s(ldat[:bg_saturation]), bg_a, bg_b, false)
    @dark[name] = ddat
    @light[name] = ldat
  end
end

#generateObject



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/autocolors/colorscheme.rb', line 17

def generate
  # OVERALL CONTRAST: Value between 0.75 and 1.0 used to contract/spread
  # out intensity values.
  @contrast =     nrand(0.95, 0.1, 0.75,  1.1)
  # OVERALL CHROMACITY: Value between 0.0 and 1.0 used to
  # contract/intensify colorfulness
  @chromacity =   nrand(0.8,  0.3,  0.0,  1.0)
  # OVERALL COLORFULNESS: Value between 0.3 and 1.0 determining how many
  # hues overall end up in the colorscheme
  @colorfulness = nrand(0.8,  0.4,  0.5,  1.0)

  @intensity = rand_seq(0.0, 1.0, 8, @contrast).map{|i| simplelogit(i) * 105}
  @fcolor = (0..7).map {|i| i.to_f / 7.0 * 100.0 * Math.sqrt(2.0) * @chromacity }

  hues = rand_seq(0.0, 1.0, 10, @colorfulness).shuffle
  @base_colors = hues.map do |h|
    c = Color.new([50, 10, 10])
    c.hue = h
    [c.ca, c.cb, 1]
  end

  require 'pp'
  puts "Intensity"
  pp @intensity
  puts "Chromacity"
  pp @fcolor
  puts "Base Colors"
  pp @base_colors

  do_concrete_mapping
end

#lab(intensity, chroma, a, b, rand_adjust = false) ⇒ Object (protected)



123
124
125
126
127
128
# File 'lib/autocolors/colorscheme.rb', line 123

def lab(intensity, chroma, a, b, rand_adjust=false)
  ivar = rand_adjust ? (@colorfulness * rand * 16) - 8 : 0
  c = Color.new([[[intensity + ivar,0].max,140].min, a, b])
  c.chroma = chroma
  return c
end

#light_i(idx) ⇒ Object (protected)



119
# File 'lib/autocolors/colorscheme.rb', line 119

def light_i(idx) @intensity[[[7 - idx,0].max,7].min] end

#light_s(idx) ⇒ Object (protected)



121
# File 'lib/autocolors/colorscheme.rb', line 121

def light_s(idx) @fcolor[[idx,7].min]        end

#new_color(base_idx, diff_level, depth) ⇒ Object (protected)



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/autocolors/colorscheme.rb', line 130

def new_color(base_idx, diff_level, depth)
  # TODO: Use actual mapping metrics for very even spread
  a, b, count = @base_colors[base_idx]
  c = Color.new([50, a, b])
  current_hue = c.hue
  diff_level = diff_level.to_f  # Usually 1 or 2 - from prime marks
  depth = depth.to_f            # Usually 1, less frequently 2, 3, to 5...
  count = count.to_f            # How many others already based off of the same parent
  direction = rand(2) == 1 ? -1.0 : 1.0
  maxdiff = @colorfulness * (1.0 / @base_colors.size) * 1.5 # allocated roughly per major color group
  cdiff = direction * maxdiff / (depth+0.5) * count * 2.5 * diff_level
  cdiff += current_hue
  cdiff = 1.0 + cdiff if cdiff < 0.0
  cdiff = cdiff - 1.0 if cdiff > 1.0
  c.hue = cdiff
  new_idx = @base_colors.size
  @base_colors[new_idx] = [c.ca, c.cb, 1]
  @base_colors[base_idx][2] += 1
  return new_idx
end

#new_color_old(base_idx, diff_level, depth) ⇒ Object (protected)



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/autocolors/colorscheme.rb', line 151

def new_color_old(base_idx, diff_level, depth)
  a,b,count = @base_colors[base_idx]
  base_diff = (diff_level.to_f + 1.0) * 4.0 / ((depth.to_f + 1.0) / 2.0) * count.to_f
  a_dir = rand(2) == 1 ? -1.0 : 1.0
  b_dir = rand(2) == 1 ? -1.0 : 1.0
  a_p = a + (base_diff * a_dir)
  b_p = b + (base_diff * b_dir)
  new_idx = @base_colors.size
  @base_colors[new_idx] = [a_p, b_p, 1]
  @base_colors[base_idx][2] += 1
  return new_idx
end

#nrand(mean = 0, stddev = nil, floor = nil, ceil = nil) ⇒ Object (protected)



167
168
169
170
171
172
173
174
175
176
# File 'lib/autocolors/colorscheme.rb', line 167

def nrand(mean=0, stddev=nil, floor=nil, ceil=nil)
  theta = 2 * Math::PI * rand
  rho = Math.sqrt(-2 * Math.log(1 - rand))
  scale = stddev * rho
  res = (rand >= 0.5) ? Math.cos(theta) : Math.sin(theta)
  res = mean.to_f + scale * res
  res = floor if (! floor.nil?) && (res < floor)
  res = ceil  if (! ceil.nil?) && (res > ceil)
  return res
end

#nrand_colorObject (protected)



165
# File 'lib/autocolors/colorscheme.rb', line 165

def nrand_color; nrand(0.0, 40.0, -120.0, 120.0) end

#rand_colorObject (protected)



164
# File 'lib/autocolors/colorscheme.rb', line 164

def rand_color; rand(180) - 90 end

#rand_seq(min, max, points, contraction = 1.0) ⇒ Object (protected)



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/autocolors/colorscheme.rb', line 178

def rand_seq(min, max, points, contraction=1.0)
  max = max.to_f; min = min.to_f; contraction = contraction.to_f
  spread = max - min
  base_step = spread / (points.to_f - 1.0) * contraction
  partial = base_step / 3.0
  curr = 0.0
  res = [curr]
  while res.size < points
    curr += nrand(base_step, partial / 2.0, base_step - (partial), base_step + (partial))
    res << curr
  end
  if curr > spread
    factor = spread / curr
    res.map!{|v| factor * v}
  else
    offset = (spread - curr) * rand
    res.map!{|v| offset + v}
  end
  return res.map{|v| v + min}
end

#simplelogit(x) ⇒ Object (protected)

(inverse s-curve, steepest on edges)



200
201
202
203
204
# File 'lib/autocolors/colorscheme.rb', line 200

def simplelogit(x)
  return 0.0 if x <= 0.0
  return 1.0 if x >= 1.0
  [[Math.log(x.to_f / (1.0 - x.to_f)) / Math.log(Math::E ** 4.5) + 0.5,0.0].max,1.0].min
end