dragonfly 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dragonfly might be problematic. Click here for more details.

Files changed (67) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.rails.2.3.5 +0 -1
  3. data/History.md +12 -0
  4. data/README.md +4 -2
  5. data/VERSION +1 -1
  6. data/config.ru +1 -1
  7. data/dragonfly.gemspec +256 -179
  8. data/extra_docs/Analysers.md +15 -6
  9. data/extra_docs/Configuration.md +13 -2
  10. data/extra_docs/Encoding.md +20 -7
  11. data/extra_docs/GeneralUsage.md +8 -5
  12. data/extra_docs/Generators.md +17 -7
  13. data/extra_docs/Heroku.md +1 -2
  14. data/extra_docs/MimeTypes.md +1 -1
  15. data/extra_docs/Models.md +1 -1
  16. data/extra_docs/Mongo.md +2 -2
  17. data/extra_docs/Processing.md +15 -7
  18. data/extra_docs/Rack.md +2 -3
  19. data/extra_docs/Rails2.md +2 -3
  20. data/extra_docs/Rails3.md +2 -3
  21. data/extra_docs/Sinatra.md +2 -2
  22. data/extra_docs/URLs.md +6 -4
  23. data/features/3.0.3.feature +8 -0
  24. data/features/steps/rails_steps.rb +2 -2
  25. data/features/support/env.rb +1 -1
  26. data/fixtures/files/app/views/albums/new.html.erb +4 -4
  27. data/fixtures/rails_2.3.5/template.rb +0 -1
  28. data/fixtures/{rails_3.0.0 → rails_3.0.3}/template.rb +0 -1
  29. data/irbrc.rb +1 -1
  30. data/lib/dragonfly/analysis/image_magick_analyser.rb +47 -0
  31. data/lib/dragonfly/app.rb +2 -0
  32. data/lib/dragonfly/config/image_magick.rb +41 -0
  33. data/lib/dragonfly/data_storage/file_data_store.rb +4 -2
  34. data/lib/dragonfly/data_storage/s3data_store.rb +7 -3
  35. data/lib/dragonfly/encoding/image_magick_encoder.rb +57 -0
  36. data/lib/dragonfly/generation/hash_with_css_style_keys.rb +23 -0
  37. data/lib/dragonfly/generation/image_magick_generator.rb +140 -0
  38. data/lib/dragonfly/generation/r_magick_generator.rb +0 -18
  39. data/lib/dragonfly/image_magick_utils.rb +81 -0
  40. data/lib/dragonfly/processing/image_magick_processor.rb +99 -0
  41. data/lib/dragonfly/rails/images.rb +1 -1
  42. data/lib/dragonfly/temp_object.rb +7 -6
  43. data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +15 -0
  44. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +5 -49
  45. data/spec/dragonfly/analysis/shared_analyser_spec.rb +51 -0
  46. data/spec/dragonfly/app_spec.rb +2 -0
  47. data/spec/dragonfly/data_storage/data_store_spec.rb +6 -0
  48. data/spec/dragonfly/data_storage/file_data_store_spec.rb +1 -1
  49. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +11 -1
  50. data/spec/dragonfly/deprecation_spec.rb +2 -2
  51. data/spec/dragonfly/encoding/image_magick_encoder_spec.rb +41 -0
  52. data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +3 -6
  53. data/spec/dragonfly/generation/hash_with_css_style_keys_spec.rb +24 -0
  54. data/spec/dragonfly/generation/image_magick_generator_spec.rb +12 -0
  55. data/spec/dragonfly/generation/r_magick_generator_spec.rb +12 -123
  56. data/spec/dragonfly/generation/shared_generator_spec.rb +91 -0
  57. data/spec/dragonfly/image_magick_utils_spec.rb +16 -0
  58. data/spec/dragonfly/processing/image_magick_processor_spec.rb +29 -0
  59. data/spec/dragonfly/processing/r_magick_processor_spec.rb +2 -212
  60. data/spec/dragonfly/processing/shared_processing_spec.rb +215 -0
  61. data/spec/image_matchers.rb +6 -0
  62. data/spec/spec_helper.rb +11 -0
  63. data/yard/templates/default/fulldoc/html/css/common.css +9 -2
  64. data/yard/templates/default/layout/html/layout.erb +12 -1
  65. metadata +310 -11
  66. data/.gitignore +0 -15
  67. data/features/rails_3.0.0.feature +0 -8
@@ -1,5 +1,4 @@
1
1
  gem 'rack-cache', :require => 'rack/cache'
2
- gem 'rmagick', '2.12.2', :require => 'RMagick'
3
2
 
4
3
  gem 'capybara'
5
4
  gem 'cucumber-rails'
data/irbrc.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "rubygems"
2
2
  require "bundler/setup"
3
3
  require File.dirname(__FILE__) + '/lib/dragonfly'
4
- APP = Dragonfly[:images].configure_with(:rmagick)
4
+ APP = Dragonfly[:images].configure_with(:imagemagick)
5
5
 
6
6
  # available_uids = `find #{APP.datastore.root_path} ! -type d`.split("\n").map do |file|
7
7
  # file.sub("#{APP.datastore.root_path}/", '')
@@ -0,0 +1,47 @@
1
+ module Dragonfly
2
+ module Analysis
3
+ class ImageMagickAnalyser
4
+
5
+ include ImageMagickUtils
6
+ include Configurable
7
+
8
+ def width(temp_object)
9
+ identify(temp_object)[:width]
10
+ end
11
+
12
+ def height(temp_object)
13
+ identify(temp_object)[:height]
14
+ end
15
+
16
+ def aspect_ratio(temp_object)
17
+ attrs = identify(temp_object)
18
+ attrs[:width].to_f / attrs[:height]
19
+ end
20
+
21
+ def portrait?(temp_object)
22
+ attrs = identify(temp_object)
23
+ attrs[:width] <= attrs[:height]
24
+ end
25
+
26
+ def landscape?(temp_object)
27
+ attrs = identify(temp_object)
28
+ attrs[:width] >= attrs[:height]
29
+ end
30
+
31
+ def depth(temp_object)
32
+ identify(temp_object)[:depth]
33
+ end
34
+
35
+ def number_of_colours(temp_object)
36
+ details = raw_identify(temp_object, '-verbose -unique')
37
+ details[/Colors: (\d+)/, 1].to_i
38
+ end
39
+ alias number_of_colors number_of_colours
40
+
41
+ def format(temp_object)
42
+ identify(temp_object)[:format]
43
+ end
44
+
45
+ end
46
+ end
47
+ end
data/lib/dragonfly/app.rb CHANGED
@@ -67,6 +67,8 @@ module Dragonfly
67
67
  attr_accessor :job_definitions
68
68
 
69
69
  SAVED_CONFIGS = {
70
+ :imagemagick => 'ImageMagick',
71
+ :image_magick => 'ImageMagick',
70
72
  :rmagick => 'RMagick',
71
73
  :r_magick => 'RMagick',
72
74
  :rails => 'Rails',
@@ -0,0 +1,41 @@
1
+ module Dragonfly
2
+ module Config
3
+
4
+ # ImageMagick is a saved configuration for Dragonfly apps, which does the following:
5
+ # - registers an imagemagick analyser
6
+ # - registers an imagemagick processor
7
+ # - registers an imagemagick encoder
8
+ # - adds thumb shortcuts like '280x140!', etc.
9
+ # Look at the source code for apply_configuration to see exactly how it configures the app.
10
+ module ImageMagick
11
+
12
+ def self.apply_configuration(app, opts={})
13
+ app.configure do |c|
14
+ c.analyser.register(Analysis::ImageMagickAnalyser)
15
+ c.processor.register(Processing::ImageMagickProcessor)
16
+ c.encoder.register(Encoding::ImageMagickEncoder)
17
+ c.generator.register(Generation::ImageMagickGenerator)
18
+
19
+ c.job :thumb do |geometry, format|
20
+ process :thumb, geometry
21
+ encode format if format
22
+ end
23
+ c.job :gif do
24
+ encode :gif
25
+ end
26
+ c.job :jpg do
27
+ encode :jpg
28
+ end
29
+ c.job :png do
30
+ encode :png
31
+ end
32
+ c.job :convert do |args, format|
33
+ process :convert, args, format
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -67,7 +67,7 @@ module Dragonfly
67
67
  end
68
68
 
69
69
  def relative(absolute_path)
70
- absolute_path[/^#{root_path}\/(.*)$/, 1]
70
+ absolute_path[/^#{root_path}\/?(.*)$/, 1]
71
71
  end
72
72
 
73
73
  def directory_empty?(path)
@@ -79,7 +79,9 @@ module Dragonfly
79
79
  end
80
80
 
81
81
  def relative_path_for(filename)
82
- "#{Time.now.strftime '%Y/%m/%d'}/#{filename.gsub(/[^\w.]+/,'_')}"
82
+ time = Time.now
83
+ msec = time.usec / 1000
84
+ "#{time.strftime '%Y/%m/%d/%H_%M_%S'}_#{msec}_#{filename.gsub(/[^\w.]+/,'_')}"
83
85
  end
84
86
 
85
87
  def store_extra_data(data_path, temp_object)
@@ -34,10 +34,14 @@ module Dragonfly
34
34
  def store(temp_object, opts={})
35
35
  uid = opts[:path] || generate_uid(temp_object.name || 'file')
36
36
  ensure_initialized
37
- object = use_filesystem ? temp_object.file : temp_object.data
38
37
  extra_data = temp_object.attributes
39
- S3Object.store(uid, object, bucket_name, s3_metadata_for(extra_data))
40
- object.close if use_filesystem
38
+ if use_filesystem
39
+ temp_object.file do |f|
40
+ S3Object.store(uid, f, bucket_name, s3_metadata_for(extra_data))
41
+ end
42
+ else
43
+ S3Object.store(uid, temp_object.data, bucket_name, s3_metadata_for(extra_data))
44
+ end
41
45
  uid
42
46
  end
43
47
 
@@ -0,0 +1,57 @@
1
+ module Dragonfly
2
+ module Encoding
3
+ class ImageMagickEncoder
4
+
5
+ include Configurable
6
+ include ImageMagickUtils
7
+
8
+ configurable_attr :supported_formats, [
9
+ :ai,
10
+ :bmp,
11
+ :eps,
12
+ :gif,
13
+ :gif87,
14
+ :ico,
15
+ :j2c,
16
+ :jp2,
17
+ :jpeg,
18
+ :jpg,
19
+ :pbm,
20
+ :pcd,
21
+ :pct,
22
+ :pcx,
23
+ :pdf,
24
+ :pict,
25
+ :pjpeg,
26
+ :png,
27
+ :png24,
28
+ :png32,
29
+ :png8,
30
+ :pnm,
31
+ :ppm,
32
+ :ps,
33
+ :psd,
34
+ :ras,
35
+ :tga,
36
+ :tiff,
37
+ :wbmp,
38
+ :xbm,
39
+ :xpm,
40
+ :xwd
41
+ ]
42
+
43
+ def encode(temp_object, format, args='')
44
+ format = format.to_s.downcase
45
+ throw :unable_to_handle unless supported_formats.include?(format.to_sym)
46
+ details = identify(temp_object)
47
+
48
+ if details[:format] == format.to_sym && args.empty?
49
+ temp_object
50
+ else
51
+ convert(temp_object, args, format)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ module Dragonfly
2
+ module Generation
3
+
4
+ # HashWithCssStyleKeys is solely for being able to access a hash
5
+ # which has css-style keys (e.g. 'font-size') with the underscore
6
+ # symbol version
7
+ # @example
8
+ # opts = {'font-size' => '23px', :color => 'white'}
9
+ # opts = HashWithCssStyleKeys[opts]
10
+ # opts[:font_size] # ===> '23px'
11
+ # opts[:color] # ===> 'white'
12
+ class HashWithCssStyleKeys < Hash
13
+ def [](key)
14
+ super || (
15
+ str_key = key.to_s
16
+ css_key = str_key.gsub('_','-')
17
+ super(str_key) || super(css_key) || super(css_key.to_sym)
18
+ )
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,140 @@
1
+ module Dragonfly
2
+ module Generation
3
+ class ImageMagickGenerator
4
+
5
+ FONT_STYLES = {
6
+ 'normal' => 'normal',
7
+ 'italic' => 'italic',
8
+ 'oblique' => 'oblique'
9
+ }
10
+
11
+ FONT_STRETCHES = {
12
+ 'normal' => 'normal',
13
+ 'semi-condensed' => 'semi-condensed',
14
+ 'condensed' => 'condensed',
15
+ 'extra-condensed' => 'extra-condensed',
16
+ 'ultra-condensed' => 'ultra-condensed',
17
+ 'semi-expanded' => 'semi-expanded',
18
+ 'expanded' => 'expanded',
19
+ 'extra-expanded' => 'extra-expanded',
20
+ 'ultra-expanded' => 'ultra-expanded'
21
+ }
22
+
23
+ FONT_WEIGHTS = {
24
+ 'normal' => 'normal',
25
+ 'bold' => 'bold',
26
+ 'bolder' => 'bolder',
27
+ 'lighter' => 'lighter',
28
+ '100' => 100,
29
+ '200' => 200,
30
+ '300' => 300,
31
+ '400' => 400,
32
+ '500' => 500,
33
+ '600' => 600,
34
+ '700' => 700,
35
+ '800' => 800,
36
+ '900' => 900
37
+ }
38
+
39
+ include ImageMagickUtils
40
+ include Configurable
41
+
42
+ def plasma(width, height, format='png')
43
+ tempfile = new_tempfile(format)
44
+ run "#{convert_command} -size #{width}x#{height} plasma:fractal #{tempfile.path}"
45
+ [
46
+ tempfile,
47
+ {:format => format.to_sym, :name => "plasma.#{format}"}
48
+ ]
49
+ end
50
+
51
+ def text(string, opts={})
52
+ opts = HashWithCssStyleKeys[opts]
53
+ args = []
54
+ format = (opts[:format] || :png)
55
+ background = opts[:background_color] || 'none'
56
+ font_size = (opts[:font_size] || 12).to_i
57
+ escaped_string = "\"#{string.gsub(/"/, '\"')}\""
58
+
59
+ # Settings
60
+ args.push("-gravity NorthWest")
61
+ args.push("-antialias")
62
+ args.push("-pointsize #{font_size}")
63
+ args.push("-font '#{opts[:font]}'") if opts[:font]
64
+ args.push("-family '#{opts[:font_family]}'") if opts[:font_family]
65
+ args.push("-fill #{opts[:color]}") if opts[:color]
66
+ args.push("-stroke #{opts[:stroke_color]}") if opts[:stroke_color]
67
+ args.push("-style #{FONT_STYLES[opts[:font_style]]}") if opts[:font_style]
68
+ args.push("-stretch #{FONT_STRETCHES[opts[:font_stretch]]}") if opts[:font_stretch]
69
+ args.push("-weight #{FONT_WEIGHTS[opts[:font_weight]]}") if opts[:font_weight]
70
+ args.push("-background #{background}")
71
+ args.push("label:#{escaped_string}")
72
+
73
+ # Padding
74
+ pt, pr, pb, pl = parse_padding_string(opts[:padding]) if opts[:padding]
75
+ padding_top = (opts[:padding_top] || pt || 0)
76
+ padding_right = (opts[:padding_right] || pr || 0)
77
+ padding_bottom = (opts[:padding_bottom] || pb || 0)
78
+ padding_left = (opts[:padding_left] || pl || 0)
79
+
80
+ tempfile = new_tempfile(format)
81
+ run "#{convert_command} #{args.join(' ')} #{tempfile.path}"
82
+
83
+ if (padding_top || padding_right || padding_bottom || padding_left)
84
+ attrs = identify(tempfile)
85
+ text_width = attrs[:width].to_i
86
+ text_height = attrs[:height].to_i
87
+ width = padding_left + text_width + padding_right
88
+ height = padding_top + text_height + padding_bottom
89
+
90
+ args = args.slice(0, args.length - 2)
91
+ args.push("-size #{width}x#{height}")
92
+ args.push("xc:#{background}")
93
+ args.push("-annotate 0x0+#{padding_left}+#{padding_top} #{escaped_string}")
94
+ run "#{convert_command} #{args.join(' ')} #{tempfile.path}"
95
+ end
96
+
97
+ [
98
+ tempfile,
99
+ {:format => format, :name => "text.#{format}"}
100
+ ]
101
+ end
102
+
103
+ private
104
+
105
+ # Use css-style padding declaration, i.e.
106
+ # 10 (all sides)
107
+ # 10 5 (top/bottom, left/right)
108
+ # 10 5 10 (top, left/right, bottom)
109
+ # 10 5 10 5 (top, right, bottom, left)
110
+ def parse_padding_string(str)
111
+ padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i}
112
+ case padding_parts.size
113
+ when 1
114
+ p = padding_parts.first
115
+ [p,p,p,p]
116
+ when 2
117
+ p,q = padding_parts
118
+ [p,q,p,q]
119
+ when 3
120
+ p,q,r = padding_parts
121
+ [p,q,r,q]
122
+ when 4
123
+ padding_parts
124
+ else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
125
+ end
126
+ end
127
+
128
+ def scale_factor_for(font_size)
129
+ # Scale approximately to 64 if below
130
+ min_size = 64
131
+ if font_size < min_size
132
+ (min_size.to_f / font_size).ceil
133
+ else
134
+ 1
135
+ end.to_f
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -38,24 +38,6 @@ module Dragonfly
38
38
  '900' => 900
39
39
  }
40
40
 
41
- # HashWithCssStyleKeys is solely for being able to access a hash
42
- # which has css-style keys (e.g. 'font-size') with the underscore
43
- # symbol version
44
- # @example
45
- # opts = {'font-size' => '23px', :color => 'white'}
46
- # opts = HashWithCssStyleKeys[opts]
47
- # opts[:font_size] # ===> '23px'
48
- # opts[:color] # ===> 'white'
49
- class HashWithCssStyleKeys < Hash
50
- def [](key)
51
- super || (
52
- str_key = key.to_s
53
- css_key = str_key.gsub('_','-')
54
- super(str_key) || super(css_key) || super(css_key.to_sym)
55
- )
56
- end
57
- end
58
-
59
41
  include RMagickUtils
60
42
  include Configurable
61
43
  configurable_attr :use_filesystem, true
@@ -0,0 +1,81 @@
1
+ require 'tempfile'
2
+
3
+ module Dragonfly
4
+ module ImageMagickUtils
5
+
6
+ # Exceptions
7
+ class ShellCommandFailed < RuntimeError; end
8
+
9
+ class << self
10
+ include Configurable
11
+ configurable_attr :convert_command, "convert"
12
+ configurable_attr :identify_command, "identify"
13
+ configurable_attr :log_commands, false
14
+ end
15
+
16
+ include Loggable
17
+
18
+ private
19
+
20
+ def convert(temp_object, args='', format=nil)
21
+ tempfile = new_tempfile(format)
22
+ run "#{convert_command} #{args} #{temp_object.path} #{tempfile.path}"
23
+ tempfile
24
+ end
25
+
26
+ def identify(temp_object)
27
+ # example of details string:
28
+ # myimage.png PNG 200x100 200x100+0+0 8-bit DirectClass 31.2kb
29
+ details = raw_identify(temp_object)
30
+ filename, format, geometry, geometry_2, depth, image_class, size = details.split(' ')
31
+ width, height = geometry.split('x')
32
+ {
33
+ :filename => filename,
34
+ :format => format.downcase.to_sym,
35
+ :width => width.to_i,
36
+ :height => height.to_i,
37
+ :depth => depth.to_i,
38
+ :image_class => image_class
39
+ }
40
+ end
41
+
42
+ def raw_identify(temp_object, args='')
43
+ run "#{identify_command} #{args} #{temp_object.path}"
44
+ end
45
+
46
+ def new_tempfile(ext=nil)
47
+ tempfile = ext ? Tempfile.new(['dragonfly', ".#{ext}"]) : Tempfile.new('dragonfly')
48
+ tempfile.binmode
49
+ tempfile.close
50
+ tempfile
51
+ end
52
+
53
+ def convert_command
54
+ ImageMagickUtils.convert_command
55
+ end
56
+
57
+ def identify_command
58
+ ImageMagickUtils.identify_command
59
+ end
60
+
61
+ def run(command)
62
+ log.debug("Running command: #{command}") if ImageMagickUtils.log_commands
63
+ begin
64
+ result = `#{command}`
65
+ rescue Errno::ENOENT
66
+ raise_shell_command_failed(command)
67
+ end
68
+ if $?.exitstatus == 1
69
+ throw :unable_to_handle
70
+ elsif !$?.success?
71
+ raise_shell_command_failed(command)
72
+ end
73
+ result
74
+ end
75
+
76
+ def raise_shell_command_failed(command)
77
+ raise ShellCommandFailed, "Command failed (#{command}) with exit status #{$?.exitstatus}"
78
+ end
79
+
80
+ end
81
+ end