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 +20 -0
- data/README +135 -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 +114 -0
- data/lib/rcomposite/layermask.rb +16 -0
- data/lib/rcomposite/layerset.rb +194 -0
- data/lib/rcomposite.rb +8 -0
- metadata +71 -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,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
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
|
+
|