ss-attachment_fu 3.2.17

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.
@@ -0,0 +1,413 @@
1
+ module Technoweenie # :nodoc:
2
+ module AttachmentFu # :nodoc:
3
+ module Backends
4
+ # = AWS::S3 Storage Backend
5
+ #
6
+ # Enables use of {Amazon's Simple Storage Service}[http://aws.amazon.com/s3] as a storage mechanism
7
+ #
8
+ # == Requirements
9
+ #
10
+ # Requires the {AWS::S3 Library}[http://amazon.rubyforge.org] for S3 by Marcel Molina Jr. installed either
11
+ # as a gem or a as a Rails plugin.
12
+ #
13
+ # == Configuration
14
+ #
15
+ # Configuration is done via <tt>#{Rails.root}/config/amazon_s3.yml</tt> and is loaded according to the <tt>#{Rails.env}</tt>.
16
+ # The minimum connection options that you must specify are a bucket name, your access key id and your secret access key.
17
+ # If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon.
18
+ # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
19
+ #
20
+ # If you wish to use Amazon CloudFront to serve the files, you can also specify a distibution domain for the bucket.
21
+ # To read more about CloudFront, visit http://aws.amazon.com/cloudfront
22
+ #
23
+ # Example configuration (#{Rails.root}/config/amazon_s3.yml)
24
+ #
25
+ # development:
26
+ # bucket_name: appname_development
27
+ # access_key_id: <your key>
28
+ # secret_access_key: <your key>
29
+ # distribution_domain: XXXX.cloudfront.net
30
+ #
31
+ # test:
32
+ # bucket_name: appname_test
33
+ # access_key_id: <your key>
34
+ # secret_access_key: <your key>
35
+ # distribution_domain: XXXX.cloudfront.net
36
+ #
37
+ # production:
38
+ # bucket_name: appname
39
+ # access_key_id: <your key>
40
+ # secret_access_key: <your key>
41
+ # distribution_domain: XXXX.cloudfront.net
42
+ #
43
+ # You can change the location of the config path by passing a full path to the :s3_config_path option.
44
+ #
45
+ # has_attachment :storage => :s3, :s3_config_path => (#{Rails.root} + '/config/s3.yml')
46
+ #
47
+ # === Required configuration parameters
48
+ #
49
+ # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
50
+ # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
51
+ # * <tt>:bucket_name</tt> - A unique bucket name (think of the bucket_name as being like a database name).
52
+ #
53
+ # If any of these required arguments is missing, a MissingAccessKey exception will be raised from AWS::S3.
54
+ #
55
+ # == About bucket names
56
+ #
57
+ # Bucket names have to be globaly unique across the S3 system. And you can only have up to 100 of them,
58
+ # so it's a good idea to think of a bucket as being like a database, hence the correspondance in this
59
+ # implementation to the development, test, and production environments.
60
+ #
61
+ # The number of objects you can store in a bucket is, for all intents and purposes, unlimited.
62
+ #
63
+ # === Optional configuration parameters
64
+ #
65
+ # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>.
66
+ # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if <tt>:use_ssl</tt> is set.
67
+ # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
68
+ # * <tt>:distribution_domain</tt> - The CloudFront distribution domain for the bucket. This can either be the assigned
69
+ # distribution domain (ie. XXX.cloudfront.net) or a chosen domain using a CNAME. See CloudFront for more details.
70
+ #
71
+ # == Usage
72
+ #
73
+ # To specify S3 as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:s3</tt>.
74
+ #
75
+ # class Photo < ActiveRecord::Base
76
+ # has_attachment :storage => :s3
77
+ # end
78
+ #
79
+ # === Customizing the path
80
+ #
81
+ # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
82
+ # in S3 urls that look like: http(s)://:server/:bucket_name/:table_name/:id/:filename with :table_name
83
+ # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
84
+ # option:
85
+ #
86
+ # class Photo < ActiveRecord::Base
87
+ # has_attachment :storage => :s3, :path_prefix => 'my/custom/path'
88
+ # end
89
+ #
90
+ # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
91
+ #
92
+ # === Using different bucket names on different models
93
+ #
94
+ # By default the bucket name that the file will be stored to is the one specified by the
95
+ # <tt>:bucket_name</tt> key in the amazon_s3.yml file. You can use the <tt>:bucket_key</tt> option
96
+ # to overide this behavior on a per model basis. For instance if you want a bucket that will hold
97
+ # only Photos you can do this:
98
+ #
99
+ # class Photo < ActiveRecord::Base
100
+ # has_attachment :storage => :s3, :bucket_key => :photo_bucket_name
101
+ # end
102
+ #
103
+ # And then your amazon_s3.yml file needs to look like this.
104
+ #
105
+ # development:
106
+ # bucket_name: appname_development
107
+ # access_key_id: <your key>
108
+ # secret_access_key: <your key>
109
+ #
110
+ # test:
111
+ # bucket_name: appname_test
112
+ # access_key_id: <your key>
113
+ # secret_access_key: <your key>
114
+ #
115
+ # production:
116
+ # bucket_name: appname
117
+ # photo_bucket_name: appname_photos
118
+ # access_key_id: <your key>
119
+ # secret_access_key: <your key>
120
+ #
121
+ # If the bucket_key you specify is not there in a certain environment then attachment_fu will
122
+ # default to the <tt>bucket_name</tt> key. This way you only have to create special buckets
123
+ # this can be helpful if you only need special buckets in certain environments.
124
+ #
125
+ # === Permissions
126
+ #
127
+ # By default, files are stored on S3 with public access permissions. You can customize this using
128
+ # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are
129
+ # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
130
+ #
131
+ # === Other options
132
+ #
133
+ # Of course, all the usual configuration options apply, such as content_type and thumbnails:
134
+ #
135
+ # class Photo < ActiveRecord::Base
136
+ # has_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
137
+ # has_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
138
+ # end
139
+ #
140
+ # === Accessing S3 URLs
141
+ #
142
+ # You can get an object's URL using the s3_url accessor. For example, assuming that for your postcard app
143
+ # you had a bucket name like 'postcard_world_development', and an attachment model called Photo:
144
+ #
145
+ # @postcard.s3_url # => http(s)://s3.amazonaws.com/postcard_world_development/photos/1/mexico.jpg
146
+ #
147
+ # The resulting url is in the form: http(s)://:server/:bucket_name/:table_name/:id/:file.
148
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
149
+ #
150
+ # Additionally, you can get an object's base path relative to the bucket root using
151
+ # <tt>base_path</tt>:
152
+ #
153
+ # @photo.file_base_path # => photos/1
154
+ #
155
+ # And the full path (including the filename) using <tt>full_filename</tt>:
156
+ #
157
+ # @photo.full_filename # => photos/
158
+ #
159
+ # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path.
160
+ # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
161
+ #
162
+ # === Accessing CloudFront URLs
163
+ #
164
+ # You can get an object's CloudFront URL using the cloudfront_url accessor. Using the example from above:
165
+ # @postcard.cloudfront_url # => http://XXXX.cloudfront.net/photos/1/mexico.jpg
166
+ #
167
+ # The resulting url is in the form: http://:distribution_domain/:table_name/:id/:file
168
+ #
169
+ # If you set :cloudfront to true in your model, the public_filename will be the CloudFront
170
+ # URL, not the S3 URL.
171
+ module S3Backend
172
+ class RequiredLibraryNotFoundError < StandardError; end
173
+ class ConfigFileNotFoundError < StandardError; end
174
+
175
+ def self.included(base) #:nodoc:
176
+ mattr_reader :bucket_name, :s3_config
177
+
178
+ begin
179
+ require 'aws/s3'
180
+ include AWS::S3
181
+ rescue LoadError
182
+ raise RequiredLibraryNotFoundError.new('AWS::S3 could not be loaded')
183
+ end
184
+
185
+ begin
186
+ @@s3_config_path = base.attachment_options[:s3_config_path] || File.join(Rails.root, 'config', 'amazon_s3.yml')
187
+ @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[Rails.env].symbolize_keys
188
+ #rescue
189
+ # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
190
+ end
191
+
192
+ bucket_key = base.attachment_options[:bucket_key]
193
+
194
+ if bucket_key and s3_config[bucket_key.to_sym]
195
+ eval_string = "def bucket_name()\n \"#{s3_config[bucket_key.to_sym]}\"\nend"
196
+ else
197
+ eval_string = "def bucket_name()\n \"#{s3_config[:bucket_name]}\"\nend"
198
+ end
199
+ base.class_eval(eval_string, __FILE__, __LINE__)
200
+
201
+ Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy))
202
+
203
+ # Bucket.create(@@bucket_name)
204
+
205
+ base.before_update :rename_file
206
+ end
207
+
208
+ def self.protocol
209
+ @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
210
+ end
211
+
212
+ def self.hostname
213
+ @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
214
+ end
215
+
216
+ def self.port_string
217
+ @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}"
218
+ end
219
+
220
+ def self.distribution_domain
221
+ @distribution_domain = s3_config[:distribution_domain]
222
+ end
223
+
224
+ module ClassMethods
225
+ def s3_protocol
226
+ Technoweenie::AttachmentFu::Backends::S3Backend.protocol
227
+ end
228
+
229
+ def s3_hostname
230
+ Technoweenie::AttachmentFu::Backends::S3Backend.hostname
231
+ end
232
+
233
+ def s3_port_string
234
+ Technoweenie::AttachmentFu::Backends::S3Backend.port_string
235
+ end
236
+
237
+ def cloudfront_distribution_domain
238
+ Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
239
+ end
240
+ end
241
+
242
+ # Overwrites the base filename writer in order to store the old filename
243
+ def filename=(value)
244
+ @old_filename = filename unless filename.nil? || @old_filename
245
+ write_attribute :filename, sanitize_filename(value)
246
+ end
247
+
248
+ # The attachment ID used in the full path of a file
249
+ def attachment_path_id
250
+ ((respond_to?(:parent_id) && parent_id) || id).to_s
251
+ end
252
+
253
+ # The pseudo hierarchy containing the file relative to the bucket name
254
+ # Example: <tt>:table_name/:id</tt>
255
+ def base_path(thumbnail = nil)
256
+ file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix]
257
+ File.join(file_system_path, attachment_path_id)
258
+ end
259
+
260
+ # The full path to the file relative to the bucket name
261
+ # Example: <tt>:table_name/:id/:filename</tt>
262
+ def full_filename(thumbnail = nil)
263
+ File.join(base_path(thumbnail), thumbnail_name_for(thumbnail))
264
+ end
265
+
266
+ # All public objects are accessible via a GET request to the S3 servers. You can generate a
267
+ # url for an object using the s3_url method.
268
+ #
269
+ # @photo.s3_url
270
+ #
271
+ # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where
272
+ # the <tt>:server</tt> variable defaults to <tt>AWS::S3 URL::DEFAULT_HOST</tt> (s3.amazonaws.com) and can be
273
+ # set using the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
274
+ #
275
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
276
+ def s3_url(thumbnail = nil)
277
+ File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail))
278
+ end
279
+
280
+ # All public objects are accessible via a GET request to CloudFront. You can generate a
281
+ # url for an object using the cloudfront_url method.
282
+ #
283
+ # @photo.cloudfront_url
284
+ #
285
+ # The resulting url is in the form: <tt>http://:distribution_domain/:table_name/:id/:file</tt> using
286
+ # the <tt>:distribution_domain</tt> variable set in the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
287
+ #
288
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
289
+ def cloudfront_url(thumbnail = nil)
290
+ s3_protocol + cloudfront_distribution_domain + "/" + full_filename(thumbnail)
291
+ end
292
+
293
+ def public_filename(*args)
294
+ if attachment_options[:cloudfront]
295
+ cloudfront_url(*args)
296
+ else
297
+ s3_url(*args)
298
+ end
299
+ end
300
+
301
+ # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
302
+ # authenticated url for an object like this:
303
+ #
304
+ # @photo.authenticated_s3_url
305
+ #
306
+ # By default authenticated urls expire 5 minutes after they were generated.
307
+ #
308
+ # Expiration options can be specified either with an absolute time using the <tt>:expires</tt> option,
309
+ # or with a number of seconds relative to now with the <tt>:expires_in</tt> option:
310
+ #
311
+ # # Absolute expiration date (October 13th, 2025)
312
+ # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i)
313
+ #
314
+ # # Expiration in five hours from now
315
+ # @photo.authenticated_s3_url(:expires_in => 5.hours)
316
+ #
317
+ # You can specify whether the url should go over SSL with the <tt>:use_ssl</tt> option.
318
+ # By default, the ssl settings for the current connection will be used:
319
+ #
320
+ # @photo.authenticated_s3_url(:use_ssl => true)
321
+ #
322
+ # Finally, the optional thumbnail argument will output the thumbnail's filename (if any):
323
+ #
324
+ # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true)
325
+ def authenticated_s3_url(*args)
326
+ options = args.extract_options!
327
+ options[:expires_in] = options[:expires_in].to_i if options[:expires_in]
328
+ thumbnail = args.shift
329
+ S3Object.url_for(full_filename(thumbnail), bucket_name, options)
330
+ end
331
+
332
+ def create_temp_file
333
+ write_to_temp_file current_data
334
+ end
335
+
336
+ def current_data
337
+ if attachment_options[:encrypted_storage] && self.respond_to?(:encryption_key) && self.encryption_key != nil
338
+ EncryptedData.decrypt_data(S3Object.value(full_filename, bucket_name), self.encryption_key)
339
+ else
340
+ S3Object.value full_filename, bucket_name
341
+ end
342
+ end
343
+
344
+ def s3_protocol
345
+ Technoweenie::AttachmentFu::Backends::S3Backend.protocol
346
+ end
347
+
348
+ def s3_hostname
349
+ Technoweenie::AttachmentFu::Backends::S3Backend.hostname
350
+ end
351
+
352
+ def s3_port_string
353
+ Technoweenie::AttachmentFu::Backends::S3Backend.port_string
354
+ end
355
+
356
+ def cloudfront_distribution_domain
357
+ Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
358
+ end
359
+
360
+ protected
361
+ # Called in the after_destroy callback
362
+ def destroy_file
363
+ S3Object.delete full_filename, bucket_name
364
+ end
365
+
366
+ def rename_file
367
+ return unless @old_filename && @old_filename != filename
368
+
369
+ old_full_filename = File.join(base_path, @old_filename)
370
+
371
+ S3Object.rename(
372
+ old_full_filename,
373
+ full_filename,
374
+ bucket_name,
375
+ :access => attachment_options[:s3_access]
376
+ )
377
+
378
+ @old_filename = nil
379
+ true
380
+ end
381
+
382
+ def save_to_storage
383
+ if save_attachment?
384
+ if attachment_options[:encrypted_storage]
385
+ S3Object.store(
386
+ full_filename,
387
+ (temp_path ? File.open(temp_path) : temp_data),
388
+ bucket_name,
389
+ :content_type => content_type,
390
+ :cache_control => attachment_options[:cache_control],
391
+ :access => attachment_options[:s3_access],
392
+ 'x-amz-server-side-encryption' => 'AES256',
393
+ 'Content-Disposition' => "attachment; filename=\"#{filename}\""
394
+ )
395
+ else
396
+ S3Object.store(
397
+ full_filename,
398
+ (temp_path ? File.open(temp_path) : temp_data),
399
+ bucket_name,
400
+ :content_type => content_type,
401
+ :cache_control => attachment_options[:cache_control],
402
+ :access => attachment_options[:s3_access]
403
+ )
404
+ end
405
+ end
406
+
407
+ @old_filename = nil
408
+ true
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
@@ -0,0 +1,66 @@
1
+ require 'red_artisan/core_image/processor'
2
+
3
+ module Technoweenie # :nodoc:
4
+ module AttachmentFu # :nodoc:
5
+ module Processors
6
+ module CoreImageProcessor
7
+ def self.included(base)
8
+ base.send :extend, ClassMethods
9
+ base.alias_method_chain :process_attachment, :processing
10
+ end
11
+
12
+ module ClassMethods
13
+ def with_image(file, &block)
14
+ block.call OSX::CIImage.from(file)
15
+ end
16
+ end
17
+
18
+ protected
19
+ def process_attachment_with_processing
20
+ return unless process_attachment_without_processing
21
+ with_image do |img|
22
+ self.width = img.extent.size.width if respond_to?(:width)
23
+ self.height = img.extent.size.height if respond_to?(:height)
24
+ resize_image_or_thumbnail! img
25
+ callback_with_args :after_resize, img
26
+ end if image?
27
+ end
28
+
29
+ # Performs the actual resizing operation for a thumbnail
30
+ def resize_image(img, size)
31
+ processor = ::RedArtisan::CoreImage::Processor.new(img)
32
+ size = size.first if size.is_a?(Array) && size.length == 1
33
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
34
+ if size.is_a?(Fixnum)
35
+ processor.fit(size)
36
+ else
37
+ processor.resize(size[0], size[1])
38
+ end
39
+ else
40
+ new_size = [img.extent.size.width, img.extent.size.height] / size.to_s
41
+ processor.resize(new_size[0], new_size[1])
42
+ end
43
+
44
+ processor.render do |result|
45
+ self.width = result.extent.size.width if respond_to?(:width)
46
+ self.height = result.extent.size.height if respond_to?(:height)
47
+ out_file = random_tempfile_filename
48
+ temp_paths.unshift Tempfile.new(out_file, Technoweenie::AttachmentFu.tempfile_path).path
49
+ properties = nil
50
+ # We don't check the source image since we're forcing the output to JPEG, apparently…
51
+ # Beware: apparently CoreImage only takes the percentage as a HINT, using a different actual quality…
52
+ quality = get_jpeg_quality
53
+ properties = { OSX::NSImageCompressionFactor => quality / 100.0 } if quality
54
+ result.save(self.temp_path, OSX::NSJPEGFileType, properties)
55
+ #
56
+ # puts "#{self.temp_path} @ #{quality.inspect} -> #{%x(identify -format '%Q' "#{self.temp_path}")}"
57
+ #
58
+ self.size = File.size(self.temp_path)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'gd2'
3
+ module Technoweenie # :nodoc:
4
+ module AttachmentFu # :nodoc:
5
+ module Processors
6
+ module Gd2Processor
7
+ def self.included(base)
8
+ base.send :extend, ClassMethods
9
+ base.alias_method_chain :process_attachment, :processing
10
+ end
11
+
12
+ module ClassMethods
13
+ # Yields a block containing a GD2 Image for the given binary data.
14
+ def with_image(file, &block)
15
+ im = GD2::Image.import(file)
16
+ block.call(im)
17
+ end
18
+ end
19
+
20
+ protected
21
+ def process_attachment_with_processing
22
+ return unless process_attachment_without_processing && image?
23
+ with_image do |img|
24
+ resize_image_or_thumbnail! img
25
+ self.width = img.width
26
+ self.height = img.height
27
+ callback_with_args :after_resize, img
28
+ end
29
+ end
30
+
31
+ # Performs the actual resizing operation for a thumbnail
32
+ def resize_image(img, size)
33
+ size = size.first if size.is_a?(Array) && size.length == 1
34
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
35
+ if size.is_a?(Fixnum)
36
+ # Borrowed from image science's #thumbnail method and adapted
37
+ # for this.
38
+ scale = size.to_f / (img.width > img.height ? img.width.to_f : img.height.to_f)
39
+ img.resize!((img.width * scale).round(1), (img.height * scale).round(1), false)
40
+ else
41
+ img.resize!(size.first, size.last, false)
42
+ end
43
+ else
44
+ w, h = [img.width, img.height] / size.to_s
45
+ img.resize!(w, h, false)
46
+ end
47
+ self.width = img.width if respond_to?(:width)
48
+ self.height = img.height if respond_to?(:height)
49
+ out_file = random_tempfile_filename
50
+ temp_paths.unshift out_file
51
+ jpeg = out_file =~ /\.jpe?g\z/i
52
+ quality = jpeg && get_jpeg_quality
53
+ self.size = img.export(self.temp_path, quality ? { :quality => quality } : {})
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,80 @@
1
+ require 'image_science'
2
+ module Technoweenie # :nodoc:
3
+ module AttachmentFu # :nodoc:
4
+ module Processors
5
+ module ImageScienceProcessor
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.alias_method_chain :process_attachment, :processing
9
+ end
10
+
11
+ module ClassMethods
12
+ # Yields a block containing an Image Science image for the given binary data.
13
+ def with_image(file, &block)
14
+ ::ImageScience.with_image file, &block
15
+ end
16
+ end
17
+
18
+ protected
19
+ def process_attachment_with_processing
20
+ return unless process_attachment_without_processing && image?
21
+ with_image do |img|
22
+ self.width = img.width if respond_to?(:width)
23
+ self.height = img.height if respond_to?(:height)
24
+ resize_image_or_thumbnail! img
25
+ end
26
+ end
27
+
28
+ # Performs the actual resizing operation for a thumbnail
29
+ def resize_image(img, size)
30
+ # create a dummy temp file to write to
31
+ # ImageScience doesn't handle all gifs properly, so it converts them to
32
+ # pngs for thumbnails. It has something to do with trying to save gifs
33
+ # with a larger palette than 256 colors, which is all the gif format
34
+ # supports.
35
+ filename.sub! /gif$/i, 'png'
36
+ content_type.sub!(/gif$/, 'png')
37
+ temp_paths.unshift write_to_temp_file(filename)
38
+ grab_dimensions = lambda do |img|
39
+ self.width = img.width if respond_to?(:width)
40
+ self.height = img.height if respond_to?(:height)
41
+
42
+ # We don't check for quality being a 0-100 value as we also allow FreeImage JPEG_xxx constants.
43
+ quality = content_type[/jpe?g/i] && get_jpeg_quality(false)
44
+ # Traditional ImageScience has a 1-arg save method, tdd-image_science has 1 mandatory + 1 optional
45
+ if quality && img.method(:save).arity == -2
46
+ img.save self.temp_path, quality
47
+ else
48
+ img.save self.temp_path
49
+ end
50
+ self.size = File.size(self.temp_path)
51
+ callback_with_args :after_resize, img
52
+ end
53
+
54
+ size = size.first if size.is_a?(Array) && size.length == 1
55
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
56
+ if size.is_a?(Fixnum)
57
+ img.thumbnail(size, &grab_dimensions)
58
+ else
59
+ img.resize(size[0], size[1], &grab_dimensions)
60
+ end
61
+ else
62
+ new_size = [img.width, img.height] / size.to_s
63
+ if size.ends_with?('!')
64
+ aspect = new_size[0].to_f / new_size[1].to_f
65
+ ih, iw = img.height, img.width
66
+ w, h = (ih * aspect), (iw / aspect)
67
+ w = [iw, w].min.to_i
68
+ h = [ih, h].min.to_i
69
+ img.with_crop((iw-w)/2, (ih-h)/2, (iw+w)/2, (ih+h)/2) { |crop|
70
+ crop.resize(new_size[0], new_size[1], &grab_dimensions)
71
+ }
72
+ else
73
+ img.resize(new_size[0], new_size[1], &grab_dimensions)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end