gradient 0.1.0 → 0.2.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
  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