robinboening-fleximage 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +14 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +257 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/autotest.rb +5 -0
- data/fleximage.gemspec +179 -0
- data/init.rb +1 -0
- data/lib/dsl_accessor.rb +52 -0
- data/lib/fleximage.rb +56 -0
- data/lib/fleximage/aviary_controller.rb +75 -0
- data/lib/fleximage/blank.rb +70 -0
- data/lib/fleximage/helper.rb +41 -0
- data/lib/fleximage/image_proxy.rb +69 -0
- data/lib/fleximage/legacy_view.rb +63 -0
- data/lib/fleximage/model.rb +711 -0
- data/lib/fleximage/operator/background.rb +62 -0
- data/lib/fleximage/operator/base.rb +189 -0
- data/lib/fleximage/operator/border.rb +50 -0
- data/lib/fleximage/operator/crop.rb +58 -0
- data/lib/fleximage/operator/image_overlay.rb +85 -0
- data/lib/fleximage/operator/resize.rb +92 -0
- data/lib/fleximage/operator/shadow.rb +87 -0
- data/lib/fleximage/operator/text.rb +104 -0
- data/lib/fleximage/operator/trim.rb +14 -0
- data/lib/fleximage/operator/unsharp_mask.rb +36 -0
- data/lib/fleximage/rmagick_image_patch.rb +5 -0
- data/lib/fleximage/view.rb +57 -0
- data/tasks/fleximage_tasks.rake +154 -0
- data/test/fixtures/100x1.jpg +0 -0
- data/test/fixtures/100x100.jpg +0 -0
- data/test/fixtures/1x1.jpg +0 -0
- data/test/fixtures/1x100.jpg +0 -0
- data/test/fixtures/cmyk.jpg +0 -0
- data/test/fixtures/not_a_photo.xml +1 -0
- data/test/fixtures/photo.jpg +0 -0
- data/test/mock_file.rb +21 -0
- data/test/rails_root/app/controllers/application.rb +10 -0
- data/test/rails_root/app/controllers/avatars_controller.rb +85 -0
- data/test/rails_root/app/controllers/photo_bares_controller.rb +85 -0
- data/test/rails_root/app/controllers/photo_dbs_controller.rb +85 -0
- data/test/rails_root/app/controllers/photo_files_controller.rb +85 -0
- data/test/rails_root/app/helpers/application_helper.rb +3 -0
- data/test/rails_root/app/helpers/avatars_helper.rb +2 -0
- data/test/rails_root/app/helpers/photo_bares_helper.rb +2 -0
- data/test/rails_root/app/helpers/photo_dbs_helper.rb +2 -0
- data/test/rails_root/app/helpers/photo_files_helper.rb +2 -0
- data/test/rails_root/app/locales/de.yml +7 -0
- data/test/rails_root/app/locales/en.yml +8 -0
- data/test/rails_root/app/models/abstract.rb +8 -0
- data/test/rails_root/app/models/avatar.rb +4 -0
- data/test/rails_root/app/models/photo_bare.rb +7 -0
- data/test/rails_root/app/models/photo_custom_error.rb +10 -0
- data/test/rails_root/app/models/photo_db.rb +3 -0
- data/test/rails_root/app/models/photo_file.rb +3 -0
- data/test/rails_root/app/models/photo_s3.rb +5 -0
- data/test/rails_root/app/views/avatars/edit.html.erb +17 -0
- data/test/rails_root/app/views/avatars/index.html.erb +20 -0
- data/test/rails_root/app/views/avatars/new.html.erb +16 -0
- data/test/rails_root/app/views/avatars/show.html.erb +8 -0
- data/test/rails_root/app/views/layouts/avatars.html.erb +17 -0
- data/test/rails_root/app/views/layouts/photo_bares.html.erb +17 -0
- data/test/rails_root/app/views/layouts/photo_dbs.html.erb +17 -0
- data/test/rails_root/app/views/layouts/photo_files.html.erb +17 -0
- data/test/rails_root/app/views/photo_bares/edit.html.erb +12 -0
- data/test/rails_root/app/views/photo_bares/index.html.erb +18 -0
- data/test/rails_root/app/views/photo_bares/new.html.erb +11 -0
- data/test/rails_root/app/views/photo_bares/show.html.erb +3 -0
- data/test/rails_root/app/views/photo_dbs/edit.html.erb +32 -0
- data/test/rails_root/app/views/photo_dbs/index.html.erb +26 -0
- data/test/rails_root/app/views/photo_dbs/new.html.erb +31 -0
- data/test/rails_root/app/views/photo_dbs/show.html.erb +23 -0
- data/test/rails_root/app/views/photo_files/edit.html.erb +27 -0
- data/test/rails_root/app/views/photo_files/index.html.erb +24 -0
- data/test/rails_root/app/views/photo_files/new.html.erb +26 -0
- data/test/rails_root/app/views/photo_files/show.html.erb +18 -0
- data/test/rails_root/config/boot.rb +109 -0
- data/test/rails_root/config/database.yml +7 -0
- data/test/rails_root/config/environment.rb +66 -0
- data/test/rails_root/config/environments/development.rb +18 -0
- data/test/rails_root/config/environments/production.rb +19 -0
- data/test/rails_root/config/environments/sqlite3.rb +0 -0
- data/test/rails_root/config/environments/test.rb +22 -0
- data/test/rails_root/config/initializers/inflections.rb +10 -0
- data/test/rails_root/config/initializers/load_translations.rb +4 -0
- data/test/rails_root/config/initializers/mime_types.rb +5 -0
- data/test/rails_root/config/routes.rb +43 -0
- data/test/rails_root/db/migrate/001_create_photo_files.rb +16 -0
- data/test/rails_root/db/migrate/002_create_photo_dbs.rb +16 -0
- data/test/rails_root/db/migrate/003_create_photo_bares.rb +12 -0
- data/test/rails_root/db/migrate/004_create_avatars.rb +13 -0
- data/test/rails_root/db/migrate/005_create_photo_s3s.rb +12 -0
- data/test/rails_root/public/.htaccess +40 -0
- data/test/rails_root/public/404.html +30 -0
- data/test/rails_root/public/422.html +30 -0
- data/test/rails_root/public/500.html +30 -0
- data/test/rails_root/public/dispatch.cgi +10 -0
- data/test/rails_root/public/dispatch.fcgi +24 -0
- data/test/rails_root/public/dispatch.rb +10 -0
- data/test/rails_root/public/favicon.ico +0 -0
- data/test/rails_root/public/images/rails.png +0 -0
- data/test/rails_root/public/index.html +277 -0
- data/test/rails_root/public/javascripts/application.js +2 -0
- data/test/rails_root/public/javascripts/controls.js +963 -0
- data/test/rails_root/public/javascripts/dragdrop.js +972 -0
- data/test/rails_root/public/javascripts/effects.js +1120 -0
- data/test/rails_root/public/javascripts/prototype.js +4225 -0
- data/test/rails_root/public/robots.txt +5 -0
- data/test/rails_root/public/stylesheets/scaffold.css +74 -0
- data/test/rails_root/vendor/plugins/fleximage/init.rb +2 -0
- data/test/s3_stubs.rb +7 -0
- data/test/test_helper.rb +82 -0
- data/test/unit/abstract_test.rb +20 -0
- data/test/unit/basic_model_test.rb +40 -0
- data/test/unit/blank_test.rb +23 -0
- data/test/unit/default_image_path_option_test.rb +16 -0
- data/test/unit/dsl_accessor_test.rb +120 -0
- data/test/unit/file_upload_from_local_test.rb +31 -0
- data/test/unit/file_upload_from_strings_test.rb +23 -0
- data/test/unit/file_upload_from_url_test.rb +35 -0
- data/test/unit/file_upload_to_db_test.rb +41 -0
- data/test/unit/has_store_test.rb +4 -0
- data/test/unit/i18n_messages_test.rb +49 -0
- data/test/unit/image_directory_option_test.rb +20 -0
- data/test/unit/image_proxy_test.rb +17 -0
- data/test/unit/image_storage_format_option_test.rb +31 -0
- data/test/unit/magic_columns_test.rb +34 -0
- data/test/unit/minimum_image_size_test.rb +56 -0
- data/test/unit/operator_base_test.rb +124 -0
- data/test/unit/operator_resize_test.rb +18 -0
- data/test/unit/preprocess_image_option_test.rb +21 -0
- data/test/unit/require_image_option_test.rb +30 -0
- data/test/unit/temp_image_test.rb +23 -0
- data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
- metadata +258 -0
@@ -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
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Fleximage
|
2
|
+
module Operator
|
3
|
+
|
4
|
+
# Add a drop shadow to the image.
|
5
|
+
#
|
6
|
+
# image.shadow(options = {})
|
7
|
+
#
|
8
|
+
# Use the following keys in the +options+ hash:
|
9
|
+
#
|
10
|
+
# * +offset+: distance of the dropsahdow form the image, in FlexImage *size* format. Positive
|
11
|
+
# number move it down and right, negative numbers move it up and left.
|
12
|
+
#
|
13
|
+
# * +blur+: how blurry the shadow is. Roughly corresponds to distance in pixels of the blur.
|
14
|
+
#
|
15
|
+
# * +background+: a color for the background of the image. What the shadow fades into.
|
16
|
+
# Use an RMagick named color or use the +color+ method in FlexImage::Controller, or a
|
17
|
+
# Magick::Pixel object.
|
18
|
+
#
|
19
|
+
# * +color+: color of the shadow itself.
|
20
|
+
# Use an RMagick named color or use the +color+ method in FlexImage::Controller, or a
|
21
|
+
# Magick::Pixel object.
|
22
|
+
#
|
23
|
+
# * +opacity+: opacity of the shadow. A value between 0.0 and 1.0, where 1 is opaque and 0 is
|
24
|
+
# transparent.
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# @photo.operate do |image|
|
29
|
+
# # Default settings
|
30
|
+
# image.shadow(
|
31
|
+
# :color => 'black', # or color(0, 0, 0)
|
32
|
+
# :background => 'white', # or color(255, 255, 255)
|
33
|
+
# :blur => 8,
|
34
|
+
# :offset => '2x2',
|
35
|
+
# :opacity => 0.75
|
36
|
+
# )
|
37
|
+
#
|
38
|
+
# # Huge, red shadow
|
39
|
+
# image.shadow(
|
40
|
+
# :color => color(255, 0, 0),
|
41
|
+
# :background => 'black', # or color(255, 255, 255)
|
42
|
+
# :blur => 30,
|
43
|
+
# :offset => '20x10',
|
44
|
+
# :opacity => 1
|
45
|
+
# )
|
46
|
+
# end
|
47
|
+
class Shadow < Operator::Base
|
48
|
+
def operate(options = {})
|
49
|
+
options = options.symbolize_keys if options.respond_to?(:symbolize_keys)
|
50
|
+
defaults = {
|
51
|
+
:offset => 2,
|
52
|
+
:blur => 8,
|
53
|
+
:background => 'white',
|
54
|
+
:color => 'black',
|
55
|
+
:opacity => 0.75
|
56
|
+
}
|
57
|
+
options = options.is_a?(Hash) ? defaults.update(options) : defaults
|
58
|
+
|
59
|
+
# verify options
|
60
|
+
options[:offset] = size_to_xy(options[:offset])
|
61
|
+
options[:blur] = options[:blur].to_i
|
62
|
+
|
63
|
+
options[:background] = Magick::Pixel.from_color(options[:background]) unless options[:background].is_a?(Magick::Pixel)
|
64
|
+
options[:color] = Magick::Pixel.from_color(options[:color]) unless options[:color].is_a?(Magick::Pixel)
|
65
|
+
options[:color].opacity = (1 - options[:opacity]) * 255
|
66
|
+
|
67
|
+
# generate shadow image
|
68
|
+
shadow = @image.dup
|
69
|
+
shadow.background_color = options[:color]
|
70
|
+
shadow.erase!
|
71
|
+
shadow.border!(options[:offset].max + options[:blur] * 3, options[:offset].max + options[:blur] * 3, options[:background])
|
72
|
+
shadow = shadow.blur_image(0, options[:blur] / 2)
|
73
|
+
|
74
|
+
# apply shadow
|
75
|
+
@image = shadow.composite(
|
76
|
+
@image,
|
77
|
+
symbol_to_gravity(:top_left),
|
78
|
+
(shadow.columns - @image.columns) / 2 - options[:offset][0],
|
79
|
+
(shadow.rows - @image.rows) / 2 - options[:offset][1],
|
80
|
+
symbol_to_blending_mode(:over)
|
81
|
+
)
|
82
|
+
@image.trim!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Fleximage
|
2
|
+
module Operator
|
3
|
+
|
4
|
+
# Draw text on the image. Customize size, position, color, dropshadow, and font.
|
5
|
+
#
|
6
|
+
# image.text(string_to_write, options = {})
|
7
|
+
#
|
8
|
+
# Use the following keys in the +options+ hash:
|
9
|
+
#
|
10
|
+
# * alignment: symbol like in <tt>ImageOverlay</tt>
|
11
|
+
# * offset: size string
|
12
|
+
# * antialias: true or false
|
13
|
+
# * color: string or <tt>color(r, g, b)</tt>
|
14
|
+
# * font_size: integer
|
15
|
+
# * font: path to a font file relative to +RAILS_ROOT+
|
16
|
+
# * rotate: degrees as an integer
|
17
|
+
# * shadow: <tt>{:blur => 1, :opacity => 1.0}</tt>
|
18
|
+
# * font_weight: RMagick font weight constant or value. See: http://www.imagemagick.org/RMagick/doc/draw.html#font_weight
|
19
|
+
# * stroke: hash that, if present, will stroke the text. The hash should have both <tt>:width</tt> (integer) and <tt>:color</tt> (string or color object).
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# @photo.operate do |image|
|
24
|
+
# image.text('I like Cheese',
|
25
|
+
# :alignment => :top_left,
|
26
|
+
# :offset => '300x150',
|
27
|
+
# :antialias => true,
|
28
|
+
# :color => 'pink',
|
29
|
+
# :font_size => 24,
|
30
|
+
# :font => 'path/to/myfont.ttf',
|
31
|
+
# :rotate => -15,
|
32
|
+
# :shadow => {
|
33
|
+
# :blur => 1,
|
34
|
+
# :opacity => 0.5,
|
35
|
+
# },
|
36
|
+
# :stroke => {
|
37
|
+
# :width => 3,
|
38
|
+
# :color => color(0, 0, 0),
|
39
|
+
# }
|
40
|
+
# )
|
41
|
+
# end
|
42
|
+
class Text < Operator::Base
|
43
|
+
def operate(string_to_write, options = {})
|
44
|
+
options = {
|
45
|
+
:alignment => :top_left,
|
46
|
+
:offset => '0x0',
|
47
|
+
:antialias => true,
|
48
|
+
:color => 'black',
|
49
|
+
:font_size => '12',
|
50
|
+
:font => nil,
|
51
|
+
:text_align => :left,
|
52
|
+
:rotate => 0,
|
53
|
+
:shadow => nil,
|
54
|
+
:stroke => {
|
55
|
+
:width => 0,
|
56
|
+
:color => 'white',
|
57
|
+
}
|
58
|
+
}.merge(options)
|
59
|
+
options[:offset] = size_to_xy(options[:offset])
|
60
|
+
|
61
|
+
# prepare drawing surface
|
62
|
+
text = Magick::Draw.new
|
63
|
+
text.gravity = symbol_to_gravity(options[:alignment])
|
64
|
+
text.fill = options[:color]
|
65
|
+
text.text_antialias = options[:antialias]
|
66
|
+
text.pointsize = options[:font_size].to_i
|
67
|
+
text.rotation = options[:rotate]
|
68
|
+
text.font_weight = options[:font_weight] if options[:font_weight]
|
69
|
+
|
70
|
+
if options[:stroke][:width] > 0
|
71
|
+
text.stroke_width = options[:stroke][:width]
|
72
|
+
text.stroke = options[:stroke][:color]
|
73
|
+
end
|
74
|
+
|
75
|
+
# assign font path with to rails root unless the path is absolute
|
76
|
+
if options[:font]
|
77
|
+
font = options[:font]
|
78
|
+
font = "#{RAILS_ROOT}/#{font}" unless font =~ %r{^(~?|[A-Za-z]:)/}
|
79
|
+
text.font = font
|
80
|
+
end
|
81
|
+
|
82
|
+
# draw text on transparent image
|
83
|
+
temp_image = Magick::Image.new(@image.columns, @image.rows) { self.background_color = 'none' }
|
84
|
+
temp_image = temp_image.annotate(text, 0, 0, options[:offset][0], options[:offset][1], string_to_write)
|
85
|
+
|
86
|
+
# add drop shadow to text image
|
87
|
+
if options[:shadow]
|
88
|
+
shadow_args = [2, 2, 1, 1]
|
89
|
+
if options[:shadow].is_a?(Hash)
|
90
|
+
#shadow_args[0], shadow_args[1] = size_to_xy(options[:shadow][:offset]) if options[:shadow][:offset]
|
91
|
+
shadow_args[2] = options[:shadow][:blur] if options[:shadow][:blur]
|
92
|
+
shadow_args[3] = options[:shadow][:opacity] if options[:shadow][:opacity]
|
93
|
+
end
|
94
|
+
shadow = temp_image.shadow(*shadow_args)
|
95
|
+
temp_image = shadow.composite(temp_image, 0, 0, symbol_to_blending_mode(:over))
|
96
|
+
end
|
97
|
+
|
98
|
+
# composite text on original image
|
99
|
+
@image.composite!(temp_image, 0, 0, symbol_to_blending_mode(:over))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fleximage
|
2
|
+
module Operator
|
3
|
+
|
4
|
+
# Sharpen an image using an unsharp mask filter.
|
5
|
+
#
|
6
|
+
# image.unsharp_mask(options = {})
|
7
|
+
#
|
8
|
+
# Use the following keys in the +options+ hash:
|
9
|
+
#
|
10
|
+
# * +radius+: The radius of the Gaussian operator. The default is 0.0.
|
11
|
+
# * +sigma+: The standard deviation of the Gaussian operator. A good starting value is 1.0, which is the default.
|
12
|
+
# * +amount+: The percentage of the blurred image to be added to the receiver, specified as a fraction between 0 and 1.0. A good starting value is 1.0, which is the default.
|
13
|
+
# * +threshold+: The threshold needed to apply the amount, specified as a fraction between 0 and 1.0. A good starting value is 0.05, which is the default.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# @photo.operate do |image|
|
18
|
+
# image.unsharp_mask
|
19
|
+
# end
|
20
|
+
class UnsharpMask < Operator::Base
|
21
|
+
def operate(options = {})
|
22
|
+
options = options.symbolize_keys if options.respond_to?(:symbolize_keys)
|
23
|
+
options = {
|
24
|
+
:radius => 0.0,
|
25
|
+
:sigma => 1.0,
|
26
|
+
:amount => 1.0,
|
27
|
+
:threshold => 0.05
|
28
|
+
}.merge(options)
|
29
|
+
|
30
|
+
# sharpen image
|
31
|
+
@image = @image.unsharp_mask(options[:radius], options[:sigma], options[:amount], options[:threshold])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Fleximage
|
2
|
+
|
3
|
+
# Renders a .flexi template
|
4
|
+
class View < ActionView::TemplateHandler #:nodoc:
|
5
|
+
class TemplateDidNotReturnImage < RuntimeError #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.call(template)
|
9
|
+
"Fleximage::View.new(self).render(template)"
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(action_view)
|
13
|
+
@view = action_view
|
14
|
+
end
|
15
|
+
|
16
|
+
def render(template)
|
17
|
+
# process the view
|
18
|
+
result = @view.instance_eval do
|
19
|
+
|
20
|
+
# Shorthand color creation
|
21
|
+
def color(*args)
|
22
|
+
Fleximage::Operator::Base.color(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
#execute the template
|
26
|
+
eval(template.source)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Raise an error if object returned from template is not an image record
|
30
|
+
unless result.class.include?(Fleximage::Model::InstanceMethods)
|
31
|
+
raise TemplateDidNotReturnImage,
|
32
|
+
".flexi template was expected to return a model instance that acts_as_fleximage, but got an instance of <#{result.class}> instead."
|
33
|
+
end
|
34
|
+
|
35
|
+
# Figure out the proper format
|
36
|
+
requested_format = (@view.params[:format] || :jpg).to_sym
|
37
|
+
unless [:jpg, :gif, :png].include?(requested_format)
|
38
|
+
raise 'Image must be requested with an image type format. jpg, gif and png only are supported.'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set proper content type
|
42
|
+
@view.controller.response.content_type = Mime::Type.lookup_by_extension(requested_format.to_s).to_s
|
43
|
+
|
44
|
+
# Set proper caching headers
|
45
|
+
if defined?(Rails) && Rails.env == 'production'
|
46
|
+
@view.controller.response.headers['Cache-Control'] = 'public, max-age=86400'
|
47
|
+
end
|
48
|
+
|
49
|
+
# return rendered result
|
50
|
+
return result.output_image(:format => requested_format)
|
51
|
+
ensure
|
52
|
+
|
53
|
+
# ensure garbage collection happens after every flex image render
|
54
|
+
GC.start
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
namespace :fleximage do
|
2
|
+
|
3
|
+
# Find the model class
|
4
|
+
def model_class
|
5
|
+
raise 'You must specify a FLEXIMAGE_CLASS=MyClass' unless ENV['FLEXIMAGE_CLASS']
|
6
|
+
@model_class ||= ENV['FLEXIMAGE_CLASS'].camelcase.constantize
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Populate width and height magic columns from the current image store. Useful when migrating from on old installation."
|
10
|
+
task :dimensions => :environment do
|
11
|
+
model_class.find(:all).each do |obj|
|
12
|
+
if obj.has_image?
|
13
|
+
img = obj.load_image
|
14
|
+
obj.update_attribute :image_width, img.columns if obj.respond_to?(:image_width=)
|
15
|
+
obj.update_attribute :image_height, img.rows if obj.respond_to?(:image_height=)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
namespace :convert do
|
21
|
+
|
22
|
+
def convert_directory_format(to_format)
|
23
|
+
model_class.find(:all).each do |obj|
|
24
|
+
|
25
|
+
# Get the creation date
|
26
|
+
creation = obj[:created_at] || obj[:created_on]
|
27
|
+
|
28
|
+
# Generate both types of file paths
|
29
|
+
flat_path = "#{RAILS_ROOT}/#{model_class.image_directory}/#{obj.id}.#{model_class.image_storage_format}"
|
30
|
+
nested_path = "#{RAILS_ROOT}/#{model_class.image_directory}/#{creation.year}/#{creation.month}/#{creation.day}/#{obj.id}.#{model_class.image_storage_format}"
|
31
|
+
|
32
|
+
# Assign old path and new path based on desired directory format
|
33
|
+
if to_format == :nested
|
34
|
+
old_path = flat_path
|
35
|
+
new_path = nested_path
|
36
|
+
else
|
37
|
+
old_path = nested_path
|
38
|
+
new_path = flat_path
|
39
|
+
end
|
40
|
+
|
41
|
+
# Move the files
|
42
|
+
if old_path != new_path && File.exists?(old_path)
|
43
|
+
FileUtils.mkdir_p(File.dirname(new_path))
|
44
|
+
FileUtils.move old_path, new_path
|
45
|
+
puts "#{old_path} -> #{new_path}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def convert_image_format(to_format)
|
51
|
+
model_class.find(:all).each do |obj|
|
52
|
+
|
53
|
+
# convert DB stored images
|
54
|
+
if model_class.db_store?
|
55
|
+
if obj.image_file_data && obj.image_file_data.any?
|
56
|
+
begin
|
57
|
+
image = Magick::Image.from_blob(obj.image_file_data).first
|
58
|
+
image.format = to_format.to_s.upcase
|
59
|
+
obj.image_file_data = image.to_blob
|
60
|
+
obj.save
|
61
|
+
rescue Exception => e
|
62
|
+
puts "Could not convert image for #{model_class} with id #{obj.id}\n #{e.class} #{e}\n"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Convert file system stored images
|
67
|
+
else
|
68
|
+
# Generate both types of file paths
|
69
|
+
png_path = obj.file_path.gsub(/\.jpg$/, '.png')
|
70
|
+
jpg_path = obj.file_path.gsub(/\.png$/, '.jpg')
|
71
|
+
|
72
|
+
# Output stub
|
73
|
+
output = (to_format == :jpg) ? 'PNG -> JPG' : 'JPG -> PNG'
|
74
|
+
|
75
|
+
# Assign old path and new path based on desired image format
|
76
|
+
if to_format == :jpg
|
77
|
+
old_path = png_path
|
78
|
+
new_path = jpg_path
|
79
|
+
else
|
80
|
+
old_path = jpg_path
|
81
|
+
new_path = png_path
|
82
|
+
end
|
83
|
+
|
84
|
+
# Perform conversion
|
85
|
+
if File.exists?(old_path)
|
86
|
+
image = Magick::Image.read(old_path).first
|
87
|
+
image.format = to_format.to_s.upcase
|
88
|
+
image.write(new_path)
|
89
|
+
File.delete(old_path)
|
90
|
+
|
91
|
+
puts "#{output} : Image #{obj.id}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def ensure_db_store
|
98
|
+
col = model_class.columns.find {|c| c.name == 'image_file_data'}
|
99
|
+
unless col && col.type == :binary
|
100
|
+
raise "No image_file_data field of type :binary for this model!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "Convert a flat images/123.png style image store to a images/2007/11/12/123.png style. Requires FLEXIMAGE_CLASS=ModelName"
|
105
|
+
task :to_nested => :environment do
|
106
|
+
convert_directory_format :nested
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "Convert a nested images/2007/11/12/123.png style image store to a images/123.png style. Requires FLEXIMAGE_CLASS=ModelName"
|
110
|
+
task :to_flat => :environment do
|
111
|
+
convert_directory_format :flat
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "Convert master images stored as JPGs to PNGs"
|
115
|
+
task :to_png => :environment do
|
116
|
+
convert_image_format :png
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "Convert master images stored as PNGs to JPGs"
|
120
|
+
task :to_jpg => :environment do
|
121
|
+
convert_image_format :jpg
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "Convert master image storage to use the database. Loads all file-stored images into the database."
|
125
|
+
task :to_db => :environment do
|
126
|
+
ensure_db_store
|
127
|
+
model_class.find(:all).each do |obj|
|
128
|
+
if File.exists?(obj.file_path)
|
129
|
+
File.open(obj.file_path, 'rb') do |f|
|
130
|
+
obj.image_file_data = f.read
|
131
|
+
obj.save
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
puts "--- All images successfully moved to the database. Check to make sure the transfer worked cleanly before deleting your file system image store."
|
137
|
+
end
|
138
|
+
|
139
|
+
desc "Convert master image storage to use the file system. Loads all database images into files."
|
140
|
+
task :to_filestore => :environment do
|
141
|
+
ensure_db_store
|
142
|
+
model_class.find(:all).each do |obj|
|
143
|
+
if obj.image_file_data && obj.image_file_data.any?
|
144
|
+
File.open(obj.file_path, 'wb+') do |f|
|
145
|
+
f.write obj.image_file_data
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
puts "--- All images successfully moved to the file system. Remember to remove your image_file_data field from your models database table."
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|