corbanbrook-rcomposite 0.3.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.
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
+