transition_helpers.rb 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. require 'chroma'
  2. module TransitionHelpers
  3. module Defaults
  4. PERIOD = 500
  5. NUM_PERIODS = 20
  6. DURATION = PERIOD * NUM_PERIODS
  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. "Transition was a different length than expected.\n" <<
  42. " Expected : #{expected}\n" <<
  43. " Seen : #{seen}"
  44. expected.zip(seen).each_with_index do |x, i|
  45. a, b = x
  46. diff = (a - b).abs
  47. expect(diff).to be <= allowed_variation, generate_msg.call(a, b, i)
  48. end
  49. end
  50. def rgb_to_hs(*color)
  51. if color.length > 1
  52. r, g, b = color
  53. else
  54. r, g, b = coerce_color(color.first)
  55. end
  56. hsv = Chroma::Converters::HsvConverter.convert_rgb(Chroma::ColorModes::Rgb.new(r, g, b))
  57. { hue: hsv.h.round, saturation: (100*hsv.s).round }
  58. end
  59. def coerce_color(c)
  60. c.split(',').map(&:to_i) unless c.is_a?(Array)
  61. end
  62. def calculate_color_transition_steps(start_color:, end_color:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
  63. start_color = coerce_color(start_color)
  64. end_color = coerce_color(end_color)
  65. part_transitions = start_color.zip(end_color).map do |c|
  66. s, e = c
  67. calculate_transition_steps(start_value: s, end_value: e, duration: duration, period: period, num_periods: num_periods)
  68. end
  69. # If some colors don't transition, they'll stay at the same value while others move.
  70. # Turn this: [[1,2,3], [0], [4,5,6]]
  71. # Into this: [[1,2,3], [0,0,0], [4,5,6]]
  72. longest = part_transitions.max_by { |x| x.length }.length
  73. part_transitions.map! { |x| x + [x.last]*(longest-x.length) }
  74. # Zip individual parts into 3-tuples
  75. # Turn this: [[1,2,3], [0,0,0], [4,5,6]]
  76. # Into this: [[1,0,4], [2,0,5], [3,0,6]]
  77. transition_colors = part_transitions.first.zip(*part_transitions[1..part_transitions.length])
  78. # Undergo the RGB -> HSV w/ value = 100
  79. transition_colors.map do |x|
  80. r, g, b = x
  81. rgb_to_hs(r, g, b)
  82. end
  83. end
  84. def calculate_transition_steps(start_value:, end_value:, duration: nil, period: nil, num_periods: Defaults::NUM_PERIODS)
  85. if !duration.nil? || !period.nil?
  86. period ||= Defaults::PERIOD
  87. duration ||= Defaults::DURATION
  88. num_periods = [1, (duration / period.to_f).ceil].max
  89. end
  90. diff = end_value - start_value
  91. step_size = [1, (diff.abs / num_periods.to_f).ceil].max
  92. step_size = -step_size if end_value < start_value
  93. steps = []
  94. val = start_value
  95. while val != end_value
  96. steps << val
  97. if (end_value - val).abs < step_size.abs
  98. val += (end_value - val)
  99. else
  100. val += step_size
  101. end
  102. end
  103. steps << end_value
  104. steps
  105. end
  106. end