Hacking attachment_fu to work with Flash/Flex uploads and crop square images

Rick Olson’s attachment_fu is my favorite file upload plug-in because let’s you use three different image manipulation tools [rmagick, mini-magick, image science] and storage options [file system, database, amazon s3]. However it doesn’t yet support two features I use on every CMS I build, Flash/Flex file upload (images will upload but won’t be resized) and square image cropping. Here’s how to tweak it to get both features working.

First up, support for Flash/Flex upload (I should really drop the ‘Flash/’ part as I only use Flex now) , first up Flex upload… Ilya Devers posted the solution on Google groups, but I get to claim 1% credit as my blog is mentioned in his post :P

The problem is really on the Flex side of things as all uploads come through as ‘application/octet-stream’ for the mime-type. attachement_fu can upload any kind of file so it checks the mime-type before running it’s resize code, since it’s looking for an image it skips over the Flex uploaded files. Ilya’s rather ingenious solution is to override attachment_fu and use the file system to check the file type. To overide attachment_fu add the ‘uploaded_data=’ and ‘get_content_type’ methods to your upload model.

class Upload < ActiveRecord::Base
  belongs_to :image

  has_attachment :content_type => :image,
                 :storage => :file_system,
                 :processor => 'MiniMagick',
                 :max_size => 2000.kilobytes,
                 :resize_to => '620x465>',
                 :thumbnails => { :thumb => [90, 90] }

  #override from has_attachment plugin
  def uploaded_data=(file_data)
    return nil if file_data.nil? || file_data.size == 0
    self.filename = file_data.original_filename if respond_to?(:filename)
    if file_data.is_a?(StringIO)
      file_data.rewind
      self.temp_data = file_data.read
    else
      self.temp_path = file_data.path
    end
    # in the original the next line occured earlier, and just used file_data.content_type
    self.content_type = get_content_type((file_data.content_type.chomp if file_data.content_type))
  end

  #uses the os's "file" utility to determine the file type, yanked and modified slightly from file_column.
  def get_content_type(fallback=nil)
    begin
      content_type = `file -bi "#{File.join(temp_path)}"`.chomp
      content_type = fallback unless $?.success?
      content_type.gsub!(/;.+$/,"") if content_type
      content_type
    rescue
      fallback
    end
  end
end

Next is cropping square images with mini-magick. Currently if you request a square image attachment_fu will stretch rather than crop the image, this time I’ll ‘borrow’ the solution from Craig Ambrose. This time you have to dig deeper down into the depths of the rails plugins directory to edit ‘vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb’ and replace the resize_image method with the following.

# Performs the actual resizing operation for a thumbnail
def resize_image(img, size)
  size = size.first if size.is_a?(Array) && size.length == 1
  if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
    if size.is_a?(Fixnum)
      resize_and_crop(img, size)
    else
      size[0] == size[1] ? resize_and_crop(img, size[0]) : img.resize(size.join('x'))
    end
  else
    img.resize(size.to_s)
  end
  self.temp_path = img
end

def resize_and_crop(image, square_size)
  if image[:width] < image[:height]
    shave_off = ((image[:height] - image[:width])/2).round
    image.shave("0x#{shave_off}")
  elsif image[:width] > image[:height]
    shave_off = ((image[:width] - image[:height])/2).round
    image.shave("#{shave_off}x0")
  end
  image.resize("#{square_size}x#{square_size}")
  return image
end

To crop an image you use ‘:thumb => [90, 90]‘ as in the Model code above. That’s it!

This entry was posted in Flash, Flex, Ruby, Ruby on Rails. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

9 Comments

  1. Posted January 14, 2008 at 2:39 pm | Permalink

    This was a great help. Thanks for post.

    -Mike (guy you met at onAIR who had the music site)

  2. Posted January 16, 2008 at 3:22 pm | Permalink

    Hey Alastair! Nice write up. I wish I would have seen your write up for squaring thumbnails. I ended up having the Flash guys do some masking magic on the images, but this would have been better. Oh well. And it’s nice to know there are other people out there who just want to push their apps out there in the best, fastest, easiest way possible and get on with the next app. :)

  3. Posted February 15, 2008 at 5:51 pm | Permalink

    I made the following modification for my purposes:

    def resize_image(img, size)
    logger.debug(”Size: #{size.inspect}”)
    size = size.first if size.is_a?(Array) && size.length == 1
    if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
    if size.is_a?(Fixnum)
    resize_and_crop(img, size)
    else
    #size[0] == size[1] ? resize_and_crop(img, size[0]) : img.resize(size.join(’x'))
    #resize_and_crop_irregular(img, size[0], size[1])
    size[0] == size[1] ? resize_and_crop(img, size[0]) : resize_and_crop_irregular(img, size[0], size[1])
    end
    else
    img.resize(size.to_s)
    end
    self.temp_path = img
    end

    def resize_and_crop_irregular(image, nu_width, nu_height)
    original_ratio = (image[:width].to_f / image[:height].to_f).to_f
    nu_ratio = (nu_width.to_f / nu_height.to_f).to_f

    if nu_ratio original_ratio
    new_ratio = (nu_width.to_f / nu_height.to_f).to_f
    corrected_height = (image[:width].to_f / new_ratio).to_f
    shave_off = ((image[:height] – corrected_height)/2).round
    image.shave(”0x#{shave_off}”)
    end
    image.resize(”#{nu_width}x#{nu_height}”)
    return image
    end

  4. Dave Spurr
    Posted April 19, 2008 at 11:31 am | Permalink

    Very helpful, I’ve not had so much trouble working with Flex and anything else since I started trying to upload files, there were so many issues and this was just one of them.

    However your fix didn’t work for me, it always called the rescue block, so I commented that out and then did some dumping of the various variables, the results for content_type (after the file -bi “#{File.join(temp_path)} part was):

    ERROR: cannot open `â??/tmp/CGI4338-1â??’ (No such file or directory)

    I had no idea what was going on as the temp_path was:

    /tmp/CGI4338-1

    I couldn’t figure this out so I added the following hack to the rescue block:

    content_type = Mime::Type.lookup_by_extension( File.extname( self.filename ).gsub( /\./, ” ) )
    if content_type.to_s.match( /.*\/.*/ )
    content_type.to_s
    else
    fallback
    end

    I don’t really like this, but it works (I’ve registered the appropriate mime types with Mime::Type.register). Do you have any idea what that error is on the file -bi line.

    Anyway it was still a great help to come across this post as it didn’t mean I spent as long figuring out why the thumbnails weren’t being created.

    Thanks again.

  5. Posted September 3, 2008 at 8:12 pm | Permalink

    Hi,

    I’ve integrated these changes you’ve made into the Ruboss “Flexible Rails” framework to allow file uploading (as well as Restful_Authentication) to be possible from Flex to Rails. Thanks for all of your help. I haven’t completely finished the Flex part yet, but this tutorial was great.

    Here’s the beginnings of a RESTful Flex on Rails social networking site. Just laying the groundwork a little bit. Ruboss Tutorial

    Peace,
    Lance

  6. Alan
    Posted December 7, 2008 at 8:20 pm | Permalink

    It seems lot of people have been able to successfully make use of this blog.
    unfortunately i’m still struggling. my questions:

    1. how does the rails controller code look like? how are you guys constructing model object out of posted data?

    thx

  7. Alan
    Posted December 7, 2008 at 9:41 pm | Permalink

    does this hack even work with latest version of fu plugin anymore????
    nothing seems tobe work for me

  8. Rich
    Posted December 11, 2008 at 9:16 am | Permalink

    Alan,

    It works. attachment_fu requires default naming of some form variables to make things work. Be certain that your incoming parameters hash has a filename and uploaded_data parameter set. In Flex, you can set the name of your uploaded file with

    file.upload(request, “uploaded_data”);

    where file is a FileReference object.

  9. Daryth
    Posted February 5, 2010 at 1:53 pm | Permalink

    A little bit to late, but you could use
    content_type = `file –brief –mime “#{File.join(temp_path)}”`.chomp

    Because option `file -bi …`
    returns ‘regular_file’ instead of something like ‘image/jpeg’ on Mac OS-X

One Trackback

  1. By acts_as_attachment?attachment_fu???? at ?????? on November 16, 2008 at 2:04 am

    [...] Hacking attachment_fu to work with Flash/Flex uploads and crop square images [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word