| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- require 'chroma'
- module TransitionHelpers
- module Defaults
- DURATION = 4500
- PERIOD = 450
- NUM_PERIODS = 10
- end
- def highlight_value(a, highlight_ix)
- str = a
- .each_with_index
- .map do |x, i|
- i == highlight_ix ? ">>#{x}<<" : x
- end
- .join(', ')
- "[#{str}]"
- end
- def color_transitions_are_equal(expected:, seen:)
- %i(hue saturation).each do |label|
- e = expected.map { |x| x[label] }
- s = seen.map { |x| x[label] }
- transitions_are_equal(expected: e, seen: s, label: label, allowed_variation: label == :saturation ? 5 : 20)
- end
- end
- def transitions_are_equal(expected:, seen:, allowed_variation: 0, label: nil)
- generate_msg = ->(a, b, i) do
- s = "Transition step value"
- if !label.nil?
- s << " for #{label} "
- end
- s << "at index #{i} "
- s << if allowed_variation == 0
- "should be equal to expected value. Expected: #{a}, saw: #{b}."
- else
- "should be within #{allowed_variation} of expected value. Expected: #{a}, saw: #{b}."
- end
- s << " Steps:\n"
- s << " Expected : #{highlight_value(expected, i)},\n"
- s << " Seen : #{highlight_value(seen, i)}"
- end
- expect(expected.length).to eq(seen.length)
- expected.zip(seen).each_with_index do |x, i|
- a, b = x
- diff = (a - b).abs
- expect(diff).to be <= allowed_variation, generate_msg.call(a, b, i)
- end
- end
- def rgb_to_hs(*color)
- if color.length > 1
- r, g, b = color
- else
- r, g, b = coerce_color(color.first)
- end
- hsv = Chroma::Converters::HsvConverter.convert_rgb(Chroma::ColorModes::Rgb.new(r, g, b))
- { hue: hsv.h.round, saturation: (100*hsv.s).round }
- end
- def coerce_color(c)
- c.split(',').map(&:to_i) unless c.is_a?(Array)
- end
- def calculate_color_transition_steps(start_color:, end_color:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
- start_color = coerce_color(start_color)
- end_color = coerce_color(end_color)
- part_transitions = start_color.zip(end_color).map do |c|
- s, e = c
- calculate_transition_steps(start_value: s, end_value: e, duration: duration, period: period, num_periods: num_periods)
- end
- # If some colors don't transition, they'll stay at the same value while others move.
- # Turn this: [[1,2,3], [0], [4,5,6]]
- # Into this: [[1,2,3], [0,0,0], [4,5,6]]
- longest = part_transitions.max_by { |x| x.length }.length
- part_transitions.map! { |x| x + [x.last]*(longest-x.length) }
- # Zip individual parts into 3-tuples
- # Turn this: [[1,2,3], [0,0,0], [4,5,6]]
- # Into this: [[1,0,4], [2,0,5], [3,0,6]]
- transition_colors = part_transitions.first.zip(*part_transitions[1..part_transitions.length])
- # Undergo the RGB -> HSV w/ value = 100
- transition_colors.map do |x|
- r, g, b = x
- rgb_to_hs(r, g, b)
- end
- end
- def calculate_transition_steps(start_value:, end_value:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
- if !duration.nil? || !period.nil?
- period ||= Defaults::PERIOD
- duration ||= Defaults::DURATION
- num_periods = [1, (duration / period.to_f).ceil].max
- end
- diff = end_value - start_value
- step_size = [1, (diff.abs / num_periods.to_f).ceil].max
- step_size = -step_size if end_value < start_value
- steps = []
- val = start_value
- while val != end_value
- steps << val
- if (end_value - val).abs < step_size.abs
- val += (end_value - val)
- else
- val += step_size
- end
- end
- steps << end_value
- steps
- end
- end
|