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.
- data/.gitignore +27 -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 +235 -0
- data/init.rb +1 -0
- data/lib/dsl_accessor.rb +52 -0
- data/lib/fleximage.rb +59 -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 +689 -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/string_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/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 +15 -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/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/test_helper.rb +81 -0
- data/test/unit/abstract_test.rb +20 -0
- data/test/unit/basic_model_test.rb +30 -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/i18n_messages_test.rb +49 -0
- data/test/unit/image_directory_option_test.rb +18 -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 +30 -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 +17 -0
- data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
- metadata +279 -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
|