tdd-attachment_fu 0.9.6

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 (49) hide show
  1. data/CHANGELOG +44 -0
  2. data/LICENSE +20 -0
  3. data/README +197 -0
  4. data/Rakefile +22 -0
  5. data/amazon_s3.yml.tpl +17 -0
  6. data/init.rb +16 -0
  7. data/install.rb +7 -0
  8. data/lib/geometry.rb +96 -0
  9. data/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb +211 -0
  10. data/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +39 -0
  11. data/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +126 -0
  12. data/lib/technoweenie/attachment_fu/backends/s3_backend.rb +394 -0
  13. data/lib/technoweenie/attachment_fu/processors/core_image_processor.rb +65 -0
  14. data/lib/technoweenie/attachment_fu/processors/gd2_processor.rb +62 -0
  15. data/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +80 -0
  16. data/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +138 -0
  17. data/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +65 -0
  18. data/lib/technoweenie/attachment_fu.rb +514 -0
  19. data/rackspace_cloudfiles.yml.tpl +14 -0
  20. data/test/backends/db_file_test.rb +16 -0
  21. data/test/backends/file_system_test.rb +143 -0
  22. data/test/backends/remote/cloudfiles_test.rb +102 -0
  23. data/test/backends/remote/s3_test.rb +119 -0
  24. data/test/base_attachment_tests.rb +77 -0
  25. data/test/basic_test.rb +70 -0
  26. data/test/database.yml +18 -0
  27. data/test/extra_attachment_test.rb +67 -0
  28. data/test/fixtures/attachment.rb +249 -0
  29. data/test/fixtures/files/fake/rails.png +0 -0
  30. data/test/fixtures/files/foo.txt +1 -0
  31. data/test/fixtures/files/rails.jpg +0 -0
  32. data/test/fixtures/files/rails.png +0 -0
  33. data/test/geometry_test.rb +114 -0
  34. data/test/processors/core_image_test.rb +50 -0
  35. data/test/processors/gd2_test.rb +44 -0
  36. data/test/processors/image_science_test.rb +47 -0
  37. data/test/processors/mini_magick_test.rb +116 -0
  38. data/test/processors/rmagick_test.rb +267 -0
  39. data/test/schema.rb +134 -0
  40. data/test/test_helper.rb +153 -0
  41. data/test/validation_test.rb +55 -0
  42. data/vendor/red_artisan/core_image/filters/color.rb +27 -0
  43. data/vendor/red_artisan/core_image/filters/effects.rb +31 -0
  44. data/vendor/red_artisan/core_image/filters/perspective.rb +25 -0
  45. data/vendor/red_artisan/core_image/filters/quality.rb +25 -0
  46. data/vendor/red_artisan/core_image/filters/scale.rb +47 -0
  47. data/vendor/red_artisan/core_image/filters/watermark.rb +32 -0
  48. data/vendor/red_artisan/core_image/processor.rb +123 -0
  49. metadata +103 -0
@@ -0,0 +1,80 @@
1
+ require 'image_science'
2
+ module Technoweenie # :nodoc:
3
+ module AttachmentFu # :nodoc:
4
+ module Processors
5
+ module ImageScienceProcessor
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.alias_method_chain :process_attachment, :processing
9
+ end
10
+
11
+ module ClassMethods
12
+ # Yields a block containing an Image Science image for the given binary data.
13
+ def with_image(file, &block)
14
+ ::ImageScience.with_image file, &block
15
+ end
16
+ end
17
+
18
+ protected
19
+ def process_attachment_with_processing
20
+ return unless process_attachment_without_processing && image?
21
+ with_image do |img|
22
+ self.width = img.width if respond_to?(:width)
23
+ self.height = img.height if respond_to?(:height)
24
+ resize_image_or_thumbnail! img
25
+ end
26
+ end
27
+
28
+ # Performs the actual resizing operation for a thumbnail
29
+ def resize_image(img, size)
30
+ # create a dummy temp file to write to
31
+ # ImageScience doesn't handle all gifs properly, so it converts them to
32
+ # pngs for thumbnails. It has something to do with trying to save gifs
33
+ # with a larger palette than 256 colors, which is all the gif format
34
+ # supports.
35
+ filename.sub! /gif$/i, 'png'
36
+ content_type.sub!(/gif$/, 'png')
37
+ temp_paths.unshift write_to_temp_file(filename)
38
+ grab_dimensions = lambda do |img|
39
+ self.width = img.width if respond_to?(:width)
40
+ self.height = img.height if respond_to?(:height)
41
+
42
+ # We don't check for quality being a 0-100 value as we also allow FreeImage JPEG_xxx constants.
43
+ quality = attachment_options[:jpeg_quality]
44
+ # Traditional ImageScience has a 1-arg save method, tdd-image_science has 1 mandatory + 1 optional
45
+ if quality && img.method(:save).arity == -2
46
+ img.save self.temp_path, quality
47
+ else
48
+ img.save self.temp_path
49
+ end
50
+ self.size = File.size(self.temp_path)
51
+ callback_with_args :after_resize, img
52
+ end
53
+
54
+ size = size.first if size.is_a?(Array) && size.length == 1
55
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
56
+ if size.is_a?(Fixnum)
57
+ img.thumbnail(size, &grab_dimensions)
58
+ else
59
+ img.resize(size[0], size[1], &grab_dimensions)
60
+ end
61
+ else
62
+ new_size = [img.width, img.height] / size.to_s
63
+ if size.ends_with?('!')
64
+ aspect = new_size[0].to_f / new_size[1].to_f
65
+ ih, iw = img.height, img.width
66
+ w, h = (ih * aspect), (iw / aspect)
67
+ w = [iw, w].min.to_i
68
+ h = [ih, h].min.to_i
69
+ img.with_crop((iw-w)/2, (ih-h)/2, (iw+w)/2, (ih+h)/2) { |crop|
70
+ crop.resize(new_size[0], new_size[1], &grab_dimensions)
71
+ }
72
+ else
73
+ img.resize(new_size[0], new_size[1], &grab_dimensions)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,138 @@
1
+ require 'mini_magick'
2
+ module Technoweenie # :nodoc:
3
+ module AttachmentFu # :nodoc:
4
+ module Processors
5
+ module MiniMagickProcessor
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.alias_method_chain :process_attachment, :processing
9
+ end
10
+
11
+ module ClassMethods
12
+ # Yields a block containing an MiniMagick Image for the given binary data.
13
+ def with_image(file, &block)
14
+ begin
15
+ binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.from_file(file) unless !Object.const_defined?(:MiniMagick)
16
+ rescue
17
+ # Log the failure to load the image.
18
+ logger.debug("Exception working with image: #{$!}")
19
+ binary_data = nil
20
+ end
21
+ block.call binary_data if block && binary_data
22
+ ensure
23
+ !binary_data.nil?
24
+ end
25
+ end
26
+
27
+ protected
28
+ def process_attachment_with_processing
29
+ return unless process_attachment_without_processing
30
+ with_image do |img|
31
+ resize_image_or_thumbnail! img
32
+ self.width = img[:width] if respond_to?(:width)
33
+ self.height = img[:height] if respond_to?(:height)
34
+ callback_with_args :after_resize, img
35
+ end if image?
36
+ end
37
+
38
+ # Performs the actual resizing operation for a thumbnail
39
+ def resize_image(img, size)
40
+ size = size.first if size.is_a?(Array) && size.length == 1
41
+ img.combine_options do |commands|
42
+ commands.strip unless attachment_options[:keep_profile]
43
+
44
+ # GIF is not handled correctly, so we move to PNG, as in other processors…
45
+ format = img['format']
46
+ if format == 'GIF'
47
+ commands.format('PNG')
48
+ elsif format == 'JPEG'
49
+ quality = attachment_options[:jpeg_quality]
50
+ quality = quality ? quality.to_i : -1
51
+ # FIXME: this DOESN'T work, for whatever reason…
52
+ commands.quality(quality) if quality.between?(0, 100)
53
+ end
54
+
55
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
56
+ if size.is_a?(Fixnum)
57
+ size = [size, size]
58
+ commands.resize(size.join('x'))
59
+ else
60
+ commands.resize(size.join('x') + '!')
61
+ end
62
+ # extend to thumbnail size
63
+ elsif size.is_a?(String) and size =~ /e$/
64
+ size = size.gsub(/e/, '')
65
+ commands.resize(size.to_s + '>')
66
+ commands.background('#ffffff')
67
+ commands.gravity('center')
68
+ commands.extent(size)
69
+ # crop thumbnail, the smart way
70
+ elsif size.is_a?(String) and size =~ /c$/
71
+ size = size.gsub(/c/, '')
72
+
73
+ # calculate sizes and aspect ratio
74
+ thumb_width, thumb_height = size.split("x")
75
+ thumb_width = thumb_width.to_f
76
+ thumb_height = thumb_height.to_f
77
+
78
+ thumb_aspect = thumb_width.to_f / thumb_height.to_f
79
+ image_width, image_height = img[:width].to_f, img[:height].to_f
80
+ image_aspect = image_width / image_height
81
+
82
+ # only crop if image is not smaller in both dimensions
83
+ unless image_width < thumb_width and image_height < thumb_height
84
+ command = calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
85
+
86
+ # crop image
87
+ commands.extract(command)
88
+ end
89
+
90
+ # don not resize if image is not as height or width then thumbnail
91
+ if image_width < thumb_width or image_height < thumb_height
92
+ commands.background('#ffffff')
93
+ commands.gravity('center')
94
+ commands.extent(size)
95
+ # resize image
96
+ else
97
+ commands.resize("#{size.to_s}")
98
+ end
99
+ # crop end
100
+ else
101
+ commands.resize(size.to_s)
102
+ end
103
+ end
104
+ temp_paths.unshift img
105
+ end
106
+
107
+ def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
108
+ # only crop if image is not smaller in both dimensions
109
+
110
+ # special cases, image smaller in one dimension then thumbsize
111
+ if image_width < thumb_width
112
+ offset = (image_height / 2) - (thumb_height / 2)
113
+ command = "#{image_width}x#{thumb_height}+0+#{offset}"
114
+ elsif image_height < thumb_height
115
+ offset = (image_width / 2) - (thumb_width / 2)
116
+ command = "#{thumb_width}x#{image_height}+#{offset}+0"
117
+
118
+ # normal thumbnail generation
119
+ # calculate height and offset y, width is fixed
120
+ elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
121
+ height = image_width / thumb_aspect
122
+ offset = (image_height / 2) - (height / 2)
123
+ command = "#{image_width}x#{height}+0+#{offset}"
124
+ # calculate width and offset x, height is fixed
125
+ else
126
+ width = image_height * thumb_aspect
127
+ offset = (image_width / 2) - (width / 2)
128
+ command = "#{width}x#{image_height}+#{offset}+0"
129
+ end
130
+ # crop image
131
+ command
132
+ end
133
+
134
+
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,65 @@
1
+ require 'RMagick'
2
+ module Technoweenie # :nodoc:
3
+ module AttachmentFu # :nodoc:
4
+ module Processors
5
+ module RmagickProcessor
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.alias_method_chain :process_attachment, :processing
9
+ end
10
+
11
+ module ClassMethods
12
+ # Yields a block containing an RMagick Image for the given binary data.
13
+ def with_image(file, &block)
14
+ begin
15
+ binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
16
+ binary_data && binary_data.auto_orient!
17
+ rescue
18
+ # Log the failure to load the image. This should match ::Magick::ImageMagickError
19
+ # but that would cause acts_as_attachment to require rmagick.
20
+ logger.debug("Exception working with image: #{$!}")
21
+ binary_data = nil
22
+ end
23
+ block.call binary_data if block && binary_data
24
+ ensure
25
+ !binary_data.nil?
26
+ end
27
+ end
28
+
29
+ protected
30
+ def process_attachment_with_processing
31
+ return unless process_attachment_without_processing
32
+ with_image do |img|
33
+ resize_image_or_thumbnail! img
34
+ self.width = img.columns if respond_to?(:width)
35
+ self.height = img.rows if respond_to?(:height)
36
+ callback_with_args :after_resize, img
37
+ end if image?
38
+ end
39
+
40
+ # Performs the actual resizing operation for a thumbnail
41
+ def resize_image(img, size)
42
+ size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
43
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
44
+ size = [size, size] if size.is_a?(Fixnum)
45
+ img.thumbnail!(*size)
46
+ elsif size.is_a?(String) && size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
47
+ dimensions = size[1..size.size].split("x")
48
+ img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
49
+ else
50
+ img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
51
+ end
52
+ img.strip! unless attachment_options[:keep_profile]
53
+ opts = attachment_options
54
+ out_file = write_to_temp_file(img.to_blob {
55
+ qty = opts[:jpeg_quality]
56
+ qty = qty ? qty.to_i : -1
57
+ # FIXME: this DOESN'T work, for whatever reason…
58
+ self.quality = qty if img.format.to_s[/JPEG/] && qty.between?(0, 100)
59
+ })
60
+ temp_paths.unshift out_file
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end