diff --git a/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb b/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb index 3fc17500..96bc5d77 100644 --- a/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +++ b/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb @@ -7,7 +7,7 @@ def self.included(base) base.send :extend, ClassMethods base.alias_method_chain :process_attachment, :processing end - + module ClassMethods # Yields a block containing an MiniMagick Image for the given binary data. def with_image(file, &block) @@ -23,23 +23,29 @@ def with_image(file, &block) !binary_data.nil? end end - + protected def process_attachment_with_processing return unless process_attachment_without_processing with_image do |img| resize_image_or_thumbnail! img - self.width = img[:width] if respond_to?(:width) - self.height = img[:height] if respond_to?(:height) + self.width = img[:width] if respond_to?(:width) + self.height = img[:height] if respond_to?(:height) callback_with_args :after_resize, img end if image? end - + # Performs the actual resizing operation for a thumbnail def resize_image(img, size) size = size.first if size.is_a?(Array) && size.length == 1 img.combine_options do |commands| commands.strip unless attachment_options[:keep_profile] + + # gif are not handled correct, this is a hack, but it seems to work. + if img.output =~ / GIF / + img.format("png") + end + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum)) if size.is_a?(Fixnum) size = [size, size] @@ -47,13 +53,80 @@ def resize_image(img, size) else commands.resize(size.join('x') + '!') end + # extend to thumbnail size + elsif size.is_a?(String) and size =~ /e$/ + size = size.gsub(/e/, '') + commands.resize(size.to_s + '>') + commands.background('#ffffff') + commands.gravity('center') + commands.extent(size) + # crop thumbnail, the smart way + elsif size.is_a?(String) and size =~ /c$/ + size = size.gsub(/c/, '') + + # calculate sizes and aspect ratio + thumb_width, thumb_height = size.split("x") + thumb_width = thumb_width.to_f + thumb_height = thumb_height.to_f + + thumb_aspect = thumb_width.to_f / thumb_height.to_f + image_width, image_height = img[:width].to_f, img[:height].to_f + image_aspect = image_width / image_height + + # only crop if image is not smaller in both dimensions + unless image_width < thumb_width and image_height < thumb_height + command = calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect) + + # crop image + commands.extract(command) + end + + # don not resize if image is not as height or width then thumbnail + if image_width < thumb_width or image_height < thumb_height + commands.background('#ffffff') + commands.gravity('center') + commands.extent(size) + # resize image + else + commands.resize("#{size.to_s}") + end + # crop end else commands.resize(size.to_s) end end temp_paths.unshift img end + + def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect) + # only crop if image is not smaller in both dimensions + + # special cases, image smaller in one dimension then thumbsize + if image_width < thumb_width + offset = (image_height / 2) - (thumb_height / 2) + command = "#{image_width}x#{thumb_height}+0+#{offset}" + elsif image_height < thumb_height + offset = (image_width / 2) - (thumb_width / 2) + command = "#{thumb_width}x#{image_height}+#{offset}+0" + + # normal thumbnail generation + # calculate height and offset y, width is fixed + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height + height = image_width / thumb_aspect + offset = (image_height / 2) - (height / 2) + command = "#{image_width}x#{height}+0+#{offset}" + # calculate width and offset x, height is fixed + else + width = image_height * thumb_aspect + offset = (image_width / 2) - (width / 2) + command = "#{width}x#{image_height}+#{offset}+0" + end + # crop image + command + end + + end end end -end +end \ No newline at end of file diff --git a/test/fixtures/attachment.rb b/test/fixtures/attachment.rb index 7476d7d9..5c86300b 100644 --- a/test/fixtures/attachment.rb +++ b/test/fixtures/attachment.rb @@ -35,7 +35,7 @@ class ImageOrPdfAttachment < Attachment class ImageWithThumbsAttachment < Attachment has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55] after_resize do |record, img| - record.aspect_ratio = img.columns.to_f / img.rows.to_f + # record.aspect_ratio = img.columns.to_f / img.rows.to_f end end @@ -53,7 +53,7 @@ class ImageWithThumbsFileAttachment < FileAttachment has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55] after_resize do |record, img| - record.aspect_ratio = img.columns.to_f / img.rows.to_f + # record.aspect_ratio = img.columns.to_f / img.rows.to_f end end @@ -130,9 +130,44 @@ class MiniMagickAttachment < ActiveRecord::Base has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 end + class ImageThumbnailCrop < MiniMagickAttachment + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', + :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"} + + # TODO this is a bad duplication, this method is in the MiniMagick Processor + def self.calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect) + # only crop if image is not smaller in both dimensions + + # special cases, image smaller in one dimension then thumbsize + if image_width < thumb_width + offset = (image_height / 2) - (thumb_height / 2) + command = "#{image_width}x#{thumb_height}+0+#{offset}" + elsif image_height < thumb_height + offset = (image_width / 2) - (thumb_width / 2) + command = "#{thumb_width}x#{image_height}+#{offset}+0" + + # normal thumbnail generation + # calculate height and offset y, width is fixed + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height + height = image_width / thumb_aspect + offset = (image_height / 2) - (height / 2) + command = "#{image_width}x#{height}+0+#{offset}" + # calculate width and offset x, height is fixed + else + width = image_height * thumb_aspect + offset = (image_width / 2) - (width / 2) + command = "#{width}x#{image_height}+#{offset}+0" + end + # crop image + command + end + end + rescue MissingSourceFile end + + begin class S3Attachment < ActiveRecord::Base has_attachment :storage => :s3, :processor => :rmagick, :s3_config_path => File.join(File.dirname(__FILE__), '../amazon_s3.yml') diff --git a/test/processors/mini_magick_test.rb b/test/processors/mini_magick_test.rb index 244a4a23..710ea410 100644 --- a/test/processors/mini_magick_test.rb +++ b/test/processors/mini_magick_test.rb @@ -23,9 +23,81 @@ def test_should_resize_image assert_equal 31, geo.width assert_equal 40, geo.height end + + def test_should_crop_image(klass = ImageThumbnailCrop) + attachment_model klass + attachment = upload_file :filename => '/files/rails.png' + assert_valid attachment + assert attachment.image? + # has_attachment :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"} + + square = attachment.thumbnails.detect { |t| t.filename =~ /_square/ } + vertical = attachment.thumbnails.detect { |t| t.filename =~ /_vertical/ } + horizontal = attachment.thumbnails.detect { |t| t.filename =~ /_horizontal/ } + + # test excat resize + assert_equal 50, square.width + assert_equal 50, square.height + + assert_equal 30, vertical.width + assert_equal 60, vertical.height + + assert_equal 60, horizontal.width + assert_equal 30, horizontal.height + end + + # tests the first step in resize, crop the image in original size to right format + def test_should_crop_image_right(klass = ImageThumbnailCrop) + @@testcases.collect do |testcase| + image_width, image_height, thumb_width, thumb_height = testcase[:data] + image_aspect, thumb_aspect = image_width/image_height, thumb_width/thumb_height + crop_comand = klass.calculate_offset(image_width, image_height, image_aspect, thumb_width, thumb_height,thumb_aspect) + # pattern matching on crop command + if testcase.has_key?(:height) + assert crop_comand.match(/^#{image_width}x#{testcase[:height]}\+0\+#{testcase[:yoffset]}$/) + else + assert crop_comand.match(/^#{testcase[:width]}x#{image_height}\+#{testcase[:xoffset]}\+0$/) + end + end + end + else def test_flunk puts "MiniMagick not loaded, tests not running" end end + + @@testcases = [ + # image_aspect <= 1 && thumb_aspect >= 1 + {:data => [10.0,40.0,2.0,1.0], :height => 5.0, :yoffset => 17.5}, # 1b + {:data => [10.0,40.0,1.0,1.0], :height => 10.0, :yoffset => 15.0}, # 1b + + # image_aspect < 1 && thumb_aspect < 1 + {:data => [10.0,40.0,1.0,2.0], :height => 20.0, :yoffset => 10.0}, # 1a + {:data => [2.0,3.0,1.0,2.0], :width => 1.5, :xoffset => 0.25}, # 1a + + # image_aspect = thumb_aspect + {:data => [10.0,10.0,1.0,1.0], :height => 10.0, :yoffset => 0.0}, # QUADRAT 1c + + # image_aspect >= 1 && thumb_aspect > 1 && image_aspect < thumb_aspect + {:data => [6.0,3.0,4.0,1.0], :height => 1.5, :yoffset => 0.75}, # 2b + {:data => [6.0,6.0,4.0,1.0], :height => 1.5, :yoffset => 2.25}, # 2b + + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 2a + + # image_aspect > 1 && thumb_aspect < 1 && image_aspect < thumb_aspect + {:data => [10.0,5.0,0.1,2.0], :width => 0.25, :xoffset => 4.875}, # 4 + {:data => [10.0,5.0,1.0,2.0], :width => 2.5, :xoffset => 3.75}, # 4 + + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 3a + # image_aspect > 1 && thumb_aspect > 1 && image_aspect < thumb_aspect + {:data => [9.0,3.0,5.0,1.0], :height => 1.8, :yoffset => 0.6} # 3a + ] + + + + + end