lib/pry-theme/rgb.rb in pry-theme-0.1.3 vs lib/pry-theme/rgb.rb in pry-theme-0.2.0

- old
+ new

@@ -1,24 +1,249 @@ module PryTheme - module RGB + # @since 0.2.0 + # @api private + # + # Represents an RGB colour. It's possible to convert an RGB instance into + # {HEX} or {TERM} colours. However, this conversion is half-duplex. If an RGB + # instance gets converted to {TERM} format, there is a high chance that it + # will be approximated to fit in range of colour model (colour model can be + # set via an argument of the conversion method). This class validates its + # input (you won't see malformed of nonexistent RGB colours). + # + # @example Conversion to HEX + # RGB.new([0, 0, 0]).to_hex #=> (HEX: #000000) + # @example Conversion to TERM + # RGB.new([0, 0, 0]).to_term(8) #=> (TERM-8: 0) + # + # # Approximation. + # RGB.new([254, 244, 231]).to_term(8) #=> (TERM-8: 7) + class RGB - SYSTEM = [ - [0x00, 0x00, 0x00], [0x80, 0x00, 0x00], [0x00, 0x80, 0x00], - [0x80, 0x80, 0x00], [0x00, 0x00, 0x80], [0x80, 0x00, 0x80], - [0x00, 0x80, 0x80], [0xc0, 0xc0, 0xc0], [0x80, 0x80, 0x80], - [0xff, 0x00, 0x00], [0x00, 0xff, 0x00], [0xff, 0xff, 0x00], - [0x00, 0x00, 0xff], [0xff, 0x00, 0xff], [0x00, 0xff, 0xff], - [0xff, 0xff, 0xff] + # 8 colours. For the standard GNU/Linux terminal emulator. + LINUX = [ + [ 0, 0, 0], [128, 0, 0], [ 0, 128, 0], + [128, 128, 0], [ 0, 0, 128], [128, 0, 128], + [ 0, 128, 128], [192, 192, 192] ] + # 16 colours. For cmd.exe on Windows and other miserable terminals. + SYSTEM = LINUX + [ + [128, 128, 128], [255, 0, 0], [ 0, 255, 0], [255, 255, 0], + [ 0, 0, 255], [255, 0, 255], [ 0, 255, 255], [255, 255, 255] + ] + + # The next 216 colours. For men. COLORS = [ - [0, 0, 0], [0, 0, 95], [0, 0, 135], [0, 0, 175], [0, 0, 215], [0, 0, 255], [0, 95, 0], [0, 95, 95], [0, 95, 135], [0, 95, 175], [0, 95, 215], [0, 95, 255], [0, 135, 0], [0, 135, 95], [0, 135, 135], [0, 135, 175], [0, 135, 215], [0, 135, 255], [0, 175, 0], [0, 175, 95], [0, 175, 135], [0, 175, 175], [0, 175, 215], [0, 175, 255], [0, 215, 0], [0, 215, 95], [0, 215, 135], [0, 215, 175], [0, 215, 215], [0, 215, 255], [0, 255, 0], [0, 255, 95], [0, 255, 135], [0, 255, 175], [0, 255, 215], [0, 255, 255], [95, 0, 0], [95, 0, 95], [95, 0, 135], [95, 0, 175], [95, 0, 215], [95, 0, 255], [95, 95, 0], [95, 95, 95], [95, 95, 135], [95, 95, 175], [95, 95, 215], [95, 95, 255], [95, 135, 0], [95, 135, 95], [95, 135, 135], [95, 135, 175], [95, 135, 215], [95, 135, 255], [95, 175, 0], [95, 175, 95], [95, 175, 135], [95, 175, 175], [95, 175, 215], [95, 175, 255], [95, 215, 0], [95, 215, 95], [95, 215, 135], [95, 215, 175], [95, 215, 215], [95, 215, 255], [95, 255, 0], [95, 255, 95], [95, 255, 135], [95, 255, 175], [95, 255, 215], [95, 255, 255], [135, 0, 0], [135, 0, 95], [135, 0, 135], [135, 0, 175], [135, 0, 215], [135, 0, 255], [135, 95, 0], [135, 95, 95], [135, 95, 135], [135, 95, 175], [135, 95, 215], [135, 95, 255], [135, 135, 0], [135, 135, 95], [135, 135, 135], [135, 135, 175], [135, 135, 215], [135, 135, 255], [135, 175, 0], [135, 175, 95], [135, 175, 135], [135, 175, 175], [135, 175, 215], [135, 175, 255], [135, 215, 0], [135, 215, 95], [135, 215, 135], [135, 215, 175], [135, 215, 215], [135, 215, 255], [135, 255, 0], [135, 255, 95], [135, 255, 135], [135, 255, 175], [135, 255, 215], [135, 255, 255], [175, 0, 0], [175, 0, 95], [175, 0, 135], [175, 0, 175], [175, 0, 215], [175, 0, 255], [175, 95, 0], [175, 95, 95], [175, 95, 135], [175, 95, 175], [175, 95, 215], [175, 95, 255], [175, 135, 0], [175, 135, 95], [175, 135, 135], [175, 135, 175], [175, 135, 215], [175, 135, 255], [175, 175, 0], [175, 175, 95], [175, 175, 135], [175, 175, 175], [175, 175, 215], [175, 175, 255], [175, 215, 0], [175, 215, 95], [175, 215, 135], [175, 215, 175], [175, 215, 215], [175, 215, 255], [175, 255, 0], [175, 255, 95], [175, 255, 135], [175, 255, 175], [175, 255, 215], [175, 255, 255], [215, 0, 0], [215, 0, 95], [215, 0, 135], [215, 0, 175], [215, 0, 215], [215, 0, 255], [215, 95, 0], [215, 95, 95], [215, 95, 135], [215, 95, 175], [215, 95, 215], [215, 95, 255], [215, 135, 0], [215, 135, 95], [215, 135, 135], [215, 135, 175], [215, 135, 215], [215, 135, 255], [215, 175, 0], [215, 175, 95], [215, 175, 135], [215, 175, 175], [215, 175, 175], [215, 175, 215], [215, 175, 255], [215, 215, 0], [215, 215, 95], [215, 215, 135], [215, 215, 175], [215, 215, 215], [215, 215, 255], [215, 255, 0], [215, 255, 95], [215, 255, 135], [215, 255, 175], [215, 255, 215], [215, 255, 255], [255, 0, 0], [255, 0, 95], [255, 0, 135], [255, 0, 175], [255, 0, 215], [255, 0, 255], [255, 95, 0], [255, 95, 95], [255, 95, 135], [255, 95, 175], [255, 95, 215], [255, 95, 255], [255, 135, 0], [255, 135, 95], [255, 135, 135], [255, 135, 175], [255, 135, 215], [255, 135, 255], [255, 175, 0], [255, 175, 95], [255, 175, 135], [255, 175, 175], [255, 175, 215], [255, 175, 255], [255, 215, 0], [255, 215, 95], [255, 215, 135], [255, 215, 175], [255, 215, 215], [255, 215, 255], [255, 255, 0], [255, 255, 95], [255, 255, 135], [255, 255, 175], [255, 255, 215] + [ 0, 0, 0], [ 0, 0, 95], [ 0, 0, 135], [ 0, 0, 175], + [ 0, 0, 215], [ 0, 0, 255], [ 0, 95, 0], [ 0, 95, 95], + [ 0, 95, 135], [ 0, 95, 175], [ 0, 95, 215], [ 0, 95, 255], + [ 0, 135, 0], [ 0, 135, 95], [ 0, 135, 135], [ 0, 135, 175], + [ 0, 135, 215], [ 0, 135, 255], [ 0, 175, 0], [ 0, 175, 95], + [ 0, 175, 135], [ 0, 175, 175], [ 0, 175, 215], [ 0, 175, 255], + [ 0, 215, 0], [ 0, 215, 95], [ 0, 215, 135], [ 0, 215, 175], + [ 0, 215, 215], [ 0, 215, 255], [ 0, 255, 0], [ 0, 255, 95], + [ 0, 255, 135], [ 0, 255, 175], [ 0, 255, 215], [ 0, 255, 255], + [ 95, 0, 0], [ 95, 0, 95], [ 95, 0, 135], [ 95, 0, 175], + [ 95, 0, 215], [ 95, 0, 255], [ 95, 95, 0], [ 95, 95, 95], + [ 95, 95, 135], [ 95, 95, 175], [ 95, 95, 215], [ 95, 95, 255], + [ 95, 135, 0], [ 95, 135, 95], [ 95, 135, 135], [ 95, 135, 175], + [ 95, 135, 215], [ 95, 135, 255], [ 95, 175, 0], [ 95, 175, 95], + [ 95, 175, 135], [ 95, 175, 175], [ 95, 175, 215], [ 95, 175, 255], + [ 95, 215, 0], [ 95, 215, 95], [ 95, 215, 135], [ 95, 215, 175], + [ 95, 215, 215], [ 95, 215, 255], [ 95, 255, 0], [ 95, 255, 95], + [ 95, 255, 135], [ 95, 255, 175], [ 95, 255, 215], [ 95, 255, 255], + [135, 0, 0], [135, 0, 95], [135, 0, 135], [135, 0, 175], + [135, 0, 215], [135, 0, 255], [135, 95, 0], [135, 95, 95], + [135, 95, 135], [135, 95, 175], [135, 95, 215], [135, 95, 255], + [135, 135, 0], [135, 135, 95], [135, 135, 135], [135, 135, 175], + [135, 135, 215], [135, 135, 255], [135, 175, 0], [135, 175, 95], + [135, 175, 135], [135, 175, 175], [135, 175, 215], [135, 175, 255], + [135, 215, 0], [135, 215, 95], [135, 215, 135], [135, 215, 175], + [135, 215, 215], [135, 215, 255], [135, 255, 0], [135, 255, 95], + [135, 255, 135], [135, 255, 175], [135, 255, 215], [135, 255, 255], + [175, 0, 0], [175, 0, 95], [175, 0, 135], [175, 0, 175], + [175, 0, 215], [175, 0, 255], [175, 95, 0], [175, 95, 95], + [175, 95, 135], [175, 95, 175], [175, 95, 215], [175, 95, 255], + [175, 135, 0], [175, 135, 95], [175, 135, 135], [175, 135, 175], + [175, 135, 215], [175, 135, 255], [175, 175, 0], [175, 175, 95], + [175, 175, 135], [175, 175, 175], [175, 175, 215], [175, 175, 255], + [175, 215, 0], [175, 215, 95], [175, 215, 135], [175, 215, 175], + [175, 215, 215], [175, 215, 255], [175, 255, 0], [175, 255, 95], + [175, 255, 135], [175, 255, 175], [175, 255, 215], [175, 255, 255], + [215, 0, 0], [215, 0, 95], [215, 0, 135], [215, 0, 175], + [215, 0, 215], [215, 0, 255], [215, 95, 0], [215, 95, 95], + [215, 95, 135], [215, 95, 175], [215, 95, 215], [215, 95, 255], + [215, 135, 0], [215, 135, 95], [215, 135, 135], [215, 135, 175], + [215, 135, 215], [215, 135, 255], [215, 175, 0], [215, 175, 95], + [215, 175, 135], [215, 175, 175], [215, 175, 175], [215, 175, 215], + [215, 175, 255], [215, 215, 0], [215, 215, 95], [215, 215, 135], + [215, 215, 175], [215, 215, 215], [215, 215, 255], [215, 255, 0], + [215, 255, 95], [215, 255, 135], [215, 255, 175], [215, 255, 215], + [215, 255, 255], [255, 0, 0], [255, 0, 95], [255, 0, 135], + [255, 0, 175], [255, 0, 215], [255, 0, 255], [255, 95, 0], + [255, 95, 95], [255, 95, 135], [255, 95, 175], [255, 95, 215], + [255, 95, 255], [255, 135, 0], [255, 135, 95], [255, 135, 135], + [255, 135, 175], [255, 135, 215], [255, 135, 255], [255, 175, 0], + [255, 175, 95], [255, 175, 135], [255, 175, 175], [255, 175, 215], + [255, 175, 255], [255, 215, 0], [255, 215, 95], [255, 215, 135], + [255, 215, 175], [255, 215, 215], [255, 215, 255], [255, 255, 0], + [255, 255, 95], [255, 255, 135], [255, 255, 175], [255, 255, 215] ] - GREYSCALE = (0x08..0xee).step(0x0a).map { |v| [v] * 3 } + # The next 16 colours. For zen. + GREYSCALE = (0x08..0xEE).step(0x0A).map { |v| [v] * 3 } - def self.table - SYSTEM + COLORS + GREYSCALE + # Combine everything into a full featured 256 colour RGB model. + TABLE = SYSTEM + COLORS + GREYSCALE + + # The key points that are used to calculate the nearest match of an RGB. + BYTEPOINTS_256 = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] + + # @param [Array<Integer>, String] value a String will be converted to Array + # @raise [TypeError] if the +value+ is neither Array or String + def initialize(value) + @value = + case value + when Array + validate_array(value) + value + when String + validate_array(value = value.scan(/\d+/).map!(&:to_i)) + value + else + raise TypeError, "can't convert #{ value.class } into PryTheme::RGB" + end + end + + # @return [String] + def inspect + "(RGB: #{ to_s })" + end + + # Converts the RGB to a terminal colour equivalent. + # + # @note Accepts the following numbers: 256, 16, 8. + # @param [Integer] color_model + # @raise [ArgumentError] if +color_model+ parameter is incorrect + # @return [TERM] a TERM representation of the RGB + def to_term(color_model = 256) + term = case color_model + when 256 then PryTheme::RGB::TABLE.index(@value) + when 16 then PryTheme::RGB::SYSTEM.index(@value) + when 8 then PryTheme::RGB::LINUX.index(@value) + else raise ArgumentError, + "invalid value for PryTheme::HEX#to_term(): #{ @value }" + end + term = find_among_term_colors(term, color_model) if term.nil? + PryTheme::TERM.new(term, color_model) + end + + # Converts the RGB to a HEX colour equivalent. + # @return [HEX] a HEX representation of the RGB + def to_hex + PryTheme::HEX.new("#%02x%02x%02x" % @value) + end + + # @example + # RGB.new([0, 12, 255]).to_s #=> "0, 12, 255" + # @return [String] + def to_s + @value.join(', ') + end + + # @example + # RGB.new([0, 12, 255]).to_s #=> [0, 12, 255] + # @return [Array<Integer>] + def to_a + @value + end + + # @example + # RGB.new([0, 0, 0]).to_css #=> 'rgb(0, 0, 0)' + # @return [String] + def to_css + "rgb(#{ to_s })" + end + + private + + # Checks whether the +ary+ has correct number of elements and these elements + # are valid RGB numbers. + # + # @param [Array<Integer>] ary + # @raise [ArgumentError] if the +ary+ is invalid + # @return [void] + def validate_array(ary) + correct_size = ary.size.equal?(3) + correct_vals = ary.all?{ |val| val.is_a?(Fixnum) && val.between?(0, 255) } + return true if correct_size && correct_vals + raise ArgumentError, + %|invalid value for PryTheme::RGB#validate_array(): "#{ ary }"| + end + + # Approximates the given +byte+ to a terminal colour value within range of + # 256 colours. + # + # @param [Integer] byte a number between 0 and 255 + # @return [Integer] approximated number + def nearest_term_256(byte) + for i in 0..4 + lower, upper = BYTEPOINTS_256[i], BYTEPOINTS_256[i + 1] + next unless byte.between?(lower, upper) + + distance_from_lower = (lower - byte).abs + distance_from_upper = (upper - byte).abs + closest = distance_from_lower < distance_from_upper ? lower : upper + end + closest + end + + # The same as {#nearest_term_256}, but returns a number beteen 0 and 15. + # + # @note Oh, come on. At least it works! + # @todo use more realistic algorithm. + def nearest_term_16(byte) + byte / 16 + end + + # The same as {#nearest_term_256}, but returns a number beteen 0 and 7. + # + # @note Oh, come on. At least it works! + # @todo use more realistic algorithm. + def nearest_term_8(byte) + byte / 32 + end + + # Finds an approximated +term+ colour among the colour numbers within the + # given +color_model+. + # + # @param [Integer] term a colour to be approximated + # @param [Integer] color_model possible values {#to_term} + # @return [Integer] approximated number, which fits in range of color_model + def find_among_term_colors(term, color_model) + rgb = @value.map { |byte| nearest_term_256(byte) } + term = PryTheme::RGB::TABLE.index(rgb) + approximate(term, color_model) + end + + # Approximates +term+ in correspondence with +color_model+ + # + # @see #nearest_term_16 + # @see #nearest_term_8 + # @param [Integer] term a colour to be approximated + # @param [Integer] color_model possible values {#to_term} + # @return [Integer] approximated number, which fits in range of color_model + def approximate(term, color_model) + needs_approximation = (term > color_model - 1) + + if needs_approximation + case color_model + when 16 then nearest_term_16(term) + when 8 then nearest_term_8(term) + end + else + term + end end end end