dynamic_image 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
 - data/README.rdoc +59 -0
 - data/Rakefile +42 -0
 - data/VERSION +1 -0
 - data/app/controllers/images_controller.rb +79 -0
 - data/app/models/image.rb +183 -0
 - data/config/routes.rb +16 -0
 - data/dynamic_image.gemspec +68 -0
 - data/init.rb +1 -0
 - data/install.rb +1 -0
 - data/lib/binary_storage/active_record_extensions.rb +144 -0
 - data/lib/binary_storage/blob.rb +105 -0
 - data/lib/binary_storage.rb +28 -0
 - data/lib/dynamic_image/active_record_extensions.rb +57 -0
 - data/lib/dynamic_image/engine.rb +6 -0
 - data/lib/dynamic_image/filterset.rb +79 -0
 - data/lib/dynamic_image/helper.rb +121 -0
 - data/lib/dynamic_image.rb +77 -0
 - data/lib/generators/dynamic_image/USAGE +6 -0
 - data/lib/generators/dynamic_image/dynamic_image_generator.rb +38 -0
 - data/lib/generators/dynamic_image/templates/migrations/create_images.rb +22 -0
 - data/test/dynamic_image_test.rb +8 -0
 - data/test/test_helper.rb +3 -0
 - data/uninstall.rb +1 -0
 - metadata +112 -0
 
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2006-2010 Inge Jørgensen
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.rdoc
    ADDED
    
    | 
         @@ -0,0 +1,59 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            = DynamicImage
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            DynamicImage is a Rails plugin providing transparent uploading 
         
     | 
| 
      
 4 
     | 
    
         
            +
            and processing of image files.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Images are processed and cached on demand without need for any 
         
     | 
| 
      
 7 
     | 
    
         
            +
            configuration.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            == Installation:
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            Install the gem:
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
             gem install dynamic_image
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            Add the gem to your Gemfile:
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             gem 'dynamic_image'
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            Do the migrations:
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
             rails generate dynamic_image migrations
         
     | 
| 
      
 23 
     | 
    
         
            +
             rake db:migrate
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            == Getting started:
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            Use belongs_to_image in your models:
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
             class User
         
     | 
| 
      
 31 
     | 
    
         
            +
               belongs_to_image :profile_picture
         
     | 
| 
      
 32 
     | 
    
         
            +
             end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            Create a form:
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
             <%= form_for @user, :html => {:multipart => true} do |f| %>
         
     | 
| 
      
 37 
     | 
    
         
            +
               Name: <%= f.text_field :name %>
         
     | 
| 
      
 38 
     | 
    
         
            +
               Profile picture: <%= f.file_field :profile_picture %>
         
     | 
| 
      
 39 
     | 
    
         
            +
               <%= submit_tag "Save" %>
         
     | 
| 
      
 40 
     | 
    
         
            +
             <% end %>
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            Use the dynamic_image_tag helper to show images:
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
             <%= dynamic_image_tag @user.profile_picture, :size => '64x64' %>
         
     | 
| 
      
 45 
     | 
    
         
            +
             <%= dynamic_image_tag @user.profile_picture, :size => '150x' %>
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            == Caching
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            Processing images on the fly is expensive. Therefore, page caching is enabled
         
     | 
| 
      
 51 
     | 
    
         
            +
            by default, even in development mode. To disable page caching, add the following
         
     | 
| 
      
 52 
     | 
    
         
            +
            line in your initializers or environment.rb:
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
             DynamicImage.page_caching = false
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            == Copyright
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            Copyright © 2006-2010 Inge Jørgensen. See LICENSE for details.
         
     | 
| 
      
 59 
     | 
    
         
            +
            	
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rake'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rake/testtask'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rake/rdoctask'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require "rake"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            begin
         
     | 
| 
      
 8 
     | 
    
         
            +
            	require "jeweler"
         
     | 
| 
      
 9 
     | 
    
         
            +
            	Jeweler::Tasks.new do |gem|
         
     | 
| 
      
 10 
     | 
    
         
            +
            		gem.name     = "dynamic_image"
         
     | 
| 
      
 11 
     | 
    
         
            +
            		gem.summary  = "DynamicImage is a rails plugin providing transparent uploading and processing of image files."
         
     | 
| 
      
 12 
     | 
    
         
            +
            		gem.email    = "inge@elektronaut.no"
         
     | 
| 
      
 13 
     | 
    
         
            +
            		gem.homepage = "http://github.com/elektronaut/dynamic_image"
         
     | 
| 
      
 14 
     | 
    
         
            +
            		gem.authors  = ["Inge Jørgensen"]
         
     | 
| 
      
 15 
     | 
    
         
            +
            		gem.files    = Dir["*", "{lib}/**/*", "{app}/**/*", "{config}/**/*"]
         
     | 
| 
      
 16 
     | 
    
         
            +
            		gem.add_dependency("rmagick", "~> 2.12.2")
         
     | 
| 
      
 17 
     | 
    
         
            +
            		gem.add_dependency("vector2d", "~> 1.0.0")
         
     | 
| 
      
 18 
     | 
    
         
            +
            	end
         
     | 
| 
      
 19 
     | 
    
         
            +
            	Jeweler::GemcutterTasks.new
         
     | 
| 
      
 20 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 21 
     | 
    
         
            +
            	puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            desc 'Default: run unit tests.'
         
     | 
| 
      
 25 
     | 
    
         
            +
            task :default => :test
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            desc 'Test the dynamic_image plugin.'
         
     | 
| 
      
 28 
     | 
    
         
            +
            Rake::TestTask.new(:test) do |t|
         
     | 
| 
      
 29 
     | 
    
         
            +
            	t.libs << 'lib'
         
     | 
| 
      
 30 
     | 
    
         
            +
            	t.libs << 'test'
         
     | 
| 
      
 31 
     | 
    
         
            +
            	t.pattern = 'test/**/*_test.rb'
         
     | 
| 
      
 32 
     | 
    
         
            +
            	t.verbose = true
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            desc 'Generate documentation for the dynamic_image plugin.'
         
     | 
| 
      
 36 
     | 
    
         
            +
            Rake::RDocTask.new(:rdoc) do |rdoc|
         
     | 
| 
      
 37 
     | 
    
         
            +
            	rdoc.rdoc_dir = 'rdoc'
         
     | 
| 
      
 38 
     | 
    
         
            +
            	rdoc.title    = 'DynamicImage'
         
     | 
| 
      
 39 
     | 
    
         
            +
            	rdoc.options << '--line-numbers' << '--inline-source'
         
     | 
| 
      
 40 
     | 
    
         
            +
            	rdoc.rdoc_files.include('README')
         
     | 
| 
      
 41 
     | 
    
         
            +
            	rdoc.rdoc_files.include('lib/**/*.rb')
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
    
        data/VERSION
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            0.9.0
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class ImagesController < ActionController::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            	after_filter :cache_dynamic_image
         
     | 
| 
      
 4 
     | 
    
         
            +
            	after_filter :run_garbage_collection_for_dynamic_image_controller
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            	unloadable
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            	public
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            		# Return the requested image. Rescale, filter and cache it where appropriate.
         
     | 
| 
      
 11 
     | 
    
         
            +
            		def render_dynamic_image
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            			render_missing_image and return unless Image.exists?(params[:id])
         
     | 
| 
      
 14 
     | 
    
         
            +
            			image = Image.find(params[:id])
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            			minTime = Time.rfc2822(request.env["HTTP_IF_MODIFIED_SINCE"]) rescue nil
         
     | 
| 
      
 17 
     | 
    
         
            +
            			if minTime && image.created_at? && image.created_at <= minTime
         
     | 
| 
      
 18 
     | 
    
         
            +
            				render :text => '304 Not Modified', :status => 304
         
     | 
| 
      
 19 
     | 
    
         
            +
            				return
         
     | 
| 
      
 20 
     | 
    
         
            +
            			end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            			unless image.data?
         
     | 
| 
      
 23 
     | 
    
         
            +
            				logger.warn "Image #{image.id} exists, but has no data"
         
     | 
| 
      
 24 
     | 
    
         
            +
            				render_missing_image and return
         
     | 
| 
      
 25 
     | 
    
         
            +
            			end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            			if size = params[:size]
         
     | 
| 
      
 28 
     | 
    
         
            +
            			    if size =~ /^x[\d]+$/ || size =~ /^[\d]+x$/
         
     | 
| 
      
 29 
     | 
    
         
            +
            					if params[:original]
         
     | 
| 
      
 30 
     | 
    
         
            +
            						image.cropped = false
         
     | 
| 
      
 31 
     | 
    
         
            +
            					end
         
     | 
| 
      
 32 
     | 
    
         
            +
            			        size = Vector2d.new(size)
         
     | 
| 
      
 33 
     | 
    
         
            +
            				    image_size = Vector2d.new(image.size)
         
     | 
| 
      
 34 
     | 
    
         
            +
            				    size = image_size.constrain_both(size).round.to_s
         
     | 
| 
      
 35 
     | 
    
         
            +
            				end
         
     | 
| 
      
 36 
     | 
    
         
            +
            	    		imagedata = image.get_processed(size, params[:filterset])
         
     | 
| 
      
 37 
     | 
    
         
            +
            		    else
         
     | 
| 
      
 38 
     | 
    
         
            +
            	    		imagedata = image
         
     | 
| 
      
 39 
     | 
    
         
            +
            	        end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            			DynamicImage.dirty_memory = true # Flag memory for GC
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            			if image
         
     | 
| 
      
 44 
     | 
    
         
            +
            				response.headers['Cache-Control'] = nil
         
     | 
| 
      
 45 
     | 
    
         
            +
            				response.headers['Last-Modified'] = imagedata.created_at.httpdate if imagedata.created_at?
         
     | 
| 
      
 46 
     | 
    
         
            +
            				send_data( 
         
     | 
| 
      
 47 
     | 
    
         
            +
            					imagedata.data, 
         
     | 
| 
      
 48 
     | 
    
         
            +
            					:filename    => image.filename, 
         
     | 
| 
      
 49 
     | 
    
         
            +
            					:type        => image.content_type, 
         
     | 
| 
      
 50 
     | 
    
         
            +
            					:disposition => 'inline'
         
     | 
| 
      
 51 
     | 
    
         
            +
            				)
         
     | 
| 
      
 52 
     | 
    
         
            +
            			end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            		end
         
     | 
| 
      
 55 
     | 
    
         
            +
            		
         
     | 
| 
      
 56 
     | 
    
         
            +
            	protected
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            		def render_missing_image
         
     | 
| 
      
 59 
     | 
    
         
            +
            			if self.respond_to?(:render_error)
         
     | 
| 
      
 60 
     | 
    
         
            +
            				render_error 404
         
     | 
| 
      
 61 
     | 
    
         
            +
            			else
         
     | 
| 
      
 62 
     | 
    
         
            +
            				render :status => 404, :text => "404: Image not found"
         
     | 
| 
      
 63 
     | 
    
         
            +
            			end
         
     | 
| 
      
 64 
     | 
    
         
            +
            		end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            		# Enforce caching of dynamic images, even if caching is turned off
         
     | 
| 
      
 67 
     | 
    
         
            +
            		def cache_dynamic_image
         
     | 
| 
      
 68 
     | 
    
         
            +
            			cache_setting = ActionController::Base.perform_caching
         
     | 
| 
      
 69 
     | 
    
         
            +
            			ActionController::Base.perform_caching = true
         
     | 
| 
      
 70 
     | 
    
         
            +
            			cache_page
         
     | 
| 
      
 71 
     | 
    
         
            +
            			ActionController::Base.perform_caching = cache_setting
         
     | 
| 
      
 72 
     | 
    
         
            +
            		end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            		# Perform garbage collection if necessary
         
     | 
| 
      
 75 
     | 
    
         
            +
            		def run_garbage_collection_for_dynamic_image_controller
         
     | 
| 
      
 76 
     | 
    
         
            +
            			DynamicImage.clean_dirty_memory
         
     | 
| 
      
 77 
     | 
    
         
            +
            		end
         
     | 
| 
      
 78 
     | 
    
         
            +
            		
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
    
        data/app/models/image.rb
    ADDED
    
    | 
         @@ -0,0 +1,183 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Image < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
            	unloadable
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            	binary_storage :data, :sha1_hash
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            	validates_format_of :content_type, 
         
     | 
| 
      
 7 
     | 
    
         
            +
            	                    :with => /^image/,
         
     | 
| 
      
 8 
     | 
    
         
            +
            		                :message => "you can only upload pictures"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            	attr_accessor :filterset, :data_checked, :skip_maxsize
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            	# Sanitize the filename and set the name to the filename if omitted
         
     | 
| 
      
 13 
     | 
    
         
            +
            	validate do |image|
         
     | 
| 
      
 14 
     | 
    
         
            +
            		image.name     = File.basename(image.filename, ".*") if !image.name || image.name.strip == ""
         
     | 
| 
      
 15 
     | 
    
         
            +
            		image.filename = image.friendly_file_name(image.filename)
         
     | 
| 
      
 16 
     | 
    
         
            +
            		if image.cropped?
         
     | 
| 
      
 17 
     | 
    
         
            +
            			image.errors.add(:crop_start, "must be a vector") unless image.crop_start =~ /^[\d]+x[\d]+$/
         
     | 
| 
      
 18 
     | 
    
         
            +
            			image.errors.add(:crop_size,  "must be a vector") unless image.crop_size  =~ /^[\d]+x[\d]+$/
         
     | 
| 
      
 19 
     | 
    
         
            +
            		else
         
     | 
| 
      
 20 
     | 
    
         
            +
            			image.crop_size  = image.original_size
         
     | 
| 
      
 21 
     | 
    
         
            +
            			image.crop_start = "0x0"
         
     | 
| 
      
 22 
     | 
    
         
            +
            		end
         
     | 
| 
      
 23 
     | 
    
         
            +
            	end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            	# Create the binary from an image file.
         
     | 
| 
      
 26 
     | 
    
         
            +
            	def imagefile=(image_file)
         
     | 
| 
      
 27 
     | 
    
         
            +
            		self.filename     = image_file.original_filename rescue File.basename( image_file.path )
         
     | 
| 
      
 28 
     | 
    
         
            +
            		self.content_type = image_file.content_type.chomp rescue "image/"+image_file.path.split(/\./).last.downcase.gsub(/jpg/,"jpeg") # ugly hack
         
     | 
| 
      
 29 
     | 
    
         
            +
            		set_image_data(image_file.read)
         
     | 
| 
      
 30 
     | 
    
         
            +
            	end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            	# Return the image hotspot
         
     | 
| 
      
 33 
     | 
    
         
            +
            	def hotspot
         
     | 
| 
      
 34 
     | 
    
         
            +
            		(self.hotspot?) ? self.hotspot : (Vector2d.new(self.size) * 0.5).round.to_s
         
     | 
| 
      
 35 
     | 
    
         
            +
            	end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            	# Check the image data
         
     | 
| 
      
 38 
     | 
    
         
            +
            	def set_image_data(data)
         
     | 
| 
      
 39 
     | 
    
         
            +
            		self.data = data
         
     | 
| 
      
 40 
     | 
    
         
            +
            		if self.data?
         
     | 
| 
      
 41 
     | 
    
         
            +
            			image     = Magick::ImageList.new.from_blob(self.data)
         
     | 
| 
      
 42 
     | 
    
         
            +
            			size      = Vector2d.new(image.columns, image.rows)
         
     | 
| 
      
 43 
     | 
    
         
            +
            			if DynamicImage.crash_size
         
     | 
| 
      
 44 
     | 
    
         
            +
            				crashsize = Vector2d.new(DynamicImage.crash_size)
         
     | 
| 
      
 45 
     | 
    
         
            +
            				if (size.x > crashsize.x || size.y > crashsize.y)
         
     | 
| 
      
 46 
     | 
    
         
            +
            					raise "Image too large!"
         
     | 
| 
      
 47 
     | 
    
         
            +
            				end
         
     | 
| 
      
 48 
     | 
    
         
            +
            			end
         
     | 
| 
      
 49 
     | 
    
         
            +
            			if DynamicImage.max_size && !self.skip_maxsize
         
     | 
| 
      
 50 
     | 
    
         
            +
            				maxsize = Vector2d.new(DynamicImage.max_size)
         
     | 
| 
      
 51 
     | 
    
         
            +
            				if (size.x > maxsize.x || size.y > maxsize.y)
         
     | 
| 
      
 52 
     | 
    
         
            +
            					size = size.constrain_both(maxsize).round
         
     | 
| 
      
 53 
     | 
    
         
            +
            					image.resize!(size.x, size.y)
         
     | 
| 
      
 54 
     | 
    
         
            +
            					self.data = image.to_blob
         
     | 
| 
      
 55 
     | 
    
         
            +
            				end
         
     | 
| 
      
 56 
     | 
    
         
            +
            			end
         
     | 
| 
      
 57 
     | 
    
         
            +
            			# Convert image to a proper format
         
     | 
| 
      
 58 
     | 
    
         
            +
            			unless image.format =~ /(JPEG|PNG|GIF)/
         
     | 
| 
      
 59 
     | 
    
         
            +
            				self.data = image.to_blob{self.format = 'JPEG'; self.quality = 90}
         
     | 
| 
      
 60 
     | 
    
         
            +
            				self.filename += ".jpg"
         
     | 
| 
      
 61 
     | 
    
         
            +
            				self.content_type = "image/jpeg"
         
     | 
| 
      
 62 
     | 
    
         
            +
            			end
         
     | 
| 
      
 63 
     | 
    
         
            +
            			self.original_size = size.round.to_s
         
     | 
| 
      
 64 
     | 
    
         
            +
            		end
         
     | 
| 
      
 65 
     | 
    
         
            +
            	end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            	# Returns the image width
         
     | 
| 
      
 68 
     | 
    
         
            +
            	def original_width
         
     | 
| 
      
 69 
     | 
    
         
            +
            		Vector2d.new(self.original_size).x.to_i
         
     | 
| 
      
 70 
     | 
    
         
            +
            	end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            	# Returns the image height
         
     | 
| 
      
 73 
     | 
    
         
            +
            	def original_height
         
     | 
| 
      
 74 
     | 
    
         
            +
            		Vector2d.new(self.original_size).y.to_i
         
     | 
| 
      
 75 
     | 
    
         
            +
            	end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            	def crop_start_x
         
     | 
| 
      
 78 
     | 
    
         
            +
            		Vector2d.new(self.crop_start).x.to_i
         
     | 
| 
      
 79 
     | 
    
         
            +
            	end
         
     | 
| 
      
 80 
     | 
    
         
            +
            	def crop_start_y
         
     | 
| 
      
 81 
     | 
    
         
            +
            		Vector2d.new(self.crop_start).y.to_i
         
     | 
| 
      
 82 
     | 
    
         
            +
            	end
         
     | 
| 
      
 83 
     | 
    
         
            +
            	def crop_width
         
     | 
| 
      
 84 
     | 
    
         
            +
            		Vector2d.new(self.crop_size).x.to_i
         
     | 
| 
      
 85 
     | 
    
         
            +
            	end
         
     | 
| 
      
 86 
     | 
    
         
            +
            	def crop_height
         
     | 
| 
      
 87 
     | 
    
         
            +
            		Vector2d.new(self.crop_size).y.to_i
         
     | 
| 
      
 88 
     | 
    
         
            +
            	end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            	# Returns original or cropped size
         
     | 
| 
      
 91 
     | 
    
         
            +
            	def size
         
     | 
| 
      
 92 
     | 
    
         
            +
            		(self.cropped?) ? self.crop_size : self.original_size
         
     | 
| 
      
 93 
     | 
    
         
            +
            	end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
            	def size=(new_size)
         
     | 
| 
      
 96 
     | 
    
         
            +
            		self.original_size = new_size
         
     | 
| 
      
 97 
     | 
    
         
            +
            	end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
            	# Convert file name to a more file system friendly one.
         
     | 
| 
      
 100 
     | 
    
         
            +
            	# TODO: international chars
         
     | 
| 
      
 101 
     | 
    
         
            +
            	def friendly_file_name( file_name )
         
     | 
| 
      
 102 
     | 
    
         
            +
            		[["æ","ae"], ["ø","oe"], ["å","aa"]].each do |int|
         
     | 
| 
      
 103 
     | 
    
         
            +
            			file_name = file_name.gsub(int[0], int[1])
         
     | 
| 
      
 104 
     | 
    
         
            +
            		end
         
     | 
| 
      
 105 
     | 
    
         
            +
            		File.basename(file_name).gsub(/[^\w\d\.-]/, "_")
         
     | 
| 
      
 106 
     | 
    
         
            +
            	end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
            	# Get the base part of a filename
         
     | 
| 
      
 109 
     | 
    
         
            +
            	def base_part_of(file_name)
         
     | 
| 
      
 110 
     | 
    
         
            +
            		name = File.basename(file_name)
         
     | 
| 
      
 111 
     | 
    
         
            +
            		name.gsub(/[ˆ\w._-]/, '')
         
     | 
| 
      
 112 
     | 
    
         
            +
            	end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
            	# Rescale and crop the image, and return it as a blob.
         
     | 
| 
      
 115 
     | 
    
         
            +
            	def rescaled_and_cropped_data(*args)
         
     | 
| 
      
 116 
     | 
    
         
            +
            		DynamicImage.dirty_memory = true                                                       # Flag to perform GC
         
     | 
| 
      
 117 
     | 
    
         
            +
            		image_data = Magick::ImageList.new.from_blob(self.data)
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
            		if self.cropped?
         
     | 
| 
      
 120 
     | 
    
         
            +
            			cropped_start = Vector2d.new(self.crop_start).round
         
     | 
| 
      
 121 
     | 
    
         
            +
            			cropped_size  = Vector2d.new(self.crop_size).round
         
     | 
| 
      
 122 
     | 
    
         
            +
            			image_data = image_data.crop(cropped_start.x, cropped_start.y, cropped_size.x, cropped_size.y, true)
         
     | 
| 
      
 123 
     | 
    
         
            +
            		end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            		size         = Vector2d.new(self.size)
         
     | 
| 
      
 126 
     | 
    
         
            +
            		rescale_size = size.dup.constrain_one(args).round                                      # Rescale dimensions
         
     | 
| 
      
 127 
     | 
    
         
            +
            		crop_to_size = Vector2d.new(args).round                                                # Crop size
         
     | 
| 
      
 128 
     | 
    
         
            +
            		new_hotspot  = Vector2d.new(hotspot) * (rescale_size / size)                           # Recalculated hotspot
         
     | 
| 
      
 129 
     | 
    
         
            +
            		rect = [(new_hotspot-(crop_to_size/2)).round, (new_hotspot+(crop_to_size/2)).round]    # Array containing crop coords
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            		# Adjustments
         
     | 
| 
      
 132 
     | 
    
         
            +
            		x = rect[0].x; rect.each{|r| r.x += (x.abs)}            if x < 0
         
     | 
| 
      
 133 
     | 
    
         
            +
            		y = rect[0].y; rect.each{|r| r.y += (y.abs)}            if y < 0
         
     | 
| 
      
 134 
     | 
    
         
            +
            		x = rect[1].x; rect.each{|r| r.x -= (x-rescale_size.x)} if x > rescale_size.x
         
     | 
| 
      
 135 
     | 
    
         
            +
            		y = rect[1].y; rect.each{|r| r.y -= (y-rescale_size.y)} if y > rescale_size.y
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
            		rect[0].round!
         
     | 
| 
      
 138 
     | 
    
         
            +
            		rect[1].round!
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
            		image_data = image_data.resize(rescale_size.x, rescale_size.y)
         
     | 
| 
      
 141 
     | 
    
         
            +
            		image_data = image_data.crop(rect[0].x, rect[0].y, crop_to_size.x, crop_to_size.y)
         
     | 
| 
      
 142 
     | 
    
         
            +
            		image_data.to_blob{self.quality = 90}
         
     | 
| 
      
 143 
     | 
    
         
            +
            	end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            	def constrain_size(*max_size)
         
     | 
| 
      
 146 
     | 
    
         
            +
            		Vector2d.new(self.size).constrain_both(max_size.flatten).round.to_s
         
     | 
| 
      
 147 
     | 
    
         
            +
            	end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            	# Get a duplicate image with resizing and filters applied.
         
     | 
| 
      
 150 
     | 
    
         
            +
            	def get_processed(size, filterset=nil)
         
     | 
| 
      
 151 
     | 
    
         
            +
            		size                      = Vector2d.new(size).round.to_s
         
     | 
| 
      
 152 
     | 
    
         
            +
            		processed_image           = Image.new
         
     | 
| 
      
 153 
     | 
    
         
            +
            		processed_image.filterset = filterset || 'default'
         
     | 
| 
      
 154 
     | 
    
         
            +
            		processed_image.data      = self.rescaled_and_cropped_data(size)
         
     | 
| 
      
 155 
     | 
    
         
            +
            		processed_image.size      = size
         
     | 
| 
      
 156 
     | 
    
         
            +
            		processed_image.apply_filters
         
     | 
| 
      
 157 
     | 
    
         
            +
            		processed_image
         
     | 
| 
      
 158 
     | 
    
         
            +
            	end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            	# Apply filters to image data
         
     | 
| 
      
 161 
     | 
    
         
            +
            	def apply_filters
         
     | 
| 
      
 162 
     | 
    
         
            +
            		filterset_name = self.filterset || 'default'
         
     | 
| 
      
 163 
     | 
    
         
            +
            		filterset = DynamicImage::Filterset[filterset_name]
         
     | 
| 
      
 164 
     | 
    
         
            +
            		if filterset
         
     | 
| 
      
 165 
     | 
    
         
            +
            			DynamicImage.dirty_memory = true # Flag for GC
         
     | 
| 
      
 166 
     | 
    
         
            +
            			data = Magick::ImageList.new.from_blob(self.data)
         
     | 
| 
      
 167 
     | 
    
         
            +
            			data = filterset.process(data)
         
     | 
| 
      
 168 
     | 
    
         
            +
            			self.data = data.to_blob
         
     | 
| 
      
 169 
     | 
    
         
            +
            		end
         
     | 
| 
      
 170 
     | 
    
         
            +
            	end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            	# Decorates to_json with additional attributes
         
     | 
| 
      
 173 
     | 
    
         
            +
            	def to_json
         
     | 
| 
      
 174 
     | 
    
         
            +
            		attributes.merge({
         
     | 
| 
      
 175 
     | 
    
         
            +
            			:original_width  => self.original_width, 
         
     | 
| 
      
 176 
     | 
    
         
            +
            			:original_height => self.original_height,
         
     | 
| 
      
 177 
     | 
    
         
            +
            			:crop_width      => self.crop_width,
         
     | 
| 
      
 178 
     | 
    
         
            +
            			:crop_height     => self.crop_height,
         
     | 
| 
      
 179 
     | 
    
         
            +
            			:crop_start_x    => self.crop_start_x,
         
     | 
| 
      
 180 
     | 
    
         
            +
            			:crop_start_y    => self.crop_start_y
         
     | 
| 
      
 181 
     | 
    
         
            +
            		}).to_json
         
     | 
| 
      
 182 
     | 
    
         
            +
            	end
         
     | 
| 
      
 183 
     | 
    
         
            +
            end
         
     | 
    
        data/config/routes.rb
    ADDED
    
    | 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Rails 3 routes
         
     | 
| 
      
 2 
     | 
    
         
            +
            Rails.application.routes.draw do |map|
         
     | 
| 
      
 3 
     | 
    
         
            +
            	match "dynamic_images/:id/:original(/:size(/:filterset))/*filename" => "images#render_dynamic_image", :size => /\d*x\d*/, :original => /original/
         
     | 
| 
      
 4 
     | 
    
         
            +
            	match "dynamic_images/:id(/:size(/:filterset))/*filename" => "images#render_dynamic_image", :size => /\d*x\d*/
         
     | 
| 
      
 5 
     | 
    
         
            +
            	# Legacy
         
     | 
| 
      
 6 
     | 
    
         
            +
            	match "dynamic_image/:id/:original(/:size(/:filterset))/*filename" => "images#render_dynamic_image", :size => /\d*x\d*/, :original => /original/
         
     | 
| 
      
 7 
     | 
    
         
            +
            	match "dynamic_image/:id(/:size(/:filterset))/*filename" => "images#render_dynamic_image", :size => /\d*x\d*/
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            # Rails 2 routes
         
     | 
| 
      
 11 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/:original/:size/:filterset/*filename', {:controller => 'images', :action => 'render_dynamic_image', :requirements => { :size => /[\d]*x[\d]*/, :original => /original/ }})
         
     | 
| 
      
 12 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/:original/:size/*filename', {:controller => 'images', :action => 'render_dynamic_image', :requirements => { :size => /[\d]*x[\d]*/, :original => /original/ }})
         
     | 
| 
      
 13 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/:original/*filename', {:controller => 'images', :action => 'render_dynamic_image', :requirements => { :original => /original/ }})
         
     | 
| 
      
 14 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/:size/:filterset/*filename', {:controller => 'images', :action => 'render_dynamic_image', :requirements => { :size => /[\d]*x[\d]*/ }})
         
     | 
| 
      
 15 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/:size/*filename', {:controller => 'images', :action => 'render_dynamic_image', :requirements => { :size => /[\d]*x[\d]*/ }})
         
     | 
| 
      
 16 
     | 
    
         
            +
            #@set.add_route('dynamic_image/:id/*filename', {:controller => 'images', :action => 'render_dynamic_image'})
         
     | 
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Generated by jeweler
         
     | 
| 
      
 2 
     | 
    
         
            +
            # DO NOT EDIT THIS FILE DIRECTLY
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
         
     | 
| 
      
 4 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.name = %q{dynamic_image}
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.version = "0.9.0"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.authors = ["Inge J\303\270rgensen"]
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.date = %q{2010-05-19}
         
     | 
| 
      
 13 
     | 
    
         
            +
              s.email = %q{inge@elektronaut.no}
         
     | 
| 
      
 14 
     | 
    
         
            +
              s.extra_rdoc_files = [
         
     | 
| 
      
 15 
     | 
    
         
            +
                "LICENSE",
         
     | 
| 
      
 16 
     | 
    
         
            +
                 "README.rdoc"
         
     | 
| 
      
 17 
     | 
    
         
            +
              ]
         
     | 
| 
      
 18 
     | 
    
         
            +
              s.files = [
         
     | 
| 
      
 19 
     | 
    
         
            +
                "LICENSE",
         
     | 
| 
      
 20 
     | 
    
         
            +
                 "README.rdoc",
         
     | 
| 
      
 21 
     | 
    
         
            +
                 "Rakefile",
         
     | 
| 
      
 22 
     | 
    
         
            +
                 "VERSION",
         
     | 
| 
      
 23 
     | 
    
         
            +
                 "app/controllers/images_controller.rb",
         
     | 
| 
      
 24 
     | 
    
         
            +
                 "app/models/image.rb",
         
     | 
| 
      
 25 
     | 
    
         
            +
                 "config/routes.rb",
         
     | 
| 
      
 26 
     | 
    
         
            +
                 "dynamic_image.gemspec",
         
     | 
| 
      
 27 
     | 
    
         
            +
                 "init.rb",
         
     | 
| 
      
 28 
     | 
    
         
            +
                 "install.rb",
         
     | 
| 
      
 29 
     | 
    
         
            +
                 "lib/binary_storage.rb",
         
     | 
| 
      
 30 
     | 
    
         
            +
                 "lib/binary_storage/active_record_extensions.rb",
         
     | 
| 
      
 31 
     | 
    
         
            +
                 "lib/binary_storage/blob.rb",
         
     | 
| 
      
 32 
     | 
    
         
            +
                 "lib/dynamic_image.rb",
         
     | 
| 
      
 33 
     | 
    
         
            +
                 "lib/dynamic_image/active_record_extensions.rb",
         
     | 
| 
      
 34 
     | 
    
         
            +
                 "lib/dynamic_image/engine.rb",
         
     | 
| 
      
 35 
     | 
    
         
            +
                 "lib/dynamic_image/filterset.rb",
         
     | 
| 
      
 36 
     | 
    
         
            +
                 "lib/dynamic_image/helper.rb",
         
     | 
| 
      
 37 
     | 
    
         
            +
                 "lib/generators/dynamic_image/USAGE",
         
     | 
| 
      
 38 
     | 
    
         
            +
                 "lib/generators/dynamic_image/dynamic_image_generator.rb",
         
     | 
| 
      
 39 
     | 
    
         
            +
                 "lib/generators/dynamic_image/templates/migrations/create_images.rb",
         
     | 
| 
      
 40 
     | 
    
         
            +
                 "uninstall.rb"
         
     | 
| 
      
 41 
     | 
    
         
            +
              ]
         
     | 
| 
      
 42 
     | 
    
         
            +
              s.homepage = %q{http://github.com/elektronaut/dynamic_image}
         
     | 
| 
      
 43 
     | 
    
         
            +
              s.rdoc_options = ["--charset=UTF-8"]
         
     | 
| 
      
 44 
     | 
    
         
            +
              s.require_paths = ["lib"]
         
     | 
| 
      
 45 
     | 
    
         
            +
              s.rubygems_version = %q{1.3.6}
         
     | 
| 
      
 46 
     | 
    
         
            +
              s.summary = %q{DynamicImage is a rails plugin providing transparent uploading and processing of image files.}
         
     | 
| 
      
 47 
     | 
    
         
            +
              s.test_files = [
         
     | 
| 
      
 48 
     | 
    
         
            +
                "test/dynamic_image_test.rb",
         
     | 
| 
      
 49 
     | 
    
         
            +
                 "test/test_helper.rb"
         
     | 
| 
      
 50 
     | 
    
         
            +
              ]
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              if s.respond_to? :specification_version then
         
     | 
| 
      
 53 
     | 
    
         
            +
                current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
         
     | 
| 
      
 54 
     | 
    
         
            +
                s.specification_version = 3
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
         
     | 
| 
      
 57 
     | 
    
         
            +
                  s.add_runtime_dependency(%q<rmagick>, ["~> 2.12.2"])
         
     | 
| 
      
 58 
     | 
    
         
            +
                  s.add_runtime_dependency(%q<vector2d>, ["~> 1.0.0"])
         
     | 
| 
      
 59 
     | 
    
         
            +
                else
         
     | 
| 
      
 60 
     | 
    
         
            +
                  s.add_dependency(%q<rmagick>, ["~> 2.12.2"])
         
     | 
| 
      
 61 
     | 
    
         
            +
                  s.add_dependency(%q<vector2d>, ["~> 1.0.0"])
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
              else
         
     | 
| 
      
 64 
     | 
    
         
            +
                s.add_dependency(%q<rmagick>, ["~> 2.12.2"])
         
     | 
| 
      
 65 
     | 
    
         
            +
                s.add_dependency(%q<vector2d>, ["~> 1.0.0"])
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
    
        data/init.rb
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dynamic_image'
         
     | 
    
        data/install.rb
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Install hook code here
         
     | 
| 
         @@ -0,0 +1,144 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'binary_storage'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module BinaryStorage
         
     | 
| 
      
 4 
     | 
    
         
            +
            	module ActiveRecordExtensions
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            		def self.included(base)
         
     | 
| 
      
 7 
     | 
    
         
            +
            			base.send(:extend, BinaryStorage::ActiveRecordExtensions::ClassMethods)
         
     | 
| 
      
 8 
     | 
    
         
            +
            		end
         
     | 
| 
      
 9 
     | 
    
         
            +
            		
         
     | 
| 
      
 10 
     | 
    
         
            +
            		module ClassMethods
         
     | 
| 
      
 11 
     | 
    
         
            +
            			
         
     | 
| 
      
 12 
     | 
    
         
            +
            			def register_binary(klass, binary_name, binary_column)
         
     | 
| 
      
 13 
     | 
    
         
            +
            				@@binary_columns ||= {}
         
     | 
| 
      
 14 
     | 
    
         
            +
            				@@binary_columns[klass] ||= {}
         
     | 
| 
      
 15 
     | 
    
         
            +
            				@@binary_columns[klass][binary_name] = binary_column
         
     | 
| 
      
 16 
     | 
    
         
            +
            			end
         
     | 
| 
      
 17 
     | 
    
         
            +
            			
         
     | 
| 
      
 18 
     | 
    
         
            +
            			def binary_column(klass, binary_name)
         
     | 
| 
      
 19 
     | 
    
         
            +
            				if @@binary_columns && @@binary_columns[klass] && @@binary_columns[klass][binary_name]
         
     | 
| 
      
 20 
     | 
    
         
            +
            					@@binary_columns[klass][binary_name]
         
     | 
| 
      
 21 
     | 
    
         
            +
            				else
         
     | 
| 
      
 22 
     | 
    
         
            +
            					nil
         
     | 
| 
      
 23 
     | 
    
         
            +
            				end
         
     | 
| 
      
 24 
     | 
    
         
            +
            			end
         
     | 
| 
      
 25 
     | 
    
         
            +
            			
         
     | 
| 
      
 26 
     | 
    
         
            +
            			# Count existing references to a binary
         
     | 
| 
      
 27 
     | 
    
         
            +
            			def binary_reference_count(hash_string)
         
     | 
| 
      
 28 
     | 
    
         
            +
            				references = 0
         
     | 
| 
      
 29 
     | 
    
         
            +
            				if @@binary_columns
         
     | 
| 
      
 30 
     | 
    
         
            +
            					@@binary_columns.each do |klass, binaries|
         
     | 
| 
      
 31 
     | 
    
         
            +
            						binaries.each do |binary_name, binary_column|
         
     | 
| 
      
 32 
     | 
    
         
            +
            							references += klass.count(:all, :conditions => ["`#{binary_column} = ?`", hash_string])
         
     | 
| 
      
 33 
     | 
    
         
            +
            						end
         
     | 
| 
      
 34 
     | 
    
         
            +
            					end
         
     | 
| 
      
 35 
     | 
    
         
            +
            				end
         
     | 
| 
      
 36 
     | 
    
         
            +
            			end
         
     | 
| 
      
 37 
     | 
    
         
            +
            			
         
     | 
| 
      
 38 
     | 
    
         
            +
            			def binary_storage(binary_name, binary_column)
         
     | 
| 
      
 39 
     | 
    
         
            +
            				binary_name   = binary_name.to_s
         
     | 
| 
      
 40 
     | 
    
         
            +
            				binary_column = binary_column.to_s
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            				register_binary(self, binary_name, binary_column)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            				class_eval <<-end_eval
         
     | 
| 
      
 45 
     | 
    
         
            +
            					before_save do |binary_model|
         
     | 
| 
      
 46 
     | 
    
         
            +
            						binary_model.save_binary("#{binary_name}")
         
     | 
| 
      
 47 
     | 
    
         
            +
            					end
         
     | 
| 
      
 48 
     | 
    
         
            +
            					
         
     | 
| 
      
 49 
     | 
    
         
            +
            					after_destroy do |model|
         
     | 
| 
      
 50 
     | 
    
         
            +
            						binary_model.destroy_binary("#{binary_name}")
         
     | 
| 
      
 51 
     | 
    
         
            +
            					end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            					def #{binary_name}
         
     | 
| 
      
 54 
     | 
    
         
            +
            						self.get_binary_data("#{binary_name}")
         
     | 
| 
      
 55 
     | 
    
         
            +
            					end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            					def #{binary_name}=(binary_data)
         
     | 
| 
      
 58 
     | 
    
         
            +
            						self.set_binary_data("#{binary_name}", binary_data)
         
     | 
| 
      
 59 
     | 
    
         
            +
            					end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            					def #{binary_name}?
         
     | 
| 
      
 62 
     | 
    
         
            +
            						self.has_binary_data?("#{binary_name}")
         
     | 
| 
      
 63 
     | 
    
         
            +
            					end
         
     | 
| 
      
 64 
     | 
    
         
            +
            				end_eval
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            				send(:include, BinaryStorage::ActiveRecordExtensions::InstanceMethods)
         
     | 
| 
      
 67 
     | 
    
         
            +
            			end
         
     | 
| 
      
 68 
     | 
    
         
            +
            		end
         
     | 
| 
      
 69 
     | 
    
         
            +
            		
         
     | 
| 
      
 70 
     | 
    
         
            +
            		module InstanceMethods
         
     | 
| 
      
 71 
     | 
    
         
            +
            			
         
     | 
| 
      
 72 
     | 
    
         
            +
            			def binaries
         
     | 
| 
      
 73 
     | 
    
         
            +
            				@binaries ||= {}
         
     | 
| 
      
 74 
     | 
    
         
            +
            			end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
            			def binary_column(binary_name)
         
     | 
| 
      
 77 
     | 
    
         
            +
            				if column_name = self.class.binary_column(self.class, binary_name) 
         
     | 
| 
      
 78 
     | 
    
         
            +
            					column_name
         
     | 
| 
      
 79 
     | 
    
         
            +
            				else 
         
     | 
| 
      
 80 
     | 
    
         
            +
            					raise "Binary column #{binary_name} not defined!"
         
     | 
| 
      
 81 
     | 
    
         
            +
            				end
         
     | 
| 
      
 82 
     | 
    
         
            +
            			end
         
     | 
| 
      
 83 
     | 
    
         
            +
            			
         
     | 
| 
      
 84 
     | 
    
         
            +
            			def binary_hash_string(binary_name)
         
     | 
| 
      
 85 
     | 
    
         
            +
            				self.attributes[binary_column(binary_name)]
         
     | 
| 
      
 86 
     | 
    
         
            +
            			end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            			def save_binary(binary_name)
         
     | 
| 
      
 89 
     | 
    
         
            +
            				if binaries.has_key?(binary_name)
         
     | 
| 
      
 90 
     | 
    
         
            +
            					if binary = binaries[binary_name]
         
     | 
| 
      
 91 
     | 
    
         
            +
            						binary.save
         
     | 
| 
      
 92 
     | 
    
         
            +
            						self.attributes = self.attributes.merge({binary_column(binary_name).to_sym => binary.hash_string})
         
     | 
| 
      
 93 
     | 
    
         
            +
            					else
         
     | 
| 
      
 94 
     | 
    
         
            +
            						self.attributes = self.attributes.merge({binary_column(binary_name).to_sym => nil})
         
     | 
| 
      
 95 
     | 
    
         
            +
            					end
         
     | 
| 
      
 96 
     | 
    
         
            +
            				end
         
     | 
| 
      
 97 
     | 
    
         
            +
            			end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
            			def destroy_binary(binary_name)
         
     | 
| 
      
 100 
     | 
    
         
            +
            				if binary = binaries[binary_name]
         
     | 
| 
      
 101 
     | 
    
         
            +
            					if hash_string = binary.hash_string
         
     | 
| 
      
 102 
     | 
    
         
            +
            						references = self.class.binary_reference_count
         
     | 
| 
      
 103 
     | 
    
         
            +
            						if references < 1
         
     | 
| 
      
 104 
     | 
    
         
            +
            							binary.delete!
         
     | 
| 
      
 105 
     | 
    
         
            +
            						end
         
     | 
| 
      
 106 
     | 
    
         
            +
            					end
         
     | 
| 
      
 107 
     | 
    
         
            +
            				end
         
     | 
| 
      
 108 
     | 
    
         
            +
            			end
         
     | 
| 
      
 109 
     | 
    
         
            +
            			
         
     | 
| 
      
 110 
     | 
    
         
            +
            			def get_binary_data(binary_name)
         
     | 
| 
      
 111 
     | 
    
         
            +
            				# Set directly?
         
     | 
| 
      
 112 
     | 
    
         
            +
            				if binary = binaries[binary_name]
         
     | 
| 
      
 113 
     | 
    
         
            +
            					binary.data
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
            				# Try loading
         
     | 
| 
      
 116 
     | 
    
         
            +
            				elsif hash_string = binary_hash_string(binary_name)
         
     | 
| 
      
 117 
     | 
    
         
            +
            					if binary = BinaryStorage::Blob.find(hash_string)
         
     | 
| 
      
 118 
     | 
    
         
            +
            						binaries[binary_name] = binary # Cache it
         
     | 
| 
      
 119 
     | 
    
         
            +
            						binary.data
         
     | 
| 
      
 120 
     | 
    
         
            +
            					else
         
     | 
| 
      
 121 
     | 
    
         
            +
            						nil
         
     | 
| 
      
 122 
     | 
    
         
            +
            					end
         
     | 
| 
      
 123 
     | 
    
         
            +
            				end
         
     | 
| 
      
 124 
     | 
    
         
            +
            			end
         
     | 
| 
      
 125 
     | 
    
         
            +
            			
         
     | 
| 
      
 126 
     | 
    
         
            +
            			def set_binary_data(binary_name, binary_data)
         
     | 
| 
      
 127 
     | 
    
         
            +
            				binary = (binary_data) ? BinaryStorage::Blob.new(binary_data) : nil
         
     | 
| 
      
 128 
     | 
    
         
            +
            				binaries[binary_name] = binary
         
     | 
| 
      
 129 
     | 
    
         
            +
            			end
         
     | 
| 
      
 130 
     | 
    
         
            +
            			
         
     | 
| 
      
 131 
     | 
    
         
            +
            			def has_binary_data?(binary_name)
         
     | 
| 
      
 132 
     | 
    
         
            +
            				if binaries[binary_name]
         
     | 
| 
      
 133 
     | 
    
         
            +
            					true
         
     | 
| 
      
 134 
     | 
    
         
            +
            				else
         
     | 
| 
      
 135 
     | 
    
         
            +
            					hash_string = binary_hash_string(binary_name)
         
     | 
| 
      
 136 
     | 
    
         
            +
            					(hash_string && BinaryStorage::Blob.exists?(hash_string)) ? true : false
         
     | 
| 
      
 137 
     | 
    
         
            +
            				end
         
     | 
| 
      
 138 
     | 
    
         
            +
            			end
         
     | 
| 
      
 139 
     | 
    
         
            +
            		end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            	end
         
     | 
| 
      
 142 
     | 
    
         
            +
            end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
            ActiveRecord::Base.send(:include, BinaryStorage::ActiveRecordExtensions)
         
     | 
| 
         @@ -0,0 +1,105 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module BinaryStorage
         
     | 
| 
      
 2 
     | 
    
         
            +
            	class Blob
         
     | 
| 
      
 3 
     | 
    
         
            +
            		
         
     | 
| 
      
 4 
     | 
    
         
            +
            		class << self
         
     | 
| 
      
 5 
     | 
    
         
            +
            			def find(hash_string)
         
     | 
| 
      
 6 
     | 
    
         
            +
            				blob = self.new(:hash_string => hash_string)
         
     | 
| 
      
 7 
     | 
    
         
            +
            				return nil unless blob.exists?
         
     | 
| 
      
 8 
     | 
    
         
            +
            				blob.load
         
     | 
| 
      
 9 
     | 
    
         
            +
            				blob
         
     | 
| 
      
 10 
     | 
    
         
            +
            			end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            			def exists?(hash_string)
         
     | 
| 
      
 13 
     | 
    
         
            +
            				self.new(:hash_string => hash_string).exists?
         
     | 
| 
      
 14 
     | 
    
         
            +
            			end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            			def create(data)
         
     | 
| 
      
 17 
     | 
    
         
            +
            				blob = self.new(data)
         
     | 
| 
      
 18 
     | 
    
         
            +
            				blob.save
         
     | 
| 
      
 19 
     | 
    
         
            +
            				blob
         
     | 
| 
      
 20 
     | 
    
         
            +
            			end
         
     | 
| 
      
 21 
     | 
    
         
            +
            			
         
     | 
| 
      
 22 
     | 
    
         
            +
            			def storage_dir(hash_string=nil)
         
     | 
| 
      
 23 
     | 
    
         
            +
            				root = BinaryStorage.storage_dir
         
     | 
| 
      
 24 
     | 
    
         
            +
            				(hash_string) ? File.join(root, hash_string.match(/^(..)/)[1]) : root
         
     | 
| 
      
 25 
     | 
    
         
            +
            			end
         
     | 
| 
      
 26 
     | 
    
         
            +
            			
         
     | 
| 
      
 27 
     | 
    
         
            +
            			def storage_path(hash_string)
         
     | 
| 
      
 28 
     | 
    
         
            +
            				File.join(storage_dir(hash_string), hash_string.gsub(/^(..)/, ''))
         
     | 
| 
      
 29 
     | 
    
         
            +
            			end
         
     | 
| 
      
 30 
     | 
    
         
            +
            		end
         
     | 
| 
      
 31 
     | 
    
         
            +
            		
         
     | 
| 
      
 32 
     | 
    
         
            +
            		def initialize(*args)
         
     | 
| 
      
 33 
     | 
    
         
            +
            			args = *args
         
     | 
| 
      
 34 
     | 
    
         
            +
            			options = {
         
     | 
| 
      
 35 
     | 
    
         
            +
            				:hash_string => nil,
         
     | 
| 
      
 36 
     | 
    
         
            +
            				:data        => nil
         
     | 
| 
      
 37 
     | 
    
         
            +
            			}
         
     | 
| 
      
 38 
     | 
    
         
            +
            			if args.kind_of?(Hash)
         
     | 
| 
      
 39 
     | 
    
         
            +
            				options.merge!(args)
         
     | 
| 
      
 40 
     | 
    
         
            +
            			else
         
     | 
| 
      
 41 
     | 
    
         
            +
            				options[:data] = args
         
     | 
| 
      
 42 
     | 
    
         
            +
            			end
         
     | 
| 
      
 43 
     | 
    
         
            +
            			@hash_string = options[:hash_string]
         
     | 
| 
      
 44 
     | 
    
         
            +
            			@data        = options[:data]
         
     | 
| 
      
 45 
     | 
    
         
            +
            		end
         
     | 
| 
      
 46 
     | 
    
         
            +
            		
         
     | 
| 
      
 47 
     | 
    
         
            +
            		def data
         
     | 
| 
      
 48 
     | 
    
         
            +
            			@data
         
     | 
| 
      
 49 
     | 
    
         
            +
            		end
         
     | 
| 
      
 50 
     | 
    
         
            +
            		
         
     | 
| 
      
 51 
     | 
    
         
            +
            		def data=(new_data)
         
     | 
| 
      
 52 
     | 
    
         
            +
            			@hash_string = nil
         
     | 
| 
      
 53 
     | 
    
         
            +
            			@data        = new_data
         
     | 
| 
      
 54 
     | 
    
         
            +
            		end
         
     | 
| 
      
 55 
     | 
    
         
            +
            		
         
     | 
| 
      
 56 
     | 
    
         
            +
            		def hash_string
         
     | 
| 
      
 57 
     | 
    
         
            +
            			unless @hash_string
         
     | 
| 
      
 58 
     | 
    
         
            +
            				if @data
         
     | 
| 
      
 59 
     | 
    
         
            +
            					@hash_string = BinaryStorage.hexdigest(data)
         
     | 
| 
      
 60 
     | 
    
         
            +
            				else
         
     | 
| 
      
 61 
     | 
    
         
            +
            					raise "Binary has no data!" 
         
     | 
| 
      
 62 
     | 
    
         
            +
            				end
         
     | 
| 
      
 63 
     | 
    
         
            +
            			end
         
     | 
| 
      
 64 
     | 
    
         
            +
            			@hash_string
         
     | 
| 
      
 65 
     | 
    
         
            +
            		end
         
     | 
| 
      
 66 
     | 
    
         
            +
            		
         
     | 
| 
      
 67 
     | 
    
         
            +
            		def storage_dir
         
     | 
| 
      
 68 
     | 
    
         
            +
            			BinaryStorage::Blob.storage_dir(hash_string)
         
     | 
| 
      
 69 
     | 
    
         
            +
            		end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            		def storage_path
         
     | 
| 
      
 72 
     | 
    
         
            +
            			BinaryStorage::Blob.storage_path(hash_string)
         
     | 
| 
      
 73 
     | 
    
         
            +
            		end
         
     | 
| 
      
 74 
     | 
    
         
            +
            		
         
     | 
| 
      
 75 
     | 
    
         
            +
            		def exists?
         
     | 
| 
      
 76 
     | 
    
         
            +
            			File.exists?(storage_path)
         
     | 
| 
      
 77 
     | 
    
         
            +
            		end
         
     | 
| 
      
 78 
     | 
    
         
            +
            		
         
     | 
| 
      
 79 
     | 
    
         
            +
            		def empty?
         
     | 
| 
      
 80 
     | 
    
         
            +
            			(hash_string && !exists?) || !data || data.empty?
         
     | 
| 
      
 81 
     | 
    
         
            +
            		end
         
     | 
| 
      
 82 
     | 
    
         
            +
            		
         
     | 
| 
      
 83 
     | 
    
         
            +
            		def load
         
     | 
| 
      
 84 
     | 
    
         
            +
            			raise "File not found" unless exists?
         
     | 
| 
      
 85 
     | 
    
         
            +
            			@data = File.open(storage_path, "rb") {|io| io.read }
         
     | 
| 
      
 86 
     | 
    
         
            +
            		end
         
     | 
| 
      
 87 
     | 
    
         
            +
            		
         
     | 
| 
      
 88 
     | 
    
         
            +
            		def delete
         
     | 
| 
      
 89 
     | 
    
         
            +
            			if exists?
         
     | 
| 
      
 90 
     | 
    
         
            +
            				FileUtils.rm(storage_path)
         
     | 
| 
      
 91 
     | 
    
         
            +
            			end
         
     | 
| 
      
 92 
     | 
    
         
            +
            		end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
            		def save
         
     | 
| 
      
 95 
     | 
    
         
            +
            			unless exists?
         
     | 
| 
      
 96 
     | 
    
         
            +
            				FileUtils.mkdir_p(storage_dir)
         
     | 
| 
      
 97 
     | 
    
         
            +
            				file = File.new(storage_path, 'wb')
         
     | 
| 
      
 98 
     | 
    
         
            +
            				file.write(@data)
         
     | 
| 
      
 99 
     | 
    
         
            +
            				file.close
         
     | 
| 
      
 100 
     | 
    
         
            +
            			end
         
     | 
| 
      
 101 
     | 
    
         
            +
            			return true
         
     | 
| 
      
 102 
     | 
    
         
            +
            		end
         
     | 
| 
      
 103 
     | 
    
         
            +
            		
         
     | 
| 
      
 104 
     | 
    
         
            +
            	end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'digest/sha1'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rails'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), 'binary_storage/active_record_extensions')
         
     | 
| 
      
 8 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), 'binary_storage/blob')
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            module BinaryStorage
         
     | 
| 
      
 11 
     | 
    
         
            +
            	class << self
         
     | 
| 
      
 12 
     | 
    
         
            +
            		def storage_dir
         
     | 
| 
      
 13 
     | 
    
         
            +
            			@@storage_dir ||= Rails.root.join('db/binary_storage', Rails.env)
         
     | 
| 
      
 14 
     | 
    
         
            +
            		end
         
     | 
| 
      
 15 
     | 
    
         
            +
            		
         
     | 
| 
      
 16 
     | 
    
         
            +
            		def storage_dir=(new_storage_dir)
         
     | 
| 
      
 17 
     | 
    
         
            +
            			@@storage_dir = new_storage_dir
         
     | 
| 
      
 18 
     | 
    
         
            +
            		end
         
     | 
| 
      
 19 
     | 
    
         
            +
            		
         
     | 
| 
      
 20 
     | 
    
         
            +
            		def hexdigest_file(path)
         
     | 
| 
      
 21 
     | 
    
         
            +
            			Digest::SHA1.file(path).hexdigest
         
     | 
| 
      
 22 
     | 
    
         
            +
            		end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            		def hexdigest(string)
         
     | 
| 
      
 25 
     | 
    
         
            +
            			Digest::SHA1.hexdigest(string)
         
     | 
| 
      
 26 
     | 
    
         
            +
            		end
         
     | 
| 
      
 27 
     | 
    
         
            +
            	end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,57 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dynamic_image'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module DynamicImage
         
     | 
| 
      
 4 
     | 
    
         
            +
            	module ActiveRecordExtensions
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            		def self.included(base)
         
     | 
| 
      
 7 
     | 
    
         
            +
            			base.send :extend, ClassMethods
         
     | 
| 
      
 8 
     | 
    
         
            +
            		end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            		module ClassMethods
         
     | 
| 
      
 11 
     | 
    
         
            +
            			# By using <tt>belongs_to_image</tt> over <tt>belongs_to</tt>, you gain the ability to
         
     | 
| 
      
 12 
     | 
    
         
            +
            			# set the image directly from an uploaded file. This works exactly like <tt>belongs_to</tt>,
         
     | 
| 
      
 13 
     | 
    
         
            +
            			# except the class name will default to 'Image' - not the name of the association.
         
     | 
| 
      
 14 
     | 
    
         
            +
            			# 
         
     | 
| 
      
 15 
     | 
    
         
            +
            			# Example:
         
     | 
| 
      
 16 
     | 
    
         
            +
            			#
         
     | 
| 
      
 17 
     | 
    
         
            +
            			#   # Model code
         
     | 
| 
      
 18 
     | 
    
         
            +
            			#   class Person < ActiveRecord::Base
         
     | 
| 
      
 19 
     | 
    
         
            +
            			#     belongs_to_image :mugshot
         
     | 
| 
      
 20 
     | 
    
         
            +
            			#   end
         
     | 
| 
      
 21 
     | 
    
         
            +
            			#
         
     | 
| 
      
 22 
     | 
    
         
            +
            			#   # View code
         
     | 
| 
      
 23 
     | 
    
         
            +
            			#   <% form_for 'person', @person, :html => {:multipart => true} do |f| %>
         
     | 
| 
      
 24 
     | 
    
         
            +
            			#     <%= f.file_field :mugshot %>
         
     | 
| 
      
 25 
     | 
    
         
            +
            			#   <% end %>
         
     | 
| 
      
 26 
     | 
    
         
            +
            			#
         
     | 
| 
      
 27 
     | 
    
         
            +
            			def belongs_to_image(association_id, options={})
         
     | 
| 
      
 28 
     | 
    
         
            +
            				options[:class_name] ||= 'Image'
         
     | 
| 
      
 29 
     | 
    
         
            +
            				options[:foreign_key] ||= options[:class_name].downcase+'_id'
         
     | 
| 
      
 30 
     | 
    
         
            +
            				belongs_to association_id, options
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            				# Overwrite the setter method
         
     | 
| 
      
 33 
     | 
    
         
            +
            				class_eval <<-end_eval
         
     | 
| 
      
 34 
     | 
    
         
            +
            					alias_method :associated_#{association_id}=, :#{association_id}=
         
     | 
| 
      
 35 
     | 
    
         
            +
            					def #{association_id}=(img_obj)
         
     | 
| 
      
 36 
     | 
    
         
            +
            						# Convert a Tempfile to a proper Image
         
     | 
| 
      
 37 
     | 
    
         
            +
            						unless img_obj.kind_of?(ActiveRecord::Base)
         
     | 
| 
      
 38 
     | 
    
         
            +
            							DynamicImage.dirty_memory = true # Flag for GC
         
     | 
| 
      
 39 
     | 
    
         
            +
            							img_obj = Image.create(:imagefile => img_obj)
         
     | 
| 
      
 40 
     | 
    
         
            +
            						end
         
     | 
| 
      
 41 
     | 
    
         
            +
            						# Quietly skip blank strings
         
     | 
| 
      
 42 
     | 
    
         
            +
            						unless img_obj.kind_of?(String) && img_obj.blank?
         
     | 
| 
      
 43 
     | 
    
         
            +
            							self.associated_#{association_id} = img_obj
         
     | 
| 
      
 44 
     | 
    
         
            +
            						end
         
     | 
| 
      
 45 
     | 
    
         
            +
            					end
         
     | 
| 
      
 46 
     | 
    
         
            +
            				end_eval
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            				send :include, DynamicImage::ActiveRecordExtensions::InstanceMethods
         
     | 
| 
      
 49 
     | 
    
         
            +
            			end 
         
     | 
| 
      
 50 
     | 
    
         
            +
            		end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            		module InstanceMethods
         
     | 
| 
      
 53 
     | 
    
         
            +
            		end
         
     | 
| 
      
 54 
     | 
    
         
            +
            	end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            ActiveRecord::Base.send(:include, DynamicImage::ActiveRecordExtensions)
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dynamic_image'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module DynamicImage
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            	@@filtersets = Hash.new
         
     | 
| 
      
 6 
     | 
    
         
            +
            	
         
     | 
| 
      
 7 
     | 
    
         
            +
            	# Singleton methods for the filtersets hash.
         
     | 
| 
      
 8 
     | 
    
         
            +
            	class << @@filtersets
         
     | 
| 
      
 9 
     | 
    
         
            +
            		def names; keys; end
         
     | 
| 
      
 10 
     | 
    
         
            +
            	end
         
     | 
| 
      
 11 
     | 
    
         
            +
            	
         
     | 
| 
      
 12 
     | 
    
         
            +
            	# Accessor for the filtersets hash. Installed filter names are available through the <tt>names</tt> method. Example:
         
     | 
| 
      
 13 
     | 
    
         
            +
            	#   @filter_names = DynamicImage.filtersets.names
         
     | 
| 
      
 14 
     | 
    
         
            +
            	def self.filtersets
         
     | 
| 
      
 15 
     | 
    
         
            +
            		@@filtersets
         
     | 
| 
      
 16 
     | 
    
         
            +
            	end
         
     | 
| 
      
 17 
     | 
    
         
            +
            	
         
     | 
| 
      
 18 
     | 
    
         
            +
            	# Base class for filter sets. Extending this with your own subclasses will automatically enable them for use.
         
     | 
| 
      
 19 
     | 
    
         
            +
            	# You'll need to overwrite <tt>Filterset.process</tt> in order to make your filter useful. Note that it's a class
         
     | 
| 
      
 20 
     | 
    
         
            +
            	# method.
         
     | 
| 
      
 21 
     | 
    
         
            +
            	#
         
     | 
| 
      
 22 
     | 
    
         
            +
            	# === Example
         
     | 
| 
      
 23 
     | 
    
         
            +
            	#
         
     | 
| 
      
 24 
     | 
    
         
            +
            	#   class BlogThumbnailsFilterset < DynamicImage::Filterset
         
     | 
| 
      
 25 
     | 
    
         
            +
            	#     def self.process(image)
         
     | 
| 
      
 26 
     | 
    
         
            +
            	#       image = image.sepiatone     # convert the image to sepia tones
         
     | 
| 
      
 27 
     | 
    
         
            +
            	#     end
         
     | 
| 
      
 28 
     | 
    
         
            +
            	#   end
         
     | 
| 
      
 29 
     | 
    
         
            +
            	#
         
     | 
| 
      
 30 
     | 
    
         
            +
            	# The filter set is now available for use in your application:
         
     | 
| 
      
 31 
     | 
    
         
            +
            	#
         
     | 
| 
      
 32 
     | 
    
         
            +
            	#   <%= dynamic_image_tag( @blog_post.image, :size => "120x100", :filterset => 'blog_thumbnails' ) %>
         
     | 
| 
      
 33 
     | 
    
         
            +
            	#
         
     | 
| 
      
 34 
     | 
    
         
            +
            	# === Applying effects by default
         
     | 
| 
      
 35 
     | 
    
         
            +
            	#
         
     | 
| 
      
 36 
     | 
    
         
            +
            	# If <tt>Image.get_oricessed</tt> is called without filters, it will look for a set named 'default'.
         
     | 
| 
      
 37 
     | 
    
         
            +
            	# This means that you can automatically apply effects on resized images by defining a class called <tt>DefaultFilterset</tt>:
         
     | 
| 
      
 38 
     | 
    
         
            +
            	#
         
     | 
| 
      
 39 
     | 
    
         
            +
            	#   class DefaultFilterset < DynamicImage::Filterset
         
     | 
| 
      
 40 
     | 
    
         
            +
            	#     def self.process(image)
         
     | 
| 
      
 41 
     | 
    
         
            +
            	#       image = image.unsharp_mask  # apply unsharp mask on images by default.
         
     | 
| 
      
 42 
     | 
    
         
            +
            	#     end
         
     | 
| 
      
 43 
     | 
    
         
            +
            	#   end
         
     | 
| 
      
 44 
     | 
    
         
            +
            	#
         
     | 
| 
      
 45 
     | 
    
         
            +
            	# === Chaining filters
         
     | 
| 
      
 46 
     | 
    
         
            +
            	#
         
     | 
| 
      
 47 
     | 
    
         
            +
            	# You can only apply one filterset on an image, but compound filters can easily be created:
         
     | 
| 
      
 48 
     | 
    
         
            +
            	#
         
     | 
| 
      
 49 
     | 
    
         
            +
            	#   class CompoundFilterset < DynamicImage::Filterset
         
     | 
| 
      
 50 
     | 
    
         
            +
            	#     def self.process(image)
         
     | 
| 
      
 51 
     | 
    
         
            +
            	#       image = MyFirstFilterset.process(image)
         
     | 
| 
      
 52 
     | 
    
         
            +
            	#       image = SomeOtherFilterset.process(image)
         
     | 
| 
      
 53 
     | 
    
         
            +
            	#       image = DefaultFilterset.process(image)
         
     | 
| 
      
 54 
     | 
    
         
            +
            	#     end
         
     | 
| 
      
 55 
     | 
    
         
            +
            	#   end
         
     | 
| 
      
 56 
     | 
    
         
            +
            	#
         
     | 
| 
      
 57 
     | 
    
         
            +
            	class Filterset
         
     | 
| 
      
 58 
     | 
    
         
            +
            		include ::Magick
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            		# Detect inheritance and store the new filterset in the lookup table.
         
     | 
| 
      
 61 
     | 
    
         
            +
            		def self.inherited(sub)
         
     | 
| 
      
 62 
     | 
    
         
            +
            			filter_name = sub.name.gsub!( /Filterset$/, '' ).underscore
         
     | 
| 
      
 63 
     | 
    
         
            +
            			DynamicImage.filtersets[filter_name] = sub
         
     | 
| 
      
 64 
     | 
    
         
            +
            		end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            		# Get a Filterset class by name. Accepts a symbol or string, CamelCase and under_scores both work.
         
     | 
| 
      
 67 
     | 
    
         
            +
            		def self.[](filter_name)
         
     | 
| 
      
 68 
     | 
    
         
            +
            			filter_name = filter_name.to_s if filter_name.kind_of? Symbol
         
     | 
| 
      
 69 
     | 
    
         
            +
            			filter_name = filter_name.underscore
         
     | 
| 
      
 70 
     | 
    
         
            +
            			DynamicImage.filtersets[filter_name] || nil
         
     | 
| 
      
 71 
     | 
    
         
            +
            		end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            		# Process the image. This is a dummy method, you should overwrite it in your subclass.
         
     | 
| 
      
 74 
     | 
    
         
            +
            		def self.process(image)
         
     | 
| 
      
 75 
     | 
    
         
            +
            			# This is a stub
         
     | 
| 
      
 76 
     | 
    
         
            +
            		end
         
     | 
| 
      
 77 
     | 
    
         
            +
            		
         
     | 
| 
      
 78 
     | 
    
         
            +
            	end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,121 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dynamic_image'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module DynamicImage
         
     | 
| 
      
 4 
     | 
    
         
            +
            	module Helper
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            		# Returns an hash consisting of the URL to the dynamic image and parsed options. This is mostly for internal use by 
         
     | 
| 
      
 7 
     | 
    
         
            +
            		# dynamic_image_tag and dynamic_image_url.
         
     | 
| 
      
 8 
     | 
    
         
            +
            		def dynamic_image_options(image, options = {})
         
     | 
| 
      
 9 
     | 
    
         
            +
            			options.symbolize_keys!
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            			options     = {:crop => false}.merge(options)
         
     | 
| 
      
 12 
     | 
    
         
            +
            			url_options = {:controller => "/images", :action => :render_dynamic_image, :id => image}
         
     | 
| 
      
 13 
     | 
    
         
            +
            		
         
     | 
| 
      
 14 
     | 
    
         
            +
            			if options[:original]
         
     | 
| 
      
 15 
     | 
    
         
            +
            				url_options[:original] = 'original'
         
     | 
| 
      
 16 
     | 
    
         
            +
            				options.delete(:original)
         
     | 
| 
      
 17 
     | 
    
         
            +
            			end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            			# Image sizing
         
     | 
| 
      
 20 
     | 
    
         
            +
            			if options[:size]
         
     | 
| 
      
 21 
     | 
    
         
            +
            				new_size   = Vector2d.new(options[:size])
         
     | 
| 
      
 22 
     | 
    
         
            +
            				image_size = Vector2d.new(image.size)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            	            unless options[:upscale]
         
     | 
| 
      
 25 
     | 
    
         
            +
            				    new_size.x = image_size.x if new_size.x > 0 && new_size.x > image_size.x
         
     | 
| 
      
 26 
     | 
    
         
            +
            				    new_size.y = image_size.y if new_size.y > 0 && new_size.y > image_size.y
         
     | 
| 
      
 27 
     | 
    
         
            +
            			    end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            				unless options[:crop]
         
     | 
| 
      
 30 
     | 
    
         
            +
            					new_size = image_size.constrain_both(new_size)
         
     | 
| 
      
 31 
     | 
    
         
            +
            				end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            				options[:size] = new_size.round.to_s
         
     | 
| 
      
 34 
     | 
    
         
            +
            				url_options[:size] = options[:size]
         
     | 
| 
      
 35 
     | 
    
         
            +
            			end
         
     | 
| 
      
 36 
     | 
    
         
            +
            			options.delete :crop
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            			if options[:no_size_attr]
         
     | 
| 
      
 39 
     | 
    
         
            +
            				options.delete :no_size_attr
         
     | 
| 
      
 40 
     | 
    
         
            +
            				options.delete :size
         
     | 
| 
      
 41 
     | 
    
         
            +
            			end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            			# Filterset
         
     | 
| 
      
 44 
     | 
    
         
            +
            			if options[:filterset]
         
     | 
| 
      
 45 
     | 
    
         
            +
            				url_options[:filterset] = options[:filterset]
         
     | 
| 
      
 46 
     | 
    
         
            +
            				options.delete :filterset
         
     | 
| 
      
 47 
     | 
    
         
            +
            			end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            			# Filename
         
     | 
| 
      
 50 
     | 
    
         
            +
            			if options[:filename]
         
     | 
| 
      
 51 
     | 
    
         
            +
            				filename = options[:filename]
         
     | 
| 
      
 52 
     | 
    
         
            +
            				unless filename =~ /\.[\w]{1,4}$/
         
     | 
| 
      
 53 
     | 
    
         
            +
            					filename += "." + image.filename.split(".").last
         
     | 
| 
      
 54 
     | 
    
         
            +
            				end
         
     | 
| 
      
 55 
     | 
    
         
            +
            				url_options[:filename] = filename
         
     | 
| 
      
 56 
     | 
    
         
            +
            			else
         
     | 
| 
      
 57 
     | 
    
         
            +
            				url_options[:filename] = image.filename
         
     | 
| 
      
 58 
     | 
    
         
            +
            			end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            			# Alt attribute
         
     | 
| 
      
 61 
     | 
    
         
            +
            			options[:alt] ||= image.name if image.name?
         
     | 
| 
      
 62 
     | 
    
         
            +
            			options[:alt] ||= image.filename.split('.').first.capitalize
         
     | 
| 
      
 63 
     | 
    
         
            +
            		
         
     | 
| 
      
 64 
     | 
    
         
            +
            			if options.has_key?(:only_path)
         
     | 
| 
      
 65 
     | 
    
         
            +
            				url_options[:only_path] = options[:only_path]
         
     | 
| 
      
 66 
     | 
    
         
            +
            				options[:only_path] = nil
         
     | 
| 
      
 67 
     | 
    
         
            +
            			end
         
     | 
| 
      
 68 
     | 
    
         
            +
            			if options.has_key?(:host)
         
     | 
| 
      
 69 
     | 
    
         
            +
            				url_options[:host] = options[:host]
         
     | 
| 
      
 70 
     | 
    
         
            +
            				options[:host] = nil
         
     | 
| 
      
 71 
     | 
    
         
            +
            			end
         
     | 
| 
      
 72 
     | 
    
         
            +
            		
         
     | 
| 
      
 73 
     | 
    
         
            +
            			{:url => url_for(url_options), :options => options}
         
     | 
| 
      
 74 
     | 
    
         
            +
            		end
         
     | 
| 
      
 75 
     | 
    
         
            +
            	
         
     | 
| 
      
 76 
     | 
    
         
            +
            		# Returns an image tag for the provided image model, works similar to the rails <tt>image_tag</tt> helper. 
         
     | 
| 
      
 77 
     | 
    
         
            +
            		# The <tt>alt</tt> tag is set to the image title unless explicitly provided.
         
     | 
| 
      
 78 
     | 
    
         
            +
            		#
         
     | 
| 
      
 79 
     | 
    
         
            +
            		# The following options are supported (the rest will be forwarded to <tt>image_tag</tt>):
         
     | 
| 
      
 80 
     | 
    
         
            +
            		#
         
     | 
| 
      
 81 
     | 
    
         
            +
            		#   :size         - Resize the image to fit these proportions. Size is given as a string with the format
         
     | 
| 
      
 82 
     | 
    
         
            +
            		#                   '100x100'. Either dimension can be omitted, for example: '100x'
         
     | 
| 
      
 83 
     | 
    
         
            +
            		#   :crop         - Boolean, default: false. Crop the image to the size given.
         
     | 
| 
      
 84 
     | 
    
         
            +
            		#   :no_size_attr - Boolean, default: false. Do not include width and height attributes in the image tag.
         
     | 
| 
      
 85 
     | 
    
         
            +
            		#   :filterset    - Apply the given filterset to the image
         
     | 
| 
      
 86 
     | 
    
         
            +
            		#
         
     | 
| 
      
 87 
     | 
    
         
            +
            		# == Examples
         
     | 
| 
      
 88 
     | 
    
         
            +
            		#
         
     | 
| 
      
 89 
     | 
    
         
            +
            		# Tag for original image, without rescaling:
         
     | 
| 
      
 90 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image) %>
         
     | 
| 
      
 91 
     | 
    
         
            +
            		#
         
     | 
| 
      
 92 
     | 
    
         
            +
            		# Tag for image, rescaled to fit within 100x100 (size will be 100x100 or smaller):
         
     | 
| 
      
 93 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image, :size => "100x100") %>
         
     | 
| 
      
 94 
     | 
    
         
            +
            		#
         
     | 
| 
      
 95 
     | 
    
         
            +
            		# Tag for image, cropped and rescaled to 100x100 (size will be 100x100 in all cases):
         
     | 
| 
      
 96 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image, :size => "100x100", :crop => true) %>
         
     | 
| 
      
 97 
     | 
    
         
            +
            		#
         
     | 
| 
      
 98 
     | 
    
         
            +
            		# Tag for image with a filter set applied:
         
     | 
| 
      
 99 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image, :size => "100x100", :filterset => @filterset) %>
         
     | 
| 
      
 100 
     | 
    
         
            +
            		#
         
     | 
| 
      
 101 
     | 
    
         
            +
            		# Tag for image with a named filter set applied:
         
     | 
| 
      
 102 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image, :size => "100x100", :filterset => "thumbnails") %>
         
     | 
| 
      
 103 
     | 
    
         
            +
            		#
         
     | 
| 
      
 104 
     | 
    
         
            +
            		# Tag for image without the width/height attributes, and with a custom alt attribute
         
     | 
| 
      
 105 
     | 
    
         
            +
            		#   <%= dynamic_image_tag(@image, :size => "100x100", :no_size_attr => true, :alt => "Thumbnail for post" %>
         
     | 
| 
      
 106 
     | 
    
         
            +
            	
         
     | 
| 
      
 107 
     | 
    
         
            +
            		def dynamic_image_tag(image, options = {})
         
     | 
| 
      
 108 
     | 
    
         
            +
            			parsed_options = dynamic_image_options(image, options)
         
     | 
| 
      
 109 
     | 
    
         
            +
            			image_tag(parsed_options[:url], parsed_options[:options] ).gsub(/\?[\d]+/,'')
         
     | 
| 
      
 110 
     | 
    
         
            +
            		end
         
     | 
| 
      
 111 
     | 
    
         
            +
            	
         
     | 
| 
      
 112 
     | 
    
         
            +
            		# Returns an url corresponding to the provided image model.
         
     | 
| 
      
 113 
     | 
    
         
            +
            		# Special options are documented in ApplicationHelper.dynamic_image_tag, only <tt>:size</tt>, <tt>:filterset</tt> and <tt>:crop</tt> apply.
         
     | 
| 
      
 114 
     | 
    
         
            +
            		def dynamic_image_url(image, options = {})
         
     | 
| 
      
 115 
     | 
    
         
            +
            			parsed_options = dynamic_image_options(image, options)
         
     | 
| 
      
 116 
     | 
    
         
            +
            			parsed_options[:url]
         
     | 
| 
      
 117 
     | 
    
         
            +
            		end
         
     | 
| 
      
 118 
     | 
    
         
            +
            	end
         
     | 
| 
      
 119 
     | 
    
         
            +
            end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            ActionView::Base.send(:include, DynamicImage::Helper)
         
     | 
| 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'digest/sha1'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            # Gem dependencies
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'rmagick'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'vector2d'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'rails'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'action_controller'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'active_support'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            require 'binary_storage'
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            if Rails::VERSION::MAJOR == 3
         
     | 
| 
      
 15 
     | 
    
         
            +
            	# Load the engine
         
     | 
| 
      
 16 
     | 
    
         
            +
            	require 'dynamic_image/engine' if defined?(Rails)
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            require 'dynamic_image/active_record_extensions'
         
     | 
| 
      
 20 
     | 
    
         
            +
            require 'dynamic_image/filterset'
         
     | 
| 
      
 21 
     | 
    
         
            +
            require 'dynamic_image/helper'
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            module DynamicImage
         
     | 
| 
      
 24 
     | 
    
         
            +
            	@@dirty_memory = false
         
     | 
| 
      
 25 
     | 
    
         
            +
            	@@page_caching = true
         
     | 
| 
      
 26 
     | 
    
         
            +
            	
         
     | 
| 
      
 27 
     | 
    
         
            +
            	class << self
         
     | 
| 
      
 28 
     | 
    
         
            +
            		
         
     | 
| 
      
 29 
     | 
    
         
            +
            		def dirty_memory=(flag)
         
     | 
| 
      
 30 
     | 
    
         
            +
            			@@dirty_memory = flag
         
     | 
| 
      
 31 
     | 
    
         
            +
            		end
         
     | 
| 
      
 32 
     | 
    
         
            +
            		
         
     | 
| 
      
 33 
     | 
    
         
            +
            		def dirty_memory
         
     | 
| 
      
 34 
     | 
    
         
            +
            			@@dirty_memory
         
     | 
| 
      
 35 
     | 
    
         
            +
            		end
         
     | 
| 
      
 36 
     | 
    
         
            +
            		
         
     | 
| 
      
 37 
     | 
    
         
            +
            		def page_caching=(flag)
         
     | 
| 
      
 38 
     | 
    
         
            +
            			@@page_caching = flag
         
     | 
| 
      
 39 
     | 
    
         
            +
            		end
         
     | 
| 
      
 40 
     | 
    
         
            +
            		
         
     | 
| 
      
 41 
     | 
    
         
            +
            		def page_caching
         
     | 
| 
      
 42 
     | 
    
         
            +
            			@@page_caching
         
     | 
| 
      
 43 
     | 
    
         
            +
            		end
         
     | 
| 
      
 44 
     | 
    
         
            +
            		
         
     | 
| 
      
 45 
     | 
    
         
            +
            		def max_size
         
     | 
| 
      
 46 
     | 
    
         
            +
            			@@max_size ||= "2000x2000"
         
     | 
| 
      
 47 
     | 
    
         
            +
            		end
         
     | 
| 
      
 48 
     | 
    
         
            +
            		
         
     | 
| 
      
 49 
     | 
    
         
            +
            		def max_size=(new_max_size)
         
     | 
| 
      
 50 
     | 
    
         
            +
            			@@max_size = new_max_size
         
     | 
| 
      
 51 
     | 
    
         
            +
            		end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            		def crash_size
         
     | 
| 
      
 54 
     | 
    
         
            +
            			@@crash_size ||= "10000x10000"
         
     | 
| 
      
 55 
     | 
    
         
            +
            		end
         
     | 
| 
      
 56 
     | 
    
         
            +
            		
         
     | 
| 
      
 57 
     | 
    
         
            +
            		def crash_size=(new_crash_size)
         
     | 
| 
      
 58 
     | 
    
         
            +
            			@@crash_size = new_crash_size
         
     | 
| 
      
 59 
     | 
    
         
            +
            		end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            		# RMagick stores image data internally, Ruby doesn't see the used memory.
         
     | 
| 
      
 62 
     | 
    
         
            +
            		# This method performs garbage collection if @@dirty_memory has been flagged.
         
     | 
| 
      
 63 
     | 
    
         
            +
            		# More details here: http://rubyforge.org/forum/message.php?msg_id=1995
         
     | 
| 
      
 64 
     | 
    
         
            +
            		def clean_dirty_memory(options={})
         
     | 
| 
      
 65 
     | 
    
         
            +
            			options.symbolize_keys!
         
     | 
| 
      
 66 
     | 
    
         
            +
            			if @@dirty_memory || options[:force]
         
     | 
| 
      
 67 
     | 
    
         
            +
            				gc_disabled = GC.enable
         
     | 
| 
      
 68 
     | 
    
         
            +
            				GC.start
         
     | 
| 
      
 69 
     | 
    
         
            +
            				GC.disable if gc_disabled
         
     | 
| 
      
 70 
     | 
    
         
            +
            				@@dirty_memory = false
         
     | 
| 
      
 71 
     | 
    
         
            +
            				true
         
     | 
| 
      
 72 
     | 
    
         
            +
            			else
         
     | 
| 
      
 73 
     | 
    
         
            +
            				false
         
     | 
| 
      
 74 
     | 
    
         
            +
            			end
         
     | 
| 
      
 75 
     | 
    
         
            +
            		end
         
     | 
| 
      
 76 
     | 
    
         
            +
            	end
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Rails 2: class DynamicImageGenerator < Rails::Generator::NamedBase
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rails/generators'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rails/generators/migration'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class DynamicImageGenerator < Rails::Generators::Base
         
     | 
| 
      
 7 
     | 
    
         
            +
            	
         
     | 
| 
      
 8 
     | 
    
         
            +
            	include Rails::Generators::Migration
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            	class << self
         
     | 
| 
      
 11 
     | 
    
         
            +
            		def source_root
         
     | 
| 
      
 12 
     | 
    
         
            +
            			@source_root ||= File.join(File.dirname(__FILE__), 'templates')
         
     | 
| 
      
 13 
     | 
    
         
            +
            		end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            		def next_migration_number(dirname)
         
     | 
| 
      
 16 
     | 
    
         
            +
            			if ActiveRecord::Base.timestamped_migrations
         
     | 
| 
      
 17 
     | 
    
         
            +
            				Time.now.utc.strftime("%Y%m%d%H%M%S")
         
     | 
| 
      
 18 
     | 
    
         
            +
            			else
         
     | 
| 
      
 19 
     | 
    
         
            +
            				"%.3d" % (current_migration_number(dirname) + 1)
         
     | 
| 
      
 20 
     | 
    
         
            +
            			end
         
     | 
| 
      
 21 
     | 
    
         
            +
            		end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            	end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            	def migrations
         
     | 
| 
      
 26 
     | 
    
         
            +
            		migration_template 'migrations/create_images.rb', 'db/migrate/create_images.rb'
         
     | 
| 
      
 27 
     | 
    
         
            +
            	end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            	# def manifest
         
     | 
| 
      
 30 
     | 
    
         
            +
            	# 	record do |m|
         
     | 
| 
      
 31 
     | 
    
         
            +
            	# 		#m.file 'controllers/images_controller.rb', 'app/controllers/images_controller.rb'
         
     | 
| 
      
 32 
     | 
    
         
            +
            	# 		#m.file 'models/image.rb', 'app/models/image.rb'
         
     | 
| 
      
 33 
     | 
    
         
            +
            	# 		#m.file 'models/binary.rb', 'app/models/binary.rb'
         
     | 
| 
      
 34 
     | 
    
         
            +
            	# 		m.file 'migrations/20090909231629_create_binaries.rb', 'db/migrate/20090909231629_create_binaries.rb'
         
     | 
| 
      
 35 
     | 
    
         
            +
            	# 		m.file 'migrations/20090909231630_create_images.rb', 'db/migrate/20090909231630_create_images.rb'
         
     | 
| 
      
 36 
     | 
    
         
            +
            	# 	end
         
     | 
| 
      
 37 
     | 
    
         
            +
            	# end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class CreateImages < ActiveRecord::Migration
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.up
         
     | 
| 
      
 3 
     | 
    
         
            +
                create_table :images do |t|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  t.column :name,          :string
         
     | 
| 
      
 5 
     | 
    
         
            +
                  t.column :filename,      :string
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.column :content_type,  :string
         
     | 
| 
      
 7 
     | 
    
         
            +
                  t.column :original_size, :string
         
     | 
| 
      
 8 
     | 
    
         
            +
                  t.column :hotspot,       :string
         
     | 
| 
      
 9 
     | 
    
         
            +
                  t.column :sha1_hash,     :string
         
     | 
| 
      
 10 
     | 
    
         
            +
                  t.column :cropped,       :boolean, :null => false, :default => false
         
     | 
| 
      
 11 
     | 
    
         
            +
                  t.column :crop_start,    :string
         
     | 
| 
      
 12 
     | 
    
         
            +
                  t.column :crop_size,     :string
         
     | 
| 
      
 13 
     | 
    
         
            +
                  t.column :created_at,    :datetime
         
     | 
| 
      
 14 
     | 
    
         
            +
                  t.column :updated_at,    :datetime
         
     | 
| 
      
 15 
     | 
    
         
            +
                  t.column :filters,       :text
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def self.down
         
     | 
| 
      
 20 
     | 
    
         
            +
                drop_table :images
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
    
        data/test/test_helper.rb
    ADDED
    
    
    
        data/uninstall.rb
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Uninstall hook code here
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,112 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: dynamic_image
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 5 
     | 
    
         
            +
              segments: 
         
     | 
| 
      
 6 
     | 
    
         
            +
              - 0
         
     | 
| 
      
 7 
     | 
    
         
            +
              - 9
         
     | 
| 
      
 8 
     | 
    
         
            +
              - 0
         
     | 
| 
      
 9 
     | 
    
         
            +
              version: 0.9.0
         
     | 
| 
      
 10 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 11 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 12 
     | 
    
         
            +
            - "Inge J\xC3\xB8rgensen"
         
     | 
| 
      
 13 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 14 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 15 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            date: 2010-05-19 00:00:00 +02:00
         
     | 
| 
      
 18 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 19 
     | 
    
         
            +
            dependencies: 
         
     | 
| 
      
 20 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency 
         
     | 
| 
      
 21 
     | 
    
         
            +
              name: rmagick
         
     | 
| 
      
 22 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 23 
     | 
    
         
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         
     | 
| 
      
 24 
     | 
    
         
            +
                requirements: 
         
     | 
| 
      
 25 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 26 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 27 
     | 
    
         
            +
                    segments: 
         
     | 
| 
      
 28 
     | 
    
         
            +
                    - 2
         
     | 
| 
      
 29 
     | 
    
         
            +
                    - 12
         
     | 
| 
      
 30 
     | 
    
         
            +
                    - 2
         
     | 
| 
      
 31 
     | 
    
         
            +
                    version: 2.12.2
         
     | 
| 
      
 32 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 33 
     | 
    
         
            +
              version_requirements: *id001
         
     | 
| 
      
 34 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency 
         
     | 
| 
      
 35 
     | 
    
         
            +
              name: vector2d
         
     | 
| 
      
 36 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 37 
     | 
    
         
            +
              requirement: &id002 !ruby/object:Gem::Requirement 
         
     | 
| 
      
 38 
     | 
    
         
            +
                requirements: 
         
     | 
| 
      
 39 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 40 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 41 
     | 
    
         
            +
                    segments: 
         
     | 
| 
      
 42 
     | 
    
         
            +
                    - 1
         
     | 
| 
      
 43 
     | 
    
         
            +
                    - 0
         
     | 
| 
      
 44 
     | 
    
         
            +
                    - 0
         
     | 
| 
      
 45 
     | 
    
         
            +
                    version: 1.0.0
         
     | 
| 
      
 46 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 47 
     | 
    
         
            +
              version_requirements: *id002
         
     | 
| 
      
 48 
     | 
    
         
            +
            description: 
         
     | 
| 
      
 49 
     | 
    
         
            +
            email: inge@elektronaut.no
         
     | 
| 
      
 50 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            extra_rdoc_files: 
         
     | 
| 
      
 55 
     | 
    
         
            +
            - LICENSE
         
     | 
| 
      
 56 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 57 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 58 
     | 
    
         
            +
            - LICENSE
         
     | 
| 
      
 59 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 60 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 61 
     | 
    
         
            +
            - VERSION
         
     | 
| 
      
 62 
     | 
    
         
            +
            - app/controllers/images_controller.rb
         
     | 
| 
      
 63 
     | 
    
         
            +
            - app/models/image.rb
         
     | 
| 
      
 64 
     | 
    
         
            +
            - config/routes.rb
         
     | 
| 
      
 65 
     | 
    
         
            +
            - dynamic_image.gemspec
         
     | 
| 
      
 66 
     | 
    
         
            +
            - init.rb
         
     | 
| 
      
 67 
     | 
    
         
            +
            - install.rb
         
     | 
| 
      
 68 
     | 
    
         
            +
            - lib/binary_storage.rb
         
     | 
| 
      
 69 
     | 
    
         
            +
            - lib/binary_storage/active_record_extensions.rb
         
     | 
| 
      
 70 
     | 
    
         
            +
            - lib/binary_storage/blob.rb
         
     | 
| 
      
 71 
     | 
    
         
            +
            - lib/dynamic_image.rb
         
     | 
| 
      
 72 
     | 
    
         
            +
            - lib/dynamic_image/active_record_extensions.rb
         
     | 
| 
      
 73 
     | 
    
         
            +
            - lib/dynamic_image/engine.rb
         
     | 
| 
      
 74 
     | 
    
         
            +
            - lib/dynamic_image/filterset.rb
         
     | 
| 
      
 75 
     | 
    
         
            +
            - lib/dynamic_image/helper.rb
         
     | 
| 
      
 76 
     | 
    
         
            +
            - lib/generators/dynamic_image/USAGE
         
     | 
| 
      
 77 
     | 
    
         
            +
            - lib/generators/dynamic_image/dynamic_image_generator.rb
         
     | 
| 
      
 78 
     | 
    
         
            +
            - lib/generators/dynamic_image/templates/migrations/create_images.rb
         
     | 
| 
      
 79 
     | 
    
         
            +
            - uninstall.rb
         
     | 
| 
      
 80 
     | 
    
         
            +
            has_rdoc: true
         
     | 
| 
      
 81 
     | 
    
         
            +
            homepage: http://github.com/elektronaut/dynamic_image
         
     | 
| 
      
 82 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 85 
     | 
    
         
            +
            rdoc_options: 
         
     | 
| 
      
 86 
     | 
    
         
            +
            - --charset=UTF-8
         
     | 
| 
      
 87 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 88 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 89 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 90 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 91 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 92 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 93 
     | 
    
         
            +
                  segments: 
         
     | 
| 
      
 94 
     | 
    
         
            +
                  - 0
         
     | 
| 
      
 95 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 96 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 97 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 98 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 99 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 100 
     | 
    
         
            +
                  segments: 
         
     | 
| 
      
 101 
     | 
    
         
            +
                  - 0
         
     | 
| 
      
 102 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 103 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 106 
     | 
    
         
            +
            rubygems_version: 1.3.6
         
     | 
| 
      
 107 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 108 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 109 
     | 
    
         
            +
            summary: DynamicImage is a rails plugin providing transparent uploading and processing of image files.
         
     | 
| 
      
 110 
     | 
    
         
            +
            test_files: 
         
     | 
| 
      
 111 
     | 
    
         
            +
            - test/dynamic_image_test.rb
         
     | 
| 
      
 112 
     | 
    
         
            +
            - test/test_helper.rb
         
     |