phantom_svg 1.0.0

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.
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