transition_helpers.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. require 'chroma'
  2. module TransitionHelpers
  3. module Defaults
  4. DURATION = 4500
  5. PERIOD = 450
  6. NUM_PERIODS = 10
  7. end
  8. def highlight_value(a, highlight_ix)
  9. str = a
  10. .each_with_index
  11. .map do |x, i|
  12. i == highlight_ix ? ">>#{x}<<" : x
  13. end
  14. .join(', ')
  15. "[#{str}]"
  16. end
  17. def color_transitions_are_equal(expected:, seen:)
  18. %i(hue saturation).each do |label|
  19. e = expected.map { |x| x[label] }
  20. s = seen.map { |x| x[label] }
  21. transitions_are_equal(expected: e, seen: s, label: label, allowed_variation: label == :saturation ? 5 : 20)
  22. end
  23. end
  24. def transitions_are_equal(expected:, seen:, allowed_variation: 0, label: nil)
  25. generate_msg = ->(a, b, i) do
  26. s = "Transition step value"
  27. if !label.nil?
  28. s << " for #{label} "
  29. end
  30. s << "at index #{i} "
  31. s << if allowed_variation == 0
  32. "should be equal to expected value. Expected: #{a}, saw: #{b}."
  33. else
  34. "should be within #{allowed_variation} of expected value. Expected: #{a}, saw: #{b}."
  35. end
  36. s << " Steps:\n"
  37. s << " Expected : #{highlight_value(expected, i)},\n"
  38. s << " Seen : #{highlight_value(seen, i)}"
  39. end
  40. expect(expected.length).to eq(seen.length)
  41. expected.zip(seen).each_with_index do |x, i|
  42. a, b = x
  43. diff = (a - b).abs
  44. expect(diff).to be <= allowed_variation, generate_msg.call(a, b, i)
  45. end
  46. end
  47. def rgb_to_hs(*color)
  48. if color.length > 1
  49. r, g, b = color
  50. else
  51. r, g, b = coerce_color(color.first)
  52. end
  53. hsv = Chroma::Converters::HsvConverter.convert_rgb(Chroma::ColorModes::Rgb.new(r, g, b))
  54. { hue: hsv.h.round, saturation: (100*hsv.s).round }
  55. end
  56. def coerce_color(c)
  57. c.split(',').map(&:to_i) unless c.is_a?(Array)
  58. end
  59. def calculate_color_transition_steps(start_color:, end_color:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
  60. start_color = coerce_color(start_color)
  61. end_color = coerce_color(end_color)
  62. part_transitions = start_color.zip(end_color).map do |c|
  63. s, e = c
  64. calculate_transition_steps(start_value: s, end_value: e, duration: duration, period: period, num_periods: num_periods)
  65. end
  66. # If some colors don't transition, they'll stay at the same value while others move.
  67. # Turn this: [[1,2,3], [0], [4,5,6]]
  68. # Into this: [[1,2,3], [0,0,0], [4,5,6]]
  69. longest = part_transitions.max_by { |x| x.length }.length
  70. part_transitions.map! { |x| x + [x.last]*(longest-x.length) }
  71. # Zip individual parts into 3-tuples
  72. # Turn this: [[1,2,3], [0,0,0], [4,5,6]]
  73. # Into this: [[1,0,4], [2,0,5], [3,0,6]]
  74. transition_colors = part_transitions.first.zip(*part_transitions[1..part_transitions.length])
  75. # Undergo the RGB -> HSV w/ value = 100
  76. transition_colors.map do |x|
  77. r, g, b = x
  78. rgb_to_hs(r, g, b)
  79. end
  80. end
  81. def calculate_transition_steps(start_value:, end_value:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
  82. if !duration.nil? || !period.nil?
  83. period ||= Defaults::PERIOD
  84. duration ||= Defaults::DURATION
  85. num_periods = [1, (duration / period.to_f).ceil].max
  86. end
  87. diff = end_value - start_value
  88. step_size = [1, (diff.abs / num_periods.to_f).ceil].max
  89. step_size = -step_size if end_value < start_value
  90. steps = []
  91. val = start_value
  92. while val != end_value
  93. steps << val
  94. if (end_value - val).abs < step_size.abs
  95. val += (end_value - val)
  96. else
  97. val += step_size
  98. end
  99. end
  100. steps << end_value
  101. steps
  102. end
  103. end