phantom_svg 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +11 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +16 -0
  7. data/Guardfile +12 -0
  8. data/LICENSE +165 -0
  9. data/README.md +6 -0
  10. data/lib/phantom/frame.rb +71 -0
  11. data/lib/phantom/parser/abstract_animation_reader.rb +117 -0
  12. data/lib/phantom/parser/json_animation_reader.rb +42 -0
  13. data/lib/phantom/parser/raster.rb +115 -0
  14. data/lib/phantom/parser/svg_reader.rb +131 -0
  15. data/lib/phantom/parser/svg_writer.rb +163 -0
  16. data/lib/phantom/parser/xml_animation_reader.rb +32 -0
  17. data/lib/phantom/svg.rb +139 -0
  18. data/lib/phantom_svg.rb +1 -0
  19. data/phantom_svg.gemspec +25 -0
  20. data/spec/images/apngasm.png +0 -0
  21. data/spec/images/ninja.svg +63 -0
  22. data/spec/images/stuck_out_tongue/0.svg +103 -0
  23. data/spec/images/stuck_out_tongue/1.svg +103 -0
  24. data/spec/images/stuck_out_tongue/10.svg +103 -0
  25. data/spec/images/stuck_out_tongue/11.svg +103 -0
  26. data/spec/images/stuck_out_tongue/2.svg +103 -0
  27. data/spec/images/stuck_out_tongue/3.svg +103 -0
  28. data/spec/images/stuck_out_tongue/4.svg +103 -0
  29. data/spec/images/stuck_out_tongue/5.svg +103 -0
  30. data/spec/images/stuck_out_tongue/6.svg +103 -0
  31. data/spec/images/stuck_out_tongue/7.svg +103 -0
  32. data/spec/images/stuck_out_tongue/8.svg +103 -0
  33. data/spec/images/stuck_out_tongue/9.svg +103 -0
  34. data/spec/images/stuck_out_tongue/loops_test.json +9 -0
  35. data/spec/images/stuck_out_tongue/loops_test.xml +4 -0
  36. data/spec/images/stuck_out_tongue/skip_first_test.json +10 -0
  37. data/spec/images/stuck_out_tongue/skip_first_test.xml +5 -0
  38. data/spec/images/stuck_out_tongue/test1.json +20 -0
  39. data/spec/images/stuck_out_tongue/test1.xml +15 -0
  40. data/spec/images/stuck_out_tongue/test2.json +13 -0
  41. data/spec/images/stuck_out_tongue/test2.xml +4 -0
  42. data/spec/phantom/svg_spec.rb +421 -0
  43. data/spec/spec_helper.rb +81 -0
  44. metadata +170 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "stuck_out_tongue",
3
+ "loops": 3,
4
+ "skip_first": false,
5
+ "default_delay": "50/1000",
6
+ "frames": [
7
+ "*.svg"
8
+ ]
9
+ }
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <animation name="myanimation" loops="3" skip_first="false" default_delay="50/1000">
3
+ <frame src="*.svg" />
4
+ </animation>
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "stuck_out_tongue",
3
+ "loops": 0,
4
+ "skip_first": true,
5
+ "default_delay": "50/1000",
6
+ "frames": [
7
+ "../ninja.svg",
8
+ "*.svg"
9
+ ]
10
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <animation name="myanimation" loops="0" skip_first="true" default_delay="50/1000">
3
+ <frame src="../ninja.svg" />
4
+ <frame src="*.svg" />
5
+ </animation>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "stuck_out_tongue",
3
+ "loops": 0,
4
+ "skip_first": false,
5
+ "default_delay": "100/1000",
6
+ "frames": [
7
+ {"0": 1000},
8
+ "1",
9
+ "2",
10
+ "3",
11
+ "4",
12
+ "5",
13
+ "6",
14
+ "7",
15
+ "8",
16
+ "9",
17
+ "10",
18
+ {"11": "10/5"}
19
+ ]
20
+ }
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <animation name="myanimation" loops="0" skip_first="false" default_delay="100/1000">
3
+ <frame src="0" delay="1000" />
4
+ <frame src="1" />
5
+ <frame src="2" />
6
+ <frame src="3" />
7
+ <frame src="4" />
8
+ <frame src="5" />
9
+ <frame src="6" />
10
+ <frame src="7" />
11
+ <frame src="8" />
12
+ <frame src="9" />
13
+ <frame src="10" />
14
+ <frame src="11" delay="4/2" />
15
+ </animation>
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "stuck_out_tongue",
3
+ "loops": 0,
4
+ "skip_first": false,
5
+ "default_delay": "200/1000",
6
+ "frames": [
7
+ "*.svg"
8
+ ],
9
+ "delays": [
10
+ "2/1",
11
+ 3000
12
+ ]
13
+ }
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <animation name="myanimation" loops="0" skip_first="false" default_delay="200/1000">
3
+ <frame src="*.svg" />
4
+ </animation>
@@ -0,0 +1,421 @@
1
+ require 'phantom/svg'
2
+
3
+ # Set Current directory.
4
+ Dir.chdir(SPEC_ROOT_DIR)
5
+
6
+ describe Phantom::SVG::Base do
7
+
8
+ describe 'loading a non-animated svg' do
9
+ before(:all) do
10
+ @image_name = 'ninja'
11
+ @source = "#{SPEC_SOURCE_DIR}/#{@image_name}.svg"
12
+ end
13
+
14
+ before do
15
+ @loader = Phantom::SVG::Base.new(@source)
16
+ end
17
+
18
+ it 'frames vector has a size of 1.' do
19
+ expect(@loader.frames.size).to eq(1)
20
+ end
21
+
22
+ it 'frame duration equals 0.1.' do
23
+ expect(@loader.frames[0].duration).to eq(0.1)
24
+ end
25
+
26
+ it 'frame surfaces is not nil.' do
27
+ expect(@loader.frames[0].surfaces).not_to be_nil
28
+ end
29
+
30
+ it 'frame surfaces is not empty.' do
31
+ expect(@loader.frames[0].surfaces).not_to be_empty
32
+ end
33
+
34
+ it 'frame width equal to \'64px\'.' do
35
+ expect(@loader.frames[0].width).to eq('64px')
36
+ end
37
+
38
+ it 'frame height equal to \'64px\'.' do
39
+ expect(@loader.frames[0].height).to eq('64px')
40
+ end
41
+
42
+ it 'frame namespaces is not empty.' do
43
+ expect(@loader.frames[0].namespaces).not_to be_empty
44
+ end
45
+
46
+ after do
47
+ # nop
48
+ end
49
+ end
50
+
51
+ describe 'creating an animated SVG' do
52
+ before(:all) do
53
+ @image_name = 'stuck_out_tongue'
54
+ @source = "#{SPEC_SOURCE_DIR}/#{@image_name}/*.svg"
55
+ @destination_dir = "#{SPEC_TEMP_DIR}/#{@image_name}"
56
+ @destination = "#{@destination_dir}/#{@image_name}.svg"
57
+ FileUtils.mkdir_p(@destination_dir)
58
+ end
59
+
60
+ before do
61
+ @loader = Phantom::SVG::Base.new
62
+ end
63
+
64
+ it 'saves a non-zero-byte file.' do
65
+ @loader.add_frame_from_file(@source)
66
+ write_size = @loader.save_svg(@destination)
67
+ expect(write_size).not_to eq(0)
68
+ end
69
+
70
+ it 'is an animation with a frame vector size of 12.' do
71
+ @loader.add_frame_from_file(@destination)
72
+ expect(@loader.frames.size).to eq(12)
73
+ end
74
+
75
+ it ' has a frame surfaces length not equal to 1.' do
76
+ @loader.add_frame_from_file(@destination)
77
+
78
+ range = 0..(@loader.frames.length - 1)
79
+ range.each do |i|
80
+ frame = @loader.frames[i]
81
+ expect(frame.surfaces.to_s.length).not_to eq(1)
82
+
83
+ @loader.save_svg_frame("#{@destination_dir}/#{i}.svg", frame)
84
+ end
85
+ end
86
+
87
+ after do
88
+ # nop
89
+ end
90
+ end
91
+
92
+ describe 'creating an animated SVG file from a JSON animation spec file' do
93
+ before(:all) do
94
+ @image_name = 'stuck_out_tongue'
95
+ @source_dir = "#{SPEC_SOURCE_DIR}/#{@image_name}"
96
+ @destination_dir = SPEC_TEMP_DIR
97
+ end
98
+
99
+ before do
100
+ @loader = Phantom::SVG::Base.new
101
+ end
102
+
103
+ it 'loads frames from "test1.json".' do
104
+ test_name = 'test1'
105
+ @loader.add_frame_from_file("#{@source_dir}/#{test_name}.json")
106
+ expect(@loader.frames.size).to eq(12)
107
+ @loader.frames.each do |frame|
108
+ expect(frame.duration.instance_of?(Float)).to eq(true)
109
+ expect(frame.width).to eq('64px')
110
+ expect(frame.height).to eq('64px')
111
+ expect(frame.surfaces).not_to be_nil
112
+ expect(frame.surfaces).not_to be_empty
113
+ expect(frame.surfaces.to_s.length).not_to eq(1)
114
+ expect(frame.namespaces).not_to be_empty
115
+ end
116
+ write_size = @loader.save_svg("#{@destination_dir}/json_#{test_name}.svg")
117
+ expect(write_size).not_to eq(0)
118
+ end
119
+
120
+ it 'loads frames from "test2.json".' do
121
+ test_name = 'test2'
122
+ @loader.add_frame_from_file("#{@source_dir}/#{test_name}.json")
123
+ expect(@loader.frames.size).to eq(12)
124
+ @loader.frames.each do |frame|
125
+ expect(frame.duration.instance_of?(Float)).to eq(true)
126
+ expect(frame.width).to eq('64px')
127
+ expect(frame.height).to eq('64px')
128
+ expect(frame.surfaces).not_to be_nil
129
+ expect(frame.surfaces).not_to be_empty
130
+ expect(frame.surfaces.to_s.length).not_to eq(1)
131
+ expect(frame.namespaces).not_to be_empty
132
+ end
133
+ write_size = @loader.save_svg("#{@destination_dir}/json_#{test_name}.svg")
134
+ expect(write_size).not_to eq(0)
135
+ end
136
+ end
137
+
138
+ describe 'creating an animated SVG from file from an XML animation spec' do
139
+ before(:all) do
140
+ @image_name = 'stuck_out_tongue'
141
+ @source_dir = "#{SPEC_SOURCE_DIR}/#{@image_name}"
142
+ @destination_dir = SPEC_TEMP_DIR
143
+ end
144
+
145
+ before do
146
+ @loader = Phantom::SVG::Base.new
147
+ end
148
+
149
+ it 'loads frames from "test1.xml".' do
150
+ test_name = 'test1'
151
+ @loader.add_frame_from_file("#{@source_dir}/#{test_name}.xml")
152
+ expect(@loader.frames.size).to eq(12)
153
+ @loader.frames.each do |frame|
154
+ expect(frame.duration.instance_of?(Float)).to eq(true)
155
+ expect(frame.width).to eq('64px')
156
+ expect(frame.height).to eq('64px')
157
+ expect(frame.surfaces).not_to be_nil
158
+ expect(frame.surfaces).not_to be_empty
159
+ expect(frame.surfaces.to_s.length).not_to eq(1)
160
+ expect(frame.namespaces).not_to be_empty
161
+ end
162
+ write_size = @loader.save_svg("#{@destination_dir}/xml_#{test_name}.svg")
163
+ expect(write_size).not_to eq(0)
164
+ end
165
+
166
+ it 'load frames from "test2.xml".' do
167
+ test_name = 'test2'
168
+ @loader.add_frame_from_file("#{@source_dir}/#{test_name}.xml")
169
+ expect(@loader.frames.size).to eq(12)
170
+ @loader.frames.each do |frame|
171
+ expect(frame.duration.instance_of?(Float)).to eq(true)
172
+ expect(frame.width).to eq('64px')
173
+ expect(frame.height).to eq('64px')
174
+ expect(frame.surfaces).not_to be_nil
175
+ expect(frame.surfaces).not_to be_empty
176
+ expect(frame.surfaces.to_s.length).not_to eq(1)
177
+ expect(frame.namespaces).not_to be_empty
178
+ end
179
+ write_size = @loader.save_svg("#{@destination_dir}/xml_#{test_name}.svg")
180
+ expect(write_size).not_to eq(0)
181
+ end
182
+ end
183
+
184
+ describe 'converting a keyframe animated SVG to APNG' do
185
+ before(:all) do
186
+ @image_name = 'stuck_out_tongue'
187
+ @source = "#{SPEC_SOURCE_DIR}/#{@image_name}/*.svg"
188
+ @destination = "#{SPEC_TEMP_DIR}/svg2apng.png"
189
+ end
190
+
191
+ before do
192
+ options = { duration: 0.5 }
193
+ @loader = Phantom::SVG::Base.new(@source, options)
194
+ end
195
+
196
+ it 'successfully converts.' do
197
+ expect(@loader.frames.size).to eq(12)
198
+ is_succeeded = @loader.save_apng(@destination)
199
+ expect(is_succeeded).to eq(true)
200
+ end
201
+ end
202
+
203
+ describe 'converting an APNG to a keyframe animated SVG' do
204
+ before(:all) do
205
+ @apng_name = 'apngasm'
206
+ @source = "#{SPEC_SOURCE_DIR}/#{@apng_name}.png"
207
+ @destination_dir = "#{SPEC_TEMP_DIR}/#{@apng_name}"
208
+ @destination = "#{@destination_dir}/#{@apng_name}.svg"
209
+ FileUtils.mkdir_p(@destination_dir)
210
+ end
211
+
212
+ before do
213
+ @loader = Phantom::SVG::Base.new
214
+ end
215
+
216
+ it 'loads an APNG and saves a keyframe animated SVG.' do
217
+ @loader.add_frame_from_file(@source)
218
+ expect(@loader.frames.size).to eq(34)
219
+
220
+ write_size = @loader.save_svg(@destination)
221
+ expect(write_size).not_to eq(0)
222
+
223
+ range = 0..(@loader.frames.length - 1)
224
+ range.each do |i|
225
+ frame = @loader.frames[i]
226
+ write_size = @loader.save_svg_frame("#{@destination_dir}/#{i}.svg", frame)
227
+ expect(write_size).not_to eq(0)
228
+ end
229
+ end
230
+
231
+ it 'adds frames.' do
232
+ @loader.add_frame_from_file(@destination)
233
+ expect(@loader.frames.size).to eq(34)
234
+
235
+ @loader.frames.each do |frame|
236
+ expect(frame.duration).to eq(0.1)
237
+ expect(frame.width).to eq('64px')
238
+ expect(frame.height).to eq('64px')
239
+ expect(frame.surfaces.to_s.length).not_to eq(0)
240
+ expect(frame.namespaces.length).not_to eq(0)
241
+ end
242
+ end
243
+ end
244
+
245
+ describe 'using a finite loop count' do
246
+ before(:all) do
247
+ @image_name = 'stuck_out_tongue'
248
+ @source_dir = "#{SPEC_SOURCE_DIR}/#{@image_name}"
249
+ @source = "#{@source_dir}/*.svg"
250
+ @destination_dir = SPEC_TEMP_DIR
251
+ @destination_svg = "#{@destination_dir}/loops_test.svg"
252
+ @destination_png = "#{@destination_dir}/loops_test.png"
253
+ end
254
+
255
+ before do
256
+ @loader = Phantom::SVG::Base.new
257
+ end
258
+
259
+ it 'saves a keyframe animated SVG.' do
260
+ @loader.add_frame_from_file(@source)
261
+ @loader.loops = 2
262
+
263
+ write_size = @loader.save_svg(@destination_svg)
264
+ expect(write_size).not_to eq(0)
265
+ end
266
+
267
+ it 'correctly saved the keyframe animated SVG.' do
268
+ @loader.add_frame_from_file(@destination_svg)
269
+
270
+ expect(@loader.frames.size).to eq(12)
271
+ expect(@loader.width).to eq('64px')
272
+ expect(@loader.height).to eq('64px')
273
+ expect(@loader.loops).to eq(2)
274
+ expect(@loader.skip_first).to eq(false)
275
+ end
276
+
277
+ it 'exports/saves to APNG.' do
278
+ @loader.add_frame_from_file(@source)
279
+ @loader.loops = 2
280
+
281
+ write_size = @loader.save_apng(@destination_png)
282
+ expect(write_size).not_to eq(0)
283
+ end
284
+
285
+ it 'correctly saved the APNG.' do
286
+ @loader.add_frame_from_file(@destination_png)
287
+
288
+ expect(@loader.frames.size).to eq(12)
289
+ expect(@loader.width).to eq('64px')
290
+ expect(@loader.height).to eq('64px')
291
+ expect(@loader.loops).to eq(2)
292
+ expect(@loader.skip_first).to eq(false)
293
+ end
294
+
295
+ it 'loads an animation spec from a JSON file.' do
296
+ test_name = 'loops_test'
297
+ source = "#{@source_dir}/#{test_name}.json"
298
+ destination = "#{@destination_dir}/#{test_name}_json.svg"
299
+
300
+ @loader.add_frame_from_file(source)
301
+ @loader.save_svg(destination)
302
+ @loader.reset
303
+ @loader.add_frame_from_file(destination)
304
+
305
+ expect(@loader.frames.size).to eq(12)
306
+ expect(@loader.width).to eq('64px')
307
+ expect(@loader.height).to eq('64px')
308
+ expect(@loader.loops).to eq(3)
309
+ expect(@loader.skip_first).to eq(false)
310
+ expect(@loader.frames[0].duration).to eq(0.05)
311
+ end
312
+
313
+ it 'loads an animation spec from an XML file.' do
314
+ test_name = 'loops_test'
315
+ source = "#{@source_dir}/#{test_name}.xml"
316
+ destination = "#{@destination_dir}/#{test_name}_xml.svg"
317
+
318
+ @loader.add_frame_from_file(source)
319
+ @loader.save_svg(destination)
320
+ @loader.reset
321
+ @loader.add_frame_from_file(destination)
322
+
323
+ expect(@loader.frames.size).to eq(12)
324
+ expect(@loader.width).to eq('64px')
325
+ expect(@loader.height).to eq('64px')
326
+ expect(@loader.loops).to eq(3)
327
+ expect(@loader.skip_first).to eq(false)
328
+ expect(@loader.frames[0].duration).to eq(0.05)
329
+ end
330
+ end
331
+
332
+ describe 'using the skip_first flag' do
333
+ before(:all) do
334
+ @image_name = 'stuck_out_tongue'
335
+ @source_dir = "#{SPEC_SOURCE_DIR}/#{@image_name}"
336
+ @source = "#{@source_dir}/*.svg"
337
+ @source_skip_frame = "#{SPEC_SOURCE_DIR}/ninja.svg"
338
+ @destination_dir = SPEC_TEMP_DIR
339
+ @destination_svg = "#{@destination_dir}/skip_first_test.svg"
340
+ @destination_png = "#{@destination_dir}/skip_first_test.png"
341
+ end
342
+
343
+ before do
344
+ @loader = Phantom::SVG::Base.new
345
+ end
346
+
347
+ it 'can save a keyframe animated SVG' do
348
+ @loader.add_frame_from_file(@source_skip_frame)
349
+ @loader.add_frame_from_file(@source)
350
+ @loader.skip_first = true
351
+
352
+ write_size = @loader.save_svg(@destination_svg)
353
+ expect(write_size).not_to eq(0)
354
+ end
355
+
356
+ it 'successfully saves a keyframe animated SVG.' do
357
+ @loader.add_frame_from_file(@destination_svg)
358
+
359
+ expect(@loader.frames.size).to eq(13)
360
+ expect(@loader.width).to eq('64px')
361
+ expect(@loader.height).to eq('64px')
362
+ expect(@loader.loops).to eq(0)
363
+ expect(@loader.skip_first).to eq(true)
364
+ end
365
+
366
+ it 'exports/saves to APNG' do
367
+ @loader.add_frame_from_file(@source_skip_frame)
368
+ @loader.add_frame_from_file(@source)
369
+ @loader.skip_first = true
370
+
371
+ write_size = @loader.save_apng(@destination_png)
372
+ expect(write_size).not_to eq(0)
373
+ end
374
+
375
+ it 'correctly saved an APNG.' do
376
+ @loader.add_frame_from_file(@destination_png)
377
+
378
+ expect(@loader.frames.size).to eq(13)
379
+ expect(@loader.width).to eq('64px')
380
+ expect(@loader.height).to eq('64px')
381
+ expect(@loader.loops).to eq(0)
382
+ expect(@loader.skip_first).to eq(true)
383
+ end
384
+
385
+ it 'loads an animation spec from a JSON.' do
386
+ test_name = 'skip_first_test'
387
+ source = "#{@source_dir}/#{test_name}.json"
388
+ destination = "#{@destination_dir}/#{test_name}_json.svg"
389
+
390
+ @loader.add_frame_from_file(source)
391
+ @loader.save_svg(destination)
392
+ @loader.reset
393
+ @loader.add_frame_from_file(destination)
394
+
395
+ expect(@loader.frames.size).to eq(13)
396
+ expect(@loader.width).to eq('64px')
397
+ expect(@loader.height).to eq('64px')
398
+ expect(@loader.loops).to eq(0)
399
+ expect(@loader.skip_first).to eq(true)
400
+ expect(@loader.frames[1].duration).to eq(0.05)
401
+ end
402
+
403
+ it 'loads an animation spec from an XML file.' do
404
+ test_name = 'skip_first_test'
405
+ source = "#{@source_dir}/#{test_name}.xml"
406
+ destination = "#{@destination_dir}/#{test_name}_xml.svg"
407
+
408
+ @loader.add_frame_from_file(source)
409
+ @loader.save_svg(destination)
410
+ @loader.reset
411
+ @loader.add_frame_from_file(destination)
412
+
413
+ expect(@loader.frames.size).to eq(13)
414
+ expect(@loader.width).to eq('64px')
415
+ expect(@loader.height).to eq('64px')
416
+ expect(@loader.loops).to eq(0)
417
+ expect(@loader.skip_first).to eq(true)
418
+ expect(@loader.frames[1].duration).to eq(0.05)
419
+ end
420
+ end
421
+ end