fleximage 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. data/.gitignore +27 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +257 -0
  4. data/Rakefile +49 -0
  5. data/VERSION +1 -0
  6. data/autotest.rb +5 -0
  7. data/fleximage.gemspec +235 -0
  8. data/init.rb +1 -0
  9. data/lib/dsl_accessor.rb +52 -0
  10. data/lib/fleximage.rb +59 -0
  11. data/lib/fleximage/aviary_controller.rb +75 -0
  12. data/lib/fleximage/blank.rb +70 -0
  13. data/lib/fleximage/helper.rb +41 -0
  14. data/lib/fleximage/image_proxy.rb +69 -0
  15. data/lib/fleximage/legacy_view.rb +63 -0
  16. data/lib/fleximage/model.rb +689 -0
  17. data/lib/fleximage/operator/background.rb +62 -0
  18. data/lib/fleximage/operator/base.rb +189 -0
  19. data/lib/fleximage/operator/border.rb +50 -0
  20. data/lib/fleximage/operator/crop.rb +58 -0
  21. data/lib/fleximage/operator/image_overlay.rb +85 -0
  22. data/lib/fleximage/operator/resize.rb +92 -0
  23. data/lib/fleximage/operator/shadow.rb +87 -0
  24. data/lib/fleximage/operator/text.rb +104 -0
  25. data/lib/fleximage/operator/trim.rb +14 -0
  26. data/lib/fleximage/operator/unsharp_mask.rb +36 -0
  27. data/lib/fleximage/rmagick_image_patch.rb +5 -0
  28. data/lib/fleximage/string_patch.rb +5 -0
  29. data/lib/fleximage/view.rb +57 -0
  30. data/tasks/fleximage_tasks.rake +154 -0
  31. data/test/fixtures/100x1.jpg +0 -0
  32. data/test/fixtures/100x100.jpg +0 -0
  33. data/test/fixtures/1x1.jpg +0 -0
  34. data/test/fixtures/1x100.jpg +0 -0
  35. data/test/fixtures/cmyk.jpg +0 -0
  36. data/test/fixtures/not_a_photo.xml +1 -0
  37. data/test/fixtures/photo.jpg +0 -0
  38. data/test/mock_file.rb +21 -0
  39. data/test/rails_root/app/controllers/application.rb +10 -0
  40. data/test/rails_root/app/controllers/avatars_controller.rb +85 -0
  41. data/test/rails_root/app/controllers/photo_bares_controller.rb +85 -0
  42. data/test/rails_root/app/controllers/photo_dbs_controller.rb +85 -0
  43. data/test/rails_root/app/controllers/photo_files_controller.rb +85 -0
  44. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  45. data/test/rails_root/app/helpers/avatars_helper.rb +2 -0
  46. data/test/rails_root/app/helpers/photo_bares_helper.rb +2 -0
  47. data/test/rails_root/app/helpers/photo_dbs_helper.rb +2 -0
  48. data/test/rails_root/app/helpers/photo_files_helper.rb +2 -0
  49. data/test/rails_root/app/locales/de.yml +7 -0
  50. data/test/rails_root/app/locales/en.yml +8 -0
  51. data/test/rails_root/app/models/abstract.rb +8 -0
  52. data/test/rails_root/app/models/avatar.rb +4 -0
  53. data/test/rails_root/app/models/photo_bare.rb +7 -0
  54. data/test/rails_root/app/models/photo_custom_error.rb +10 -0
  55. data/test/rails_root/app/models/photo_db.rb +3 -0
  56. data/test/rails_root/app/models/photo_file.rb +3 -0
  57. data/test/rails_root/app/views/avatars/edit.html.erb +17 -0
  58. data/test/rails_root/app/views/avatars/index.html.erb +20 -0
  59. data/test/rails_root/app/views/avatars/new.html.erb +16 -0
  60. data/test/rails_root/app/views/avatars/show.html.erb +8 -0
  61. data/test/rails_root/app/views/layouts/avatars.html.erb +17 -0
  62. data/test/rails_root/app/views/layouts/photo_bares.html.erb +17 -0
  63. data/test/rails_root/app/views/layouts/photo_dbs.html.erb +17 -0
  64. data/test/rails_root/app/views/layouts/photo_files.html.erb +17 -0
  65. data/test/rails_root/app/views/photo_bares/edit.html.erb +12 -0
  66. data/test/rails_root/app/views/photo_bares/index.html.erb +18 -0
  67. data/test/rails_root/app/views/photo_bares/new.html.erb +11 -0
  68. data/test/rails_root/app/views/photo_bares/show.html.erb +3 -0
  69. data/test/rails_root/app/views/photo_dbs/edit.html.erb +32 -0
  70. data/test/rails_root/app/views/photo_dbs/index.html.erb +26 -0
  71. data/test/rails_root/app/views/photo_dbs/new.html.erb +31 -0
  72. data/test/rails_root/app/views/photo_dbs/show.html.erb +23 -0
  73. data/test/rails_root/app/views/photo_files/edit.html.erb +27 -0
  74. data/test/rails_root/app/views/photo_files/index.html.erb +24 -0
  75. data/test/rails_root/app/views/photo_files/new.html.erb +26 -0
  76. data/test/rails_root/app/views/photo_files/show.html.erb +18 -0
  77. data/test/rails_root/config/boot.rb +109 -0
  78. data/test/rails_root/config/database.yml +7 -0
  79. data/test/rails_root/config/environment.rb +66 -0
  80. data/test/rails_root/config/environments/development.rb +18 -0
  81. data/test/rails_root/config/environments/production.rb +19 -0
  82. data/test/rails_root/config/environments/sqlite3.rb +0 -0
  83. data/test/rails_root/config/environments/test.rb +22 -0
  84. data/test/rails_root/config/initializers/inflections.rb +10 -0
  85. data/test/rails_root/config/initializers/load_translations.rb +4 -0
  86. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  87. data/test/rails_root/config/routes.rb +43 -0
  88. data/test/rails_root/db/migrate/001_create_photo_files.rb +15 -0
  89. data/test/rails_root/db/migrate/002_create_photo_dbs.rb +16 -0
  90. data/test/rails_root/db/migrate/003_create_photo_bares.rb +12 -0
  91. data/test/rails_root/db/migrate/004_create_avatars.rb +13 -0
  92. data/test/rails_root/public/.htaccess +40 -0
  93. data/test/rails_root/public/404.html +30 -0
  94. data/test/rails_root/public/422.html +30 -0
  95. data/test/rails_root/public/500.html +30 -0
  96. data/test/rails_root/public/dispatch.cgi +10 -0
  97. data/test/rails_root/public/dispatch.fcgi +24 -0
  98. data/test/rails_root/public/dispatch.rb +10 -0
  99. data/test/rails_root/public/favicon.ico +0 -0
  100. data/test/rails_root/public/images/rails.png +0 -0
  101. data/test/rails_root/public/index.html +277 -0
  102. data/test/rails_root/public/javascripts/application.js +2 -0
  103. data/test/rails_root/public/javascripts/controls.js +963 -0
  104. data/test/rails_root/public/javascripts/dragdrop.js +972 -0
  105. data/test/rails_root/public/javascripts/effects.js +1120 -0
  106. data/test/rails_root/public/javascripts/prototype.js +4225 -0
  107. data/test/rails_root/public/robots.txt +5 -0
  108. data/test/rails_root/public/stylesheets/scaffold.css +74 -0
  109. data/test/rails_root/vendor/plugins/fleximage/init.rb +2 -0
  110. data/test/test_helper.rb +81 -0
  111. data/test/unit/abstract_test.rb +20 -0
  112. data/test/unit/basic_model_test.rb +30 -0
  113. data/test/unit/blank_test.rb +23 -0
  114. data/test/unit/default_image_path_option_test.rb +16 -0
  115. data/test/unit/dsl_accessor_test.rb +120 -0
  116. data/test/unit/file_upload_from_local_test.rb +31 -0
  117. data/test/unit/file_upload_from_strings_test.rb +23 -0
  118. data/test/unit/file_upload_from_url_test.rb +35 -0
  119. data/test/unit/file_upload_to_db_test.rb +41 -0
  120. data/test/unit/i18n_messages_test.rb +49 -0
  121. data/test/unit/image_directory_option_test.rb +18 -0
  122. data/test/unit/image_proxy_test.rb +17 -0
  123. data/test/unit/image_storage_format_option_test.rb +31 -0
  124. data/test/unit/magic_columns_test.rb +30 -0
  125. data/test/unit/minimum_image_size_test.rb +56 -0
  126. data/test/unit/operator_base_test.rb +124 -0
  127. data/test/unit/operator_resize_test.rb +18 -0
  128. data/test/unit/preprocess_image_option_test.rb +21 -0
  129. data/test/unit/require_image_option_test.rb +30 -0
  130. data/test/unit/temp_image_test.rb +17 -0
  131. data/test/unit/use_creation_date_based_directories_option_test.rb +16 -0
  132. metadata +279 -0
@@ -0,0 +1,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,5 @@
1
+ unless "string".respond_to?(:present?)
2
+ class String
3
+ alias_method :present?, :present?
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