gradient 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: f318a955895204681e1ee985f8decfb009557eb4
4
- data.tar.gz: 9af5a313c38bf4824dc53c4f1f2346c25cef0f60
3
+ metadata.gz: f55c2143e51c8034863193036c2609ac11007591
4
+ data.tar.gz: e042d481cbd1e8b034b611715709555badd24bb6
5
5
  SHA512:
6
- metadata.gz: 6ca6cd175607983e38b4ca60d30a7a1d16edb94b245408498bddeac41165c4e0dba507ad33919aaad0bed8fdd03038b9f63e6d2b90868ee050f1a9f13c6e3644
7
- data.tar.gz: eb7551719748b8361fb6db2bf8a3195ffa0e322d9b9d435873f9dd82d052aee6a91618eae87493e80dbd847d88eb182fd0cc5db00e8285a925332cb7732c651a
6
+ metadata.gz: 42aa087d8edd0c5faf199beb34ca5eec83a250b0b0fb1cbeea0f65b9bfe73a2bb045e3a8f5907861ba16001c0aa5de6c356e6323530f39517ec0e1a2542b1fda
7
+ data.tar.gz: 07f2d6c7e4f6cec682abc15c8f78306b5530a43015b67488b384c6fd83c391a2f510058eb378d034db3b470d50f3f205d3c3c4c78f41e79add0362ef265a12ac
data/README.md CHANGED
@@ -2,6 +2,52 @@
2
2
  Library for dealing with color gradients in ruby
3
3
 
4
4
  ## Usage
5
+ Gradient works by placing points along two one dimensional planes.
6
+ One for color and one for opacity.
7
+ Start by creating a few points and use them to create a gradient map.
8
+
9
+ ```ruby
10
+ color_points = [
11
+ Gradient::ColorPoint.new(0, Color::RGB.new(30, 87, 153)),
12
+ Gradient::ColorPoint.new(0.49, Color::RGB.new(41, 137, 216)),
13
+ Gradient::ColorPoint.new(0.51, Color::RGB.new(32, 124, 202)),
14
+ Gradient::ColorPoint.new(1, Color::RGB.new(125, 185, 232)),
15
+ ]
16
+
17
+ opacity_points = [
18
+ Gradient::OpacityPoint.new(0, 1),
19
+ Gradient::OpacityPoint.new(0.5, 0),
20
+ Gradient::OpacityPoint.new(1, 1)
21
+ ]
22
+
23
+ gradient = Gradient::Map.new(color_points, opacity_points)
24
+ # => #<Gradient Map #<Point 0 #1e5799ff> #<Point 49.0 #2989d805> #<Point 50.0 #2583d100> #<Point 51.0 #207cca05> #<Point 100 #7db9e8ff>>
25
+ ```
26
+
27
+ ### Convert to CSS
28
+ If you use ruby to serve web content you can use Gradient to convert gradient maps in to [CSS3 Gradients](http://www.w3schools.com/css/css3_gradients.asp)
29
+
30
+ ```ruby
31
+ gradient.to_css
32
+ # => "background:linear-gradient(to right, rgba(30,87,153,1.0) 0%, rgba(41,137,216,0.02) 49%, rgba(37,131,209,0.0) 50%, rgba(32,124,202,0.02) 51%, rgba(125,185,232,1.0) 100%);"
33
+
34
+ gradient.to_css(property: "border-image")
35
+ # => "border-image:linear-gradient(to right, rgba(30,87,153,1.0) 0%, rgba(41,137,216,0.02) 49%, rgba(37,131,209,0.0) 50%, rgba(32,124,202,0.02) 51%, rgba(125,185,232,1.0) 100%);"
36
+ ```
37
+
38
+ If you want some more control of the css generation you can invoke the CSS Printer manually.
39
+
40
+ ```ruby
41
+ printer = Gradient::CSSPrinter.new(gradient)
42
+ printer.linear
43
+ # => "linear-gradient(to right, rgba(30,87,153,1.0) 0%, rgba(41,137,216,0.02) 49%, rgba(37,131,209,0.0) 50%, rgba(32,124,202,0.02) 51%, rgba(125,185,232,1.0) 100%)"
44
+
45
+ printer.linear(direction: "to bottom")
46
+ # => "linear-gradient(to bottom, rgba(30,87,153,1.0) 0%, rgba(41,137,216,0.02) 49%, rgba(37,131,209,0.0) 50%, rgba(32,124,202,0.02) 51%, rgba(125,185,232,1.0) 100%)"
47
+
48
+ printer.radial(shape: :circle)
49
+ # => "radial-gradient(circle, rgba(30,87,153,1.0) 0%, rgba(41,137,216,0.02) 49%, rgba(37,131,209,0.0) 50%, rgba(32,124,202,0.02) 51%, rgba(125,185,232,1.0) 100%)"
50
+ ```
5
51
 
6
52
  ### Import Adobe Photoshop Gradient (`.grd`) files
7
53
  For many artists a preferred way of creating gradients is through Photoshop.
@@ -9,14 +55,8 @@ You are able to parse `.grd` files and turn them in to a hash of `Gradient::Map`
9
55
 
10
56
  ```ruby
11
57
  Gradient::GRD.parse("./kiwi.grd")
12
- # => {"Kiwi" => <Gradient::Map:0x00000000000000
13
- # @points=[
14
- # #<Gradient::Point:0x007fae248a9358 @color=RGB [#3d1103], @location=0.0>,
15
- # #<Gradient::Point:0x007fae248a9308 @color=RGB [#29860d], @location=0.386>,
16
- # #<Gradient::Point:0x007fae248a92b8 @color=RGB [#a0cb1b], @location=0.84>,
17
- # #<Gradient::Point:0x007fae248a9268 @color=RGB [#f3f56e], @location=0.927>,
18
- # #<Gradient::Point:0x007fae248a9218 @color=RGB [#ffffff], @location=1.0>
19
- # ]>
58
+ # => {
59
+ # "Kiwi"=> #<Gradient Map #<Point 0.0 #3d1103ff> #<Point 38.6 #29860dff> #<Point 84.0 #a0cb1bff> #<Point 92.7 #f3f56eff> #<Point 100.0 #ffffffff>>
20
60
  # }
21
61
  ```
22
62
 
@@ -49,3 +89,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/zeeraw
49
89
  ## License
50
90
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
51
91
 
92
+ ## Acknowledgments
93
+ Valek Filippov and the RE-lab team decoded the `.grd` file format and provided
94
+ an [initial parser implementation](https://gitorious.org/re-lab/graphics/source/781a65604d405f29c2da487820f64de8ddb0724d:photoshop/grd).
95
+ [Andy Boughton](https://github.com/abought) later created an [implementation in python](https://github.com/abought/grd_to_cmap) which is the base for this library's implementation.
@@ -1,7 +1,10 @@
1
1
  require "gradient/version"
2
+ require "gradient/color_point"
3
+ require "gradient/opacity_point"
2
4
  require "gradient/point"
3
5
  require "gradient/map"
4
6
  require "gradient/grd"
7
+ require "gradient/css_printer"
5
8
 
6
9
  module Gradient
7
10
  require "color"
@@ -0,0 +1,8 @@
1
+ module Gradient
2
+ class ColorPoint
3
+ attr_reader :color, :location
4
+ def initialize(location, color)
5
+ @color, @location = color, location
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,50 @@
1
+ module Gradient
2
+ class CSSPrinter
3
+
4
+ ORIENTATIONS = %i(horizontal vertical diagonal_top diagonal_bottom radial)
5
+
6
+ def initialize(map)
7
+ @map = map
8
+ end
9
+
10
+ def css(type: :linear, property: "background", **args)
11
+ "#{property}:#{send(type,**args)};"
12
+ end
13
+
14
+ def linear(vendor: nil, direction: "to right", angle: nil, repeating: false)
15
+ angle_or_direction = [angle || direction]
16
+ arguments = (angle_or_direction + rgba_values)
17
+ format_css_function(vendor, repeating, "linear-gradient") + "(#{arguments.join(", ")})"
18
+ end
19
+
20
+ def radial(vendor: nil, shape: nil, size: nil, position: nil, repeating: false)
21
+ shape_and_size_at_position = []
22
+ shape_and_size_at_position << shape if shape
23
+ shape_and_size_at_position << size if size
24
+ shape_and_size_at_position << position if position
25
+ arguments = rgba_values
26
+ arguments.unshift(shape_and_size_at_position.join(" ")) if shape_and_size_at_position.any?
27
+ format_css_function(vendor, repeating, "radial-gradient") + "(#{arguments.join(", ")})"
28
+ end
29
+
30
+ def rgba_values
31
+ @map.points.map { |point| point_to_rgba(point) }
32
+ end
33
+
34
+ private def point_to_rgba(point)
35
+ red, green, blue = [:red,:green, :blue].map{|c|point.color.send(c).round}
36
+ opacity = point.opacity.round(2)
37
+ location = (point.location * 100).round
38
+ "rgba(#{red},#{green},#{blue},#{opacity}) #{location}%"
39
+ end
40
+
41
+ private def format_css_function(vendor, repeating, type)
42
+ css_function = []
43
+ css_function << "-#{vendor}" if !!vendor
44
+ css_function << "repeating" if !!repeating
45
+ css_function << type
46
+ css_function.join("-")
47
+ end
48
+
49
+ end
50
+ end
@@ -1,7 +1,6 @@
1
1
  module Gradient
2
2
  class GRD
3
3
 
4
- SHIFT_BUFFER = " "
5
4
  COLOR_TERMS = %w(Cyn Mgnt Ylw Blck Rd Grn Bl H Strt Brgh)
6
5
  PARSE_METHODS = {
7
6
  "patt" => :parse_patt,
@@ -21,55 +20,93 @@ module Gradient
21
20
 
22
21
  class << self
23
22
 
24
- def parse(file)
25
- new(file).maps
23
+ def parse(string_buffer)
24
+ new.tap do |parser|
25
+ parser.parse(string_buffer)
26
+ end.maps
27
+ end
28
+
29
+ def read(file)
30
+ new.tap do |parser|
31
+ File.open(file, "r") do |file|
32
+ while (string_buffer = file.gets)
33
+ parser.parse(string_buffer)
34
+ end
35
+ end
36
+ end.maps
26
37
  end
27
38
 
28
39
  end
29
40
 
30
- def initialize(file)
41
+ def initialize
31
42
  @maps = {}
32
- @gradients = []
43
+
33
44
  @gradient_names = []
45
+ @color_gradients = []
46
+ @transparency_gradients = []
47
+
34
48
  @current_object_name = ""
35
- @current_gradient = []
49
+ @current_color_gradient = []
50
+ @current_transparency_gradient = []
36
51
  @current_color = {}
37
- @shift = 0
52
+ @current_transparency = {}
38
53
 
39
- File.open(file, "r") do |file|
40
- parse while (@buffer = file.gets)
41
- end
54
+ @shift = 0
42
55
  end
43
56
 
44
- private def parse
57
+ def parse(buffer)
58
+ @buffer = buffer
45
59
  @offset = 28
46
60
  parse_entry while @offset < @buffer.length
47
61
  flush_current_gradient
48
62
 
49
- gradients = @gradients.map do |gradient|
50
- points = clean_gradient(gradient).map do |point_data|
51
- Gradient::Point.new(*point_data)
63
+ color_gradients = @color_gradients.map do |gradient|
64
+ clean_color_gradient(gradient).map do |color_step|
65
+ Gradient::ColorPoint.new(*color_step)
66
+ end
67
+ end
68
+
69
+ transparency_gradients = @transparency_gradients.map do |gradient|
70
+ clean_transparency_gradient(gradient).map do |transparency_step|
71
+ Gradient::OpacityPoint.new(*transparency_step)
52
72
  end
73
+ end
53
74
 
54
- Gradient::Map.new(*points)
75
+ gradients = color_gradients.zip(transparency_gradients).map do |color_points|
76
+ Gradient::Map.new(*color_points)
55
77
  end
56
78
 
57
79
  @maps = Hash[ @gradient_names.zip(gradients) ]
58
80
  end
59
81
 
60
- private def clean_gradient(color_steps)
61
- locations = color_steps.map { |g| g["Lctn"] }
82
+ private def clean_gradient(steps)
83
+ locations = steps.map { |g| g["Lctn"] }
62
84
  min_location = locations.min
63
85
  max_location = locations.max
86
+
64
87
  locations = locations.map do |location|
65
88
  ((location - min_location) * (1.0 / (max_location - min_location))).round(3)
66
89
  end
90
+ end
91
+
92
+ private def clean_color_gradient(steps)
93
+ locations = clean_gradient(steps)
94
+ colors = steps.map do |step|
95
+ convert_to_color(step)
96
+ end
97
+ locations.zip(colors)
98
+ end
67
99
 
68
- colors = color_steps.map do |color_step|
69
- convert_to_color(color_step)
100
+ private def clean_transparency_gradient(steps)
101
+ locations = clean_gradient(steps)
102
+ transparencies = steps.map do |step|
103
+ convert_to_opacity(step)
70
104
  end
105
+ locations.zip(transparencies)
106
+ end
71
107
 
72
- color_locations = locations.zip(colors)
108
+ private def convert_to_opacity(opacity_data)
109
+ opacity_data["Opct"]
73
110
  end
74
111
 
75
112
  private def convert_to_color(color_data)
@@ -139,15 +176,23 @@ module Gradient
139
176
 
140
177
  private def flush_current_gradient
141
178
  flush_current_color
142
- @gradients << @current_gradient if @current_gradient.any?
143
- @current_gradient = []
179
+ flush_current_transparency
180
+ @color_gradients << @current_color_gradient if @current_color_gradient.any?
181
+ @transparency_gradients << @current_transparency_gradient if @current_transparency_gradient.any?
182
+ @current_color_gradient = []
183
+ @current_transparency_gradient = []
144
184
  end
145
185
 
146
186
  private def flush_current_color
147
- @current_gradient << @current_color if @current_color.any?
187
+ @current_color_gradient << @current_color if @current_color.any?
148
188
  @current_color = {}
149
189
  end
150
190
 
191
+ private def flush_current_transparency
192
+ @current_transparency_gradient << @current_transparency if @current_transparency.any?
193
+ @current_transparency = {}
194
+ end
195
+
151
196
  private def parse_patt(name)
152
197
  # TODO: Figure out exactly what this is and implement it.
153
198
  log(name, "patt")
@@ -237,6 +282,11 @@ module Gradient
237
282
  @current_color[name.strip] = value
238
283
  end
239
284
 
285
+ if @current_object_name == "Trns" && name == "Opct" && type == "#Prc"
286
+ flush_current_transparency
287
+ @current_transparency[name.strip] = value / 100
288
+ end
289
+
240
290
  continue!(12)
241
291
  end
242
292
 
@@ -254,6 +304,10 @@ module Gradient
254
304
  @current_color[name.strip] = size
255
305
  end
256
306
 
307
+ if @current_object_name == "Trns" && name == "Lctn"
308
+ @current_transparency[name.strip] = size
309
+ end
310
+
257
311
  continue!
258
312
  end
259
313
 
@@ -1,8 +1,83 @@
1
1
  module Gradient
2
2
  class Map
3
- attr_reader :points
4
- def initialize(*points)
5
- @points = Array(points).sort { |a, b| a.location <=> b.location }
3
+
4
+ attr_reader :color_points, :opacity_points, :points
5
+
6
+ def initialize(color_points=[], opacity_points=[])
7
+ @color_points = sort_points(Array(color_points))
8
+ @opacity_points = sort_points(Array(opacity_points))
9
+ @all_points = sort_points(@color_points + @opacity_points)
10
+ @locations = @all_points.map { |point| point.location }.uniq
11
+ @points ||= expand_points
6
12
  end
13
+
14
+ def inspect
15
+ "#<Gradient Map #{points.map(&:inspect).join(" ")}>"
16
+ end
17
+
18
+ def to_css(**args)
19
+ @css_printer ||= Gradient::CSSPrinter.new(self)
20
+ @css_printer.css(**args)
21
+ end
22
+
23
+ private def expand_points
24
+ new_points = @locations.map do |location|
25
+ selected_points = @all_points.select { |point| point.location == location }
26
+ colored_points, opacity_points = selected_points.group_by(&:class).values_at(ColorPoint, OpacityPoint)
27
+ if colored_points && opacity_points
28
+ Gradient::Point.new(location, colored_points.first.color, opacity_points.first.opacity)
29
+ elsif colored_points
30
+ point = colored_points.first
31
+ a, b = previous_and_next_in(@opacity_points, point)
32
+ fraction = location_fraction(a, b, point)
33
+ opacity = opacity_difference(fraction, a, b)
34
+ Gradient::Point.new(location, point.color, opacity)
35
+ elsif opacity_points
36
+ point = opacity_points.first
37
+ a, b = previous_and_next_in(@color_points, point)
38
+ fraction = location_fraction(a, b, point)
39
+ color = color_difference(fraction, a, b)
40
+ Gradient::Point.new(location, color, point.opacity)
41
+ end
42
+ end
43
+ end
44
+
45
+ private def previous_and_next_in(bucket, point)
46
+ groups = bucket.group_by { |p| point_group(p, point) }
47
+ a = groups.fetch(:same) { groups.fetch(:less) }.max { |p| p.location }
48
+ b = groups.fetch(:same) { groups.fetch(:more) }.min { |p| p.location }
49
+ return a, b
50
+ end
51
+
52
+ private def point_group(a, b)
53
+ if a.location < b.location
54
+ :less
55
+ elsif a.location > b.location
56
+ :more
57
+ else
58
+ :same
59
+ end
60
+ end
61
+
62
+ private def color_difference(fraction, a, b)
63
+ red = a.color.red + fraction * (b.color.red - a.color.red)
64
+ green = a.color.green + fraction * (b.color.green - a.color.green)
65
+ blue = a.color.blue + fraction * (b.color.blue - a.color.blue)
66
+ Color::RGB.new(red, green, blue)
67
+ end
68
+
69
+ private def opacity_difference(fraction, a, b)
70
+ a.opacity + fraction * (b.opacity - a.opacity)
71
+ end
72
+
73
+ private def location_fraction(a, b, x)
74
+ return 0 if a.location == b.location
75
+ (x.location - a.location) / (b.location - a.location)
76
+ end
77
+
78
+ private def sort_points(points)
79
+ points.sort { |a, b| a.location <=> b.location }
80
+ end
81
+
7
82
  end
8
83
  end
@@ -0,0 +1,8 @@
1
+ module Gradient
2
+ class OpacityPoint
3
+ attr_reader :opacity, :location
4
+ def initialize(location, opacity)
5
+ @location, @opacity = location, opacity
6
+ end
7
+ end
8
+ end
@@ -1,8 +1,15 @@
1
1
  module Gradient
2
2
  class Point
3
- attr_reader :color, :location
4
- def initialize(location, color)
5
- @color, @location = color, location
3
+
4
+ attr_reader :location, :color, :opacity
5
+
6
+ def initialize(location, color, opacity)
7
+ @location, @color, @opacity = location, color, opacity
6
8
  end
9
+
10
+ def inspect
11
+ "#<Point #{location * 100} ##{color.hex}#{"%02x" % (opacity * 255).round}>"
12
+ end
13
+
7
14
  end
8
15
  end
@@ -1,3 +1,3 @@
1
1
  module Gradient
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gradient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Vieira
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-18 00:00:00.000000000 Z
11
+ date: 2015-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: color
@@ -98,8 +98,11 @@ files:
98
98
  - bin/setup
99
99
  - gradient.gemspec
100
100
  - lib/gradient.rb
101
+ - lib/gradient/color_point.rb
102
+ - lib/gradient/css_printer.rb
101
103
  - lib/gradient/grd.rb
102
104
  - lib/gradient/map.rb
105
+ - lib/gradient/opacity_point.rb
103
106
  - lib/gradient/point.rb
104
107
  - lib/gradient/version.rb
105
108
  homepage: https://github.com/zeeraw/gradient