redgreenblue 0.7.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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