dynamic_image 1.0.0 → 1.0.1
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.md +102 -12
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/app/controllers/images_controller.rb +79 -0
- data/app/models/image.rb +188 -0
- data/config/routes.rb +16 -0
- data/dynamic_image.gemspec +22 -1
- data/dynamic_image.sublime-project +9 -0
- data/dynamic_image.sublime-workspace +1599 -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 +104 -0
- data/lib/binary_storage.rb +28 -0
- data/lib/dynamic_image/active_record_extensions.rb +60 -0
- data/lib/dynamic_image/engine.rb +6 -0
- data/lib/dynamic_image/filterset.rb +79 -0
- data/lib/dynamic_image/helper.rb +107 -0
- data/lib/dynamic_image.rb +78 -0
- data/lib/generators/dynamic_image/USAGE +5 -0
- data/lib/generators/dynamic_image/dynamic_image_generator.rb +38 -0
- data/lib/generators/dynamic_image/templates/migrations/create_images.rb +21 -0
- data/uninstall.rb +1 -0
- metadata +27 -4
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,104 @@
|
|
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
|
@@ -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,60 @@
|
|
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)
|
@@ -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,107 @@
|
|
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)
|
@@ -0,0 +1,78 @@
|
|
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
|
@@ -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,21 @@
|
|
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
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|