corbanbrook-rcomposite 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Corban Brook
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,135 @@
1
+
2
+ RComposite
3
+
4
+
5
+ Class Summary:
6
+
7
+ * Layers: There are 4 types of Layer Models,
8
+
9
+ * Layer: The base layer model. Can load an image from disk, blob,
10
+ or RMagick Image object. Contains methods to transform, change layer mode,
11
+ change opacity level, and more.
12
+
13
+ * FillLayer: A solid color, gradient, or pattern fill layer.
14
+
15
+ * AdjustmentLayer: Invert, Threshold, Levels, Blur adjustment layer.
16
+ Applies layer effect over everything under it in the layer stack.
17
+
18
+ * LayerMask: Mask any of the above layer types. LayerMasks are a special
19
+ type of layer which is a Alpha Channel mask that can be applied to any
20
+ layer type. Since LayerMask is also a layer, it can also be transformed
21
+ in any way.
22
+
23
+ * LayerSets: 2 types of LayerSet Models,
24
+
25
+ * LayerSet: The LayerSet is a container, or directory in which to bundle
26
+ layers. LayerSet contains methods to do group transforms on its bundled
27
+ layers. Transforms like rotation, movement and scaling are performed on
28
+ all the layers it contains while maintaining each layer on a seperate plane
29
+ without merging.
30
+
31
+ * Canvas: The canvas is a special LayerSet which is a blank workspace containing
32
+ other layers and layer sets.
33
+
34
+
35
+ Usage:
36
+
37
+ First you must require the gem or lib:
38
+
39
+ require 'rubygems'
40
+ require 'rcomposite'
41
+ include RComposite # includes the RComposite module methods into this scope
42
+
43
+ After including the Module, we will need to create a blank canvas.
44
+
45
+ canvas = Canvas.new(640, 480)
46
+
47
+ Creating a layer:
48
+
49
+ photo = Layer.new :file => 'photo.jpg'
50
+
51
+ Creating a fill layer:
52
+
53
+ blue = FillLayer.new '#000090'
54
+
55
+ Changing a Layers properties:
56
+
57
+ blue.opacity 40
58
+ blue.mode Multiply
59
+
60
+ The layers may be added to the canvas. Whatever is added first will be on top:
61
+
62
+ canvas.layer blue
63
+ canvas.layer photo
64
+
65
+ The canvas can be saved:
66
+
67
+ canvas.save_as 'bluesky.jpg'
68
+
69
+ The real power of RComposite lies in its use of blocks and simplified syntax.
70
+ Take a look at the following example:
71
+
72
+ Canvas.new(320,240) do
73
+
74
+ layer_set :slides do
75
+ layer :file => 'slide1.png' do
76
+ opacity 65
77
+ offset 12, 12
78
+ mode Lighten
79
+ end
80
+
81
+ adjustment_layer Blur, 0.5, 1.5 do
82
+ layer_mask :file => 'butterfly-mask.png'
83
+ end
84
+
85
+ layer :file => 'slide2.png' do
86
+ offset 28, 28
87
+ opacity 30
88
+ mode Darken
89
+ end
90
+
91
+ rotate 45
92
+ offset 70, 50
93
+ scale 80, 80
94
+ end
95
+
96
+ fill_layer SolidColor, '#f83898FF' do
97
+ opacity 70
98
+ mode Multiply
99
+
100
+ layer_mask :file => 'butterfly-mask.png' do
101
+ offset -30, -30
102
+ image.rotate! 40
103
+ end
104
+ end
105
+
106
+ fill_layer Gradient, 0, 0, 0, 50, '#606', '#033' do
107
+ opacity 90
108
+ mode Overlay
109
+
110
+ layer_mask :file => 'butterfly-mask.png' do
111
+ offset -20, -20
112
+ end
113
+ end
114
+
115
+ layer :file => 'background.png' do
116
+ image.resize!(320, 240)
117
+ end
118
+
119
+ save_as 'composite.jpg'
120
+ end
121
+
122
+ This nice thing about RComposite is that it doesnt hide the underlaying RMagick
123
+ Image object from you. It is always available through the image accessor
124
+
125
+ layer :file => 'photo.jpg' do
126
+ image.crop_resized!(100,300) # crop_resized! is a RMagick method
127
+ end
128
+
129
+
130
+
131
+ @corban weare.buildingsky.net
132
+ ________________________________________________________________________________
133
+
134
+ Copyright (c) 2009 Corban Brook, released under the MIT license
135
+
@@ -0,0 +1,48 @@
1
+ module RComposite
2
+ Levels = 1
3
+ Curves = 2
4
+ ColorBalance = 3
5
+ BrightnessContrast = 4
6
+ HueSaturation = 5
7
+ SelectiveColor = 6
8
+ ChannelMixer = 7
9
+ GradientMap = 8
10
+ PhotoFilter = 9
11
+ Invert = 10 # negate
12
+ Threshold = 11 # threshold
13
+ Polarize = 12 #
14
+ Blur = 13
15
+
16
+ class AdjustmentLayer < Layer
17
+ attr_reader :type, :args
18
+
19
+ def initialize(type, *args, &block)
20
+ @type = type
21
+ @args = args
22
+ @mode = Normal
23
+ @offset_x = 0
24
+ @offset_y = 0
25
+ @opacity_percent = 100
26
+ @layer_mask = nil
27
+
28
+ case type
29
+ when Invert
30
+ @proc = proc { negate }
31
+ when Threshold
32
+ @proc = proc { threshold *args }
33
+ when Levels
34
+ @proc = proc { level *args }
35
+ when Blur
36
+ @proc = proc { gaussian_blur *args }
37
+ end
38
+ @proc = proc { |*args| @proc }.call(*@args)
39
+ end
40
+
41
+ def merge_down(image)
42
+ @image = image.clone.instance_eval &@proc
43
+ @image.matte = true
44
+
45
+ super(image)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ module RComposite
2
+ class Canvas < LayerSet
3
+ attr_reader :width, :height
4
+ attr_accessor :image
5
+
6
+ def initialize(width, height, &block)
7
+ @image = Magick::Image.new width, height
8
+ @width = width
9
+ @height = height
10
+ $RCompositeCanvasWidth = @width
11
+ $RCompositeCanvasHeight = @height
12
+
13
+ super(:canvas, &block)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ module RComposite
2
+ # Fill layer types
3
+
4
+ SolidColor = 0
5
+ Gradient = 1
6
+ Pattern = 2
7
+
8
+ class FillLayer < Layer
9
+ attr_accessor :image
10
+ attr_reader :offset_x, :offset_y
11
+
12
+ def initialize(type, *args, &block)
13
+ case type
14
+ when SolidColor
15
+ color = args[0]
16
+
17
+ fill = Magick::GradientFill.new(0, 0, 0, 0, color, color)
18
+
19
+ when Gradient
20
+ x1 = args[0]
21
+ y1 = args[1]
22
+ x2 = args[2]
23
+ y2 = args[3]
24
+ color1 = args[4]
25
+ color2 = args[5]
26
+
27
+ fill = Magick::GradientFill.new(x1, y1, x2, y2, color1, color2)
28
+
29
+ when Pattern
30
+ image = args[0]
31
+
32
+ fill = Magick::TextureFill.new(image)
33
+ end
34
+
35
+ @image = Magick::Image.new($RCompositeCanvasWidth, $RCompositeCanvasHeight, fill)
36
+
37
+ @image.matte = true
38
+ @mode = Normal
39
+ @offset_x = 0
40
+ @offset_y = 0
41
+ @opacity_percent = 100
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,114 @@
1
+ module RComposite
2
+ # Layer mode constants
3
+ # some of photoshops layer modes are directly equvilent to RMagick
4
+ # some are not, and some arent supported at all.
5
+
6
+ Normal = Magick::OverCompositeOp # PS equivilent
7
+ Dissolve = Magick::DissolveCompositeOp
8
+ Darken = Magick::DarkenCompositeOp # PS equivilent
9
+ Multiply = Magick::MultiplyCompositeOp # PS equivilent
10
+ #ColorBurn
11
+ #LinearBurn
12
+ Lighten = Magick::LightenCompositeOp # PS equivilent
13
+ Screen = Magick::ScreenCompositeOp # PS equivilent
14
+ #ColorDodge
15
+ #LinearDodge
16
+ Overlay = Magick::OverlayCompositeOp # PS equivilent
17
+ #SoftLight
18
+ #HardLight
19
+ #VividLight
20
+ #LinearLight
21
+ #PinLight
22
+ #HardMix
23
+ Difference = Magick::DifferenceCompositeOp # PS equivilent
24
+ #Exclusion
25
+ Hue = Magick::HueCompositeOp
26
+ Saturation = Magick::SaturateCompositeOp
27
+ Color = Magick::ColorizeCompositeOp
28
+ Luminosity = Magick::LuminizeCompositeOp
29
+
30
+ class Layer
31
+ attr_accessor :image
32
+ attr_reader :offset_x, :offset_y
33
+
34
+ def initialize(options = {})
35
+ if options[:file]
36
+ @image = Magick::Image.read(options[:file]).first
37
+ @image.background_color = 'transparent'
38
+ elsif options[:blob]
39
+ @image = Magick::Image.from_blob(options[:blob]).first
40
+ @image.background_color = 'transparent'
41
+ elsif options[:image]
42
+ if options[:image].is_a? Magick::Image
43
+ @image = options[:image]
44
+ end
45
+ end
46
+
47
+ if @image
48
+ @image.matte = true
49
+ @mode = Normal
50
+ @offset_x = 0
51
+ @offset_y = 0
52
+ @opacity_percent = 100
53
+ @layer_mask = nil
54
+ else
55
+ raise "Layer not created -- no layer source."
56
+ end
57
+ end
58
+
59
+ def layer_mask(options, &block)
60
+ @layer_mask = LayerMask.new options
61
+
62
+ @layer_mask.instance_eval &block if block_given?
63
+ end
64
+
65
+ def merge_down(image)
66
+ @layer_mask.apply self if @layer_mask
67
+ image.composite!(@image, @offset_x, @offset_y, @mode)
68
+ end
69
+
70
+ def width
71
+ @image.columns
72
+ end
73
+
74
+ def height
75
+ @image.rows
76
+ end
77
+
78
+ def offset(x, y)
79
+ @offset_x = x
80
+ @offset_y = y
81
+ end
82
+
83
+ def opacity(percent)
84
+ @opacity_percent = percent
85
+ # intercept original alpha channel with pixel intensity
86
+ alpha_channel = @image.channel(Magick::AlphaChannel).negate
87
+ intensity = (Magick::MaxRGB * (percent/100.0)).round
88
+ alpha_channel.composite!(Magick::Image.new(width, height) { self.background_color = Magick::Pixel.new(intensity,intensity,intensity) }, Magick::CenterGravity, Multiply)
89
+ alpha_channel.matte = false
90
+
91
+ @image.composite!(alpha_channel, Magick::CenterGravity, Magick::CopyOpacityCompositeOp)
92
+ return true
93
+ end
94
+
95
+
96
+ def mode(mode = nil)
97
+ return @mode if mode.nil?
98
+ @mode = mode
99
+ end
100
+
101
+ def save_as(filename)
102
+ @image.write(filename)
103
+ end
104
+
105
+ def join_set(set)
106
+ set.add_layer(self)
107
+ end
108
+
109
+ def leave_set(set)
110
+ set.remove_layer(self)
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,16 @@
1
+ module RComposite
2
+ class LayerMask < Layer
3
+ def apply(layer)
4
+ # apply layer mask to layer before merging down
5
+ fill = Magick::GradientFill.new(0,0,0,0, '#000', '#000')
6
+ mask = Magick::Image.new(layer.width, layer.height, fill)
7
+ mask.composite!(@image, @offset_x, @offset_y, Magick::OverCompositeOp)
8
+
9
+ alpha_channel = layer.image.channel(Magick::AlphaChannel).negate
10
+ alpha_channel.composite!(mask, 0, 0, Magick::MultiplyCompositeOp)
11
+ alpha_channel.matte = false
12
+ layer.image.composite!(alpha_channel, 0, 0, Magick::CopyOpacityCompositeOp)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,194 @@
1
+ module RComposite
2
+ class LayerSet
3
+ attr_accessor :layers
4
+ attr_reader :min_x, :min_y, :max_x, :max_y, :bounding_width, :bounding_height, :name
5
+
6
+ def initialize(name, &block)
7
+ @layers = []
8
+ @layer_sets = {}
9
+
10
+ bounding_box
11
+ @name = name
12
+
13
+ self.instance_eval &block if block_given?
14
+ end
15
+
16
+ def layer_set(set_or_name, &block)
17
+ if set_or_name.is_a? RComposite::LayerSet
18
+ # referencing a previously instanstiated LayerSet and adding to Canvas.
19
+ set = set_or_name
20
+ @layers << set
21
+ @layer_sets[set.name] = set
22
+
23
+ elsif @layer_sets[set_or_name]
24
+ # referencing a previously added LayerSet.
25
+ set = @layer_sets[set_or_name]
26
+
27
+ else
28
+ # creating a new LayerSet and adding to Canvas.
29
+ set = LayerSet.new set_or_name
30
+ @layers << set
31
+ @layer_sets[set_or_name] = set
32
+ end
33
+
34
+ # tie floating block methods (layer, rotate, offset, etc) to LayerSet object.
35
+ set.instance_eval &block if block_given?
36
+
37
+ return set
38
+ end
39
+
40
+ def layer(options, &block)
41
+ if options.is_a? RComposite::Layer
42
+ layer = options
43
+ else
44
+ layer = Layer.new options
45
+ end
46
+ @layers << layer
47
+
48
+ # tie floating block methods (opacity, mode, offset, etc) to Layer object.
49
+ layer.instance_eval &block if block_given?
50
+
51
+ return layer
52
+ end
53
+
54
+ def fill_layer(type, *args, &block)
55
+ layer = FillLayer.new type, *args
56
+ @layers << layer
57
+
58
+ # tie floating block methods (opacity, mode, offset, etc) to Layer object.
59
+ layer.instance_eval &block if block_given?
60
+
61
+ return layer
62
+ end
63
+
64
+ def adjustment_layer(type, *args, &block)
65
+ layer = AdjustmentLayer.new type, *args
66
+ @layers << layer
67
+
68
+ # tie floating block methods (opacity, mode, offset, etc) to Layer object.
69
+ layer.instance_eval &block if block_given?
70
+
71
+ return layer
72
+ end
73
+
74
+ def flatten
75
+ flattened_image = render
76
+
77
+ # clear layers and sets
78
+ @layers.clear
79
+ @layer_sets.clear
80
+
81
+ # only 1 flattened layer now
82
+ @layers << Layer.new(flattened_image)
83
+ end
84
+
85
+ def render
86
+ return composite_layers(@image.clone, @layers)
87
+ end
88
+
89
+ def composite_layers(image, layers)
90
+ layers.reverse.each do |layer|
91
+ case layer
92
+ when RComposite::LayerSet
93
+ image = composite_layers image, layer.layers
94
+ when RComposite::Layer
95
+ layer.merge_down image
96
+ end
97
+ end
98
+
99
+ return image
100
+ end
101
+
102
+ def save_as(filename)
103
+ render.write(filename)
104
+ end
105
+
106
+ ################################################################################################################################
107
+
108
+ def add_layer(layer)
109
+ @layers << layer
110
+ bounding_box
111
+ end
112
+
113
+ def remove_layer(layer)
114
+ @layers.delete(layer)
115
+ bounding_box
116
+ end
117
+
118
+ def bounding_box
119
+ if @layers.size > 0
120
+ x1 = []
121
+ y1 = []
122
+ x2 = []
123
+ y2 = []
124
+ @layers.each do |layer|
125
+ x1 << layer.offset_x
126
+ y1 << layer.offset_y
127
+ x2 << layer.width + layer.offset_x
128
+ y2 << layer.height + layer.offset_y
129
+ end
130
+
131
+ @min_x = x1.min
132
+ @min_y = y1.min
133
+ @max_x = x2.max
134
+ @max_y = y2.max
135
+ @bounding_width = @max_x - @min_x
136
+ @bounding_height = @max_y - @min_y
137
+ else
138
+ @min_x = 0
139
+ @min_y = 0
140
+ @max_x = 0
141
+ @max_y = 0
142
+ @bounding_width = 0
143
+ @boudning_height = 0
144
+ end
145
+ end
146
+
147
+ def offset(x, y)
148
+ bounding_box # recalculate bounding box
149
+ shift_x = x - @min_x
150
+ shift_y = y - @min_y
151
+ @layers.each do |layer|
152
+ layer.offset(layer.offset_x + shift_x, layer.offset_y + shift_y)
153
+ end
154
+ bounding_box
155
+ end
156
+
157
+ def scale(width, height)
158
+ bounding_box # recalculate bounding box
159
+ width_scale = width.to_f / bounding_width.to_f
160
+ height_scale = height.to_f / bounding_height.to_f
161
+ @layers.each do |layer|
162
+ layer.image.scale!((layer.width * width_scale).round, (layer.height * height_scale).round)
163
+ layer.offset(((layer.offset_x - @min_x) * width_scale).round + @min_x, ((layer.offset_y - @min_y) * height_scale).round + @min_y)
164
+ end
165
+ bounding_box
166
+ end
167
+
168
+ def rotate(degrees)
169
+ bounding_box # recalculate bounding box
170
+ bounding_mid_x = @bounding_width / 2
171
+ bounding_mid_y = @bounding_height / 2
172
+ @layers.each do |layer|
173
+ layer_mid_x = (layer.width/2.0).round + layer.offset_x - @min_x - bounding_mid_x
174
+ layer_mid_y = ((layer.height/2.0).round + layer.offset_y - @min_y - bounding_mid_y) * -1
175
+
176
+ radians = (degrees * -1) / (180 / Math::PI)
177
+
178
+ cos = Math.cos(radians)
179
+ sin = Math.sin(radians)
180
+
181
+ new_mid_x = ((layer_mid_x * cos) - (layer_mid_y * sin)).round
182
+ new_mid_y = ((layer_mid_x * sin) + (layer_mid_y * cos)).round
183
+
184
+ layer.image.rotate!(degrees)
185
+
186
+ new_offset_x = new_mid_x - (layer.width/2.0).round + @min_x + bounding_mid_x
187
+ new_offset_y = new_mid_y * -1 - (layer.height/2.0).round + @min_y + bounding_mid_y
188
+
189
+ layer.offset(new_offset_x, new_offset_y)
190
+ end
191
+ bounding_box
192
+ end
193
+ end
194
+ end
data/lib/rcomposite.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rmagick'
2
+
3
+ require 'rcomposite/layerset'
4
+ require 'rcomposite/layer'
5
+ require 'rcomposite/canvas'
6
+ require 'rcomposite/filllayer'
7
+ require 'rcomposite/adjustmentlayer'
8
+ require 'rcomposite/layermask'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: corbanbrook-rcomposite
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Corban Brook
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-24 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rmagick
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ description: Rubyshop is an RMagick abstraction library to easily manipulate and composite images through the use of a canvas, layers, layer masks, adjustment layers, fill layers, and layer sets (much like in Photoshop)
26
+ email: corbanbrook@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - MIT-LICENSE
34
+ files:
35
+ - README
36
+ - MIT-LICENSE
37
+ - lib/rcomposite.rb
38
+ - lib/rcomposite/layer.rb
39
+ - lib/rcomposite/layerset.rb
40
+ - lib/rcomposite/adjustmentlayer.rb
41
+ - lib/rcomposite/filllayer.rb
42
+ - lib/rcomposite/canvas.rb
43
+ - lib/rcomposite/layermask.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/corbanbrook/rcomposite
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.2.0
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: An RMagick abstration layer for easy image compositing
70
+ test_files: []
71
+