dynamic_image 0.9.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,109 +1,19 @@
1
1
  # DynamicImage
2
2
 
3
- DynamicImage is a Rails plugin that simplifies image uploading and processing.
4
- No configuration is necessary, as resizing and processing is done on demand and
5
- cached rather than on upload.
3
+ > We're programmers. Programmers are, in their hearts, architects, and the
4
+ > first thing they want to do when they get to a site is to bulldoze
5
+ > the place flat and build something grand.
6
+ > (...)
7
+ > When you throw away code and start from scratch, you are throwing away all
8
+ > that knowledge. All those collected bug fixes. Years of programming work.
9
+ > _-Joel Spolsky_
6
10
 
7
- Note: This version is currently Rails 3 specific, although the final 1.0
8
- version will also be compatible with 2.x.
11
+ Good riddance.
9
12
 
13
+ DynamicImage is being rewritten.
10
14
 
11
- ## Installation
15
+ ## License
12
16
 
13
- Install the gem:
17
+ Copyright 2006-2014 Inge Jørgensen
14
18
 
15
- gem install dynamic_image
16
-
17
- Add the gem to your Gemfile:
18
-
19
- gem 'dynamic_image'
20
-
21
- Do the migrations:
22
-
23
- rails generate dynamic_image migrations
24
- rake db:migrate
25
-
26
-
27
- ## Getting started
28
-
29
- Let's create a model with an image:
30
-
31
- class User
32
- belongs_to_image :mugshot
33
- end
34
-
35
- Uploading files is pretty straightforward, just add a <tt>file_field</tt>
36
- to your form and update your record as usual:
37
-
38
- <%= form_for @user, :html => {:multipart => true} do |f| %>
39
- Name: <%= f.text_field :name %>
40
- Mugshot: <%= f.file_field :mugshot %>
41
- <%= submit_tag "Save" %>
42
- <% end %>
43
-
44
- You can now use the <tt>dynamic_image_tag</tt> helper to show off your
45
- new image:
46
-
47
- <% if @user.mugshot? %>
48
- <%= dynamic_image_tag @user.profile_picture, :size => '64x64' %>
49
- <% end %>
50
-
51
-
52
- ## Filters
53
-
54
- I'm cleaning up the filters syntax, watch this space.
55
-
56
-
57
- ## Technical
58
-
59
- The original master files are stored in the file system and identified a
60
- SHA-1 hash of the contents. If you're familiar with the internal workings
61
- of git, this should seem familiar.
62
-
63
- Processing images on the fly is expensive. Therefore, page caching is enabled
64
- by default, even in development mode. To disable page caching, add the following
65
- line in your initializers:
66
-
67
- DynamicImage.page_caching = false
68
-
69
-
70
- ## History
71
-
72
- DynamicImage was originally created in early 2006 to handle images
73
- for the Pages CMS. It was later extracted as a Rails Engine for Rails
74
- 1.2 in 2007, which also marked the first public release as
75
- dynamic_image_engine.
76
-
77
- The API has remained more or less unchanged, but the internal workings
78
- have been refactored a few times over the years, most notably dropping
79
- the Engines dependency and transitioning from database storage to file
80
- system.
81
-
82
- The current version is based on an internal branch targeting Rails
83
- 2.3, and modified to work as a Rails 3 plugin. It's not directly
84
- compatible with earlier versions, but upgrading shouldn't be more
85
- trouble than migrating the files out of the database.
86
-
87
-
88
- ## Copyright
89
-
90
- Copyright © 2006 Inge Jørgensen.
91
-
92
- Permission is hereby granted, free of charge, to any person obtaining
93
- a copy of this software and associated documentation files (the
94
- "Software"), to deal in the Software without restriction, including
95
- without limitation the rights to use, copy, modify, merge, publish,
96
- distribute, sublicense, and/or sell copies of the Software, and to
97
- permit persons to whom the Software is furnished to do so, subject to
98
- the following conditions:
99
-
100
- The above copyright notice and this permission notice shall be
101
- included in all copies or substantial portions of the Software.
102
-
103
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
104
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
105
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
106
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
107
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
108
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
109
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+ DynamicImage is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -5,39 +5,18 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "dynamic_image"
8
- s.version = "0.9.9"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Inge J\303\270rgensen"]
12
- s.date = "2014-02-11"
12
+ s.date = "2014-06-12"
13
13
  s.email = "inge@elektronaut.no"
14
14
  s.extra_rdoc_files = [
15
- "LICENSE",
16
15
  "README.md"
17
16
  ]
18
17
  s.files = [
19
- "LICENSE",
20
18
  "README.md",
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"
19
+ "dynamic_image.gemspec"
41
20
  ]
42
21
  s.homepage = "http://github.com/elektronaut/dynamic_image"
43
22
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_image
3
3
  version: !ruby/object:Gem::Version
4
- hash: 41
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
+ - 1
7
8
  - 0
8
- - 9
9
- - 9
10
- version: 0.9.9
9
+ - 0
10
+ version: 1.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - "Inge J\xC3\xB8rgensen"
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2014-02-11 00:00:00 Z
18
+ date: 2014-06-12 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rmagick
@@ -56,31 +56,10 @@ executables: []
56
56
  extensions: []
57
57
 
58
58
  extra_rdoc_files:
59
- - LICENSE
60
59
  - README.md
61
60
  files:
62
- - LICENSE
63
61
  - README.md
64
- - Rakefile
65
- - VERSION
66
- - app/controllers/images_controller.rb
67
- - app/models/image.rb
68
- - config/routes.rb
69
62
  - dynamic_image.gemspec
70
- - init.rb
71
- - install.rb
72
- - lib/binary_storage.rb
73
- - lib/binary_storage/active_record_extensions.rb
74
- - lib/binary_storage/blob.rb
75
- - lib/dynamic_image.rb
76
- - lib/dynamic_image/active_record_extensions.rb
77
- - lib/dynamic_image/engine.rb
78
- - lib/dynamic_image/filterset.rb
79
- - lib/dynamic_image/helper.rb
80
- - lib/generators/dynamic_image/USAGE
81
- - lib/generators/dynamic_image/dynamic_image_generator.rb
82
- - lib/generators/dynamic_image/templates/migrations/create_images.rb
83
- - uninstall.rb
84
63
  homepage: http://github.com/elektronaut/dynamic_image
85
64
  licenses: []
86
65
 
data/LICENSE DELETED
@@ -1,20 +0,0 @@
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/Rakefile DELETED
@@ -1,33 +0,0 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
-
4
- require "rake"
5
-
6
- begin
7
- require "jeweler"
8
- Jeweler::Tasks.new do |gem|
9
- gem.name = "dynamic_image"
10
- gem.summary = "DynamicImage is a rails plugin providing transparent uploading and processing of image files."
11
- gem.email = "inge@elektronaut.no"
12
- gem.homepage = "http://github.com/elektronaut/dynamic_image"
13
- gem.authors = ["Inge Jørgensen"]
14
- gem.files = Dir["*", "{lib}/**/*", "{app}/**/*", "{config}/**/*"]
15
- gem.add_dependency("rmagick", "~> 2.13.2")
16
- gem.add_dependency("vector2d", "~> 1.0.0")
17
- end
18
- Jeweler::GemcutterTasks.new
19
- rescue LoadError
20
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
- end
22
-
23
- desc 'Default: run unit tests.'
24
- task :default => :test
25
-
26
- desc 'Test the dynamic_image plugin.'
27
- Rake::TestTask.new(:test) do |t|
28
- t.libs << 'lib'
29
- t.libs << 'test'
30
- t.pattern = 'test/**/*_test.rb'
31
- t.verbose = true
32
- end
33
-
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.9.9
@@ -1,79 +0,0 @@
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 DELETED
@@ -1,188 +0,0 @@
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
- if image_file.kind_of?(String) && image_file =~ /^(ht|f)tps?:\/\//
28
- self.filename = File.basename(image_file)
29
- image_file = open(image_file)
30
- else
31
- self.filename = image_file.original_filename rescue File.basename(image_file.path)
32
- end
33
- self.content_type = image_file.content_type.chomp rescue "image/"+image_file.path.split(/\./).last.downcase.gsub(/jpg/,"jpeg") # ugly hack
34
- set_image_data(image_file.read)
35
- end
36
-
37
- # Return the image hotspot
38
- def hotspot
39
- (self.hotspot?) ? self.hotspot : (Vector2d.new(self.size) * 0.5).round.to_s
40
- end
41
-
42
- # Check the image data
43
- def set_image_data(data)
44
- self.data = data
45
- if self.data
46
- image = Magick::ImageList.new.from_blob(self.data)
47
- size = Vector2d.new(image.columns, image.rows)
48
- if DynamicImage.crash_size
49
- crashsize = Vector2d.new(DynamicImage.crash_size)
50
- if (size.x > crashsize.x || size.y > crashsize.y)
51
- raise "Image too large!"
52
- end
53
- end
54
- if DynamicImage.max_size && !self.skip_maxsize
55
- maxsize = Vector2d.new(DynamicImage.max_size)
56
- if (size.x > maxsize.x || size.y > maxsize.y)
57
- size = size.constrain_both(maxsize).round
58
- image.resize!(size.x, size.y)
59
- self.data = image.to_blob
60
- end
61
- end
62
- # Convert image to a proper format
63
- unless image.format =~ /(JPEG|PNG|GIF)/
64
- self.data = image.to_blob{self.format = 'JPEG'; self.quality = 90}
65
- self.filename += ".jpg"
66
- self.content_type = "image/jpeg"
67
- end
68
- self.original_size = size.round.to_s
69
- end
70
- end
71
-
72
- # Returns the image width
73
- def original_width
74
- Vector2d.new(self.original_size).x.to_i
75
- end
76
-
77
- # Returns the image height
78
- def original_height
79
- Vector2d.new(self.original_size).y.to_i
80
- end
81
-
82
- def crop_start_x
83
- Vector2d.new(self.crop_start).x.to_i
84
- end
85
- def crop_start_y
86
- Vector2d.new(self.crop_start).y.to_i
87
- end
88
- def crop_width
89
- Vector2d.new(self.crop_size).x.to_i
90
- end
91
- def crop_height
92
- Vector2d.new(self.crop_size).y.to_i
93
- end
94
-
95
- # Returns original or cropped size
96
- def size
97
- (self.cropped?) ? self.crop_size : self.original_size
98
- end
99
-
100
- def size=(new_size)
101
- self.original_size = new_size
102
- end
103
-
104
- # Convert file name to a more file system friendly one.
105
- # TODO: international chars
106
- def friendly_file_name( file_name )
107
- [["æ","ae"], ["ø","oe"], ["å","aa"]].each do |int|
108
- file_name = file_name.gsub(int[0], int[1])
109
- end
110
- File.basename(file_name).gsub(/[^\w\d\.-]/, "_")
111
- end
112
-
113
- # Get the base part of a filename
114
- def base_part_of(file_name)
115
- name = File.basename(file_name)
116
- name.gsub(/[ˆ\w._-]/, '')
117
- end
118
-
119
- # Rescale and crop the image, and return it as a blob.
120
- def rescaled_and_cropped_data(*args)
121
- DynamicImage.dirty_memory = true # Flag to perform GC
122
- image_data = Magick::ImageList.new.from_blob(self.data)
123
-
124
- if self.cropped?
125
- cropped_start = Vector2d.new(self.crop_start).round
126
- cropped_size = Vector2d.new(self.crop_size).round
127
- image_data = image_data.crop(cropped_start.x, cropped_start.y, cropped_size.x, cropped_size.y, true)
128
- end
129
-
130
- size = Vector2d.new(self.size)
131
- rescale_size = size.dup.constrain_one(args).round # Rescale dimensions
132
- crop_to_size = Vector2d.new(args).round # Crop size
133
- new_hotspot = Vector2d.new(hotspot) * (rescale_size / size) # Recalculated hotspot
134
- rect = [(new_hotspot-(crop_to_size/2)).round, (new_hotspot+(crop_to_size/2)).round] # Array containing crop coords
135
-
136
- # Adjustments
137
- x = rect[0].x; rect.each{|r| r.x += (x.abs)} if x < 0
138
- y = rect[0].y; rect.each{|r| r.y += (y.abs)} if y < 0
139
- x = rect[1].x; rect.each{|r| r.x -= (x-rescale_size.x)} if x > rescale_size.x
140
- y = rect[1].y; rect.each{|r| r.y -= (y-rescale_size.y)} if y > rescale_size.y
141
-
142
- rect[0].round!
143
- rect[1].round!
144
-
145
- image_data = image_data.resize(rescale_size.x, rescale_size.y)
146
- image_data = image_data.crop(rect[0].x, rect[0].y, crop_to_size.x, crop_to_size.y)
147
- image_data.to_blob{self.quality = 90}
148
- end
149
-
150
- def constrain_size(*max_size)
151
- Vector2d.new(self.size).constrain_both(max_size.flatten).round.to_s
152
- end
153
-
154
- # Get a duplicate image with resizing and filters applied.
155
- def get_processed(size, filterset=nil)
156
- size = Vector2d.new(size).round.to_s
157
- processed_image = Image.new
158
- processed_image.filterset = filterset || 'default'
159
- processed_image.data = self.rescaled_and_cropped_data(size)
160
- processed_image.size = size
161
- processed_image.apply_filters
162
- processed_image
163
- end
164
-
165
- # Apply filters to image data
166
- def apply_filters
167
- filterset_name = self.filterset || 'default'
168
- filterset = DynamicImage::Filterset[filterset_name]
169
- if filterset
170
- DynamicImage.dirty_memory = true # Flag for GC
171
- data = Magick::ImageList.new.from_blob(self.data)
172
- data = filterset.process(data)
173
- self.data = data.to_blob
174
- end
175
- end
176
-
177
- # Decorates to_json with additional attributes
178
- def to_json
179
- attributes.merge({
180
- :original_width => self.original_width,
181
- :original_height => self.original_height,
182
- :crop_width => self.crop_width,
183
- :crop_height => self.crop_height,
184
- :crop_start_x => self.crop_start_x,
185
- :crop_start_y => self.crop_start_y
186
- }).to_json
187
- end
188
- end
data/config/routes.rb DELETED
@@ -1,16 +0,0 @@
1
- # Rails 3 routes
2
- Rails.application.routes.draw do
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'})
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require 'dynamic_image'
data/install.rb DELETED
@@ -1 +0,0 @@
1
- # Install hook code here
@@ -1,28 +0,0 @@
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
@@ -1,144 +0,0 @@
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)
@@ -1,104 +0,0 @@
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
- options = {
34
- :hash_string => nil,
35
- :data => nil
36
- }
37
- if args.first.kind_of?(Hash)
38
- options.merge!(args.first)
39
- else
40
- options[:data] = args.first
41
- end
42
- @hash_string = options[:hash_string]
43
- @data = options[:data]
44
- end
45
-
46
- def data
47
- @data
48
- end
49
-
50
- def data=(new_data)
51
- @hash_string = nil
52
- @data = new_data
53
- end
54
-
55
- def hash_string
56
- unless @hash_string
57
- if @data
58
- @hash_string = BinaryStorage.hexdigest(data)
59
- else
60
- raise "Binary has no data!"
61
- end
62
- end
63
- @hash_string
64
- end
65
-
66
- def storage_dir
67
- BinaryStorage::Blob.storage_dir(hash_string)
68
- end
69
-
70
- def storage_path
71
- BinaryStorage::Blob.storage_path(hash_string)
72
- end
73
-
74
- def exists?
75
- File.exists?(storage_path)
76
- end
77
-
78
- def empty?
79
- (hash_string && !exists?) || !data || data.empty?
80
- end
81
-
82
- def load
83
- raise "File not found" unless exists?
84
- @data = File.open(storage_path, "rb") {|io| io.read }
85
- end
86
-
87
- def delete
88
- if exists?
89
- FileUtils.rm(storage_path)
90
- end
91
- end
92
-
93
- def save
94
- unless exists?
95
- FileUtils.mkdir_p(storage_dir)
96
- file = File.new(storage_path, 'wb')
97
- file.write(@data)
98
- file.close
99
- end
100
- return true
101
- end
102
-
103
- end
104
- end
data/lib/dynamic_image.rb DELETED
@@ -1,78 +0,0 @@
1
- require 'tempfile'
2
- require 'digest/sha1'
3
- require 'open-uri'
4
-
5
- # Gem dependencies
6
- require 'RMagick'
7
- require 'vector2d'
8
- require 'rails'
9
- require 'action_controller'
10
- require 'active_support'
11
- require 'active_record'
12
-
13
- require 'binary_storage'
14
-
15
- if Rails::VERSION::MAJOR == 3
16
- # Load the engine
17
- require 'dynamic_image/engine' if defined?(Rails)
18
- end
19
-
20
- require 'dynamic_image/active_record_extensions'
21
- require 'dynamic_image/filterset'
22
- require 'dynamic_image/helper'
23
-
24
- module DynamicImage
25
- @@dirty_memory = false
26
- @@page_caching = true
27
-
28
- class << self
29
-
30
- def dirty_memory=(flag)
31
- @@dirty_memory = flag
32
- end
33
-
34
- def dirty_memory
35
- @@dirty_memory
36
- end
37
-
38
- def page_caching=(flag)
39
- @@page_caching = flag
40
- end
41
-
42
- def page_caching
43
- @@page_caching
44
- end
45
-
46
- def max_size
47
- @@max_size ||= "2000x2000"
48
- end
49
-
50
- def max_size=(new_max_size)
51
- @@max_size = new_max_size
52
- end
53
-
54
- def crash_size
55
- @@crash_size ||= "10000x10000"
56
- end
57
-
58
- def crash_size=(new_crash_size)
59
- @@crash_size = new_crash_size
60
- end
61
-
62
- # RMagick stores image data internally, Ruby doesn't see the used memory.
63
- # This method performs garbage collection if @@dirty_memory has been flagged.
64
- # More details here: http://rubyforge.org/forum/message.php?msg_id=1995
65
- def clean_dirty_memory(options={})
66
- options.symbolize_keys!
67
- if @@dirty_memory || options[:force]
68
- gc_disabled = GC.enable
69
- GC.start
70
- GC.disable if gc_disabled
71
- @@dirty_memory = false
72
- true
73
- else
74
- false
75
- end
76
- end
77
- end
78
- end
@@ -1,60 +0,0 @@
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
- def #{association_id}?
47
- (self.#{association_id} && self.#{association_id}.data?) ? true : false
48
- end
49
- end_eval
50
-
51
- send :include, DynamicImage::ActiveRecordExtensions::InstanceMethods
52
- end
53
- end
54
-
55
- module InstanceMethods
56
- end
57
- end
58
- end
59
-
60
- ActiveRecord::Base.send(:include, DynamicImage::ActiveRecordExtensions)
@@ -1,6 +0,0 @@
1
- require 'dynamic_image'
2
-
3
- module DynamicImage
4
- class Engine < Rails::Engine
5
- end
6
- end
@@ -1,79 +0,0 @@
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
@@ -1,107 +0,0 @@
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
- #
78
- # The following options are supported (the rest will be forwarded to <tt>image_tag</tt>):
79
- #
80
- # * :size - Resize the image to fit these proportions. Size is given as a string with the format
81
- # '100x100'. Either dimension can be omitted, for example: '100x'
82
- # * :crop - Crop the image to the size given. (Boolean, default: <tt>false</tt>)
83
- # * :no_size_attr - Do not include width and height attributes in the image tag. (Boolean, default: false)
84
- # * :filterset - Apply the given filterset to the image
85
- #
86
- # ==== Examples
87
- #
88
- # dynamic_image_tag(@image) # Original image
89
- # dynamic_image_tag(@image, :size => "100x") # Will be 100px wide
90
- # dynamic_image_tag(@image, :size => "100x100") # Will fit within 100x100
91
- # dynamic_image_tag(@image, :size => "100x100", :crop => true) # Will be cropped to 100x100
92
- #
93
- def dynamic_image_tag(image, options = {})
94
- parsed_options = dynamic_image_options(image, options)
95
- image_tag(parsed_options[:url], parsed_options[:options] ).gsub(/\?[\d]+/,'').html_safe
96
- end
97
-
98
- # Returns an url corresponding to the provided image model.
99
- # Special options are documented in ApplicationHelper.dynamic_image_tag, only <tt>:size</tt>, <tt>:filterset</tt> and <tt>:crop</tt> apply.
100
- def dynamic_image_url(image, options = {})
101
- parsed_options = dynamic_image_options(image, options)
102
- parsed_options[:url]
103
- end
104
- end
105
- end
106
-
107
- ActionView::Base.send(:include, DynamicImage::Helper)
@@ -1,5 +0,0 @@
1
- Description:
2
- Creates the migrations
3
-
4
- Usage:
5
- rails generate dynamic_image
@@ -1,38 +0,0 @@
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
@@ -1,21 +0,0 @@
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
- end
16
- end
17
-
18
- def self.down
19
- drop_table :images
20
- end
21
- end
data/uninstall.rb DELETED
@@ -1 +0,0 @@
1
- # Uninstall hook code here