rcomposite 0.3.1
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 +20 -0
- data/README +191 -0
- data/lib/rcomposite/adjustmentlayer.rb +48 -0
- data/lib/rcomposite/canvas.rb +16 -0
- data/lib/rcomposite/filllayer.rb +44 -0
- data/lib/rcomposite/layer.rb +118 -0
- data/lib/rcomposite/layermask.rb +16 -0
- data/lib/rcomposite/layerset.rb +196 -0
- data/lib/rcomposite.rb +9 -0
- metadata +73 -0
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,191 @@
|
|
1
|
+
|
2
|
+
RComposite
|
3
|
+
|
4
|
+
RComposite is an RMagick abstraction library to easily manipulate and composite
|
5
|
+
images through the use of a canvas, layers, layer masks, adjustment layers, fill
|
6
|
+
layers, and layer sets (much like in Photoshop).
|
7
|
+
|
8
|
+
|
9
|
+
Install:
|
10
|
+
|
11
|
+
Add the github repository to your gem sources:
|
12
|
+
|
13
|
+
sudo gem sources -a http://gems.github.com
|
14
|
+
|
15
|
+
Install the gem from github:
|
16
|
+
|
17
|
+
sudo gem install corbanbrook-rcomposite
|
18
|
+
|
19
|
+
Require:
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'rcomposite'
|
23
|
+
|
24
|
+
include RComposite # optional: if you wish to have the RComposite module in your project scope
|
25
|
+
|
26
|
+
...
|
27
|
+
|
28
|
+
|
29
|
+
Class Summary:
|
30
|
+
|
31
|
+
* Layers: There are 4 types of Layer Models,
|
32
|
+
|
33
|
+
* Layer: The base layer model. Can load an image from disk, blob,
|
34
|
+
or RMagick Image object. Contains methods to transform, change layer mode,
|
35
|
+
change opacity level, and more.
|
36
|
+
|
37
|
+
* FillLayer: A solid color, gradient, or pattern fill layer.
|
38
|
+
|
39
|
+
* AdjustmentLayer: Invert, Threshold, Levels, Blur adjustment layer.
|
40
|
+
Applies layer effect over everything under it in the layer stack.
|
41
|
+
|
42
|
+
* LayerMask: Mask any of the above layer types. LayerMasks are a special
|
43
|
+
type of layer which is a Alpha Channel mask that can be applied to any
|
44
|
+
layer type. Since LayerMask is also a layer, it can also be transformed
|
45
|
+
in any way.
|
46
|
+
|
47
|
+
* LayerSets: 2 types of LayerSet Models,
|
48
|
+
|
49
|
+
* LayerSet: The LayerSet is a container, or directory in which to bundle
|
50
|
+
layers. LayerSet contains methods to do group transforms on its bundled
|
51
|
+
layers. Transforms like rotation, movement and scaling are performed on
|
52
|
+
all the layers it contains while maintaining each layer on a seperate plane
|
53
|
+
without merging.
|
54
|
+
|
55
|
+
* Canvas: The canvas is a special LayerSet which is a blank workspace containing
|
56
|
+
other layers and layer sets.
|
57
|
+
|
58
|
+
|
59
|
+
Usage:
|
60
|
+
|
61
|
+
* Creating a blank canvas:
|
62
|
+
|
63
|
+
canvas = RComposite::Canvas.new(640, 480)
|
64
|
+
|
65
|
+
Canvas also accepts an optional block for easily adding layers to the stack
|
66
|
+
|
67
|
+
canvas = Rcomposite::Canvas.new(640, 480) do
|
68
|
+
layer :file => 'butterfly.png'
|
69
|
+
end
|
70
|
+
|
71
|
+
* Creating a layer:
|
72
|
+
|
73
|
+
photo = RComposite::Layer.new :file => 'photo.jpg'
|
74
|
+
|
75
|
+
* Creating a fill layer:
|
76
|
+
|
77
|
+
blue = RCompisite::FillLayer.new '#000090'
|
78
|
+
|
79
|
+
* Changing a Layers properties:
|
80
|
+
|
81
|
+
blue.opacity 40
|
82
|
+
blue.mode Multiply
|
83
|
+
|
84
|
+
* Adding layers to the canvas. (Whatever is added first will be on top):
|
85
|
+
|
86
|
+
canvas.layer blue
|
87
|
+
canvas.layer photo
|
88
|
+
|
89
|
+
* Saving the canvas:
|
90
|
+
|
91
|
+
canvas.save_as 'bluesky.jpg'
|
92
|
+
|
93
|
+
* Adding an alpha channel mask to a layer:
|
94
|
+
|
95
|
+
Masks are used to punch holes into layers to add transparency.
|
96
|
+
|
97
|
+
blue.layer_mask :file => 'alpha_channel.png'
|
98
|
+
|
99
|
+
You can add layer options to the mask just like it was a normal layer.
|
100
|
+
|
101
|
+
fill_layer SolidColor, '#f83898FF' do
|
102
|
+
layer_mask :file => 'butterfly-mask.png' do
|
103
|
+
offset -30, -30
|
104
|
+
rotate 40
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
The real power of RComposite lies in its use of blocks and simplified syntax.
|
110
|
+
Take a look at the following examples:
|
111
|
+
|
112
|
+
* Just like the Canvas, all layer types also accept an optional block parametre for setting layer options:
|
113
|
+
|
114
|
+
butterfly = RComposite::Layer :file => 'butterfly.png' do
|
115
|
+
offset 100, 100 # moves the layer on the canvas
|
116
|
+
rotate 25
|
117
|
+
end
|
118
|
+
|
119
|
+
or..
|
120
|
+
|
121
|
+
canvas.layer :file => 'butterfly.png' do
|
122
|
+
..
|
123
|
+
end
|
124
|
+
|
125
|
+
* DSL Example
|
126
|
+
|
127
|
+
Canvas.new(320,240) do
|
128
|
+
|
129
|
+
layer_set :slides do
|
130
|
+
layer :file => 'slide1.png' do
|
131
|
+
opacity 65
|
132
|
+
offset 12, 12
|
133
|
+
mode Lighten
|
134
|
+
end
|
135
|
+
|
136
|
+
adjustment_layer Blur, 0.5, 1.5 do
|
137
|
+
layer_mask :file => 'butterfly-mask.png'
|
138
|
+
end
|
139
|
+
|
140
|
+
layer :file => 'slide2.png' do
|
141
|
+
offset 28, 28
|
142
|
+
opacity 30
|
143
|
+
mode Darken
|
144
|
+
end
|
145
|
+
|
146
|
+
rotate 45
|
147
|
+
offset 70, 50
|
148
|
+
scale 80, 80
|
149
|
+
end
|
150
|
+
|
151
|
+
fill_layer SolidColor, '#f83898FF' do
|
152
|
+
opacity 70
|
153
|
+
mode Multiply
|
154
|
+
|
155
|
+
layer_mask :file => 'butterfly-mask.png' do
|
156
|
+
offset -30, -30
|
157
|
+
image.rotate! 40
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
fill_layer Gradient, 0, 0, 0, 50, '#606', '#033' do
|
162
|
+
opacity 90
|
163
|
+
mode Overlay
|
164
|
+
|
165
|
+
layer_mask :file => 'butterfly-mask.png' do
|
166
|
+
offset -20, -20
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
layer :file => 'background.png' do
|
171
|
+
image.resize!(320, 240)
|
172
|
+
end
|
173
|
+
|
174
|
+
save_as 'composite.jpg'
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
The nice thing about RComposite is that it doesnt hide the underlaying RMagick
|
179
|
+
Image object from you. It is always available through the image accessor
|
180
|
+
|
181
|
+
layer :file => 'photo.jpg' do
|
182
|
+
image.crop_resized!(100,300) # crop_resized! is a RMagick method
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
@corban weare.buildingsky.net
|
188
|
+
________________________________________________________________________________
|
189
|
+
|
190
|
+
Copyright (c) 2009 Corban Brook, released under the MIT license
|
191
|
+
|
@@ -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,118 @@
|
|
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 rotate(degrees)
|
84
|
+
@image.rotate!(degrees)
|
85
|
+
end
|
86
|
+
|
87
|
+
def opacity(percent)
|
88
|
+
@opacity_percent = percent
|
89
|
+
# intercept original alpha channel with pixel intensity
|
90
|
+
alpha_channel = @image.channel(Magick::AlphaChannel).negate
|
91
|
+
intensity = (Magick::MaxRGB * (percent/100.0)).round
|
92
|
+
alpha_channel.composite!(Magick::Image.new(width, height) { self.background_color = Magick::Pixel.new(intensity,intensity,intensity) }, Magick::CenterGravity, Multiply)
|
93
|
+
alpha_channel.matte = false
|
94
|
+
|
95
|
+
@image.composite!(alpha_channel, Magick::CenterGravity, Magick::CopyOpacityCompositeOp)
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def mode(mode = nil)
|
101
|
+
return @mode if mode.nil?
|
102
|
+
@mode = mode
|
103
|
+
end
|
104
|
+
|
105
|
+
def save_as(filename)
|
106
|
+
@image.write(filename)
|
107
|
+
end
|
108
|
+
|
109
|
+
def join_set(set)
|
110
|
+
set.add_layer(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
def leave_set(set)
|
114
|
+
set.remove_layer(self)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
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,196 @@
|
|
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
|
+
|
9
|
+
bounding_box
|
10
|
+
@name = name
|
11
|
+
|
12
|
+
self.instance_eval &block if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def stack(&block)
|
16
|
+
self.instance_eval &block if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def layer_set(ref, position = :bottom, &block)
|
20
|
+
if ref.is_a? RComposite::LayerSet
|
21
|
+
# referencing a previously instanstiated LayerSet.
|
22
|
+
set = ref
|
23
|
+
else
|
24
|
+
# creating a new LayerSet
|
25
|
+
set = LayerSet.new ref
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add layer set to render pipeline
|
29
|
+
if position == :bottom
|
30
|
+
@layers << set
|
31
|
+
elsif position == :top
|
32
|
+
@layers.unshift set
|
33
|
+
end
|
34
|
+
|
35
|
+
# tie floating block methods (layer, rotate, offset, etc) to LayerSet object.
|
36
|
+
set.instance_eval &block if block_given?
|
37
|
+
|
38
|
+
return set
|
39
|
+
end
|
40
|
+
|
41
|
+
def layer(options, &block)
|
42
|
+
if options.is_a? RComposite::Layer
|
43
|
+
layer = options
|
44
|
+
else
|
45
|
+
layer = Layer.new options
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add layer to render pipeline
|
49
|
+
@layers << layer
|
50
|
+
|
51
|
+
# tie floating block methods (opacity, mode, offset, etc) to Layer object.
|
52
|
+
layer.instance_eval &block if block_given?
|
53
|
+
|
54
|
+
return layer
|
55
|
+
end
|
56
|
+
|
57
|
+
def fill_layer(type, *args, &block)
|
58
|
+
layer = FillLayer.new type, *args
|
59
|
+
@layers << layer
|
60
|
+
|
61
|
+
# tie floating block methods (opacity, mode, offset, etc) to Layer object.
|
62
|
+
layer.instance_eval &block if block_given?
|
63
|
+
|
64
|
+
return layer
|
65
|
+
end
|
66
|
+
|
67
|
+
def adjustment_layer(type, *args, &block)
|
68
|
+
layer = AdjustmentLayer.new type, *args
|
69
|
+
@layers << layer
|
70
|
+
|
71
|
+
# tie floating block methods (opacity, mode, offset, etc) to Layer object.
|
72
|
+
layer.instance_eval &block if block_given?
|
73
|
+
|
74
|
+
return layer
|
75
|
+
end
|
76
|
+
|
77
|
+
def flatten
|
78
|
+
flattened_image = render
|
79
|
+
|
80
|
+
# clear layers and sets
|
81
|
+
@layers.clear
|
82
|
+
|
83
|
+
# only 1 flattened layer now
|
84
|
+
@layers << Layer.new(flattened_image)
|
85
|
+
end
|
86
|
+
|
87
|
+
def render
|
88
|
+
return composite_layers(@image.clone, @layers)
|
89
|
+
end
|
90
|
+
|
91
|
+
def composite_layers(image, layers)
|
92
|
+
layers.reverse.each do |layer|
|
93
|
+
case layer
|
94
|
+
when RComposite::LayerSet
|
95
|
+
image = composite_layers image, layer.layers
|
96
|
+
when RComposite::Layer
|
97
|
+
layer.merge_down image
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
return image
|
102
|
+
end
|
103
|
+
|
104
|
+
def save_as(filename)
|
105
|
+
render.write(filename)
|
106
|
+
end
|
107
|
+
|
108
|
+
################################################################################################################################
|
109
|
+
|
110
|
+
def add_layer(layer)
|
111
|
+
@layers << layer
|
112
|
+
bounding_box
|
113
|
+
end
|
114
|
+
|
115
|
+
def remove_layer(layer)
|
116
|
+
@layers.delete(layer)
|
117
|
+
bounding_box
|
118
|
+
end
|
119
|
+
|
120
|
+
def bounding_box
|
121
|
+
if @layers.size > 0
|
122
|
+
x1 = []
|
123
|
+
y1 = []
|
124
|
+
x2 = []
|
125
|
+
y2 = []
|
126
|
+
@layers.each do |layer|
|
127
|
+
x1 << layer.offset_x
|
128
|
+
y1 << layer.offset_y
|
129
|
+
x2 << layer.width + layer.offset_x
|
130
|
+
y2 << layer.height + layer.offset_y
|
131
|
+
end
|
132
|
+
|
133
|
+
@min_x = x1.min
|
134
|
+
@min_y = y1.min
|
135
|
+
@max_x = x2.max
|
136
|
+
@max_y = y2.max
|
137
|
+
@bounding_width = @max_x - @min_x
|
138
|
+
@bounding_height = @max_y - @min_y
|
139
|
+
else
|
140
|
+
@min_x = 0
|
141
|
+
@min_y = 0
|
142
|
+
@max_x = 0
|
143
|
+
@max_y = 0
|
144
|
+
@bounding_width = 0
|
145
|
+
@boudning_height = 0
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def offset(x, y)
|
150
|
+
bounding_box # recalculate bounding box
|
151
|
+
shift_x = x - @min_x
|
152
|
+
shift_y = y - @min_y
|
153
|
+
@layers.each do |layer|
|
154
|
+
layer.offset(layer.offset_x + shift_x, layer.offset_y + shift_y)
|
155
|
+
end
|
156
|
+
bounding_box
|
157
|
+
end
|
158
|
+
|
159
|
+
def scale(width, height)
|
160
|
+
bounding_box # recalculate bounding box
|
161
|
+
width_scale = width.to_f / bounding_width.to_f
|
162
|
+
height_scale = height.to_f / bounding_height.to_f
|
163
|
+
@layers.each do |layer|
|
164
|
+
layer.image.scale!((layer.width * width_scale).round, (layer.height * height_scale).round)
|
165
|
+
layer.offset(((layer.offset_x - @min_x) * width_scale).round + @min_x, ((layer.offset_y - @min_y) * height_scale).round + @min_y)
|
166
|
+
end
|
167
|
+
bounding_box
|
168
|
+
end
|
169
|
+
|
170
|
+
def rotate(degrees)
|
171
|
+
bounding_box # recalculate bounding box
|
172
|
+
bounding_mid_x = @bounding_width / 2
|
173
|
+
bounding_mid_y = @bounding_height / 2
|
174
|
+
@layers.each do |layer|
|
175
|
+
layer_mid_x = (layer.width/2.0).round + layer.offset_x - @min_x - bounding_mid_x
|
176
|
+
layer_mid_y = ((layer.height/2.0).round + layer.offset_y - @min_y - bounding_mid_y) * -1
|
177
|
+
|
178
|
+
radians = (degrees * -1) / (180 / Math::PI)
|
179
|
+
|
180
|
+
cos = Math.cos(radians)
|
181
|
+
sin = Math.sin(radians)
|
182
|
+
|
183
|
+
new_mid_x = ((layer_mid_x * cos) - (layer_mid_y * sin)).round
|
184
|
+
new_mid_y = ((layer_mid_x * sin) + (layer_mid_y * cos)).round
|
185
|
+
|
186
|
+
layer.rotate(degrees)
|
187
|
+
|
188
|
+
new_offset_x = new_mid_x - (layer.width/2.0).round + @min_x + bounding_mid_x
|
189
|
+
new_offset_y = new_mid_y * -1 - (layer.height/2.0).round + @min_y + bounding_mid_y
|
190
|
+
|
191
|
+
layer.offset(new_offset_x, new_offset_y)
|
192
|
+
end
|
193
|
+
bounding_box
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
data/lib/rcomposite.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rcomposite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Corban Brook
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-13 00:00:00 -04: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: RComposite 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
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: An RMagick abstration layer for easy image compositing
|
72
|
+
test_files: []
|
73
|
+
|