carrierwave-meta 0.0.4 → 0.0.5
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.travis.yml +7 -0
- data/README.rdoc +113 -45
- data/carrierwave-meta.gemspec +5 -0
- data/lib/carrierwave-meta.rb +3 -1
- data/lib/carrierwave-meta/active_record.rb +22 -0
- data/lib/carrierwave-meta/meta.rb +55 -21
- data/lib/carrierwave-meta/model_delegate_attribute.rb +16 -6
- data/lib/carrierwave-meta/version.rb +1 -1
- data/spec/fixtures/corrupted.eps +0 -0
- data/spec/fixtures/corrupted.pdf +0 -0
- data/spec/fixtures/flash.swf +0 -0
- data/spec/fixtures/sample.eps +0 -0
- data/spec/fixtures/sample.pdf +0 -0
- data/spec/modules/active_record_spec.rb +31 -0
- data/spec/modules/meta_spec.rb +131 -99
- data/spec/modules/model_delegate_attribute_spec.rb +13 -13
- data/spec/spec_helper.rb +22 -11
- data/spec/support/current_processor.rb +18 -0
- data/spec/support/remote.rb +27 -0
- data/spec/support/schema.rb +7 -12
- data/spec/support/test_blank_uploader.rb +1 -6
- data/spec/support/test_composed_model.rb +7 -0
- data/spec/support/test_delegate_uploader.rb +7 -9
- data/spec/support/test_model.rb +6 -5
- data/spec/support/test_uploader.rb +3 -9
- metadata +126 -48
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5eb83f3482998f4a6d77e2f0eee81aac037cc2b4
|
4
|
+
data.tar.gz: c01a5c5d1c24f5ac61b7d7f02f082c37fa9ee22f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3030e8b0b781f98cd64b6ab5bde51d70f80ad1551e7fadb6d8a508893df233c708ff72a03dcd64fe3d7ef506c6c7a83848872ccc0478857d76864a799715a3d
|
7
|
+
data.tar.gz: 80276a9f29cec556543907344b0b0d7f87ad8e5f53c651880a60f67da3545cad6b963af7323602a5d7443d38903301a7e9065980b1312bb122749ef1c9e3aac2
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.rdoc
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
= carrierwave-meta
|
2
2
|
|
3
|
+
{<img src="https://secure.travis-ci.org/gzigzigzeo/carrierwave-meta.png" alt="Build Status" />}[http://travis-ci.org/gzigzigzeo/carrierwave-meta]
|
4
|
+
{<img src="https://codeclimate.com/github/gzigzigzeo/carrierwave-meta.png" />}[https://codeclimate.com/github/gzigzigzeo/carrierwave-meta]
|
5
|
+
|
3
6
|
= Installation
|
4
7
|
|
5
8
|
Add the following line to your Gemfile:
|
@@ -10,56 +13,59 @@ Add the following line to your Gemfile:
|
|
10
13
|
class TestUploader < CarrierWave::Uploader::Base
|
11
14
|
include CarrierWave::RMagick
|
12
15
|
include CarrierWave::Meta
|
13
|
-
|
14
|
-
storage :file
|
15
16
|
|
16
|
-
process :store_meta
|
17
|
+
process :store_meta => [{md5sum: true}]
|
17
18
|
version :version do
|
18
19
|
process :resize_to_fill => [200, 200]
|
19
20
|
process :store_meta
|
20
21
|
end
|
21
22
|
end
|
22
|
-
|
23
|
+
|
23
24
|
file = File.open('test.jpg') # JPEG 500x300, 20000 bytes
|
24
25
|
uploader = TestUploader.new
|
25
26
|
uploader.store!(file)
|
26
|
-
|
27
|
+
|
27
28
|
uploader.width # 500
|
28
29
|
uploader.height # 300
|
29
30
|
uploader.image_size # [500, 300]
|
30
|
-
uploader.
|
31
|
+
uploader.file_size # 20000
|
31
32
|
uploader.content_type # "image/jpeg"
|
32
|
-
|
33
|
+
uploader.md5sum # "fuuaasdfasdf...."
|
34
|
+
|
33
35
|
uploader.version.width # 200
|
34
|
-
uploader.version.height # 200
|
35
|
-
uploader.version.image_size # [200, 200]
|
36
|
+
uploader.version.height # 200
|
37
|
+
uploader.version.image_size # [200, 200]
|
36
38
|
uploader.version.file_zie # less than 20000
|
37
39
|
uploader.version.content_type # "image/jpeg"
|
40
|
+
uploader.version.md5sum # nil
|
38
41
|
|
39
42
|
= Saving values to database
|
40
43
|
|
41
|
-
Simply create database columns to hold metadata in your model's table. Currently
|
42
|
-
image_size ([width, height]), content_type
|
44
|
+
Simply create database columns to hold metadata in your model's table. Currently
|
45
|
+
gem supports width, height, image_size ([width, height]), content_type,
|
46
|
+
file_size and MD5 fields. Versions are supported too.
|
43
47
|
|
44
48
|
class TestModel
|
45
|
-
attr_accessor :image_width
|
49
|
+
attr_accessor :image_width
|
46
50
|
attr_accessor :image_height
|
47
|
-
attr_accessor :image_image_size
|
51
|
+
attr_accessor :image_image_size
|
48
52
|
attr_accessor :image_content_type
|
49
53
|
attr_accessor :image_file_size
|
54
|
+
attr_accessor :image_md5sum
|
50
55
|
|
51
|
-
attr_accessor :image_version_width
|
56
|
+
attr_accessor :image_version_width
|
52
57
|
attr_accessor :image_version_height
|
53
58
|
attr_accessor :image_version_image_size
|
54
59
|
attr_accessor :image_version_content_type
|
55
|
-
attr_accessor :image_version_file_size
|
60
|
+
attr_accessor :image_version_file_size
|
61
|
+
attr_accessor :image_version_md5sum
|
56
62
|
end
|
57
63
|
|
58
64
|
file = File.open('test.jpg')
|
59
65
|
model = TestModel.new
|
60
66
|
uploader = TestUploader.new(model, :image)
|
61
67
|
uploader.store!(file)
|
62
|
-
|
68
|
+
|
63
69
|
uploader.width # 500
|
64
70
|
model.image_width # 500
|
65
71
|
model.image_height # 300
|
@@ -67,38 +73,66 @@ image_size ([width, height]), content_type and file_size fields. Versions are su
|
|
67
73
|
|
68
74
|
When columns are available in the model instance, metadata is stored in that columns.
|
69
75
|
|
76
|
+
= Saving values into single column
|
77
|
+
|
78
|
+
For now, works only with ActiveRecord.
|
79
|
+
|
80
|
+
class TestModel < ActiveRecord::Base
|
81
|
+
extend CarrierWave::Meta::ActiveRecord
|
82
|
+
|
83
|
+
mount_uploader :image, TestUploader
|
84
|
+
serialize :image_meta, OpenStruct
|
85
|
+
carrierwave_meta_composed :image_meta,
|
86
|
+
:image, image_version: [:width, :height, :md5sum]
|
87
|
+
end
|
88
|
+
|
89
|
+
model = TestModel.new
|
90
|
+
model.image.store!('test.jpg')
|
91
|
+
model.image_width # 200
|
92
|
+
model.image_version_width # 200
|
93
|
+
model.image_meta # {image_width: 200, image_height: 200, ...}
|
94
|
+
|
95
|
+
All you need is image_meta column, all other attributes are virtual. Note
|
96
|
+
that carrierwave_meta_composed should be called after mounting uploader.
|
97
|
+
|
70
98
|
= Behind the scenes
|
71
99
|
|
72
|
-
After the file is retrieved from store or cache metadata is recalculated
|
73
|
-
|
100
|
+
After the file is retrieved from store or cache metadata is recalculated
|
101
|
+
unless uploader has attached model instance. If uploader has attached
|
102
|
+
model instance values are read from that instance.
|
74
103
|
|
75
104
|
uploader = TestUploader.new
|
76
105
|
uploader.retrieve_from_store!('test.jpg')
|
77
|
-
|
78
106
|
uploader.version.width # 200
|
79
107
|
|
108
|
+
model = TestModel.new
|
109
|
+
model.image.store!('test.jpg')
|
110
|
+
model.image_width # 200
|
111
|
+
model.image.width # 200, actually read from image_width
|
112
|
+
|
80
113
|
= model_delegate_attribute
|
81
114
|
|
82
|
-
|
115
|
+
Is used to synchronize data between uploader and mounted model instance.
|
116
|
+
Model's instance is used like value cache.
|
83
117
|
|
84
118
|
class DelegateTestModel
|
85
119
|
attr_accessor :processed
|
86
120
|
attr_accessor :a_processed
|
87
121
|
attr_accessor :a_b_processed
|
88
122
|
end
|
89
|
-
|
123
|
+
|
90
124
|
class DelegateTestUploader < CarrierWave::Uploader::Base
|
91
125
|
model_delegate_attribute :uploaded
|
92
|
-
|
126
|
+
|
93
127
|
set_processed
|
94
|
-
|
128
|
+
|
95
129
|
version :a do
|
96
130
|
set_processed
|
97
131
|
version :b do
|
98
132
|
set_processed
|
99
133
|
end
|
100
134
|
end
|
101
|
-
|
135
|
+
|
102
136
|
def set_processed
|
103
137
|
self.processed = true
|
104
138
|
end
|
@@ -107,23 +141,25 @@ Used to synchronize data between uploader and mounted model instance. Model's in
|
|
107
141
|
model = DelegateTestModel.new
|
108
142
|
uploader = DelegateTestUploader.new(model, :image)
|
109
143
|
file = File.open('test.jpg')
|
110
|
-
|
144
|
+
|
111
145
|
uploader.store!(file)
|
112
|
-
|
146
|
+
|
113
147
|
model.processed # true
|
114
|
-
model.a_processed # true
|
148
|
+
model.a_processed # true
|
115
149
|
model.a_b_processed # true
|
116
|
-
|
150
|
+
|
117
151
|
model.a_processed = false
|
118
|
-
|
152
|
+
|
119
153
|
uploader.processed # true
|
120
154
|
uploader.a_processed # false
|
121
155
|
uploader.a_b_processed # true
|
122
156
|
|
123
157
|
When model is mounted to uploader:
|
124
158
|
|
125
|
-
1. If attribute is assigned inside uploader then corresponding property
|
126
|
-
|
159
|
+
1. If attribute is assigned inside uploader then corresponding property
|
160
|
+
in model is also assigned.
|
161
|
+
2. If attribute is retrieved from uploader, uploader checks that value is
|
162
|
+
defined in model and returns it. Otherwise returns uploader's instance variable.
|
127
163
|
3. If file is deleted, value becomes nil.
|
128
164
|
|
129
165
|
Otherwise acts as regular uploader's instance variables.
|
@@ -143,7 +179,7 @@ The uploader:
|
|
143
179
|
version :crop_source do
|
144
180
|
process :resize_to_fit => [300, 300]
|
145
181
|
process :store_meta
|
146
|
-
|
182
|
+
|
147
183
|
# This is the cropped version of parent image. Let crop to 50x50 square.
|
148
184
|
version :crop do
|
149
185
|
process :crop_to => [50, 50]
|
@@ -151,7 +187,7 @@ The uploader:
|
|
151
187
|
end
|
152
188
|
|
153
189
|
# Defines crop area dimensions.
|
154
|
-
# This should be assigned before #store! and #cache! called and should be saved in the model's instance.
|
190
|
+
# This should be assigned before #store! and #cache! called and should be saved in the model's instance.
|
155
191
|
# Otherwise cropped image would be lost after #recreate_versions! is called.
|
156
192
|
# If crop area dimensions are'nt assigned, uploader calculates crop area dimensions inside the
|
157
193
|
# parent image and creates the default image.
|
@@ -177,13 +213,13 @@ The uploader:
|
|
177
213
|
cropped_img = img.crop(x, y, width, height)
|
178
214
|
new_img = cropped_img.resize_to_fill(new_width, new_height)
|
179
215
|
destroy_image(cropped_img)
|
180
|
-
destroy_image(img)
|
216
|
+
destroy_image(img)
|
181
217
|
new_img
|
182
218
|
end
|
183
219
|
end
|
184
220
|
|
185
221
|
# Creates the default crop image.
|
186
|
-
# Here the original crop area dimensions are restored and assigned to the model's instance.
|
222
|
+
# Here the original crop area dimensions are restored and assigned to the model's instance.
|
187
223
|
def resize_to_fill_and_save_dimensions(new_width, new_height)
|
188
224
|
manipulate! do |img|
|
189
225
|
width, height = img.columns, img.rows
|
@@ -194,20 +230,20 @@ The uploader:
|
|
194
230
|
h_ratio = height.to_f / new_height.to_f
|
195
231
|
|
196
232
|
ratio = [w_ratio, h_ratio].min
|
197
|
-
|
233
|
+
|
198
234
|
self.w = ratio * new_width
|
199
235
|
self.h = ratio * new_height
|
200
236
|
self.x = (width - self.w) / 2
|
201
237
|
self.y = (height - self.h) / 2
|
202
|
-
|
238
|
+
|
203
239
|
new_img
|
204
240
|
end
|
205
241
|
end
|
206
|
-
|
242
|
+
|
207
243
|
private
|
208
244
|
def crop_args
|
209
245
|
%w(x y w h).map { |accessor| send(accessor).to_i }
|
210
|
-
end
|
246
|
+
end
|
211
247
|
end
|
212
248
|
|
213
249
|
# Post should have :crop_source_version_x, :crop_source_version_y, :crop_source_version_h, :crop_source_version_w columns
|
@@ -221,14 +257,14 @@ The uploader:
|
|
221
257
|
post.save!
|
222
258
|
|
223
259
|
post.image.crop_source.width # 300
|
224
|
-
post.image.crop_source.height # 200
|
260
|
+
post.image.crop_source.height # 200
|
225
261
|
post.image.crop_source.crop.width # 50
|
226
262
|
post.image.crop_source.crop.height # 50
|
227
|
-
|
263
|
+
|
228
264
|
# Default crop area coordinates within the limits of big image dimensions: square at the center of an image
|
229
265
|
post.image.crop_source.crop.x # 50
|
230
266
|
post.image.crop_source.crop.y # 50
|
231
|
-
post.image.crop_source.crop.w # 200
|
267
|
+
post.image.crop_source.crop.w # 200
|
232
268
|
post.image.crop_source.crop.h # 200
|
233
269
|
|
234
270
|
# Let user change the crop area with JCrop script. Pass new crop area parameters to the model.
|
@@ -238,19 +274,51 @@ The uploader:
|
|
238
274
|
post.crop_source_crop_h = 100
|
239
275
|
|
240
276
|
post.save! # Crop image is reprocessed
|
241
|
-
|
277
|
+
|
242
278
|
post.image.crop_source.crop.width # 50
|
243
279
|
post.image.crop_source.crop.height # 50
|
244
280
|
|
281
|
+
= PDF/GhostScript support
|
282
|
+
|
283
|
+
If you want to use this plugin with PDF/PostScript files than you should install
|
284
|
+
GhostScript and rebuild ImageMagick with GhostScript support:
|
285
|
+
|
286
|
+
brew install ghostscript
|
287
|
+
brew install imagemagick --with-ghostscript
|
288
|
+
gem uninstall rmagick && gem install rmagick
|
289
|
+
|
290
|
+
To switch on PDF/EPS processing you should enable GhostScript somewhere in your
|
291
|
+
app's initializer:
|
292
|
+
|
293
|
+
CarrierWave::Meta.ghostscript_enabled = true
|
294
|
+
|
245
295
|
= A note about testing
|
246
296
|
|
297
|
+
@SergeyKishenin added specs for EPS/GhostScript files. They run for image_magick
|
298
|
+
or mini_magick processor by default. To make specs work please install
|
299
|
+
GhostScript as described above. To run specs WITHOUT PDF/EPS do:
|
300
|
+
|
301
|
+
PDF_EPS=false bundle exec rspec
|
302
|
+
|
247
303
|
@fschwahn added support for mini-magick. To run tests with mini-magick do:
|
248
304
|
|
249
|
-
bundle exec
|
305
|
+
PROCESSOR=mini_magick bundle exec rspec
|
306
|
+
|
307
|
+
@skord added support for ImageSorcery. To run specs do:
|
308
|
+
|
309
|
+
PROCESSOR=image_sorcery bundle exec rspec
|
310
|
+
|
311
|
+
To run specs against VIPS processor do:
|
312
|
+
|
313
|
+
PROCESSOR=vips bundle exec rspec
|
314
|
+
|
315
|
+
To run specs against with Fog (Amazon S3) simulation:
|
316
|
+
|
317
|
+
STORAGE=fog bundle exec rspec
|
250
318
|
|
251
319
|
= TODO
|
252
320
|
|
253
321
|
1. I do not know how it would work with S3 and other remote storages. Should be tested.
|
254
322
|
2. Write integration guide for JCrop.
|
255
323
|
3. A notice about content-type.
|
256
|
-
|
324
|
+
|
data/carrierwave-meta.gemspec
CHANGED
@@ -26,4 +26,9 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_development_dependency(%q<rmagick>)
|
27
27
|
s.add_development_dependency(%q<mini_magick>)
|
28
28
|
s.add_development_dependency(%q<mime-types>)
|
29
|
+
s.add_development_dependency(%q<carrierwave-imagesorcery>)
|
30
|
+
s.add_development_dependency(%q<carrierwave-vips>)
|
31
|
+
s.add_development_dependency(%q<fog>, '~> 1.3.1')
|
32
|
+
s.add_development_dependency(%q<simplecov>)
|
33
|
+
s.add_development_dependency(%q<activerecord>, '>= 3.0')
|
29
34
|
end
|
data/lib/carrierwave-meta.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
3
|
require 'mime/types'
|
3
|
-
require
|
4
|
+
require 'carrierwave-meta/version'
|
4
5
|
require 'carrierwave-meta/model_delegate_attribute'
|
5
6
|
require 'carrierwave-meta/meta'
|
7
|
+
require 'carrierwave-meta/active_record'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
module Meta
|
3
|
+
module ActiveRecord
|
4
|
+
ALLOWED = %w(width height md5sum image_size file_size content_type)
|
5
|
+
|
6
|
+
def carrierwave_meta_composed(single_attribute, *args)
|
7
|
+
defined_attrs = args.map do |arg|
|
8
|
+
name, to_define = if arg.is_a?(Symbol)
|
9
|
+
[arg, ALLOWED]
|
10
|
+
elsif arg.is_a?(Hash)
|
11
|
+
[arg.keys.first, arg.values.first]
|
12
|
+
end
|
13
|
+
|
14
|
+
to_define.map do |attr|
|
15
|
+
delegate :"#{name}_#{attr}", to: single_attribute, allow_nil: true
|
16
|
+
delegate :"#{name}_#{attr}=", to: single_attribute, allow_nil: true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -2,6 +2,9 @@ module CarrierWave
|
|
2
2
|
module Meta
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
+
mattr_accessor :ghostscript_enabled
|
6
|
+
self.ghostscript_enabled = false
|
7
|
+
|
5
8
|
included do
|
6
9
|
include CarrierWave::ModelDelegateAttribute
|
7
10
|
include CarrierWave::MimeTypes
|
@@ -10,7 +13,7 @@ module CarrierWave
|
|
10
13
|
|
11
14
|
after :retrieve_from_cache, :set_content_type
|
12
15
|
after :retrieve_from_cache, :call_store_meta
|
13
|
-
after :retrieve_from_store, :set_content_type
|
16
|
+
after :retrieve_from_store, :set_content_type unless storage.name =~ /Fog/
|
14
17
|
after :retrieve_from_store, :call_store_meta
|
15
18
|
|
16
19
|
model_delegate_attribute :content_type, ''
|
@@ -18,9 +21,10 @@ module CarrierWave
|
|
18
21
|
model_delegate_attribute :image_size, []
|
19
22
|
model_delegate_attribute :width, 0
|
20
23
|
model_delegate_attribute :height, 0
|
24
|
+
model_delegate_attribute :md5sum, ''
|
21
25
|
end
|
22
26
|
|
23
|
-
def store_meta
|
27
|
+
def store_meta(options = {})
|
24
28
|
if self.file.present?
|
25
29
|
dimensions = get_dimensions
|
26
30
|
width, height = dimensions
|
@@ -29,41 +33,71 @@ module CarrierWave
|
|
29
33
|
self.image_size = dimensions
|
30
34
|
self.width = width
|
31
35
|
self.height = height
|
36
|
+
if options[:md5sum]
|
37
|
+
self.md5sum = Digest::MD5.hexdigest(File.read(self.file.path))
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
|
-
def set_content_type(file = nil)
|
36
|
-
set_content_type(true)
|
37
|
-
end
|
38
|
-
|
39
42
|
def image_size_s
|
40
43
|
image_size.join('x')
|
41
44
|
end
|
42
45
|
|
43
46
|
private
|
44
47
|
def call_store_meta(file = nil)
|
45
|
-
# Re-retrieve metadata for a file only if model is not present OR
|
46
|
-
|
48
|
+
# Re-retrieve metadata for a file only if model is not present OR
|
49
|
+
# model is not saved.
|
50
|
+
if model.nil? || (model.respond_to?(:new_record?) && model.new_record?)
|
51
|
+
processor_options = processors.
|
52
|
+
find { |p| p.first == :store_meta }.
|
53
|
+
try(:[], 1)
|
54
|
+
|
55
|
+
store_meta(*processor_options)
|
56
|
+
end
|
47
57
|
end
|
48
58
|
|
49
59
|
def get_dimensions
|
50
60
|
[].tap do |size|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
is_image = file.content_type =~ /image/
|
62
|
+
is_pdf =
|
63
|
+
file.content_type =~ /postscript|pdf/ &&
|
64
|
+
CarrierWave::Meta.ghostscript_enabled
|
65
|
+
|
66
|
+
is_dimensionable = is_image || is_pdf
|
67
|
+
|
68
|
+
manipulate! do |img|
|
69
|
+
if processor?(:rmagick, img) && is_dimensionable
|
70
|
+
size << img.columns
|
71
|
+
size << img.rows
|
72
|
+
elsif processor?(:mini_magick, img) && is_dimensionable
|
73
|
+
size << img['width']
|
74
|
+
size << img['height']
|
75
|
+
elsif processor?(:socrecy, img) && is_image
|
76
|
+
size << img.dimensions[:x].to_i
|
77
|
+
size << img.dimensions[:y].to_i
|
78
|
+
elsif processor?(:vips, img) && is_image
|
79
|
+
size << img.x_size
|
80
|
+
size << img.y_size
|
81
|
+
else
|
82
|
+
raise "Unsupported file type/image processor (use RMagick, MiniMagick, ImageSorcery, VIPS)"
|
63
83
|
end
|
84
|
+
img
|
64
85
|
end
|
65
86
|
end
|
66
87
|
rescue CarrierWave::ProcessingError
|
67
88
|
end
|
89
|
+
|
90
|
+
def processor?(processor, img)
|
91
|
+
processor = PROCESSORS[processor]
|
92
|
+
processor_class = processor.constantize rescue nil
|
93
|
+
processor_class.present? && img.is_a?(processor_class)
|
94
|
+
end
|
95
|
+
|
96
|
+
PROCESSORS = {
|
97
|
+
rmagick: 'Magick::Image',
|
98
|
+
mini_magick: 'MiniMagick::Image',
|
99
|
+
socrecy: 'ImageSorcery',
|
100
|
+
vips: 'VIPS::Image'
|
101
|
+
}
|
68
102
|
end
|
69
|
-
end
|
103
|
+
end
|