robinboening-fleximage 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/CHANGELOG.rdoc +14 -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 +179 -0
  8. data/init.rb +1 -0
  9. data/lib/dsl_accessor.rb +52 -0
  10. data/lib/fleximage.rb +56 -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 +711 -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/view.rb +57 -0
  29. data/tasks/fleximage_tasks.rake +154 -0
  30. data/test/fixtures/100x1.jpg +0 -0
  31. data/test/fixtures/100x100.jpg +0 -0
  32. data/test/fixtures/1x1.jpg +0 -0
  33. data/test/fixtures/1x100.jpg +0 -0
  34. data/test/fixtures/cmyk.jpg +0 -0
  35. data/test/fixtures/not_a_photo.xml +1 -0
  36. data/test/fixtures/photo.jpg +0 -0
  37. data/test/mock_file.rb +21 -0
  38. data/test/rails_root/app/controllers/application.rb +10 -0
  39. data/test/rails_root/app/controllers/avatars_controller.rb +85 -0
  40. data/test/rails_root/app/controllers/photo_bares_controller.rb +85 -0
  41. data/test/rails_root/app/controllers/photo_dbs_controller.rb +85 -0
  42. data/test/rails_root/app/controllers/photo_files_controller.rb +85 -0
  43. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  44. data/test/rails_root/app/helpers/avatars_helper.rb +2 -0
  45. data/test/rails_root/app/helpers/photo_bares_helper.rb +2 -0
  46. data/test/rails_root/app/helpers/photo_dbs_helper.rb +2 -0
  47. data/test/rails_root/app/helpers/photo_files_helper.rb +2 -0
  48. data/test/rails_root/app/locales/de.yml +7 -0
  49. data/test/rails_root/app/locales/en.yml +8 -0
  50. data/test/rails_root/app/models/abstract.rb +8 -0
  51. data/test/rails_root/app/models/avatar.rb +4 -0
  52. data/test/rails_root/app/models/photo_bare.rb +7 -0
  53. data/test/rails_root/app/models/photo_custom_error.rb +10 -0
  54. data/test/rails_root/app/models/photo_db.rb +3 -0
  55. data/test/rails_root/app/models/photo_file.rb +3 -0
  56. data/test/rails_root/app/models/photo_s3.rb +5 -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 +16 -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/db/migrate/005_create_photo_s3s.rb +12 -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/s3_stubs.rb +7 -0
  112. data/test/test_helper.rb +82 -0
  113. data/test/unit/abstract_test.rb +20 -0
  114. data/test/unit/basic_model_test.rb +40 -0
  115. data/test/unit/blank_test.rb +23 -0
  116. data/test/unit/default_image_path_option_test.rb +16 -0
  117. data/test/unit/dsl_accessor_test.rb +120 -0
  118. data/test/unit/file_upload_from_local_test.rb +31 -0
  119. data/test/unit/file_upload_from_strings_test.rb +23 -0
  120. data/test/unit/file_upload_from_url_test.rb +35 -0
  121. data/test/unit/file_upload_to_db_test.rb +41 -0
  122. data/test/unit/has_store_test.rb +4 -0
  123. data/test/unit/i18n_messages_test.rb +49 -0
  124. data/test/unit/image_directory_option_test.rb +20 -0
  125. data/test/unit/image_proxy_test.rb +17 -0
  126. data/test/unit/image_storage_format_option_test.rb +31 -0
  127. data/test/unit/magic_columns_test.rb +34 -0
  128. data/test/unit/minimum_image_size_test.rb +56 -0
  129. data/test/unit/operator_base_test.rb +124 -0
  130. data/test/unit/operator_resize_test.rb +18 -0
  131. data/test/unit/preprocess_image_option_test.rb +21 -0
  132. data/test/unit/require_image_option_test.rb +30 -0
  133. data/test/unit/temp_image_test.rb +23 -0
  134. data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
  135. 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,14 @@
1
+ module Fleximage
2
+ module Operator
3
+
4
+ # Trim off all the pixels around the image border that have the same color.
5
+ #
6
+ # image.trim
7
+ class Trim < Operator::Base
8
+ def operate()
9
+ @image.trim!(true)
10
+ end
11
+ end
12
+
13
+ end
14
+ 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,5 @@
1
+ class Magick::Image
2
+ def dispose!
3
+ destroy! if respond_to?(:destroy!)
4
+ end
5
+ 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