fleximage 1.0.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.
Files changed (132) hide show
  1. data/.gitignore +27 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +257 -0
  4. data/Rakefile +49 -0
  5. data/VERSION +1 -0
  6. data/autotest.rb +5 -0
  7. data/fleximage.gemspec +235 -0
  8. data/init.rb +1 -0
  9. data/lib/dsl_accessor.rb +52 -0
  10. data/lib/fleximage.rb +59 -0
  11. data/lib/fleximage/aviary_controller.rb +75 -0
  12. data/lib/fleximage/blank.rb +70 -0
  13. data/lib/fleximage/helper.rb +41 -0
  14. data/lib/fleximage/image_proxy.rb +69 -0
  15. data/lib/fleximage/legacy_view.rb +63 -0
  16. data/lib/fleximage/model.rb +689 -0
  17. data/lib/fleximage/operator/background.rb +62 -0
  18. data/lib/fleximage/operator/base.rb +189 -0
  19. data/lib/fleximage/operator/border.rb +50 -0
  20. data/lib/fleximage/operator/crop.rb +58 -0
  21. data/lib/fleximage/operator/image_overlay.rb +85 -0
  22. data/lib/fleximage/operator/resize.rb +92 -0
  23. data/lib/fleximage/operator/shadow.rb +87 -0
  24. data/lib/fleximage/operator/text.rb +104 -0
  25. data/lib/fleximage/operator/trim.rb +14 -0
  26. data/lib/fleximage/operator/unsharp_mask.rb +36 -0
  27. data/lib/fleximage/rmagick_image_patch.rb +5 -0
  28. data/lib/fleximage/string_patch.rb +5 -0
  29. data/lib/fleximage/view.rb +57 -0
  30. data/tasks/fleximage_tasks.rake +154 -0
  31. data/test/fixtures/100x1.jpg +0 -0
  32. data/test/fixtures/100x100.jpg +0 -0
  33. data/test/fixtures/1x1.jpg +0 -0
  34. data/test/fixtures/1x100.jpg +0 -0
  35. data/test/fixtures/cmyk.jpg +0 -0
  36. data/test/fixtures/not_a_photo.xml +1 -0
  37. data/test/fixtures/photo.jpg +0 -0
  38. data/test/mock_file.rb +21 -0
  39. data/test/rails_root/app/controllers/application.rb +10 -0
  40. data/test/rails_root/app/controllers/avatars_controller.rb +85 -0
  41. data/test/rails_root/app/controllers/photo_bares_controller.rb +85 -0
  42. data/test/rails_root/app/controllers/photo_dbs_controller.rb +85 -0
  43. data/test/rails_root/app/controllers/photo_files_controller.rb +85 -0
  44. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  45. data/test/rails_root/app/helpers/avatars_helper.rb +2 -0
  46. data/test/rails_root/app/helpers/photo_bares_helper.rb +2 -0
  47. data/test/rails_root/app/helpers/photo_dbs_helper.rb +2 -0
  48. data/test/rails_root/app/helpers/photo_files_helper.rb +2 -0
  49. data/test/rails_root/app/locales/de.yml +7 -0
  50. data/test/rails_root/app/locales/en.yml +8 -0
  51. data/test/rails_root/app/models/abstract.rb +8 -0
  52. data/test/rails_root/app/models/avatar.rb +4 -0
  53. data/test/rails_root/app/models/photo_bare.rb +7 -0
  54. data/test/rails_root/app/models/photo_custom_error.rb +10 -0
  55. data/test/rails_root/app/models/photo_db.rb +3 -0
  56. data/test/rails_root/app/models/photo_file.rb +3 -0
  57. data/test/rails_root/app/views/avatars/edit.html.erb +17 -0
  58. data/test/rails_root/app/views/avatars/index.html.erb +20 -0
  59. data/test/rails_root/app/views/avatars/new.html.erb +16 -0
  60. data/test/rails_root/app/views/avatars/show.html.erb +8 -0
  61. data/test/rails_root/app/views/layouts/avatars.html.erb +17 -0
  62. data/test/rails_root/app/views/layouts/photo_bares.html.erb +17 -0
  63. data/test/rails_root/app/views/layouts/photo_dbs.html.erb +17 -0
  64. data/test/rails_root/app/views/layouts/photo_files.html.erb +17 -0
  65. data/test/rails_root/app/views/photo_bares/edit.html.erb +12 -0
  66. data/test/rails_root/app/views/photo_bares/index.html.erb +18 -0
  67. data/test/rails_root/app/views/photo_bares/new.html.erb +11 -0
  68. data/test/rails_root/app/views/photo_bares/show.html.erb +3 -0
  69. data/test/rails_root/app/views/photo_dbs/edit.html.erb +32 -0
  70. data/test/rails_root/app/views/photo_dbs/index.html.erb +26 -0
  71. data/test/rails_root/app/views/photo_dbs/new.html.erb +31 -0
  72. data/test/rails_root/app/views/photo_dbs/show.html.erb +23 -0
  73. data/test/rails_root/app/views/photo_files/edit.html.erb +27 -0
  74. data/test/rails_root/app/views/photo_files/index.html.erb +24 -0
  75. data/test/rails_root/app/views/photo_files/new.html.erb +26 -0
  76. data/test/rails_root/app/views/photo_files/show.html.erb +18 -0
  77. data/test/rails_root/config/boot.rb +109 -0
  78. data/test/rails_root/config/database.yml +7 -0
  79. data/test/rails_root/config/environment.rb +66 -0
  80. data/test/rails_root/config/environments/development.rb +18 -0
  81. data/test/rails_root/config/environments/production.rb +19 -0
  82. data/test/rails_root/config/environments/sqlite3.rb +0 -0
  83. data/test/rails_root/config/environments/test.rb +22 -0
  84. data/test/rails_root/config/initializers/inflections.rb +10 -0
  85. data/test/rails_root/config/initializers/load_translations.rb +4 -0
  86. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  87. data/test/rails_root/config/routes.rb +43 -0
  88. data/test/rails_root/db/migrate/001_create_photo_files.rb +15 -0
  89. data/test/rails_root/db/migrate/002_create_photo_dbs.rb +16 -0
  90. data/test/rails_root/db/migrate/003_create_photo_bares.rb +12 -0
  91. data/test/rails_root/db/migrate/004_create_avatars.rb +13 -0
  92. data/test/rails_root/public/.htaccess +40 -0
  93. data/test/rails_root/public/404.html +30 -0
  94. data/test/rails_root/public/422.html +30 -0
  95. data/test/rails_root/public/500.html +30 -0
  96. data/test/rails_root/public/dispatch.cgi +10 -0
  97. data/test/rails_root/public/dispatch.fcgi +24 -0
  98. data/test/rails_root/public/dispatch.rb +10 -0
  99. data/test/rails_root/public/favicon.ico +0 -0
  100. data/test/rails_root/public/images/rails.png +0 -0
  101. data/test/rails_root/public/index.html +277 -0
  102. data/test/rails_root/public/javascripts/application.js +2 -0
  103. data/test/rails_root/public/javascripts/controls.js +963 -0
  104. data/test/rails_root/public/javascripts/dragdrop.js +972 -0
  105. data/test/rails_root/public/javascripts/effects.js +1120 -0
  106. data/test/rails_root/public/javascripts/prototype.js +4225 -0
  107. data/test/rails_root/public/robots.txt +5 -0
  108. data/test/rails_root/public/stylesheets/scaffold.css +74 -0
  109. data/test/rails_root/vendor/plugins/fleximage/init.rb +2 -0
  110. data/test/test_helper.rb +81 -0
  111. data/test/unit/abstract_test.rb +20 -0
  112. data/test/unit/basic_model_test.rb +30 -0
  113. data/test/unit/blank_test.rb +23 -0
  114. data/test/unit/default_image_path_option_test.rb +16 -0
  115. data/test/unit/dsl_accessor_test.rb +120 -0
  116. data/test/unit/file_upload_from_local_test.rb +31 -0
  117. data/test/unit/file_upload_from_strings_test.rb +23 -0
  118. data/test/unit/file_upload_from_url_test.rb +35 -0
  119. data/test/unit/file_upload_to_db_test.rb +41 -0
  120. data/test/unit/i18n_messages_test.rb +49 -0
  121. data/test/unit/image_directory_option_test.rb +18 -0
  122. data/test/unit/image_proxy_test.rb +17 -0
  123. data/test/unit/image_storage_format_option_test.rb +31 -0
  124. data/test/unit/magic_columns_test.rb +30 -0
  125. data/test/unit/minimum_image_size_test.rb +56 -0
  126. data/test/unit/operator_base_test.rb +124 -0
  127. data/test/unit/operator_resize_test.rb +18 -0
  128. data/test/unit/preprocess_image_option_test.rb +21 -0
  129. data/test/unit/require_image_option_test.rb +30 -0
  130. data/test/unit/temp_image_test.rb +17 -0
  131. data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
  132. metadata +279 -0
@@ -0,0 +1,62 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Composites a transparent image over a colored backgroud
5
+ #
6
+ # It accepts the following options:
7
+ #
8
+ # * +color+: the color of the background image.
9
+ # Use an RMagick named color or use the +color+ method in Fleximage::Controller, or a
10
+ # Magick::Pixel object.
11
+ #
12
+ # * +size+: The size of the background image, in Fleximage *size* format.
13
+ # By default the background image is the same size as the foreground image
14
+ #
15
+ # * +alignment+: A symbol that tells Fleximage where to put the foreground image on
16
+ # top of the background image. Can be any of the following:
17
+ # <tt>:center, :top, :top_right, :right, :bottom_right, :bottom, :bottom_left, :left, :top_left</tt>.
18
+ # Default is :+center+
19
+ #
20
+ # * +offset+: the number of pixels to offset the foreground image from it's :+alignment+ anchor, in FlexImage
21
+ # *size* format. Useful to give a bit a space between your image and the edge of the background, for instance.
22
+ # *NOTE:* Due to some unexpected (buggy?) RMagick behaviour :+offset+ will work strangely
23
+ # if :+alignment+ is set to a corner non-corner value, such as :+top+ or :+center+. Using :+offset+ in
24
+ # these cases will force the overlay into a corner anyway.
25
+ #
26
+ # * +blending+: The blending mode governs how the foreground image gets composited onto the background. You can
27
+ # get some funky effects with modes like :+copy_cyan+ or :+screen+. For a full list of blending
28
+ # modes checkout the RMagick documentation (http://www.simplesystems.org/RMagick/doc/constants.html#CompositeOperator).
29
+ # To use a blend mode remove the +CompositeOp+ form the name and "unserscorize" the rest. For instance,
30
+ # +MultiplyCompositeOp+ becomes :+multiply+, and +CopyBlackCompositeOp+ becomes :+copy_black+.
31
+
32
+ class Background < Operator::Base
33
+ def operate(options = {})
34
+ options = options.symbolize_keys
35
+
36
+ #default to a white background if the color option is not set
37
+ color = options[:color] || 'white'
38
+
39
+ #use the existing image's size if the size option is not set
40
+ width, height = options.key?(:size) ? size_to_xy(options[:size]) : [@image.columns, @image.rows]
41
+
42
+ #create the background image onto which we will composite the foreground image
43
+ bg = Magick::Image.new(width, height) do
44
+ self.background_color = color
45
+ self.format = 'PNG'
46
+ end
47
+
48
+ #prepare attributes for composite operation
49
+ args = []
50
+ args << @image
51
+ args << symbol_to_gravity(options[:alignment] || :center)
52
+ args += size_to_xy(options[:offset]) if options[:offset]
53
+ args << symbol_to_blending_mode(options[:blending] || :over)
54
+
55
+ #composite the foreground image onto the background
56
+ bg.composite!(*args)
57
+
58
+ return bg
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,189 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ class BadOperatorResult < Exception #:nodoc:
5
+ end
6
+
7
+ class OperationNotImplemented < Exception #:nodoc:
8
+ end
9
+
10
+ # The Operator::Base class is what all other Operator classes inherit from.
11
+ # To write your own Operator class, simply inherit from this class, and
12
+ # implement your own operate methods, with your own arguments. Just
13
+ # return a new RMagick image object that represents the new image, and
14
+ # the model will be updated automatically.
15
+ #
16
+ # You have access to a few instance variables in the operate method:
17
+ #
18
+ # * @image : The current image from the model. Use this is a starting
19
+ # point for all transformations.
20
+ # * @model : The model instance that this image transformation is happenining
21
+ # in. Use it to get data out of your model for display in your image.
22
+ class Base
23
+ # Create a operator, capturing the model object to operate on
24
+ def initialize(proxy, image, model_obj) #:nodoc:
25
+ @proxy = proxy
26
+ @image = image
27
+ @model = model_obj
28
+ end
29
+
30
+ # Start the operation
31
+ def execute(*args) #:nodoc:
32
+ # Get the result of the Operators #operate method
33
+ result = operate(*args)
34
+
35
+ # Ensure that the result is an RMagick:Image object
36
+ unless result.is_a?(Magick::Image)
37
+ raise BadOperatorResult, "expected #{self.class}#operate to return an instance of Magick::Image. \n"+
38
+ "Got instance of #{result.class} instead."
39
+ end
40
+
41
+ # Save the result to the operator proxy
42
+ @proxy.image = result
43
+ end
44
+
45
+ # Perform the operation. Override this method in your Operator::Base subclasses
46
+ # in order to write your own image operators.
47
+ def operate(*args)
48
+ raise OperationNotImplemented, "Override this method in your own subclass."
49
+ end
50
+
51
+ # ---
52
+ # - SUPPORT METHODS
53
+ # ---
54
+
55
+ # Allows access to size conversion globally. See size_to_xy for a more detailed explanation
56
+ def self.size_to_xy(size)
57
+ case
58
+ when size.is_a?(Array) && size.size == 2 # [320, 240]
59
+ size
60
+
61
+ when size.to_s.include?('x') # "320x240"
62
+ size.split('x').collect(&:to_i)
63
+
64
+ else # Anything else, convert the object to an integer and assume square dimensions
65
+ [size.to_i, size.to_i]
66
+
67
+ end
68
+ end
69
+
70
+ # This method will return a valid color Magick::Pixel object. It will also auto adjust
71
+ # for the bit depth of your ImageMagick configuration.
72
+ #
73
+ # Usage:
74
+ #
75
+ # color('red') #=> Magick::Pixel with a red color
76
+ # color(0, 255, 0) #=> Magick::Pixel with a green color
77
+ # color(0, 0, 255, 47) #=> Magick::Pixel with a blue clolor and slightly transparent
78
+ #
79
+ # # on an ImageMagick with a QuantumDepth of 16
80
+ # color(0, 255, 0) #=> Magick::Pixel with rgb of (0, 65535, 0) (auto adjusted to 16bit channels)
81
+ #
82
+ def self.color(*args)
83
+ if args.size == 1 && args.first.is_a?(String)
84
+ args.first
85
+ else
86
+
87
+ # adjust color to proper bit depth
88
+ if Magick::QuantumDepth != 8
89
+ max = case Magick::QuantumDepth
90
+ when 16
91
+ 65_535
92
+ when 32
93
+ 4_294_967_295
94
+ end
95
+
96
+ args.map! do |value|
97
+ (value.to_f/255 * max).to_i
98
+ end
99
+ end
100
+
101
+ # create the pixel
102
+ Magick::Pixel.new(*args)
103
+ end
104
+ end
105
+
106
+ def color(*args)
107
+ self.class.color(*args)
108
+ end
109
+
110
+ # Converts a size object to an [x,y] array. Acceptible formats are:
111
+ #
112
+ # * 10
113
+ # * "10"
114
+ # * "10x20"
115
+ # * [10, 20]
116
+ #
117
+ # Usage:
118
+ #
119
+ # x, y = size_to_xy("10x20")
120
+ def size_to_xy(size)
121
+ self.class.size_to_xy size
122
+ end
123
+
124
+ # Scale the image, respecting aspect ratio.
125
+ # Operation will happen in the main <tt>@image</tt> unless you supply the +img+ argument
126
+ # to operate on instead.
127
+ def scale(size, img = @image)
128
+ img.change_geometry!(size_to_xy(size).join('x')) do |cols, rows, _img|
129
+ cols = 1 if cols < 1
130
+ rows = 1 if rows < 1
131
+ _img.resize!(cols, rows)
132
+ end
133
+ end
134
+
135
+ # Scale to the desired size and crop edges off to get the exact dimensions needed.
136
+ # Operation will happen in the main <tt>@image</tt> unless you supply the +img+ argument
137
+ # to operate on instead.
138
+ def scale_and_crop(size, img = @image)
139
+ img.crop_resized!(*size_to_xy(size))
140
+ end
141
+
142
+ # Resize the image, with no respect to aspect ratio.
143
+ # Operation will happen in the main <tt>@image</tt> unless you supply the +img+ argument
144
+ # to operate on instead.
145
+ def stretch(size, img = @image)
146
+ img.resize!(*size_to_xy(size))
147
+ end
148
+
149
+ # Convert a symbol to an RMagick blending mode.
150
+ #
151
+ # The blending mode governs how the overlay gets composited onto the image. You can
152
+ # get some funky effects with modes like :+copy_cyan+ or :+screen+. For a full list of blending
153
+ # modes checkout the RMagick documentation (http://www.simplesystems.org/RMagick/doc/constants.html#CompositeOperator).
154
+ # To use a blend mode remove the +CompositeOp+ form the name and "unserscorize" the rest. For instance,
155
+ # +MultiplyCompositeOp+ becomes :+multiply+, and +CopyBlackCompositeOp+ becomes :+copy_black+.
156
+ def symbol_to_blending_mode(mode)
157
+ "Magick::#{mode.to_s.camelize}CompositeOp".constantize
158
+ rescue NameError
159
+ raise ArgumentError, ":#{mode} is not a valid blending mode."
160
+ end
161
+
162
+ def symbol_to_gravity(gravity_name)
163
+ gravity = GRAVITIES[gravity_name]
164
+
165
+ if gravity
166
+ gravity
167
+ else
168
+ raise ArgumentError, ":#{gravity_name} is not a valid gravity name.\n\nValid names are :center, :top, :top_right, :right, :bottom_right, :bottom, :bottom_left, :left, :top_left"
169
+ end
170
+ end
171
+
172
+
173
+ end # Base
174
+
175
+ # Conversion table for mapping alignment symbols to their equivalent RMagick gravity constants.
176
+ GRAVITIES = {
177
+ :center => Magick::CenterGravity,
178
+ :top => Magick::NorthGravity,
179
+ :top_right => Magick::NorthEastGravity,
180
+ :right => Magick::EastGravity,
181
+ :bottom_right => Magick::SouthEastGravity,
182
+ :bottom => Magick::SouthGravity,
183
+ :bottom_left => Magick::SouthWestGravity,
184
+ :left => Magick::WestGravity,
185
+ :top_left => Magick::NorthWestGravity,
186
+ } unless defined?(GRAVITIES)
187
+
188
+ end # Operator
189
+ end # Fleximage
@@ -0,0 +1,50 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Add a border to the outside of the image
5
+ #
6
+ # image.border(options = {})
7
+ #
8
+ # Use the following keys in the +options+ hash:
9
+ #
10
+ # * +size+: Width of the border on each side. You can use a 2 dimensional value ('5x10') if you want
11
+ # different widths for the sides and top borders, but a single integer will apply the same border on
12
+ # all sides.
13
+ #
14
+ # * +color+: the color of the border. Use an RMagick named color or use the +color+ method in
15
+ # FlexImage::Controller, or a Magick::Pixel object.
16
+ #
17
+ # Example:
18
+ #
19
+ # @photo.operate do |image|
20
+ # # Defaults
21
+ # image.border(
22
+ # :size => 10,
23
+ # :color => 'white' # or color(255, 255, 255)
24
+ # )
25
+ #
26
+ # # Big, pink and wide
27
+ # image.border(
28
+ # :size => '200x100',
29
+ # :color => color(255, 128, 128)
30
+ # )
31
+ # end
32
+ class Border < Operator::Base
33
+ def operate(options = {})
34
+ options = options.symbolize_keys if options.respond_to?(:symbolize_keys)
35
+ defaults = {
36
+ :size => '10',
37
+ :color => 'white'
38
+ }
39
+ options = options.is_a?(Hash) ? defaults.update(options) : defaults
40
+
41
+ # Get border size
42
+ options[:size] = size_to_xy(options[:size])
43
+
44
+ # apply border
45
+ @image.border!(options[:size][0], options[:size][1], options[:color])
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Crops the image without doing any resizing first. The operation crops from the :+from+ coordinate,
5
+ # and returns an image of size :+size+ down and right from there.
6
+ #
7
+ # image.crop(options = {})
8
+ #
9
+ # Use the following keys in the +options+ hash:
10
+ #
11
+ # * +gravity+: Select gravity for the crop. Default is :top_left (Magick::NorthWestGravity)
12
+ # Choose from GRAVITITES constant defined in base.rb.
13
+ # * +from+: coorinates for the upper left corner of resulting image.
14
+ # * +size+: The size of the resulting image, going down and to the right of the :+from+ coordinate.
15
+ #
16
+ # size and from options are *required*.
17
+ #
18
+ # Example:
19
+ #
20
+ # @photo.operate do |image|
21
+ # image.crop(
22
+ # :from => '100x50',
23
+ # :size => '500x350'
24
+ # )
25
+ # end
26
+ #
27
+ # or
28
+ #
29
+ # @photo.operate do |image|
30
+ # image.crop(
31
+ # :gravity => :center,
32
+ # :from => '100x50',
33
+ # :size => '500x350'
34
+ # )
35
+ # end
36
+ class Crop < Operator::Base
37
+ def operate(options = {})
38
+ options = options.symbolize_keys
39
+ options.reverse_merge!(:gravity => :top_left)
40
+
41
+ # required integer keys
42
+ [:from, :size].each do |key|
43
+ raise ArgumentError, ":#{key} must be included in crop options" unless options[key]
44
+ options[key] = size_to_xy(options[key])
45
+ end
46
+
47
+ # width and height must not be zero
48
+ options[:size].each do |dimension|
49
+ raise ArgumentError, ":size must not be zero for X or Y" if dimension.zero?
50
+ end
51
+
52
+ # crop
53
+ @image.crop!(symbol_to_gravity(options[:gravity]), options[:from][0], options[:from][1], options[:size][0], options[:size][1], true)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,85 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Adds an overlay to the base image. It's useful for things like attaching a logo,
5
+ # watermark, or even a border to the image. It will work best with a 24-bit PNG with
6
+ # alpha channel since it will properly deal with partial transparency.
7
+ #
8
+ # image.resize(image_overlay_path, options = {})
9
+ #
10
+ # +image_overlay_path+ is the path, relative to +RAILS_ROOT+ where the image you want superimposed
11
+ # can be found.
12
+ #
13
+ # Use the following keys in the +options+ hash:
14
+ #
15
+ # * +size+: The size of the overlayed image, as <tt>'123x456'</tt> or <tt>[123, 456]</tt>.
16
+ # By default the overlay is not resized before compositing.
17
+ # Use this options if you want to resize the overlay, perhaps to have a small
18
+ # logo on thumbnails and a big logo on full size images. Other than just numerical dimensions, the
19
+ # size parameter takes 2 special values :+scale_to_fit+ and :+stretch_to_fit+. :+scale_to_fit+ will
20
+ # make the overlay fit as
21
+ # much as it can inside the image without changing the aspect ratio. :+stretch_to_fit+
22
+ # will make the overlay the exact same size as the image but with a distorted aspect ratio to make
23
+ # it fit. :+stretch_to_fit+ is designed to add border to images.
24
+ #
25
+ # * +alignment+: A symbol that tells Fleximage where to put the overlay. Can be any of the following:
26
+ # <tt>:center, :top, :top_right, :right, :bottom_right, :bottom, :bottom_left, :left, :top_left</tt>.
27
+ # Default is :+center+
28
+ #
29
+ # * +offset+: the number of pixels to offset the overlay from it's :+alignment+ anchor, in
30
+ # <tt>'123x456'</tt> or <tt>[123, 456]</tt> format. Useful to give a bit a space between your logo
31
+ # and the edge of the image, for instance.
32
+ # *NOTE:* Due to some unexpected (buggy?) RMagick behaviour :+offset+ will work strangely
33
+ # if :+alignment+ is set to a non-corner value, such as :+top+ or :+center+. Using :+offset+ in
34
+ # these cases will force the overlay into a corner anyway.
35
+ #
36
+ # * +blending+: The blending mode governs how the overlay gets composited onto the image. You can
37
+ # get some funky effects with modes like :+copy_cyan+ or :+screen+. For a full list of blending
38
+ # modes checkout the RMagick documentation (http://www.simplesystems.org/RMagick/doc/constants.html#CompositeOperator).
39
+ # To use a blend mode remove the +CompositeOp+ form the name and "unserscorize" the rest. For instance,
40
+ # +MultiplyCompositeOp+ becomes :+multiply+, and +CopyBlackCompositeOp+ becomes :+copy_black+.
41
+ #
42
+ # Example:
43
+ #
44
+ # @photo.operate do |image|
45
+ # image.image_overlay('images/my_logo_with_alpha.png',
46
+ # :size => '25x25',
47
+ # :alignment => :top_right,
48
+ # :blending => :screen
49
+ # )
50
+ # end
51
+ class ImageOverlay < Operator::Base
52
+ def operate(image_overlay_path, options = {})
53
+ options = options.symbolize_keys
54
+
55
+ #load overlay
56
+ overlay = Magick::Image.read(image_overlay_path).first
57
+
58
+ #resize overlay
59
+ if options[:size]
60
+ if options[:size] == :scale_to_fit || options[:size] == :stretch_to_fit
61
+ x, y = @image.columns, @image.rows
62
+ else
63
+ x, y = size_to_xy(options[:size])
64
+ end
65
+
66
+ method = options[:size] == :stretch_to_fit ? :stretch : :scale
67
+ send(method, [x, y], overlay)
68
+ end
69
+
70
+ #prepare arguments for composite!
71
+ args = []
72
+ args << overlay #overlay image
73
+ args << symbol_to_gravity(options[:alignment] || :center) #gravity
74
+ args += size_to_xy(options[:offset]) if options[:offset] #offset
75
+ args << symbol_to_blending_mode(options[:blending] || :over) #compositing mode
76
+
77
+ #composite
78
+ @image.composite!(*args)
79
+
80
+ return @image
81
+ end
82
+ end
83
+
84
+ end
85
+ end