pothoven-attachment_fu 3.2.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/CHANGELOG +59 -0
- data/LICENSE +20 -0
- data/README +253 -0
- data/Rakefile +22 -0
- data/amazon_s3.yml.tpl +17 -0
- data/install.rb +7 -0
- data/lib/geometry.rb +96 -0
- data/lib/pothoven-attachment_fu.rb +21 -0
- data/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb +211 -0
- data/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +39 -0
- data/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +126 -0
- data/lib/technoweenie/attachment_fu/backends/s3_backend.rb +394 -0
- data/lib/technoweenie/attachment_fu/processors/core_image_processor.rb +66 -0
- data/lib/technoweenie/attachment_fu/processors/gd2_processor.rb +59 -0
- data/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +80 -0
- data/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +142 -0
- data/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +84 -0
- data/lib/technoweenie/attachment_fu.rb +577 -0
- data/rackspace_cloudfiles.yml.tpl +14 -0
- data/test/backends/db_file_test.rb +16 -0
- data/test/backends/file_system_test.rb +143 -0
- data/test/backends/remote/cloudfiles_test.rb +102 -0
- data/test/backends/remote/s3_test.rb +119 -0
- data/test/base_attachment_tests.rb +77 -0
- data/test/basic_test.rb +120 -0
- data/test/database.yml +18 -0
- data/test/extra_attachment_test.rb +67 -0
- data/test/fixtures/attachment.rb +304 -0
- data/test/fixtures/files/fake/rails.png +0 -0
- data/test/fixtures/files/foo.txt +1 -0
- data/test/fixtures/files/rails.jpg +0 -0
- data/test/fixtures/files/rails.png +0 -0
- data/test/geometry_test.rb +114 -0
- data/test/processors/core_image_test.rb +58 -0
- data/test/processors/gd2_test.rb +51 -0
- data/test/processors/image_science_test.rb +54 -0
- data/test/processors/mini_magick_test.rb +122 -0
- data/test/processors/rmagick_test.rb +272 -0
- data/test/schema.rb +136 -0
- data/test/test_helper.rb +180 -0
- data/test/validation_test.rb +55 -0
- data/vendor/red_artisan/core_image/filters/color.rb +27 -0
- data/vendor/red_artisan/core_image/filters/effects.rb +31 -0
- data/vendor/red_artisan/core_image/filters/perspective.rb +25 -0
- data/vendor/red_artisan/core_image/filters/quality.rb +25 -0
- data/vendor/red_artisan/core_image/filters/scale.rb +47 -0
- data/vendor/red_artisan/core_image/filters/watermark.rb +32 -0
- data/vendor/red_artisan/core_image/processor.rb +123 -0
- metadata +97 -0
@@ -0,0 +1,142 @@
|
|
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
|
+
format = img[:format]
|
42
|
+
img.combine_options do |commands|
|
43
|
+
commands.strip unless attachment_options[:keep_profile]
|
44
|
+
|
45
|
+
# GIF is not handled correctly, so we move to PNG, as in other processors…
|
46
|
+
if format == 'GIF'
|
47
|
+
commands.format('PNG')
|
48
|
+
end
|
49
|
+
|
50
|
+
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
|
51
|
+
if size.is_a?(Fixnum)
|
52
|
+
size = [size, size]
|
53
|
+
commands.resize(size.join('x'))
|
54
|
+
else
|
55
|
+
commands.resize(size.join('x') + '!')
|
56
|
+
end
|
57
|
+
# extend to thumbnail size
|
58
|
+
elsif size.is_a?(String) and size =~ /e$/
|
59
|
+
size = size.gsub(/e/, '')
|
60
|
+
commands.resize(size.to_s + '>')
|
61
|
+
commands.background('#ffffff')
|
62
|
+
commands.gravity('center')
|
63
|
+
commands.extent(size)
|
64
|
+
# crop thumbnail, the smart way
|
65
|
+
elsif size.is_a?(String) and size =~ /c$/
|
66
|
+
size = size.gsub(/c/, '')
|
67
|
+
|
68
|
+
# calculate sizes and aspect ratio
|
69
|
+
thumb_width, thumb_height = size.split("x")
|
70
|
+
thumb_width = thumb_width.to_f
|
71
|
+
thumb_height = thumb_height.to_f
|
72
|
+
|
73
|
+
thumb_aspect = thumb_width.to_f / thumb_height.to_f
|
74
|
+
image_width, image_height = img[:width].to_f, img[:height].to_f
|
75
|
+
image_aspect = image_width / image_height
|
76
|
+
|
77
|
+
# only crop if image is not smaller in both dimensions
|
78
|
+
unless image_width < thumb_width and image_height < thumb_height
|
79
|
+
command = calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
|
80
|
+
|
81
|
+
# crop image
|
82
|
+
commands.extract(command)
|
83
|
+
end
|
84
|
+
|
85
|
+
# don not resize if image is not as height or width then thumbnail
|
86
|
+
if image_width < thumb_width or image_height < thumb_height
|
87
|
+
commands.background('#ffffff')
|
88
|
+
commands.gravity('center')
|
89
|
+
commands.extent(size)
|
90
|
+
# resize image
|
91
|
+
else
|
92
|
+
commands.resize("#{size.to_s}")
|
93
|
+
end
|
94
|
+
# crop end
|
95
|
+
else
|
96
|
+
commands.resize(size.to_s)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
dims = img[:dimensions]
|
100
|
+
self.width = dims[0] if respond_to?(:width)
|
101
|
+
self.height = dims[1] if respond_to?(:height)
|
102
|
+
# Has to be done this far so we get proper dimensions
|
103
|
+
if format == 'JPEG'
|
104
|
+
quality = get_jpeg_quality
|
105
|
+
img.quality(quality) if quality
|
106
|
+
end
|
107
|
+
temp_paths.unshift img
|
108
|
+
self.size = File.size(self.temp_path)
|
109
|
+
end
|
110
|
+
|
111
|
+
def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
|
112
|
+
# only crop if image is not smaller in both dimensions
|
113
|
+
|
114
|
+
# special cases, image smaller in one dimension then thumbsize
|
115
|
+
if image_width < thumb_width
|
116
|
+
offset = (image_height / 2) - (thumb_height / 2)
|
117
|
+
command = "#{image_width}x#{thumb_height}+0+#{offset}"
|
118
|
+
elsif image_height < thumb_height
|
119
|
+
offset = (image_width / 2) - (thumb_width / 2)
|
120
|
+
command = "#{thumb_width}x#{image_height}+#{offset}+0"
|
121
|
+
|
122
|
+
# normal thumbnail generation
|
123
|
+
# calculate height and offset y, width is fixed
|
124
|
+
elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
|
125
|
+
height = image_width / thumb_aspect
|
126
|
+
offset = (image_height / 2) - (height / 2)
|
127
|
+
command = "#{image_width}x#{height}+0+#{offset}"
|
128
|
+
# calculate width and offset x, height is fixed
|
129
|
+
else
|
130
|
+
width = image_height * thumb_aspect
|
131
|
+
offset = (image_width / 2) - (width / 2)
|
132
|
+
command = "#{width}x#{image_height}+#{offset}+0"
|
133
|
+
end
|
134
|
+
# crop image
|
135
|
+
command
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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
|
+
# Override image resizing method so we can support the crop option
|
41
|
+
# Thanks: http://stuff-things.net/2008/02/21/quick-and-dirty-cropping-images-with-attachment_fu/
|
42
|
+
def resize_image(img, size)
|
43
|
+
img.delete_profile('*')
|
44
|
+
|
45
|
+
# resize_image take size in a number of formats, we just want
|
46
|
+
# Strings in the form of "crop: WxH"
|
47
|
+
if (size.is_a?(String) && size =~ /^crop: (\d*)x(\d*)/i) ||
|
48
|
+
(size.is_a?(Array) && size.first.is_a?(String) &&
|
49
|
+
size.first =~ /^crop: (\d*)x(\d*)/i)
|
50
|
+
img.crop_resized!($1.to_i, $2.to_i)
|
51
|
+
# We need to save the resized image in the same way the
|
52
|
+
# orignal does.
|
53
|
+
quality = img.format.to_s[/JPEG/] && get_jpeg_quality
|
54
|
+
out_file = write_to_temp_file(img.to_blob { self.quality = quality if quality })
|
55
|
+
self.temp_paths.unshift out_file
|
56
|
+
else
|
57
|
+
old_resize_image(img, size) # Otherwise let attachment_fu handle it
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Performs the actual resizing operation for a thumbnail
|
62
|
+
def old_resize_image(img, size)
|
63
|
+
size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
|
64
|
+
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
|
65
|
+
size = [size, size] if size.is_a?(Fixnum)
|
66
|
+
img.thumbnail!(*size)
|
67
|
+
elsif size.is_a?(String) && size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
|
68
|
+
dimensions = size[1..size.size].split("x")
|
69
|
+
img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
|
70
|
+
else
|
71
|
+
img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
|
72
|
+
end
|
73
|
+
self.width = img.columns if respond_to?(:width)
|
74
|
+
self.height = img.rows if respond_to?(:height)
|
75
|
+
img.strip! unless attachment_options[:keep_profile]
|
76
|
+
quality = img.format.to_s[/JPEG/] && get_jpeg_quality
|
77
|
+
out_file = write_to_temp_file(img.to_blob { self.quality = quality if quality })
|
78
|
+
temp_paths.unshift out_file
|
79
|
+
self.size = File.size(self.temp_path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|