class AutoColors::ColorScheme

Attributes

contrast[RW]
dark[RW]
light[RW]
name[RW]
saturation[RW]
seed[RW]

Public Class Methods

new(name=nil) click to toggle source
# 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

Public Instance Methods

generate() click to toggle source
# 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

Protected Instance Methods

concrete_index(entry, k) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/autocolors/colorscheme.rb, line 114
def concrete_style(entry)
  entry.data[:styles] = 'NONE'
end
dark_i(idx) click to toggle source
# File lib/autocolors/colorscheme.rb, line 118
def dark_i(idx)  @intensity[idx]     end
dark_s(idx) click to toggle source
# File lib/autocolors/colorscheme.rb, line 120
def dark_s(idx)  @fcolor[idx]        end
do_concrete_mapping() click to toggle source
# 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
lab(intensity, chroma, a, b, rand_adjust=false) click to toggle source
# 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) click to toggle source
# File lib/autocolors/colorscheme.rb, line 119
def light_i(idx) @intensity[[[7 - idx,0].max,7].min] end
light_s(idx) click to toggle source
# File lib/autocolors/colorscheme.rb, line 121
def light_s(idx) @fcolor[[idx,7].min]        end
new_color(base_idx, diff_level, depth) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_color() click to toggle source
# File lib/autocolors/colorscheme.rb, line 165
def nrand_color; nrand(0.0, 40.0, -120.0, 120.0) end
rand_color() click to toggle source
# File lib/autocolors/colorscheme.rb, line 164
def rand_color; rand(180) - 90 end
rand_seq(min, max, points, contraction=1.0) click to toggle source
# 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) click to toggle source

(inverse s-curve, steepest on edges)

# 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