<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -25,7 +25,7 @@ class Geometry
   end
 
   # Construct an object from a geometry string
-  RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!&lt;&gt;@]?)\Z/
+  RE = /\A(\d*)(?:x(\d+)?)?([-+]\d+)?([-+]\d+)?([%!&lt;&gt;@]?)\Z/
 
   def self.from_s(str)
     raise(ArgumentError, &quot;no geometry string specified&quot;) unless str
@@ -76,7 +76,7 @@ class Geometry
         new_height = orig_height if @flag &amp;&amp; orig_height.send(@flag, new_height)
     end
 
-    [new_width, new_height].collect! { |v| v.round }
+    [new_width, new_height].collect! { |v| [v.round, 1].max }
   end
 end
 
@@ -90,4 +90,4 @@ class Array
     geometry = Geometry.from_s(geometry) if geometry.is_a?(String)
     geometry.new_dimensions_for first, last
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/geometry.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,37 @@ module Technoweenie # :nodoc:
   module AttachmentFu # :nodoc:
     @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage)
     @@tempfile_path      = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
-    @@content_types      = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg']
+    @@content_types      = [
+      'image/jpeg',
+      'image/pjpeg',
+      'image/jpg',
+      'image/gif',
+      'image/png',
+      'image/x-png',
+      'image/jpg',
+      'image/x-ms-bmp',
+      'image/bmp',
+      'image/x-bmp',
+      'image/x-bitmap',
+      'image/x-xbitmap',
+      'image/x-win-bitmap',
+      'image/x-windows-bmp',
+      'image/ms-bmp',
+      'application/bmp',
+      'application/x-bmp',
+      'application/x-win-bitmap',
+      'application/preview',
+      'image/jp_',
+      'application/jpg',
+      'application/x-jpg',
+      'image/pipeg',
+      'image/vnd.swiftview-jpeg',
+      'image/x-xbitmap',
+      'application/png',
+      'application/x-png',
+      'image/gi_',
+      'image/x-citrix-pjpeg'
+    ]
     mattr_reader :content_types, :tempfile_path, :default_processors
     mattr_writer :tempfile_path
 
@@ -20,7 +50,11 @@ module Technoweenie # :nodoc:
       # *  &lt;tt&gt;:thumbnail_class&lt;/tt&gt; - Set what class to use for thumbnails.  This attachment class is used by default.
       # *  &lt;tt&gt;:path_prefix&lt;/tt&gt; - path to store the uploaded files.  Uses public/#{table_name} by default for the filesystem, and just #{table_name}
       #      for the S3 backend.  Setting this sets the :storage to :file_system.
+
       # *  &lt;tt&gt;:storage&lt;/tt&gt; - Use :file_system to specify the attachment data is stored with the file system.  Defaults to :db_system.
+      # *  &lt;tt&gt;:bucket_key&lt;/tt&gt; - Use this to specify a different bucket key other than :bucket_name in the amazon_s3.yml file.  This allows you to use
+      #      different buckets for different models. An example setting would be :image_bucket and the you would need to define the name of the corresponding
+      #      bucket in the amazon_s3.yml file.
 
       # *  &lt;tt&gt;:keep_profile&lt;/tt&gt; By default image EXIF data will be stripped to minimize image size. For small thumbnails this proivides important savings. Picture quality is not affected. Set to false if you want to keep the image profile as is. ImageScience will allways keep EXIF data.
       #
@@ -69,7 +103,11 @@ module Technoweenie # :nodoc:
         end
         attachment_options[:path_prefix]   = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
 
-        with_options :foreign_key =&gt; 'parent_id' do |m|
+        association_options = { :foreign_key =&gt; 'parent_id' }
+        if attachment_options[:association_options]
+          association_options.merge!(attachment_options[:association_options])
+        end
+        with_options(association_options) do |m|
           m.has_many   :thumbnails, :class_name =&gt; &quot;::#{attachment_options[:thumbnail_class]}&quot;
           m.belongs_to :parent, :class_name =&gt; &quot;::#{base_class}&quot; unless options[:thumbnails].empty?
         end
@@ -82,8 +120,8 @@ module Technoweenie # :nodoc:
           processors = Technoweenie::AttachmentFu.default_processors.dup
           begin
             if processors.any?
-              attachment_options[:processor] = &quot;#{processors.first}Processor&quot;
-              processor_mod = Technoweenie::AttachmentFu::Processors.const_get(attachment_options[:processor])
+              attachment_options[:processor] = processors.first
+              processor_mod = Technoweenie::AttachmentFu::Processors.const_get(&quot;#{attachment_options[:processor].to_s.classify}Processor&quot;)
               include processor_mod unless included_modules.include?(processor_mod)
             end
           rescue Object, Exception
@@ -138,6 +176,7 @@ module Technoweenie # :nodoc:
         base.after_save :after_process_attachment
         base.after_destroy :destroy_file
         base.after_validation :process_attachment
+        base.attr_accessible :uploaded_data
         if defined?(::ActiveSupport::Callbacks)
           base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
         end
@@ -244,12 +283,12 @@ module Technoweenie # :nodoc:
       def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
         thumbnailable? || raise(ThumbnailError.new(&quot;Can't create a thumbnail if the content type is not an image or there is no parent_id column&quot;))
         returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
-          thumb.attributes = {
+          thumb.temp_paths.unshift temp_file
+          thumb.send(:'attributes=', {
             :content_type             =&gt; content_type,
             :filename                 =&gt; thumbnail_name_for(file_name_suffix),
-            :temp_path                =&gt; temp_file,
             :thumbnail_resize_options =&gt; size
-          }
+          }, false)
           callback_with_args :before_thumbnail_saved, thumb
           thumb.save!
         end
@@ -302,9 +341,9 @@ module Technoweenie # :nodoc:
         end
         if file_data.is_a?(StringIO)
           file_data.rewind
-          self.temp_data = file_data.read
+          set_temp_data file_data.read
         else
-          self.temp_path = file_data
+          self.temp_paths.unshift file_data
         end
       end
 
@@ -323,22 +362,14 @@ module Technoweenie # :nodoc:
           [] : [copy_to_temp_file(full_filename)])
       end
 
-      # Adds a new temp_path to the array.  This should take a string or a Tempfile.  This class makes no
-      # attempt to remove the files, so Tempfiles should be used.  Tempfiles remove themselves when they go out of scope.
-      # You can also use string paths for temporary files, such as those used for uploaded files in a web server.
-      def temp_path=(value)
-        temp_paths.unshift value
-        temp_path
-      end
-
       # Gets the data from the latest temp file.  This will read the file into memory.
       def temp_data
         save_attachment? ? File.read(temp_path) : nil
       end
 
       # Writes the given data to a Tempfile and adds it to the collection of temp files.
-      def temp_data=(data)
-        self.temp_path = write_to_temp_file data unless data.nil?
+      def set_temp_data(data)
+        temp_paths.unshift write_to_temp_file data unless data.nil?
       end
 
       # Copies the given file to a randomly named Tempfile.
@@ -391,7 +422,11 @@ module Technoweenie # :nodoc:
         def attachment_attributes_valid?
           [:size, :content_type].each do |attr_name|
             enum = attachment_options[attr_name]
-            errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
+            if Object.const_defined?(:I18n) # Rails &gt;= 2.2
+              errors.add attr_name, I18n.translate(&quot;activerecord.errors.messages.inclusion&quot;, attr_name =&gt; enum) unless enum.nil? || enum.include?(send(attr_name))
+            else
+              errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
+            end
           end
         end
 </diff>
      <filename>lib/technoweenie/attachment_fu.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,6 @@
-require 'ftools'
+require 'fileutils'
+require 'digest/sha2'
+
 module Technoweenie # :nodoc:
   module AttachmentFu # :nodoc:
     module Backends
@@ -28,16 +30,39 @@ module Technoweenie # :nodoc:
       
         # The attachment ID used in the full path of a file
         def attachment_path_id
-          ((respond_to?(:parent_id) &amp;&amp; parent_id) || id).to_i
+          ((respond_to?(:parent_id) &amp;&amp; parent_id) || id) || 0
         end
       
-        # by default paritions files into directories e.g. 0000/0001/image.jpg
-        # to turn this off set :partition =&gt; false
+        # Partitions the given path into an array of path components.
+        #
+        # For example, given an &lt;tt&gt;*args&lt;/tt&gt; of [&quot;foo&quot;, &quot;bar&quot;], it will return
+        # &lt;tt&gt;[&quot;0000&quot;, &quot;0001&quot;, &quot;foo&quot;, &quot;bar&quot;]&lt;/tt&gt; (assuming that that id returns 1).
+        #
+        # If the id is not an integer, then path partitioning will be performed by
+        # hashing the string value of the id with SHA-512, and splitting the result
+        # into 4 components. If the id a 128-bit UUID (as set by :uuid_primary_key =&gt; true)
+        # then it will be split into 2 components.
+        # 
+        # To turn this off entirely, set :partition =&gt; false.
         def partitioned_path(*args)
           if respond_to?(:attachment_options) &amp;&amp; attachment_options[:partition] == false 
             args
-          else 
-            (&quot;%08d&quot; % attachment_path_id).scan(/..../) + args
+          elsif attachment_options[:uuid_primary_key]
+            # Primary key is a 128-bit UUID in hex format. Split it into 2 components.
+            path_id = attachment_path_id.to_s
+            component1 = path_id[0..15] || &quot;-&quot;
+            component2 = path_id[16..-1] || &quot;-&quot;
+            [component1, component2] + args
+          else
+            path_id = attachment_path_id
+            if path_id.is_a?(Integer)
+              # Primary key is an integer. Split it after padding it with 0.
+              (&quot;%08d&quot; % path_id).scan(/..../) + args
+            else
+              # Primary key is a String. Hash it, then split it into 4 components.
+              hash = Digest::SHA512.hexdigest(path_id.to_s)
+              [hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
+            end
           end
         end
       
@@ -85,8 +110,8 @@ module Technoweenie # :nodoc:
             if save_attachment?
               # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
               FileUtils.mkdir_p(File.dirname(full_filename))
-              File.cp(temp_path, full_filename)
-              File.chmod(attachment_options[:chmod] || 0644, full_filename)
+              FileUtils.cp(temp_path, full_filename)
+              FileUtils.chmod(attachment_options[:chmod] || 0644, full_filename)
             end
             @old_filename = nil
             true</diff>
      <filename>lib/technoweenie/attachment_fu/backends/file_system_backend.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,17 +18,17 @@ module Technoweenie # :nodoc:
       # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
       #
       # Example configuration (RAILS_ROOT/config/amazon_s3.yml)
-      # 
+      #
       #   development:
       #     bucket_name: appname_development
       #     access_key_id: &lt;your key&gt;
       #     secret_access_key: &lt;your key&gt;
-      #   
+      #
       #   test:
       #     bucket_name: appname_test
       #     access_key_id: &lt;your key&gt;
       #     secret_access_key: &lt;your key&gt;
-      #   
+      #
       #   production:
       #     bucket_name: appname
       #     access_key_id: &lt;your key&gt;
@@ -81,10 +81,43 @@ module Technoweenie # :nodoc:
       #
       # Which would result in URLs like &lt;tt&gt;http(s)://:server/:bucket_name/my/custom/path/:id/:filename.&lt;/tt&gt;
       #
+      # === Using different bucket names on different models
+      #
+      # By default the bucket name that the file will be stored to is the one specified by the
+      # &lt;tt&gt;:bucket_name&lt;/tt&gt; key in the amazon_s3.yml file.  You can use the &lt;tt&gt;:bucket_key&lt;/tt&gt; option
+      # to overide this behavior on a per model basis.  For instance if you want a bucket that will hold
+      # only Photos you can do this:
+      #
+      #   class Photo &lt; ActiveRecord::Base
+      #     has_attachment :storage =&gt; :s3, :bucket_key =&gt; :photo_bucket_name
+      #   end
+      #
+      # And then your amazon_s3.yml file needs to look like this.
+      #
+      #   development:
+      #     bucket_name: appname_development
+      #     access_key_id: &lt;your key&gt;
+      #     secret_access_key: &lt;your key&gt;
+      #
+      #   test:
+      #     bucket_name: appname_test
+      #     access_key_id: &lt;your key&gt;
+      #     secret_access_key: &lt;your key&gt;
+      #
+      #   production:
+      #     bucket_name: appname
+      #     photo_bucket_name: appname_photos
+      #     access_key_id: &lt;your key&gt;
+      #     secret_access_key: &lt;your key&gt;
+      #
+      #  If the bucket_key you specify is not there in a certain environment then attachment_fu will
+      #  default to the &lt;tt&gt;bucket_name&lt;/tt&gt; key.  This way you only have to create special buckets
+      #  this can be helpful if you only need special buckets in certain environments.
+      #
       # === Permissions
       #
       # By default, files are stored on S3 with public access permissions. You can customize this using
-      # the &lt;tt&gt;:s3_access&lt;/tt&gt; option to &lt;tt&gt;has_attachment&lt;/tt&gt;. Available values are 
+      # the &lt;tt&gt;:s3_access&lt;/tt&gt; option to &lt;tt&gt;has_attachment&lt;/tt&gt;. Available values are
       # &lt;tt&gt;:private&lt;/tt&gt;, &lt;tt&gt;:public_read_write&lt;/tt&gt;, and &lt;tt&gt;:authenticated_read&lt;/tt&gt;.
       #
       # === Other options
@@ -123,7 +156,6 @@ module Technoweenie # :nodoc:
 
         def self.included(base) #:nodoc:
           mattr_reader :s3_config
-          
           begin
             require 'aws/s3'
             include AWS::S3
@@ -146,16 +178,13 @@ module Technoweenie # :nodoc:
             bucket_name.gsub('.','_').camelize.constantize.establish_connection!(:server =&gt; &quot;#{bucket_name}.#{AWS::S3::DEFAULT_HOST}&quot;)
           end
           
-
-          # Bucket.create(@@bucket_name)
-
           base.before_update :rename_file
         end
 
         def self.protocol
           @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
         end
-        
+
         def self.hostname
           @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
         end
@@ -172,7 +201,7 @@ module Technoweenie # :nodoc:
           def s3_protocol
             Technoweenie::AttachmentFu::Backends::S3Backend.protocol
           end
-          
+
           def s3_hostname
             Technoweenie::AttachmentFu::Backends::S3Backend.hostname
           end
@@ -213,7 +242,7 @@ module Technoweenie # :nodoc:
           thumbnail ? File.join(base_path, thumbnail_name_for(thumbnail)) : base_path
         end
 
-        # All public objects are accessible via a GET request to the S3 servers. You can generate a 
+        # All public objects are accessible via a GET request to the S3 servers. You can generate a
         # url for an object using the s3_url method.
         #
         #   @photo.s3_url
@@ -232,7 +261,7 @@ module Technoweenie # :nodoc:
         end
         alias :public_filename :s3_url
 
-        # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an 
+        # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
         # authenticated url for an object like this:
         #
         #   @photo.authenticated_s3_url
@@ -244,7 +273,7 @@ module Technoweenie # :nodoc:
         #
         #   # Absolute expiration date (October 13th, 2025)
         #   @photo.authenticated_s3_url(:expires =&gt; Time.mktime(2025,10,13).to_i)
-        #   
+        #
         #   # Expiration in five hours from now
         #   @photo.authenticated_s3_url(:expires_in =&gt; 5.hours)
         #
@@ -258,6 +287,7 @@ module Technoweenie # :nodoc:
         #   @photo.authenticated_s3_url('thumbnail', :expires_in =&gt; 5.hours, :use_ssl =&gt; true)
         def authenticated_s3_url(*args)
           options   = args.extract_options!
+          options[:expires_in] = options[:expires_in].to_i if options[:expires_in]
           thumbnail = args.shift
           S3Object.url_for(full_filename(thumbnail), bucket_name, options)
         end
@@ -273,7 +303,7 @@ module Technoweenie # :nodoc:
         def s3_protocol
           Technoweenie::AttachmentFu::Backends::S3Backend.protocol
         end
-        
+
         def s3_hostname
           Technoweenie::AttachmentFu::Backends::S3Backend.hostname
         end
@@ -294,7 +324,7 @@ module Technoweenie # :nodoc:
 
           def rename_file
             return unless @old_filename &amp;&amp; @old_filename != filename
-            
+
             old_full_filename = File.join(base_path, @old_filename)
 
             bucket_object.rename(</diff>
      <filename>lib/technoweenie/attachment_fu/backends/s3_backend.rb</filename>
    </modified>
    <modified>
      <diff>@@ -46,7 +46,7 @@ module Technoweenie # :nodoc:
               self.height = result.extent.size.height if respond_to?(:height)
               
               # Get a new temp_path for the image before saving
-              self.temp_path = Tempfile.new(random_tempfile_filename, Technoweenie::AttachmentFu.tempfile_path).path
+              temp_paths.unshift Tempfile.new(random_tempfile_filename, Technoweenie::AttachmentFu.tempfile_path).path
               result.save self.temp_path, OSX::NSJPEGFileType
               self.size = File.size(self.temp_path)
             end</diff>
      <filename>lib/technoweenie/attachment_fu/processors/core_image_processor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -44,7 +44,7 @@ module Technoweenie # :nodoc:
               w, h = [img.width, img.height] / size.to_s
               img.resize!(w, h, false)
             end
-            self.temp_path = random_tempfile_filename
+            temp_paths.unshift random_tempfile_filename
             self.size = img.export(self.temp_path)
           end
 </diff>
      <filename>lib/technoweenie/attachment_fu/processors/gd2_processor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -34,7 +34,7 @@ module Technoweenie # :nodoc:
             # supports.
             filename.sub! /gif$/, 'png'
             content_type.sub!(/gif$/, 'png')
-            self.temp_path = write_to_temp_file(filename)
+            temp_paths.unshift write_to_temp_file(filename)
             grab_dimensions = lambda do |img|
               self.width  = img.width  if respond_to?(:width)
               self.height = img.height if respond_to?(:height)</diff>
      <filename>lib/technoweenie/attachment_fu/processors/image_science_processor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,7 +7,7 @@ module Technoweenie # :nodoc:
           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, &amp;block)
@@ -23,23 +23,29 @@ module Technoweenie # :nodoc:
             !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) &amp;&amp; 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(&quot;png&quot;)
+            end           
+            
             if size.is_a?(Fixnum) || (size.is_a?(Array) &amp;&amp; size.first.is_a?(Fixnum))
               if size.is_a?(Fixnum)
                 size = [size, size]
@@ -47,13 +53,80 @@ module Technoweenie # :nodoc:
               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 + '&gt;')
+              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(&quot;x&quot;)
+              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 &lt; thumb_width and image_height &lt; 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 &lt; thumb_width or image_height &lt; thumb_height                   
+                  commands.background('#ffffff')
+                  commands.gravity('center')
+                  commands.extent(size)
+              # resize image
+              else
+                commands.resize(&quot;#{size.to_s}&quot;)
+              end
+            # crop end
             else
               commands.resize(size.to_s)
             end
           end
-          self.temp_path = img
+          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 &lt; thumb_width
+            offset = (image_height / 2) - (thumb_height / 2)
+            command = &quot;#{image_width}x#{thumb_height}+0+#{offset}&quot;
+          elsif image_height &lt; thumb_height
+            offset = (image_width / 2) - (thumb_width / 2)
+            command = &quot;#{thumb_width}x#{image_height}+#{offset}+0&quot;
+
+          # normal thumbnail generation
+          # calculate height and offset y, width is fixed                 
+          elsif (image_aspect &lt;= thumb_aspect or image_width &lt; thumb_width) and image_height &gt; thumb_height
+            height = image_width / thumb_aspect
+            offset = (image_height / 2) - (height / 2)
+            command = &quot;#{image_width}x#{height}+0+#{offset}&quot;
+          # calculate width and offset x, height is fixed
+          else
+            width = image_height * thumb_aspect
+            offset = (image_width / 2) - (width / 2)
+            command = &quot;#{width}x#{image_height}+#{offset}+0&quot;
+          end
+          # crop image
+          command
         end
+
+
       end
     end
   end
-end
+end
\ No newline at end of file</diff>
      <filename>lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -42,11 +42,14 @@ module Technoweenie # :nodoc:
           if size.is_a?(Fixnum) || (size.is_a?(Array) &amp;&amp; size.first.is_a?(Fixnum))
             size = [size, size] if size.is_a?(Fixnum)
             img.thumbnail!(*size)
+          elsif size.is_a?(String) &amp;&amp; size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
+            dimensions = size[1..size.size].split(&quot;x&quot;)
+            img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
           else
             img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols&lt;1 ? 1 : cols, rows&lt;1 ? 1 : rows) }
           end
           img.strip! unless attachment_options[:keep_profile]
-          self.temp_path = write_to_temp_file(img.to_blob)
+          temp_paths.unshift write_to_temp_file(img.to_blob)
         end
       end
     end</diff>
      <filename>lib/technoweenie/attachment_fu/processors/rmagick_processor.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,5 @@
 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+require 'digest/sha2'
 
 class FileSystemTest &lt; Test::Unit::TestCase
   include BaseAttachmentTests
@@ -52,7 +53,7 @@ class FileSystemTest &lt; Test::Unit::TestCase
     assert_not_created do
       use_temp_file 'files/rails.png' do |file|
         attachment.filename        = 'rails2.png'
-        attachment.temp_path = File.join(fixture_path, file)
+        attachment.temp_paths.unshift File.join(fixture_path, file)
         attachment.save!
         assert  File.exists?(attachment.full_filename), &quot;#{attachment.full_filename} does not exist&quot;    
         assert !File.exists?(old_filename),             &quot;#{old_filename} still exists&quot;
@@ -77,4 +78,66 @@ class FileSystemTest &lt; Test::Unit::TestCase
   end
   
   test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment
-end
\ No newline at end of file
+  
+  def test_path_partitioning_works_on_integer_id(klass = FileAttachment)
+    attachment_model klass
+    
+    # Create a random attachment object, doesn't matter what.
+    attachment = upload_file :filename =&gt; '/files/rails.png'
+    old_id = attachment.id
+    attachment.id = 1
+    
+    begin
+      assert_equal [&quot;0000&quot;, &quot;0001&quot;, &quot;bar.txt&quot;], attachment.send(:partitioned_path, &quot;bar.txt&quot;)
+    ensure
+      attachment.id = old_id
+    end
+  end
+  
+  test_against_subclass :test_path_partitioning_works_on_integer_id, FileAttachment
+  
+  def test_path_partitioning_with_string_id_works_by_generating_hash(klass = FileAttachmentWithStringId)
+    attachment_model klass
+    
+    # Create a random attachment object, doesn't matter what.
+    attachment = upload_file :filename =&gt; '/files/rails.png'
+    old_id = attachment.id
+    attachment.id = &quot;hello world some long string&quot;
+    hash = Digest::SHA512.hexdigest(&quot;hello world some long string&quot;)
+    
+    begin
+      assert_equal [
+          hash[0..31],
+          hash[32..63],
+          hash[64..95],
+          hash[96..127],
+          &quot;bar.txt&quot;
+        ], attachment.send(:partitioned_path, &quot;bar.txt&quot;)
+    ensure
+      attachment.id = old_id
+    end
+  end
+  
+  test_against_subclass :test_path_partitioning_with_string_id_works_by_generating_hash, FileAttachmentWithStringId
+  
+  def test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid(klass = FileAttachmentWithUuid)
+    attachment_model klass
+    
+    # Create a random attachment object, doesn't matter what.
+    attachment = upload_file :filename =&gt; '/files/rails.png'
+    old_id = attachment.id
+    attachment.id = &quot;0c0743b698483569dc65909a8cdb3bf9&quot;
+    
+    begin
+      assert_equal [
+          &quot;0c0743b698483569&quot;,
+          &quot;dc65909a8cdb3bf9&quot;,
+          &quot;bar.txt&quot;
+        ], attachment.send(:partitioned_path, &quot;bar.txt&quot;)
+    ensure
+      attachment.id = old_id
+    end
+  end
+  
+  test_against_subclass :test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid, FileAttachmentWithUuid
+end</diff>
      <filename>test/backends/file_system_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -31,7 +31,7 @@ module BaseAttachmentTests
       assert_valid attachment
       assert attachment.size &gt; 0, &quot;no data was set&quot;
       
-      attachment.temp_data = 'wtf'
+      attachment.set_temp_data 'wtf'
       assert attachment.save_attachment?
       attachment.save!
       
@@ -45,7 +45,7 @@ module BaseAttachmentTests
       assert_valid attachment
       assert attachment.size &gt; 0, &quot;no data was set&quot;
       
-      attachment.temp_data = nil
+      attachment.set_temp_data nil
       assert !attachment.save_attachment?
     end
   end
@@ -55,7 +55,7 @@ module BaseAttachmentTests
     assert_not_created do # no new db_file records
       use_temp_file 'files/rails.png' do |file|
         attachment.filename = 'rails2.png'
-        attachment.temp_path = File.join(fixture_path, file)
+        attachment.temp_paths.unshift File.join(fixture_path, file)
         attachment.save!
       end
     end</diff>
      <filename>test/base_attachment_tests.rb</filename>
    </modified>
    <modified>
      <diff>@@ -24,8 +24,8 @@ class BasicTest &lt; Test::Unit::TestCase
   def test_should_normalize_content_types_to_array
     assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
     assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type]
-    assert_equal ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageAttachment.attachment_options[:content_type]
-    assert_equal ['pdf', 'image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageOrPdfAttachment.attachment_options[:content_type]
+    assert_equal Technoweenie::AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
+    assert_equal ['pdf'] + Technoweenie::AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
   end
   
   def test_should_sanitize_content_type
@@ -67,4 +67,4 @@ class BasicTest &lt; Test::Unit::TestCase
       klass.has_attachment :thumbnails =&gt; []
     end
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/basic_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -35,7 +35,7 @@ end
 class ImageWithThumbsAttachment &lt; Attachment
   has_attachment :thumbnails =&gt; { :thumb =&gt; [50, 50], :geometry =&gt; 'x50' }, :resize_to =&gt; [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
 
@@ -44,6 +44,38 @@ class FileAttachment &lt; ActiveRecord::Base
   validates_as_attachment
 end
 
+class FileAttachmentWithStringId &lt; ActiveRecord::Base
+  set_table_name 'file_attachments_with_string_id'
+  has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files', :processor =&gt; :rmagick
+  validates_as_attachment
+  
+  before_validation :auto_generate_id
+  before_save :auto_generate_id
+  @@last_id = 0
+  
+  private
+    def auto_generate_id
+      @@last_id += 1
+      self.id = &quot;id_#{@@last_id}&quot;
+    end
+end
+
+class FileAttachmentWithUuid &lt; ActiveRecord::Base
+  set_table_name 'file_attachments_with_string_id'
+  has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files', :processor =&gt; :rmagick, :uuid_primary_key =&gt; true
+  validates_as_attachment
+  
+  before_validation :auto_generate_id
+  before_save :auto_generate_id
+  @@last_id = 0
+  
+  private
+    def auto_generate_id
+      @@last_id += 1
+      self.id = &quot;%0127dx&quot; % @@last_id
+    end
+end
+
 class ImageFileAttachment &lt; FileAttachment
   has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files',
     :content_type =&gt; :image, :resize_to =&gt; [50,50]
@@ -53,7 +85,7 @@ class ImageWithThumbsFileAttachment &lt; FileAttachment
   has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files',
     :thumbnails =&gt; { :thumb =&gt; [50, 50], :geometry =&gt; 'x50' }, :resize_to =&gt; [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 +162,44 @@ begin
     has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files',
       :processor =&gt; :mini_magick, :thumbnails =&gt; { :thumb =&gt; [50, 51], :geometry =&gt; '31&gt;' }, :resize_to =&gt; 55
   end
+  class ImageThumbnailCrop &lt; MiniMagickAttachment
+    has_attachment :path_prefix =&gt; 'vendor/plugins/attachment_fu/test/files',
+    :thumbnails =&gt; { :square =&gt; &quot;50x50c&quot;, :vertical =&gt; &quot;30x60c&quot;, :horizontal =&gt; &quot;60x30c&quot;}
+    
+    # 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 &lt; thumb_width
+        offset = (image_height / 2) - (thumb_height / 2)
+        command = &quot;#{image_width}x#{thumb_height}+0+#{offset}&quot;
+      elsif image_height &lt; thumb_height
+        offset = (image_width / 2) - (thumb_width / 2)
+        command = &quot;#{thumb_width}x#{image_height}+#{offset}+0&quot;
+
+      # normal thumbnail generation
+      # calculate height and offset y, width is fixed                 
+      elsif (image_aspect &lt;= thumb_aspect or image_width &lt; thumb_width) and image_height &gt; thumb_height
+        height = image_width / thumb_aspect
+        offset = (image_height / 2) - (height / 2)
+        command = &quot;#{image_width}x#{height}+0+#{offset}&quot;
+      # calculate width and offset x, height is fixed
+      else
+        width = image_height * thumb_aspect
+        offset = (image_width / 2) - (width / 2)
+        command = &quot;#{width}x#{image_height}+#{offset}+0&quot;
+      end
+      # crop image
+      command
+    end
+  end
+
 rescue MissingSourceFile
 end
 
+
+
 begin
   class S3Attachment &lt; ActiveRecord::Base
     has_attachment :storage =&gt; :s3, :processor =&gt; :rmagick, :s3_config_path =&gt; File.join(File.dirname(__FILE__), '../amazon_s3.yml')</diff>
      <filename>test/fixtures/attachment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,6 +23,13 @@ class GeometryTest &lt; Test::Unit::TestCase
       &quot;100&quot; =&gt; [100, 128]
   end
   
+  def test_should_resize_no_height_with_x
+    assert_geometry 50, 64,
+      &quot;50x&quot;  =&gt; [50, 64],
+      &quot;60x&quot;  =&gt; [60, 77],
+      &quot;100x&quot; =&gt; [100, 128]
+  end
+  
   def test_should_resize_with_percent
     assert_geometry 50, 64,
       &quot;50x50%&quot;   =&gt; [25, 32],</diff>
      <filename>test/geometry_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,9 +23,81 @@ class MiniMagickTest &lt; Test::Unit::TestCase
       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 =&gt; '/files/rails.png'
+      assert_valid attachment
+      assert  attachment.image?
+    #  has_attachment :thumbnails =&gt; { :square =&gt; &quot;50x50c&quot;, :vertical =&gt; &quot;30x60c&quot;, :horizontal =&gt; &quot;60x30c&quot;}
+
+      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 &quot;MiniMagick not loaded, tests not running&quot;
     end
   end
+
+  @@testcases = [
+    # image_aspect &lt;= 1 &amp;&amp; thumb_aspect &gt;= 1  
+    {:data =&gt; [10.0,40.0,2.0,1.0], :height =&gt; 5.0, :yoffset =&gt; 17.5}, #   1b
+    {:data =&gt; [10.0,40.0,1.0,1.0], :height =&gt; 10.0, :yoffset =&gt; 15.0}, #  1b
+
+    # image_aspect &lt; 1 &amp;&amp; thumb_aspect &lt; 1
+    {:data =&gt; [10.0,40.0,1.0,2.0], :height =&gt; 20.0, :yoffset =&gt; 10.0}, # 1a
+    {:data =&gt; [2.0,3.0,1.0,2.0], :width =&gt; 1.5, :xoffset =&gt; 0.25}, # 1a
+
+    # image_aspect = thumb_aspect
+    {:data =&gt; [10.0,10.0,1.0,1.0], :height =&gt; 10.0, :yoffset =&gt; 0.0}, # QUADRAT 1c
+
+    # image_aspect &gt;= 1 &amp;&amp; thumb_aspect &gt; 1     &amp;&amp; image_aspect &lt; thumb_aspect
+    {:data =&gt; [6.0,3.0,4.0,1.0], :height =&gt; 1.5, :yoffset =&gt; 0.75}, # 2b  
+    {:data =&gt; [6.0,6.0,4.0,1.0], :height =&gt; 1.5, :yoffset =&gt; 2.25}, # 2b  
+
+    # image_aspect &gt; 1 &amp;&amp; thumb_aspect &gt; 1     &amp;&amp; image_aspect &gt; thumb_aspect
+    {:data =&gt; [9.0,3.0,2.0,1.0], :width =&gt; 6.0, :xoffset =&gt; 1.5}, # 2a
+
+    # image_aspect &gt; 1 &amp;&amp; thumb_aspect &lt; 1 &amp;&amp; image_aspect &lt; thumb_aspect
+    {:data =&gt; [10.0,5.0,0.1,2.0], :width =&gt; 0.25, :xoffset =&gt; 4.875}, # 4
+    {:data =&gt; [10.0,5.0,1.0,2.0], :width =&gt; 2.5, :xoffset =&gt; 3.75}, # 4
+
+    # image_aspect &gt; 1 &amp;&amp; thumb_aspect &gt; 1     &amp;&amp; image_aspect &gt; thumb_aspect
+    {:data =&gt; [9.0,3.0,2.0,1.0], :width =&gt; 6.0, :xoffset =&gt; 1.5}, # 3a    
+    # image_aspect &gt; 1 &amp;&amp; thumb_aspect &gt; 1     &amp;&amp; image_aspect &lt; thumb_aspect
+    {:data =&gt; [9.0,3.0,5.0,1.0], :height =&gt; 1.8, :yoffset =&gt; 0.6} # 3a
+  ]
+
+
+
+
+
 end</diff>
      <filename>test/processors/mini_magick_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -181,7 +181,7 @@ class RmagickTest &lt; Test::Unit::TestCase
       assert_not_created do
         use_temp_file &quot;files/rails.png&quot; do |file|
           attachment.filename        = 'rails2.png'
-          attachment.temp_path = File.join(fixture_path, file)
+          attachment.temp_paths.unshift File.join(fixture_path, file)
           attachment.save
           new_filenames = [attachment.reload.full_filename] + attachment.thumbnails.collect { |t| t.reload.full_filename }
           new_filenames.each { |f| assert  File.exists?(f), &quot;#{f} does not exist&quot; }
@@ -224,7 +224,7 @@ class RmagickTest &lt; Test::Unit::TestCase
           # #temp_path calls #full_filename, which is not getting mixed into the attachment.  Maybe we don't need to
           # set temp_path at all?
           #
-          # attachment.temp_path = File.join(fixture_path, file)
+          # attachment.temp_paths.unshift File.join(fixture_path, file)
           attachment.save!
         end
       end</diff>
      <filename>test/processors/rmagick_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -22,6 +22,19 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
     t.column :type,            :string
     t.column :aspect_ratio,    :float
   end
+  
+  create_table :file_attachments_with_string_id, :id =&gt; false, :force =&gt; true do |t|
+    t.column :id,              :string
+    t.column :parent_id,       :string
+    t.column :thumbnail,       :string 
+    t.column :filename,        :string, :limit =&gt; 255
+    t.column :content_type,    :string, :limit =&gt; 255
+    t.column :size,            :integer
+    t.column :width,           :integer
+    t.column :height,          :integer
+    t.column :type,            :string
+    t.column :aspect_ratio,    :float
+  end
 
   create_table :gd2_attachments, :force =&gt; true do |t|
     t.column :parent_id,       :integer
@@ -105,4 +118,4 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
     t.column :type,            :string
     t.column :aspect_ratio,    :float
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/schema.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>09eb7a29d252c9a109f9d2b01ae815350224347e</id>
    </parent>
    <parent>
      <id>ab1e4f7b0b9de85e0c9decf061d2ef5c1dc0feaa</id>
    </parent>
  </parents>
  <author>
    <name>Isaac Feliu</name>
    <email>isaac@vesne.com</email>
  </author>
  <url>http://github.com/isaacfeliu/attachment_fu/commit/e992baab05e75764ccca166af64d47f5b662d2a3</url>
  <id>e992baab05e75764ccca166af64d47f5b662d2a3</id>
  <committed-date>2009-03-13T06:02:21-07:00</committed-date>
  <authored-date>2009-03-13T06:02:21-07:00</authored-date>
  <message>Merge with technoweenie</message>
  <tree>87a64ca28d4d4cdc1dc59f49e1b1d73866edf64d</tree>
  <committer>
    <name>Isaac Feliu</name>
    <email>isaac@vesne.com</email>
  </committer>
</commit>
