redgreenblue 0.7.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9dea62bd74822100097e661284f2d44164a1be23ee0f81acdc778c182f0939ec
4
- data.tar.gz: baa4c5ab0b3a367841ee18d94131ad4077661d515edb29939ed661175b479ee1
3
+ metadata.gz: 28b5f73bd443540e199b93625df5c7447491b502573abe5aa14089f895fd2700
4
+ data.tar.gz: c0a3c77946d5a4d0e6c43d5769385a6c5b72df52c9f7a4cfb52d09bdc50a5153
5
5
  SHA512:
6
- metadata.gz: 81808e69a0aadb6ea54cb291fd682c1949e2178df1c31954256167552f12b990ef825561689a958cb533144c25d31852785ebcebe9d07ddb5684b3b0dfcd26ba
7
- data.tar.gz: 62370184d5b291081bd8e7a1f698647d961fd4c98525677821950f59e58e5536473d5f4c0e87da53109866e875caf94fc578fdf14d4de602d50fd0085d4fe6d9
6
+ metadata.gz: 20b4959b4e82fdf67b48dbff3a0c82713a960c56a5bed7e32a78782cab40f1fb9d0f71c2d2fe93cef887a3c97a2590133a85d68b8d57912d7ec0bc4a0817febe
7
+ data.tar.gz: bdc13584341d62b90586cf768cd114079e223e64424e0e9e6c24e4398ae74746f2530ed7e2c7540071cf99a4df4afe3a2e7bdadd7c2d0d3dbc6e5d595047d7f7
@@ -1,24 +1,30 @@
1
1
  class RGB
2
2
  end
3
3
 
4
- require 'redgreenblue/version'
4
+ %w(
5
5
 
6
- require 'redgreenblue/base'
6
+ version
7
7
 
8
- require 'redgreenblue/24bit'
9
- require 'redgreenblue/48bit'
10
- require 'redgreenblue/hex'
8
+ base
11
9
 
12
- require 'redgreenblue/hsl_hsv'
10
+ 24bit 48bit hex int
13
11
 
14
- require 'redgreenblue/lazy'
15
- require 'redgreenblue/nice'
12
+ hsl hsv hsb
16
13
 
17
- require 'redgreenblue/rgb565'
18
- require 'redgreenblue/bgr24bit'
19
- require 'redgreenblue/gif'
14
+ ostwald
20
15
 
21
- require 'redgreenblue/misc'
22
- require 'redgreenblue/random'
16
+ gamma
23
17
 
24
- require 'redgreenblue/os'
18
+ cie_1931 cie_1976
19
+
20
+ name
21
+
22
+ inspect view lazy
23
+
24
+ rgb565 bgr24bit gif terminal web gpl
25
+
26
+ mix misc random
27
+
28
+ os
29
+
30
+ ).each { |m| require "redgreenblue/#{m}" }
@@ -51,4 +51,30 @@ class RGB
51
51
  c
52
52
  end
53
53
 
54
+ # Sets the red, green, and blue values to those of the nearest 24-bit color.
55
+ def snap!
56
+ self.rgb = rgb
57
+ self
58
+ end
59
+
60
+ # Creates a new RGB object containing the nearest 24-bit color.
61
+ def snap
62
+ RGB.rgb rgb
63
+ end
64
+
65
+ # Calls the given block for each 24-bit RGB color (from black to white), passing the color as an RGB object.
66
+ #
67
+ # Returns the number of iterations.
68
+ def self.each_24bit_color
69
+ range = 0..255
70
+ range.each do |r|
71
+ range.each do |g|
72
+ range.each do |b|
73
+ yield self.rgb(r,g,b)
74
+ end
75
+ end
76
+ end
77
+ range.size ** 3
78
+ end
79
+
54
80
  end
@@ -1,41 +1,73 @@
1
1
  class RGB
2
2
 
3
- attr_reader :red, :green, :blue
4
-
5
3
  def initialize(*a)
6
4
  self.values = a.any? ? a : [ 0.5, 0.5, 0.5 ]
7
5
  end
8
6
 
9
- def red=(n)
10
- @red = limit(n)
7
+ # Returns the color space.
8
+ #
9
+ # Currently always 'sRGB'.
10
+ def color_space
11
+ 'sRGB'
12
+ end
13
+
14
+ # Returns the red component as a value between 0 and 1.
15
+ def red
16
+ @red
17
+ end
18
+
19
+ # Returns the green component as a value between 0 and 1.
20
+ def green
21
+ @green
22
+ end
23
+
24
+ # Returns the blue component as a value between 0 and 1.
25
+ def blue
26
+ @blue
27
+ end
28
+
29
+ # Sets the red component to a value between 0 and 1.
30
+ #
31
+ # Values outside the range 0..1 will be clipped.
32
+ def red=(value)
33
+ @red = limit(value)
11
34
  end
12
35
 
13
- def green=(n)
14
- @green = limit(n)
36
+ # Sets the green component to a value between 0 and 1.
37
+ #
38
+ # Values outside the range 0..1 will be clipped.
39
+ def green=(value)
40
+ @green = limit(value)
15
41
  end
16
42
 
17
- def blue=(n)
18
- @blue = limit(n)
43
+ # Sets the blue component to a value between 0 and 1.
44
+ #
45
+ # Values outside the range 0..1 will be clipped.
46
+ def blue=(value)
47
+ @blue = limit(value)
19
48
  end
20
49
 
50
+ # Returns the red, green, and blue components as three values between 0 and 1.
21
51
  def values
22
52
  [ red, green, blue ]
23
53
  end
24
54
 
25
55
  alias to_a values
26
56
 
57
+ # Sets the red, green, and blue components using three values between 0 and 1.
58
+ #
59
+ # Values outside the range 0..1 will be clipped.
27
60
  def values=(*a)
28
61
  self.red, self.green, self.blue = a.flatten
29
62
  end
30
63
 
31
- # Returns true if this object and an other object represent exactly the same color. Otherwise returns false.
64
+ # Returns true if this object and another object represent exactly the same color. Otherwise returns false.
32
65
  def ==(other)
33
66
  ( self.class == other.class ) && ( self.values == other.values )
34
67
  end
35
68
 
36
- # Returns a sorted hash of 3 key/value pairs
37
- # for red, green and blue,
38
- # sorted in order of decreasing value
69
+ # Returns a sorted hash of 3 key/value pairs for red, green, and blue,
70
+ # sorted in order of decreasing value.
39
71
  def to_h
40
72
  ([:red, :green, :blue].zip values).sort_by {
41
73
  |k,v| [-v,[:red, :green, :blue].index(k)]
@@ -44,7 +76,7 @@ class RGB
44
76
 
45
77
  private
46
78
 
47
- # limit to 0..1
79
+ # limits to range 0..1
48
80
  def limit(n)
49
81
  n <= 0 ? 0.0 : n >= 1 ? 1.0 : n
50
82
  end
@@ -1,16 +1,16 @@
1
1
  class RGB
2
2
 
3
- # bgr 24-bit methods (as used by BMP bitmaps)
4
-
3
+ # Returns a 3-byte string containing the object's color in BGR24 format.
5
4
  def bgr24
6
5
  [b, g, r].pack('C3')
7
6
  end
8
7
 
9
- def bgr24=(s)
10
- self.b, self.g, self.r = s.unpack('C3')
8
+ # Sets red, green, and blue using BGR24 data (a 3-byte string).
9
+ def bgr24=(bgr_string)
10
+ self.b, self.g, self.r = bgr_string.unpack('C3')
11
11
  end
12
12
 
13
- # factory method
13
+ # Creates a new RGB object from BGR24 data (a 3-byte string).
14
14
  def self.bgr24(bgr)
15
15
  c = self.new
16
16
  c.bgr24 = bgr
@@ -0,0 +1,51 @@
1
+ class RGB
2
+
3
+ # Returns CIE 1931 XYZ values for the RGB object.
4
+ #
5
+ # Based on:
6
+ # - http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
7
+ # - https://en.wikipedia.org/wiki/CIE_1931_color_space
8
+ # sRGB to XYZ matrix for D65 reference white calculated with Javascript by Bruce Lindbloom:
9
+ # - http://www.brucelindbloom.com/ColorCalculator.html
10
+ def cie_xyz(round: true)
11
+ r, g, b = linear_values
12
+
13
+ [
14
+
15
+ r * 0.4124_5643_9090 + g * 0.3575_7607_7644 + b * 0.1804_3748_3266,
16
+ r * 0.2126_7285_1406 + g * 0.7151_5215_5288 + b * 0.0721_7499_3307,
17
+ r * 0.0193_3389_5582 + g * 0.1191_9202_5881 + b * 0.9503_0407_8536
18
+
19
+ ].map { |v| round ? v.round(8) : v }
20
+ end
21
+
22
+ alias xyz cie_xyz
23
+
24
+ # Returns CIE 1931 xyY values for the RGB object.
25
+ #
26
+ # Based on:
27
+ # - https://en.wikipedia.org/wiki/CIE_1931_color_space
28
+ # - http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_xyY.html
29
+ # - https://ninedegreesbelow.com/photography/xyz-rgb.html
30
+ def cie_xyy
31
+ x, y, z = cie_xyz(round: false)
32
+
33
+ [
34
+
35
+ x / ( x + y + z ),
36
+ y / ( x + y + z ),
37
+ y
38
+
39
+ ].map { |v| v.round(8) }
40
+ end
41
+
42
+ alias xyy cie_xyy
43
+
44
+ # Returns CIE 1931 xy values for the RGB object.
45
+ def cie_xy
46
+ cie_xyy[0..1]
47
+ end
48
+
49
+ alias xy cie_xy
50
+
51
+ end
@@ -0,0 +1,105 @@
1
+ class RGB
2
+
3
+ # Returns CIE 1976 L*a*b* (CIELAB) values for the RGB object.
4
+ def cie_lab(round: true)
5
+ cie_lab_luv(round: round, type: :lab)
6
+ end
7
+
8
+ alias lab cie_lab
9
+
10
+ # Returns CIE 1976 LCHab values for the RGB object, derived from L*a*b* (CIELAB).
11
+ #
12
+ # When C is 0, H is nil.
13
+ def cie_lch_ab
14
+ cie_lch_ab_uv(type: :lab)
15
+ end
16
+
17
+ # Returns CIE 1976 L*u*v* (CIELUV) values for the RGB object.
18
+ def cie_luv(round: true)
19
+ cie_lab_luv(round: round, type: :luv)
20
+ end
21
+
22
+ alias luv cie_luv
23
+
24
+ # Returns CIE 1976 LCHuv values for the RGB object, derived from L*u*v* (CIELUV).
25
+ #
26
+ # When C is 0, H is nil.
27
+ def cie_lch_uv
28
+ cie_lch_ab_uv(type: :luv)
29
+ end
30
+
31
+ # Returns the object's color distance from another RGB object, according to the CIE 1976 delta E formula.
32
+ #
33
+ # Based on:
34
+ # - http://www.brucelindbloom.com/Eqn_DeltaE_CIE76.html
35
+ # - https://en.wikipedia.org/wiki/Color_difference
36
+ def delta_e_cie_1976(another)
37
+ l , a , b = cie_lab(round: false)
38
+ l2, a2, b2 = another.cie_lab(round: false)
39
+
40
+ Math.sqrt( (l - l2) ** 2 + (a - a2) ** 2 + (b - b2) ** 2 ).round(8)
41
+ end
42
+
43
+ alias de76 delta_e_cie_1976
44
+
45
+ private
46
+
47
+ # Returns either CIE 1976 L*a*b* (CIELAB) or CIE 1976 L*u*v* (CIELUV) values for the RGB object.
48
+ #
49
+ # L*a*b* formula based on:
50
+ # - http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html
51
+ # - https://en.wikipedia.org/wiki/CIELAB_color_space
52
+ # With peeking at:
53
+ # - https://github.com/halostatue/color/blob/master/lib/color/rgb.rb
54
+ #
55
+ # L*u*v* formula based on:
56
+ # - http://www.brucelindbloom.com/Eqn_XYZ_to_Luv.html
57
+ # - https://en.wikipedia.org/wiki/CIELUV
58
+ def cie_lab_luv(round: true, type: :lab)
59
+ x , y , z = cie_xyz(round: false)
60
+ xr, yr, zr = RGB.white.cie_xyz(round: false)
61
+
62
+ f = [ x / xr, y / yr, z / zr ].map { |v|
63
+ if v > ( 216.0 / 24389 )
64
+ v ** ( 1.0 / 3 ) # cube root
65
+ else
66
+ ( 24389.0 / 27 * v + 16 ) / 116.0
67
+ end
68
+ }
69
+
70
+ if type == :luv
71
+ [
72
+ l = 116 * f[1] - 16,
73
+ 13 * l * ( ( 4 * x ) / ( x + 15 * y + 3 * z ) - ( 4 * xr ) / ( xr + 15 * yr + 3 * zr ) ),
74
+ 13 * l * ( ( 9 * y ) / ( x + 15 * y + 3 * z ) - ( 9 * yr ) / ( xr + 15 * yr + 3 * zr ) )
75
+ ]
76
+ else
77
+ [
78
+ 116 * f[1] - 16,
79
+ 500 * (f[0] - f[1]),
80
+ 200 * (f[1] - f[2])
81
+ ]
82
+ end.map { |v| v.nan? ? 0.0 : ( round ? v.round(8) : v ) }
83
+ end
84
+
85
+ # Returns either CIE 1976 LCHab or CIE 1976 LCHuv values for the RGB object.
86
+ #
87
+ # Based on:
88
+ # - http://www.brucelindbloom.com/Eqn_Lab_to_LCH.html
89
+ # - http://www.brucelindbloom.com/Eqn_Luv_to_LCH.html
90
+ # - https://en.wikipedia.org/wiki/CIELAB_color_space
91
+ def cie_lch_ab_uv(type: :lab)
92
+ if type == :luv
93
+ l, v1, v2 = cie_luv(round: false)
94
+ else
95
+ l, v1, v2 = cie_lab(round: false)
96
+ end
97
+
98
+ [
99
+ l.round(8),
100
+ c = ( ( v1 ** 2 + v2 ** 2) ** ( 1.0 / 2 ) ).round(8),
101
+ c == 0 ? nil : ( Math.atan2(v2, v1) * 180.0 / Math::PI ).modulo(360).round(8)
102
+ ]
103
+ end
104
+
105
+ end
@@ -0,0 +1,44 @@
1
+ class RGB
2
+
3
+ # Returns gamma-expanded (inverse-companded) RGB values for the object (three values between 0 and 1).
4
+ #
5
+ # Based on:
6
+ # - https://en.wikipedia.org/wiki/SRGB
7
+ # - http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html
8
+ # - https://entropymine.com/imageworsener/srgbformula/
9
+ def linear_values
10
+ if color_space == 'sRGB'
11
+ values.map { |v|
12
+ if v <= 0.04045
13
+ v / 12.92
14
+ else
15
+ ( ( v + 0.055 ) / 1.055 ) ** 2.4
16
+ end
17
+ }
18
+ else
19
+ raise "can not compute gamma for color space '#{color_space}'"
20
+ end
21
+ end
22
+
23
+ # Sets the object's RGB values using three linear RGB values, each between 0 and 1.
24
+ # Linear values will be converted to the object's gamma (gamma-companded).
25
+ #
26
+ # Based on:
27
+ # - https://en.wikipedia.org/wiki/SRGB
28
+ # - http://www.brucelindbloom.com/Eqn_XYZ_to_RGB.html
29
+ # - https://entropymine.com/imageworsener/srgbformula/
30
+ def linear_values=(*a)
31
+ if color_space == 'sRGB'
32
+ self.values = a.flatten.map { |v|
33
+ if v <= 0.0031308
34
+ v * 12.92
35
+ else
36
+ 1.055 * ( v ** ( 1/2.4 ) ) - 0.055
37
+ end
38
+ }.map { |v| v.round(9) }
39
+ else
40
+ raise "can not compute gamma for color space '#{color_space}'"
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,91 @@
1
+ class RGB
2
+
3
+ #----------------------------------------------------------------------#
4
+ # Class Methods #
5
+ #----------------------------------------------------------------------#
6
+
7
+ class << self
8
+
9
+ # Creates a new RGB object from a line of gpl (Gimp color palette) input. Returns nil if not successful.
10
+ #
11
+ # @example
12
+ # RGB.gpl "255 153 204\tpink"
13
+ def gpl(line)
14
+ if line.chomp.match( /^\s*(?<r>\d{1,3})\s+(?<g>\d{1,3})\s+(?<b>\d{1,3})(\s+(?<name>.*))?/ )
15
+ color = RGB.rgb $~[:r].to_i, $~[:g].to_i, $~[:b].to_i
16
+ color.name = $~[:name] if $~[:name]
17
+ color
18
+ else
19
+ nil
20
+ end
21
+ end
22
+
23
+ # Loads a gpl (Gimp color palette) source and returns an array of RGB objects.
24
+ #
25
+ # Options:
26
+ # - file: Path to a .gpl file to be loaded.
27
+ # - url: URL for a .gpl source to be loaded.
28
+ # - compact: Defaults to true. If set to false, returns nil for each line that can not be parsed to an RGB color.
29
+ # - freeze: Defaults to false. If set to true, returns a frozen array of frozen objects.
30
+ #
31
+ # @example String
32
+ # RGB.load_gpl "255 0 0\tred\n255 153 204\tpink\n"
33
+ # @example File
34
+ # RGB.load_gpl file: '/path/to/palette.gpl'
35
+ # RGB.load_gpl file: '/path/to/palette.gpl', compact: false
36
+ # @example URL
37
+ # RGB.load_gpl url: 'https://lospec.com/palette-list/yuko-tomita-time.gpl'
38
+ def load_gpl(source=nil, file: nil, url: nil, compact: true, freeze: false)
39
+
40
+ if ! source
41
+ if file
42
+ source = File.open file
43
+ elsif url
44
+ require 'open-uri'
45
+ source = URI.open url
46
+ end
47
+ end
48
+
49
+ if source.respond_to? :each_line
50
+ list = source.each_line.map { |line| RGB.gpl(line) }
51
+
52
+ if compact
53
+ list.compact!
54
+ end
55
+
56
+ if freeze
57
+ list.freeze
58
+ list.each &:freeze
59
+ end
60
+
61
+ list
62
+ else
63
+ raise ArgumentError, 'Not a valid source'
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Reverse-engineered from:
70
+ # - https://github.com/GNOME/gimp/blob/5d79fba8238a27b8691556489898d33b3fa0dda0/app/core/gimppalette-load.c
71
+ def gpl_header(name, columns=nil)
72
+ "GIMP Palette\n" +
73
+ "Name: #{name}\n" +
74
+ ( columns ? "Columns: #{columns}\n" : '' )
75
+ end
76
+
77
+ end
78
+
79
+ #----------------------------------------------------------------------#
80
+ # Instance Methods #
81
+ #----------------------------------------------------------------------#
82
+
83
+ # Returns the color in the format used in .gpl files (Gimp color palettes), including its name (if present).
84
+ #
85
+ # You can optionally supply a name as argument.
86
+ def gpl(gpl_name=name)
87
+ ( "%3d %3d %3d" % rgb ) +
88
+ ( gpl_name.to_s.size != 0 ? "\t#{gpl_name}" : '' )
89
+ end
90
+
91
+ end