dougmcbride-fleximage 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.gitignore +27 -0
  2. data/CHANGELOG.rdoc +14 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +257 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/autotest.rb +5 -0
  8. data/fleximage.gemspec +236 -0
  9. data/init.rb +1 -0
  10. data/lib/dsl_accessor.rb +52 -0
  11. data/lib/fleximage.rb +59 -0
  12. data/lib/fleximage/aviary_controller.rb +75 -0
  13. data/lib/fleximage/blank.rb +70 -0
  14. data/lib/fleximage/helper.rb +41 -0
  15. data/lib/fleximage/image_proxy.rb +69 -0
  16. data/lib/fleximage/legacy_view.rb +63 -0
  17. data/lib/fleximage/model.rb +726 -0
  18. data/lib/fleximage/operator/background.rb +62 -0
  19. data/lib/fleximage/operator/base.rb +189 -0
  20. data/lib/fleximage/operator/border.rb +50 -0
  21. data/lib/fleximage/operator/crop.rb +58 -0
  22. data/lib/fleximage/operator/image_overlay.rb +85 -0
  23. data/lib/fleximage/operator/resize.rb +92 -0
  24. data/lib/fleximage/operator/shadow.rb +87 -0
  25. data/lib/fleximage/operator/text.rb +104 -0
  26. data/lib/fleximage/operator/trim.rb +14 -0
  27. data/lib/fleximage/operator/unsharp_mask.rb +36 -0
  28. data/lib/fleximage/rmagick_image_patch.rb +5 -0
  29. data/lib/fleximage/string_patch.rb +5 -0
  30. data/lib/fleximage/view.rb +58 -0
  31. data/tasks/fleximage_tasks.rake +154 -0
  32. data/test/fixtures/100x1.jpg +0 -0
  33. data/test/fixtures/100x100.jpg +0 -0
  34. data/test/fixtures/1x1.jpg +0 -0
  35. data/test/fixtures/1x100.jpg +0 -0
  36. data/test/fixtures/cmyk.jpg +0 -0
  37. data/test/fixtures/not_a_photo.xml +1 -0
  38. data/test/fixtures/photo.jpg +0 -0
  39. data/test/mock_file.rb +21 -0
  40. data/test/rails_root/app/controllers/application.rb +10 -0
  41. data/test/rails_root/app/controllers/avatars_controller.rb +85 -0
  42. data/test/rails_root/app/controllers/photo_bares_controller.rb +85 -0
  43. data/test/rails_root/app/controllers/photo_dbs_controller.rb +85 -0
  44. data/test/rails_root/app/controllers/photo_files_controller.rb +85 -0
  45. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  46. data/test/rails_root/app/helpers/avatars_helper.rb +2 -0
  47. data/test/rails_root/app/helpers/photo_bares_helper.rb +2 -0
  48. data/test/rails_root/app/helpers/photo_dbs_helper.rb +2 -0
  49. data/test/rails_root/app/helpers/photo_files_helper.rb +2 -0
  50. data/test/rails_root/app/locales/de.yml +7 -0
  51. data/test/rails_root/app/locales/en.yml +8 -0
  52. data/test/rails_root/app/models/abstract.rb +8 -0
  53. data/test/rails_root/app/models/avatar.rb +4 -0
  54. data/test/rails_root/app/models/photo_bare.rb +7 -0
  55. data/test/rails_root/app/models/photo_custom_error.rb +10 -0
  56. data/test/rails_root/app/models/photo_db.rb +3 -0
  57. data/test/rails_root/app/models/photo_file.rb +3 -0
  58. data/test/rails_root/app/views/avatars/edit.html.erb +17 -0
  59. data/test/rails_root/app/views/avatars/index.html.erb +20 -0
  60. data/test/rails_root/app/views/avatars/new.html.erb +16 -0
  61. data/test/rails_root/app/views/avatars/show.html.erb +8 -0
  62. data/test/rails_root/app/views/layouts/avatars.html.erb +17 -0
  63. data/test/rails_root/app/views/layouts/photo_bares.html.erb +17 -0
  64. data/test/rails_root/app/views/layouts/photo_dbs.html.erb +17 -0
  65. data/test/rails_root/app/views/layouts/photo_files.html.erb +17 -0
  66. data/test/rails_root/app/views/photo_bares/edit.html.erb +12 -0
  67. data/test/rails_root/app/views/photo_bares/index.html.erb +18 -0
  68. data/test/rails_root/app/views/photo_bares/new.html.erb +11 -0
  69. data/test/rails_root/app/views/photo_bares/show.html.erb +3 -0
  70. data/test/rails_root/app/views/photo_dbs/edit.html.erb +32 -0
  71. data/test/rails_root/app/views/photo_dbs/index.html.erb +26 -0
  72. data/test/rails_root/app/views/photo_dbs/new.html.erb +31 -0
  73. data/test/rails_root/app/views/photo_dbs/show.html.erb +23 -0
  74. data/test/rails_root/app/views/photo_files/edit.html.erb +27 -0
  75. data/test/rails_root/app/views/photo_files/index.html.erb +24 -0
  76. data/test/rails_root/app/views/photo_files/new.html.erb +26 -0
  77. data/test/rails_root/app/views/photo_files/show.html.erb +18 -0
  78. data/test/rails_root/config/boot.rb +109 -0
  79. data/test/rails_root/config/database.yml +7 -0
  80. data/test/rails_root/config/environment.rb +66 -0
  81. data/test/rails_root/config/environments/development.rb +18 -0
  82. data/test/rails_root/config/environments/production.rb +19 -0
  83. data/test/rails_root/config/environments/sqlite3.rb +0 -0
  84. data/test/rails_root/config/environments/test.rb +22 -0
  85. data/test/rails_root/config/initializers/inflections.rb +10 -0
  86. data/test/rails_root/config/initializers/load_translations.rb +4 -0
  87. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  88. data/test/rails_root/config/routes.rb +43 -0
  89. data/test/rails_root/db/migrate/001_create_photo_files.rb +16 -0
  90. data/test/rails_root/db/migrate/002_create_photo_dbs.rb +16 -0
  91. data/test/rails_root/db/migrate/003_create_photo_bares.rb +12 -0
  92. data/test/rails_root/db/migrate/004_create_avatars.rb +13 -0
  93. data/test/rails_root/public/.htaccess +40 -0
  94. data/test/rails_root/public/404.html +30 -0
  95. data/test/rails_root/public/422.html +30 -0
  96. data/test/rails_root/public/500.html +30 -0
  97. data/test/rails_root/public/dispatch.cgi +10 -0
  98. data/test/rails_root/public/dispatch.fcgi +24 -0
  99. data/test/rails_root/public/dispatch.rb +10 -0
  100. data/test/rails_root/public/favicon.ico +0 -0
  101. data/test/rails_root/public/images/rails.png +0 -0
  102. data/test/rails_root/public/index.html +277 -0
  103. data/test/rails_root/public/javascripts/application.js +2 -0
  104. data/test/rails_root/public/javascripts/controls.js +963 -0
  105. data/test/rails_root/public/javascripts/dragdrop.js +972 -0
  106. data/test/rails_root/public/javascripts/effects.js +1120 -0
  107. data/test/rails_root/public/javascripts/prototype.js +4225 -0
  108. data/test/rails_root/public/robots.txt +5 -0
  109. data/test/rails_root/public/stylesheets/scaffold.css +74 -0
  110. data/test/rails_root/vendor/plugins/fleximage/init.rb +2 -0
  111. data/test/test_helper.rb +81 -0
  112. data/test/unit/abstract_test.rb +20 -0
  113. data/test/unit/basic_model_test.rb +36 -0
  114. data/test/unit/blank_test.rb +23 -0
  115. data/test/unit/default_image_path_option_test.rb +16 -0
  116. data/test/unit/dsl_accessor_test.rb +120 -0
  117. data/test/unit/file_upload_from_local_test.rb +31 -0
  118. data/test/unit/file_upload_from_strings_test.rb +23 -0
  119. data/test/unit/file_upload_from_url_test.rb +35 -0
  120. data/test/unit/file_upload_to_db_test.rb +41 -0
  121. data/test/unit/i18n_messages_test.rb +49 -0
  122. data/test/unit/image_directory_option_test.rb +20 -0
  123. data/test/unit/image_proxy_test.rb +17 -0
  124. data/test/unit/image_storage_format_option_test.rb +31 -0
  125. data/test/unit/magic_columns_test.rb +34 -0
  126. data/test/unit/minimum_image_size_test.rb +56 -0
  127. data/test/unit/operator_base_test.rb +124 -0
  128. data/test/unit/operator_resize_test.rb +18 -0
  129. data/test/unit/preprocess_image_option_test.rb +21 -0
  130. data/test/unit/require_image_option_test.rb +30 -0
  131. data/test/unit/temp_image_test.rb +23 -0
  132. data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
  133. metadata +284 -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
@@ -0,0 +1,92 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Resize this image, constraining proportions. Options allow cropping, stretching, upsampling and
5
+ # padding.
6
+ #
7
+ # image.resize(size, options = {})
8
+ #
9
+ # +size+ is size of the output image after the resize operation. Accepts either <tt>'123x456'</tt>
10
+ # format or <tt>[123, 456]</tt> format.
11
+ #
12
+ # Use the following keys in the +options+ hash:
13
+ #
14
+ # * +crop+: pass true to this option to make the ouput image exactly
15
+ # the same dimensions as +size+. The default behaviour will resize the image without
16
+ # cropping any part meaning the image will be no bigger than the +size+. When <tt>:crop</tt>
17
+ # is true the final image is resized to fit as much as possible in the frame, and then crops it
18
+ # to make it exactly the dimensions declared by the +size+ argument.
19
+ #
20
+ # * +upsample+: By default the image will never display larger than its original dimensions,
21
+ # no matter how large the +size+ argument is. Pass +true+ to use this option to allow
22
+ # upsampling, disabling the default behaviour.
23
+ #
24
+ # * +padding+: This option will pad the space around your image with a solid color to make it exactly the requested
25
+ # size. Pass +true+, for the default of +white+, or give it a text or pixel color like <tt>"red"</tt> or
26
+ # <tt>color(255, 127, 0)</tt>. This is like the opposite of the +crop+ option. Instead of trimming the
27
+ # image to make it exactly the requested size, it will make sure the entire image is visible, but adds space
28
+ # around the edges to make it the right dimensions.
29
+ #
30
+ # * +stretch+: Set this option to true and the image will not preserve its aspect ratio. The final image will
31
+ # stretch to fit the requested +size+. The resulting image is exactly the size you ask for.
32
+ #
33
+ # Example:
34
+ #
35
+ # @photo.operate do |image|
36
+ # image.resize '200x200', :crop => true
37
+ # end
38
+ class Resize < Operator::Base
39
+ def operate(size, options = {})
40
+ options = options.symbolize_keys
41
+
42
+ # Find dimensions
43
+ x, y = size_to_xy(size)
44
+
45
+ # prevent upscaling unless :usample param exists.
46
+ unless options[:upsample]
47
+ x = @image.columns if x > @image.columns
48
+ y = @image.rows if y > @image.rows
49
+ end
50
+
51
+ # Perform image resize
52
+ case
53
+ when options[:crop] && !options[:crop].is_a?(Hash) && @image.respond_to?(:crop_resized!)
54
+ # perform resize and crop
55
+ scale_and_crop([x, y])
56
+
57
+ when options[:stretch]
58
+ # stretch the image, ignoring aspect ratio
59
+ stretch([x, y])
60
+
61
+ else
62
+ # perform the resize without crop
63
+ scale([x, y])
64
+
65
+ end
66
+
67
+ # apply padding if necesary
68
+ if padding_color = options[:padding]
69
+ # get color
70
+ padding_color = 'white' if padding_color == true
71
+
72
+ # get original x and y. This makes it play nice if the requested size is larger
73
+ # than the image and upsampling is not allowed.
74
+ x, y = size_to_xy(size)
75
+
76
+ # get proper border sizes
77
+ x_border = [0, (x - @image.columns + 1) / 2].max
78
+ y_border = [0, (y - @image.rows + 1) / 2].max
79
+
80
+ # apply padding
81
+ @image.border!(x_border, y_border, padding_color)
82
+
83
+ # crop to remove possible extra pixel
84
+ @image.crop!(0, 0, x, y, true)
85
+ end
86
+
87
+ return @image
88
+ end
89
+ end
90
+
91
+ end
92
+ end