]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/plugins/file_column/lib/magick_file_column.rb
Updating to use Rails 2.1.2. Moving the gem dependancies to the config/environment...
[rails.git] / vendor / plugins / file_column / lib / magick_file_column.rb
diff --git a/vendor/plugins/file_column/lib/magick_file_column.rb b/vendor/plugins/file_column/lib/magick_file_column.rb
new file mode 100644 (file)
index 0000000..c4dc06f
--- /dev/null
@@ -0,0 +1,260 @@
+module FileColumn # :nodoc:
+  
+  class BaseUploadedFile # :nodoc:
+    def transform_with_magick
+      if needs_transform?
+        begin
+          img = ::Magick::Image::read(absolute_path).first
+        rescue ::Magick::ImageMagickError
+          if options[:magick][:image_required]
+            @magick_errors ||= []
+            @magick_errors << "invalid image"
+          end
+          return
+        end
+        
+        if options[:magick][:versions]
+          options[:magick][:versions].each_pair do |version, version_options|
+            next if version_options[:lazy]
+            dirname = version_options[:name]
+            FileUtils.mkdir File.join(@dir, dirname)
+            transform_image(img, version_options, absolute_path(dirname))
+          end
+        end
+        if options[:magick][:size] or options[:magick][:crop] or options[:magick][:transformation] or options[:magick][:attributes]
+          transform_image(img, options[:magick], absolute_path)
+        end
+
+        GC.start
+      end
+    end
+
+    def create_magick_version_if_needed(version)
+      # RMagick might not have been loaded so far.
+      # We do not want to require it on every call of this method
+      # as this might be fairly expensive, so we just try if ::Magick
+      # exists and require it if not.
+      begin 
+        ::Magick 
+      rescue NameError
+        require 'RMagick'
+      end
+
+      if version.is_a?(Symbol)
+        version_options = options[:magick][:versions][version]
+      else
+        version_options = MagickExtension::process_options(version)
+      end
+
+      unless File.exists?(absolute_path(version_options[:name]))
+        begin
+          img = ::Magick::Image::read(absolute_path).first
+        rescue ::Magick::ImageMagickError
+          # we might be called directly from the view here
+          # so we just return nil if we cannot load the image
+          return nil
+        end
+        dirname = version_options[:name]
+        FileUtils.mkdir File.join(@dir, dirname)
+        transform_image(img, version_options, absolute_path(dirname))
+      end
+
+      version_options[:name]
+    end
+
+    attr_reader :magick_errors
+    
+    def has_magick_errors?
+      @magick_errors and !@magick_errors.empty?
+    end
+
+    private
+    
+    def needs_transform?
+      options[:magick] and just_uploaded? and 
+        (options[:magick][:size] or options[:magick][:versions] or options[:magick][:transformation] or options[:magick][:attributes])
+    end
+
+    def transform_image(img, img_options, dest_path)
+      begin
+        if img_options[:transformation]
+          if img_options[:transformation].is_a?(Symbol)
+            img = @instance.send(img_options[:transformation], img)
+          else
+            img = img_options[:transformation].call(img)
+          end
+        end
+        if img_options[:crop]
+          dx, dy = img_options[:crop].split(':').map { |x| x.to_f }
+          w, h = (img.rows * dx / dy), (img.columns * dy / dx)
+          img = img.crop(::Magick::CenterGravity, [img.columns, w].min, 
+                         [img.rows, h].min, true)
+        end
+
+        if img_options[:size]
+          img = img.change_geometry(img_options[:size]) do |c, r, i|
+            i.resize(c, r)
+          end
+        end
+      ensure
+        img.write(dest_path) do
+          if img_options[:attributes]
+            img_options[:attributes].each_pair do |property, value| 
+              self.send "#{property}=", value
+            end
+          end
+        end
+        File.chmod options[:permissions], dest_path
+      end
+    end
+  end
+
+  # If you are using file_column to upload images, you can
+  # directly process the images with RMagick,
+  # a ruby extension
+  # for accessing the popular imagemagick libraries. You can find
+  # more information about RMagick at http://rmagick.rubyforge.org.
+  #
+  # You can control what to do by adding a <tt>:magick</tt> option
+  # to your options hash. All operations are performed immediately
+  # after a new file is assigned to the file_column attribute (i.e.,
+  # when a new file has been uploaded).
+  #
+  # == Resizing images
+  #
+  # To resize the uploaded image according to an imagemagick geometry
+  # string, just use the <tt>:size</tt> option:
+  #
+  #    file_column :image, :magick => {:size => "800x600>"}
+  #
+  # If the uploaded file cannot be loaded by RMagick, file_column will
+  # signal a validation error for the corresponding attribute. If you
+  # want to allow non-image files to be uploaded in a column that uses
+  # the <tt>:magick</tt> option, you can set the <tt>:image_required</tt>
+  # attribute to +false+:
+  #
+  #    file_column :image, :magick => {:size => "800x600>",
+  #                                    :image_required => false }
+  #
+  # == Multiple versions
+  #
+  # You can also create additional versions of your image, for example
+  # thumb-nails, like this:
+  #    file_column :image, :magick => {:versions => {
+  #         :thumb => {:size => "50x50"},
+  #         :medium => {:size => "640x480>"}
+  #       }
+  #
+  # These versions will be stored in separate sub-directories, named like the
+  # symbol you used to identify the version. So in the previous example, the
+  # image versions will be stored in "thumb", "screen" and "widescreen"
+  # directories, resp. 
+  # A name different from the symbol can be set via the <tt>:name</tt> option.
+  #
+  # These versions can be accessed via FileColumnHelper's +url_for_image_column+
+  # method like this:
+  #
+  #    <%= url_for_image_column "entry", "image", :thumb %>
+  #
+  # == Cropping images
+  #
+  # If you wish to crop your images with a size ratio before scaling
+  # them according to your version geometry, you can use the :crop directive.
+  #    file_column :image, :magick => {:versions => {
+  #         :square => {:crop => "1:1", :size => "50x50", :name => "thumb"},
+  #         :screen => {:crop => "4:3", :size => "640x480>"},
+  #         :widescreen => {:crop => "16:9", :size => "640x360!"},
+  #       }
+  #    }
+  #
+  # == Custom attributes
+  #
+  # To change some of the image properties like compression level before they
+  # are saved you can set the <tt>:attributes</tt> option.
+  # For a list of available attributes go to http://www.simplesystems.org/RMagick/doc/info.html
+  # 
+  #     file_column :image, :magick => { :attributes => { :quality => 30 } }
+  # 
+  # == Custom transformations
+  #
+  # To perform custom transformations on uploaded images, you can pass a
+  # callback to file_column:
+  #    file_column :image, :magick => 
+  #       Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) }
+  #
+  # The callback you give, receives one argument, which is an instance
+  # of Magick::Image, the RMagick image class. It should return a transformed
+  # image. Instead of passing a <tt>Proc</tt> object, you can also give a
+  # <tt>Symbol</tt>, the name of an instance method of your model.
+  #
+  # Custom transformations can be combined via the standard :size and :crop
+  # features, by using the :transformation option:
+  #   file_column :image, :magick => {
+  #      :transformation => Proc.new { |image| ... },
+  #      :size => "640x480"
+  #    }
+  #
+  # In this case, the standard resizing operations will be performed after the
+  # custom transformation.
+  #
+  # Of course, custom transformations can be used in versions, as well.
+  #
+  # <b>Note:</b> You'll need the
+  # RMagick extension being installed  in order to use file_column's
+  # imagemagick integration.
+  module MagickExtension
+
+    def self.file_column(klass, attr, options) # :nodoc:
+      require 'RMagick'
+      options[:magick] = process_options(options[:magick],false) if options[:magick]
+      if options[:magick][:versions]
+        options[:magick][:versions].each_pair do |name, value|
+          options[:magick][:versions][name] = process_options(value, name.to_s)
+        end
+      end
+      state_method = "#{attr}_state".to_sym
+      after_assign_method = "#{attr}_magick_after_assign".to_sym
+      
+      klass.send(:define_method, after_assign_method) do
+        self.send(state_method).transform_with_magick
+      end
+      
+      options[:after_upload] ||= []
+      options[:after_upload] << after_assign_method
+      
+      klass.validate do |record|
+        state = record.send(state_method)
+        if state.has_magick_errors?
+          state.magick_errors.each do |error|
+            record.errors.add attr, error
+          end
+        end
+      end
+    end
+
+    
+    def self.process_options(options,create_name=true)
+      case options
+      when String then options = {:size => options}
+      when Proc, Symbol then options = {:transformation => options }
+      end
+      if options[:geometry]
+        options[:size] = options.delete(:geometry)
+      end
+      options[:image_required] = true unless options.key?(:image_required)
+      if options[:name].nil? and create_name
+        if create_name == true
+          hash = 0
+          for key in [:size, :crop]
+            hash = hash ^ options[key].hash if options[key]
+          end
+          options[:name] = hash.abs.to_s(36)
+        else
+          options[:name] = create_name
+        end
+      end
+      options
+    end
+
+  end
+end