vcs_ruby 1.1.8 → 1.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/lib/contact_sheet.rb CHANGED
@@ -1,366 +1,380 @@
1
- #
2
- # Contact Sheet Composited from the Thumbnails
3
- #
4
-
5
- require 'fileutils'
6
- require 'tmpdir'
7
- require 'yaml'
8
-
9
- require 'vcs'
10
-
11
- module VCSRuby
12
- class ContactSheet
13
- attr_accessor :signature, :title, :highlight
14
- attr_accessor :softshadow, :timestamp, :polaroid
15
- attr_reader :thumbnail_width, :thumbnail_height, :format
16
- attr_reader :length, :from, :to
17
-
18
- def initialize video, capturer
19
- @video = video
20
- @capturer = capturer
21
- @signature = "Created by Video Contact Sheet Ruby"
22
- initialize_filename
23
-
24
- if Configuration.instance.verbose?
25
- puts "Processing #{File.basename(video.full_path)}..."
26
- end
27
-
28
- return unless @video.valid?
29
-
30
- detect_video_properties
31
-
32
- @from = TimeIndex.new 0
33
- @to = @video.info.duration
34
-
35
- @timestamp = Configuration.instance.timestamp
36
- @softshadow = Configuration.instance.softshadow
37
- @polaroid = Configuration.instance.polaroid
38
-
39
- @tempdir = Dir.mktmpdir
40
-
41
- ObjectSpace.define_finalizer(self, self.class.finalize(@tempdir) )
42
- initialize_geometry(Configuration.instance.rows, Configuration.instance.columns, Configuration.instance.interval)
43
-
44
- end
45
-
46
- def initialize_filename override = nil
47
- @out_path = File.dirname(@video.full_path)
48
- @out_filename = File.basename(override || @video.full_path,'.*')
49
-
50
- extension = override ? File.extname(override) : ''
51
- @format = extension.length > 0 ? extension : '.png'
52
- end
53
-
54
- def initialize_geometry(rows, columns, interval)
55
- @has_interval = !!interval
56
- @rows = rows
57
- @columns = columns
58
- @interval = interval
59
- end
60
-
61
- def filename
62
- "#{@out_filename}#{@format}"
63
- end
64
-
65
- def format= format
66
- @format = ".#{format.to_s}"
67
- end
68
-
69
- def full_path
70
- File.join(@out_path, filename)
71
- end
72
-
73
- def rows
74
- @rows
75
- end
76
-
77
- def columns
78
- @columns
79
- end
80
-
81
- def interval
82
- @interval || (@to - @from) / (number_of_caps)
83
- end
84
-
85
- def number_of_caps
86
- if @has_interval
87
- (@to - @from) / @interval
88
- else
89
- if @rows && @columns
90
- @rows * @columns
91
- else
92
- raise "you need at least 2 parameters from columns, rows and interval"
93
- end
94
- end
95
- end
96
-
97
- def thumbnail_width= width
98
- @thumbnail_height = (width.to_f / @thumbnail_width * thumbnail_height).to_i
99
- @thumbnail_width = width
100
- end
101
-
102
- def thumbnail_height= height
103
- @thumbnail_width = (height.to_f / @thumbnail_height * thumbnail_width).to_i
104
- @thumbnail_height = height
105
- end
106
-
107
- def from= time
108
- if (TimeIndex.new(0) < time) && (time < to) && (time < @length)
109
- @from = time
110
- else
111
- raise "Invalid From Time"
112
- end
113
- end
114
-
115
- def to= time
116
- if (TimeIndex.new(0) < time) && (from < time) && (time < @length)
117
- @to = time
118
- else
119
- raise "Invalid To Time"
120
- end
121
- end
122
-
123
- def build
124
- if (@video.info.duration.total_seconds < 1.0)
125
- puts "Video is shorter than 1 sec"
126
- else
127
- initialize_filters
128
- initialize_thumbnails
129
- capture_thumbnails
130
-
131
- puts "Composing standard contact sheet..." unless Configuration.instance.quiet?
132
- montage = splice_montage(montage_thumbs)
133
-
134
- image = MiniMagick::Image.open(montage)
135
-
136
- puts "Adding header and footer..." unless Configuration.instance.quiet?
137
- final = add_header_and_footer image
138
-
139
- puts "Done. Output wrote to '#{filename}'" unless Configuration.instance.quiet?
140
- FileUtils.mv(final, full_path)
141
- end
142
- end
143
-
144
- private
145
- def self.finalize(tempdir)
146
- proc do
147
- puts "Cleaning up..." unless Configuration.instance.quiet?
148
- FileUtils.rm_r tempdir
149
- end
150
- end
151
-
152
- def initialize_filters
153
- @filters = []
154
- @filters << :resize_filter
155
- @filters << :softshadow_filter if softshadow
156
- @filters << :timestamp_filter if timestamp
157
- @filters << :polaroid_filter if polaroid
158
- end
159
-
160
- def initialize_thumbnails
161
- @thumbnails = []
162
- time = @from + (interval / 2)
163
- (1..number_of_caps).each do |i|
164
- thumb = Frame.new @video, @capturer, time
165
- time = time + interval
166
- thumb.width = thumbnail_width
167
- thumb.height = thumbnail_height
168
- thumb.filename = File::join(@tempdir, "th#{"%03d" % i}.#{@capturer.format_extension}")
169
- thumb.filters.push(*@filters)
170
-
171
- @thumbnails << thumb
172
- end
173
- end
174
-
175
- def capture_thumbnails
176
- puts "Capturing in range [#{from}..#{to}]. Total length: #{@length}" unless Configuration.instance.quiet?
177
-
178
- @thumbnails.each_with_index do |thumbnail, i|
179
- puts "Generating capture ##{i + 1}/#{number_of_caps} #{thumbnail.time}..." unless Configuration.instance.quiet?
180
- if Configuration.instance.blank_evasion?
181
- thumbnail.capture_and_evade interval
182
- else
183
- thumbnail.capture
184
- end
185
- thumbnail.apply_filters
186
- end
187
- end
188
-
189
- def detect_video_properties
190
- detect_length
191
- detect_dimensions
192
- end
193
-
194
- def detect_length
195
- @length = @video.info.duration
196
-
197
- @from = TimeIndex.new 0.0
198
- @to = @length
199
- end
200
-
201
- def detect_dimensions
202
- @thumbnail_width = @video.video.width
203
- @thumbnail_height = @video.video.height
204
- end
205
-
206
- def montage_thumbs
207
- file_path = File::join(@tempdir, 'montage.png')
208
- MiniMagick::Tool::Montage.new do |montage|
209
- montage.background Configuration.instance.contact_background
210
- @thumbnails.each do |thumbnail|
211
- montage << thumbnail.filename
212
- end
213
- montage.geometry "+#{Configuration.instance.padding}+#{Configuration.instance.padding}"
214
- # rows or columns can be nil (auto fit)
215
- montage.tile "#{@columns}x#{@rows}"
216
- montage << file_path
217
- end
218
- return file_path
219
- end
220
-
221
- def splice_montage montage_path
222
- if softshadow
223
- left = Configuration.instance.padding + 3
224
- top = Configuration.instance.padding + 5
225
- bottom = right = Configuration.instance.padding
226
- else
227
- left = right = top = bottom = Configuration.instance.padding
228
- end
229
-
230
-
231
- file_path = File::join(@tempdir, 'spliced.png')
232
- MiniMagick::Tool::Convert.new do |convert|
233
- convert << montage_path
234
- convert.background Configuration.instance.contact_background
235
-
236
- convert.splice "#{left}x#{top}"
237
- convert.gravity 'SouthEast'
238
- convert.splice "#{right}x#{bottom}"
239
-
240
- convert << file_path
241
- end
242
- file_path
243
- end
244
-
245
- def create_title montage
246
- file_path = File::join(@tempdir, 'title.png')
247
- MiniMagick::Tool::Convert.new do |convert|
248
- convert.stack do |ul|
249
- ul.size "#{montage.width}x#{Configuration.instance.title_font.line_height}"
250
- ul.xc Configuration.instance.title_background
251
- if Configuration.instance.title_font.exists?
252
- ul.font Configuration.instance.title_font.path
253
- end
254
- ul.pointsize Configuration.instance.title_font.size
255
- ul.background Configuration.instance.title_background
256
- ul.fill Configuration.instance.title_color
257
- ul.gravity 'Center'
258
- ul.annotate(0, @title)
259
- end
260
- convert.flatten
261
- convert << file_path
262
- end
263
- return file_path
264
- end
265
-
266
- def create_highlight montage
267
- puts "Generating highlight..."
268
- thumb = Frame.new @video, @highlight
269
-
270
- thumb.width = thumbnail_width
271
- thumb.height = thumbnail_height
272
- thumb.filename = File::join(@tempdir, "highlight_thumb.png")
273
- thumb.capture
274
- thumb.apply_filters
275
-
276
- file_path = File::join(@tempdir, "highlight.png")
277
- MiniMagick::Tool::Convert.new do |convert|
278
- convert.stack do |a|
279
- a.size "#{montage.width}x#{thumbnail_height+20}"
280
- a.xc Configuration.instance.highlight_background
281
- a.gravity 'Center'
282
- a << thumb.filename
283
- a.composite
284
- end
285
- convert.stack do |a|
286
- a.size "#{montage.width}x1"
287
- a.xc 'Black'
288
- end
289
- convert.append
290
- convert << file_path
291
- end
292
-
293
- file_path
294
- end
295
-
296
- def add_header_and_footer montage
297
- file_path = File::join(@tempdir, filename)
298
- header_height = Configuration.instance.header_font.line_height * 3
299
- signature_height = Configuration.instance.signature_font.line_height + 8
300
- MiniMagick::Tool::Convert.new do |convert|
301
- convert.stack do |a|
302
- a.size "#{montage.width - 18}x1"
303
- a.xc Configuration.instance.header_background
304
- a.size.+
305
- if Configuration.instance.header_font.exists?
306
- a.font Configuration.instance.header_font.path
307
- end
308
- a.pointsize Configuration.instance.header_font.size
309
- a.background Configuration.instance.header_background
310
- a.fill Configuration.instance.header_color
311
- a.stack do |b|
312
- b.gravity 'West'
313
- b.stack do |c|
314
- c.label 'Filename: '
315
- if Configuration.instance.header_font.exists?
316
- c.font Configuration.instance.header_font.path
317
- end
318
- c.label File.basename(@video.full_path)
319
- c.append.+
320
- end
321
- if Configuration.instance.header_font.exists?
322
- b.font Configuration.instance.header_font.path
323
- end
324
- b.label "File size: #{Tools.to_human_size(File.size(@video.full_path))}"
325
- b.label "Length: #{@length.to_timestamp}"
326
- b.append
327
- b.crop "#{montage.width}x#{header_height}+0+0"
328
- end
329
- a.append
330
- a.stack do |b|
331
- b.size "#{montage.width}x#{header_height}"
332
- b.gravity 'East'
333
- b.fill Configuration.instance.header_color
334
- b.annotate '+0-1'
335
- b << "Dimensions: #{@video.video.width}x#{@video.video.height}\nFormat: #{@video.video.codec(true)} / #{@video.audio ? @video.audio.codec(true) : 'no audio'}\nFPS: #{"%.02f" % @video.video.frame_rate.to_f}"
336
- end
337
- a.bordercolor Configuration.instance.header_background
338
- a.border 9
339
- end
340
- convert << create_title(montage) if @title
341
- convert << create_highlight(montage) if @highlight
342
- convert << montage.path
343
- convert.append
344
- if @signature && @signature.length > 0
345
- convert.stack do |a|
346
- a.size "#{montage.width}x#{signature_height}"
347
- a.gravity 'Center'
348
- a.xc Configuration.instance.signature_background
349
- if Configuration.instance.signature_font.exists?
350
- a.font Configuration.instance.signature_font.path
351
- end
352
- a.pointsize Configuration.instance.signature_font.size
353
- a.fill Configuration.instance.signature_color
354
- a.annotate(0, @signature)
355
- end
356
- convert.append
357
- end
358
- if @format == :jpg || @format == :jpeg
359
- convert.quality(Configuration.instance.quality)
360
- end
361
- convert << file_path
362
- end
363
- file_path
364
- end
365
- end
366
- end
1
+ #
2
+ # Contact Sheet Composited from the Thumbnails
3
+ #
4
+
5
+ require 'fileutils'
6
+ require 'tmpdir'
7
+ require 'yaml'
8
+
9
+ require 'vcs'
10
+
11
+ module VCSRuby
12
+ class ContactSheet
13
+ attr_accessor :signature, :title, :highlight
14
+ attr_accessor :softshadow, :timestamp, :polaroid
15
+ attr_reader :thumbnail_width, :thumbnail_height, :aspect_ratio, :format
16
+ attr_reader :length, :from, :to
17
+
18
+ def initialize video, capturer
19
+ @video = video
20
+ @capturer = capturer
21
+ @signature = "Created by Video Contact Sheet Ruby"
22
+ initialize_filename
23
+
24
+ if Configuration.instance.verbose?
25
+ puts "Processing #{File.basename(video.full_path)}..."
26
+ end
27
+
28
+ return unless @video.valid?
29
+
30
+ detect_video_properties
31
+
32
+ @from = TimeIndex.new 0
33
+ @to = @video.info.duration
34
+
35
+ @timestamp = Configuration.instance.timestamp
36
+ @softshadow = Configuration.instance.softshadow
37
+ @polaroid = Configuration.instance.polaroid
38
+
39
+ @tempdir = Dir.mktmpdir
40
+
41
+ ObjectSpace.define_finalizer(self, self.class.finalize(@tempdir) )
42
+ initialize_geometry(Configuration.instance.rows, Configuration.instance.columns, Configuration.instance.interval)
43
+
44
+ end
45
+
46
+ def initialize_filename override = nil
47
+ @out_path = File.dirname(@video.full_path)
48
+ @out_filename = File.basename(override || @video.full_path,'.*')
49
+
50
+ extension = override ? File.extname(override) : ''
51
+ @format = extension.length > 0 ? extension : '.png'
52
+ end
53
+
54
+ def initialize_geometry(rows, columns, interval)
55
+ @has_interval = !!interval
56
+ @rows = rows
57
+ @columns = columns
58
+ @interval = interval
59
+ end
60
+
61
+ def filename
62
+ "#{@out_filename}#{@format}"
63
+ end
64
+
65
+ def format= format
66
+ @format = ".#{format.to_s}"
67
+ end
68
+
69
+ def full_path
70
+ File.join(@out_path, filename)
71
+ end
72
+
73
+ def rows
74
+ @rows
75
+ end
76
+
77
+ def columns
78
+ @columns
79
+ end
80
+
81
+ def interval
82
+ @interval || (@to - @from) / (number_of_caps)
83
+ end
84
+
85
+ def number_of_caps
86
+ if @has_interval
87
+ (@to - @from) / @interval
88
+ else
89
+ if @rows && @columns
90
+ @rows * @columns
91
+ else
92
+ raise "you need at least 2 parameters from columns, rows and interval"
93
+ end
94
+ end
95
+ end
96
+
97
+ def thumbnail_width= width
98
+ @thumbnail_height = (width.to_f / @thumbnail_width * thumbnail_height).to_i
99
+ @thumbnail_width = width
100
+ end
101
+
102
+ def thumbnail_height= height
103
+ @thumbnail_width = (height.to_f / @thumbnail_height * thumbnail_width).to_i
104
+ @thumbnail_height = height
105
+ end
106
+
107
+ def aspect_ratio= aspect_ratio
108
+ @thumbnail_width = (@thumbnail_height * aspect_ratio).to_i
109
+ @aspect_ratio = aspect_ratio
110
+ end
111
+
112
+ def from= time
113
+ if (TimeIndex.new(0) < time) && (time < to) && (time < @length)
114
+ @from = time
115
+ else
116
+ raise "Invalid From Time"
117
+ end
118
+ end
119
+
120
+ def to= time
121
+ if (TimeIndex.new(0) < time) && (from < time) && (time < @length)
122
+ @to = time
123
+ else
124
+ raise "Invalid To Time"
125
+ end
126
+ end
127
+
128
+ def build
129
+ if (@video.info.duration.total_seconds < 1.0)
130
+ puts "Video is shorter than 1 sec"
131
+ else
132
+ initialize_filters
133
+ initialize_thumbnails
134
+ capture_thumbnails
135
+
136
+ puts "Composing standard contact sheet..." unless Configuration.instance.quiet?
137
+ montage = splice_montage(montage_thumbs)
138
+
139
+ image = MiniMagick::Image.open(montage)
140
+
141
+ puts "Adding header and footer..." unless Configuration.instance.quiet?
142
+ final = add_header_and_footer image
143
+
144
+ puts "Done. Output wrote to '#{filename}'" unless Configuration.instance.quiet?
145
+ FileUtils.mv(final, full_path)
146
+ end
147
+ end
148
+
149
+ private
150
+ def self.finalize(tempdir)
151
+ proc do
152
+ puts "Cleaning up..." unless Configuration.instance.quiet?
153
+ FileUtils.rm_r tempdir
154
+ end
155
+ end
156
+
157
+ def initialize_filters
158
+ @filters = []
159
+ @filters << :resize_filter
160
+ @filters << :softshadow_filter if softshadow
161
+ @filters << :timestamp_filter if timestamp
162
+ @filters << :polaroid_filter if polaroid
163
+ end
164
+
165
+ def initialize_thumbnails
166
+ @thumbnails = []
167
+ time = @from + (interval / 2)
168
+ (1..number_of_caps).each do |i|
169
+ thumb = Frame.new @video, @capturer, time
170
+ time = time + interval
171
+ thumb.width = thumbnail_width
172
+ thumb.height = thumbnail_height
173
+ thumb.filename = File::join(@tempdir, "th#{"%03d" % i}.#{@capturer.format_extension}")
174
+ thumb.filters.push(*@filters)
175
+
176
+ @thumbnails << thumb
177
+ end
178
+ end
179
+
180
+ def capture_thumbnails
181
+ puts "Capturing in range [#{from}..#{to}]. Total length: #{@length}" unless Configuration.instance.quiet?
182
+
183
+ @thumbnails.each_with_index do |thumbnail, i|
184
+ puts "Generating capture ##{i + 1}/#{number_of_caps} #{thumbnail.time}..." unless Configuration.instance.quiet?
185
+ if Configuration.instance.blank_evasion?
186
+ thumbnail.capture_and_evade interval
187
+ else
188
+ thumbnail.capture
189
+ end
190
+ thumbnail.apply_filters
191
+ end
192
+ end
193
+
194
+ def detect_video_properties
195
+ detect_length
196
+ detect_dimensions
197
+ end
198
+
199
+ def detect_length
200
+ @length = @video.info.duration
201
+
202
+ @from = TimeIndex.new 0.0
203
+ @to = @length
204
+ end
205
+
206
+ def detect_dimensions
207
+ @thumbnail_width = @video.video.width
208
+ @thumbnail_height = @video.video.height
209
+
210
+ @aspect_ratio = @video.video.aspect_ratio
211
+
212
+ if @aspect_ratio == 0
213
+ @aspect_ratio = Rational(@video.video.width, @video.video.height)
214
+ else
215
+ #recalculate width, for PAR 1:1 this should be the same as before
216
+ @thumbnail_width = (@aspect_ratio * @video.video.height).to_i
217
+ end
218
+ end
219
+
220
+ def montage_thumbs
221
+ file_path = File::join(@tempdir, 'montage.png')
222
+ MiniMagick::Tool::Montage.new do |montage|
223
+ montage.background Configuration.instance.contact_background
224
+ @thumbnails.each do |thumbnail|
225
+ montage << thumbnail.filename
226
+ end
227
+ montage.geometry "+#{Configuration.instance.padding}+#{Configuration.instance.padding}"
228
+ # rows or columns can be nil (auto fit)
229
+ montage.tile "#{@columns}x#{@rows}"
230
+ montage << file_path
231
+ end
232
+ return file_path
233
+ end
234
+
235
+ def splice_montage montage_path
236
+ if softshadow
237
+ left = Configuration.instance.padding + 3
238
+ top = Configuration.instance.padding + 5
239
+ bottom = right = Configuration.instance.padding
240
+ else
241
+ left = right = top = bottom = Configuration.instance.padding
242
+ end
243
+
244
+
245
+ file_path = File::join(@tempdir, 'spliced.png')
246
+ MiniMagick::Tool::Convert.new do |convert|
247
+ convert << montage_path
248
+ convert.background Configuration.instance.contact_background
249
+
250
+ convert.splice "#{left}x#{top}"
251
+ convert.gravity 'SouthEast'
252
+ convert.splice "#{right}x#{bottom}"
253
+
254
+ convert << file_path
255
+ end
256
+ file_path
257
+ end
258
+
259
+ def create_title montage
260
+ file_path = File::join(@tempdir, 'title.png')
261
+ MiniMagick::Tool::Convert.new do |convert|
262
+ convert.stack do |ul|
263
+ ul.size "#{montage.width}x#{Configuration.instance.title_font.line_height}"
264
+ ul.xc Configuration.instance.title_background
265
+ if Configuration.instance.title_font.exists?
266
+ ul.font Configuration.instance.title_font.path
267
+ end
268
+ ul.pointsize Configuration.instance.title_font.size
269
+ ul.background Configuration.instance.title_background
270
+ ul.fill Configuration.instance.title_color
271
+ ul.gravity 'Center'
272
+ ul.annotate(0, @title)
273
+ end
274
+ convert.flatten
275
+ convert << file_path
276
+ end
277
+ return file_path
278
+ end
279
+
280
+ def create_highlight montage
281
+ puts "Generating highlight..."
282
+ thumb = Frame.new @video, @highlight
283
+
284
+ thumb.width = thumbnail_width
285
+ thumb.height = thumbnail_height
286
+ thumb.filename = File::join(@tempdir, "highlight_thumb.png")
287
+ thumb.capture
288
+ thumb.apply_filters
289
+
290
+ file_path = File::join(@tempdir, "highlight.png")
291
+ MiniMagick::Tool::Convert.new do |convert|
292
+ convert.stack do |a|
293
+ a.size "#{montage.width}x#{thumbnail_height+20}"
294
+ a.xc Configuration.instance.highlight_background
295
+ a.gravity 'Center'
296
+ a << thumb.filename
297
+ a.composite
298
+ end
299
+ convert.stack do |a|
300
+ a.size "#{montage.width}x1"
301
+ a.xc 'Black'
302
+ end
303
+ convert.append
304
+ convert << file_path
305
+ end
306
+
307
+ file_path
308
+ end
309
+
310
+ def add_header_and_footer montage
311
+ file_path = File::join(@tempdir, filename)
312
+ header_height = Configuration.instance.header_font.line_height * 3
313
+ signature_height = Configuration.instance.signature_font.line_height + 8
314
+ MiniMagick::Tool::Convert.new do |convert|
315
+ convert.stack do |a|
316
+ a.size "#{montage.width - 18}x1"
317
+ a.xc Configuration.instance.header_background
318
+ a.size.+
319
+ if Configuration.instance.header_font.exists?
320
+ a.font Configuration.instance.header_font.path
321
+ end
322
+ a.pointsize Configuration.instance.header_font.size
323
+ a.background Configuration.instance.header_background
324
+ a.fill Configuration.instance.header_color
325
+ a.stack do |b|
326
+ b.gravity 'West'
327
+ b.stack do |c|
328
+ c.label 'Filename: '
329
+ if Configuration.instance.header_font.exists?
330
+ c.font Configuration.instance.header_font.path
331
+ end
332
+ c.label File.basename(@video.full_path)
333
+ c.append.+
334
+ end
335
+ if Configuration.instance.header_font.exists?
336
+ b.font Configuration.instance.header_font.path
337
+ end
338
+ b.label "File size: #{Tools.to_human_size(File.size(@video.full_path))}"
339
+ b.label "Length: #{@length.to_timestamp}"
340
+ b.append
341
+ b.crop "#{montage.width}x#{header_height}+0+0"
342
+ end
343
+ a.append
344
+ a.stack do |b|
345
+ b.size "#{montage.width}x#{header_height}"
346
+ b.gravity 'East'
347
+ b.fill Configuration.instance.header_color
348
+ b.annotate '+0-1'
349
+ b << "Dimensions: #{@video.video.width}x#{@video.video.height}\nFormat: #{@video.video.codec(true)} / #{@video.audio ? @video.audio.codec(true) : 'no audio'}\nFPS: #{"%.02f" % @video.video.frame_rate.to_f}"
350
+ end
351
+ a.bordercolor Configuration.instance.header_background
352
+ a.border 9
353
+ end
354
+ convert << create_title(montage) if @title
355
+ convert << create_highlight(montage) if @highlight
356
+ convert << montage.path
357
+ convert.append
358
+ if @signature && @signature.length > 0
359
+ convert.stack do |a|
360
+ a.size "#{montage.width}x#{signature_height}"
361
+ a.gravity 'Center'
362
+ a.xc Configuration.instance.signature_background
363
+ if Configuration.instance.signature_font.exists?
364
+ a.font Configuration.instance.signature_font.path
365
+ end
366
+ a.pointsize Configuration.instance.signature_font.size
367
+ a.fill Configuration.instance.signature_color
368
+ a.annotate(0, @signature)
369
+ end
370
+ convert.append
371
+ end
372
+ if @format == :jpg || @format == :jpeg
373
+ convert.quality(Configuration.instance.quality)
374
+ end
375
+ convert << file_path
376
+ end
377
+ file_path
378
+ end
379
+ end
380
+ end