Vixiom Axioms

December 28, 2007

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

Filed under: Flash, Flex, Ruby, Ruby on Rails Alastair @ 1:19 pm

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 => 620×465>,
                 :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!

Digg! submit Hacking attachment_fu to work with Flash/Flex uploads and crop square images to stumbleupon.com submit Hacking attachment_fu to work with Flash/Flex uploads and crop square images to del.icio.us submit Hacking attachment_fu to work with Flash/Flex uploads and crop square images to reddit.com Like this post? subscribe to the feed.

6 Comments »

  1. This was a great help. Thanks for post.

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

    Comment by Mike — January 14, 2008 @ 2:39 pm

  2. 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. :)

    Comment by Sam Freiberg — January 16, 2008 @ 3:22 pm

  3. 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

    Comment by Kevin Thompson — February 15, 2008 @ 5:51 pm

  4. 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.

    Comment by Dave Spurr — April 19, 2008 @ 11:31 am

  5. 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

    Comment by Lance — September 3, 2008 @ 8:12 pm

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

    Pingback by acts_as_attachment?attachment_fu???? at ?????? — November 16, 2008 @ 2:04 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-Spam Image

Powered by WordPress