PhantomAnimationEditor 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ce2fb904e9fb0d62da084dae1a028dee0fb052c
4
+ data.tar.gz: 3b7d664ca0ed6cd44a101f9ff201f84a4e5fab8c
5
+ SHA512:
6
+ metadata.gz: 01ebd3d47fdc22729204ca008191f308282d784ce8839281f9562a39f24a0e6294b9f2065e89678c975f1f84a10decc101918aacfdcf09ebdab7b989aa656362
7
+ data.tar.gz: 6a7d4cc3b26fda18b6ad9857eda20a8ab71552624afebcb3713bec59d0add70977d57d47e5103f67004d5f30ca1bb9ac8a117f33c4a2ad38738eaa0c750404ce
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'PhantomAnimationEditor'
3
+ s.version = '0.1.0'
4
+ s.license = 'GPL-3.0+'
5
+ s.summary = "Animation editor that generates frame animated SVG and animated PNG files"
6
+ s.description = "Animation editor primarily targeted at making small but high quality animations."
7
+ s.authors = ['Rei Kagetsuki', 'Rika Yoshida']
8
+ s.email = 'info@phantom.industries'
9
+ s.homepage = 'https://github.com/PhantomCreation/PhantomAnimationEditor'
10
+
11
+ s.files = Dir.glob('bin/*.rb') +
12
+ Dir.glob('lib/**/*.rb') +
13
+ Dir.glob('lib/**/*.glade') +
14
+ ['PhantomAnimationEditor.gemspec']
15
+ s.require_paths = ['lib']
16
+ s.executables << 'PhantomAnimationEditor'
17
+
18
+ s.add_dependency 'phantom_svg', '~> 1.2', '>= 1.2.3'
19
+ s.add_dependency 'gtk3', '~> 3.0', '>= 3.0.9'
20
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Phantom animation editor by Genshin Souzou Kabushiki Kaisha
4
+ # licensed under the GNU GPL v3
5
+
6
+ require_relative '../lib/phantom_animation_editor/editor_window.rb'
7
+
8
+ class PhantomAnimationEditorStandalone
9
+ TITLE = 'Phantom animation editor'
10
+ NAME = 'phantom-animation-editor'
11
+
12
+ def initialize(width = 800, height = 600, file = nil)
13
+ @editor_window = PhantomAnimationEditor::EditorWindow.new(width, height)
14
+ end
15
+ end
16
+
17
+ PhantomAnimationEditorStandalone.new
@@ -0,0 +1,2 @@
1
+ module PhantomAnimationEditor
2
+ end
@@ -0,0 +1,100 @@
1
+ require_relative '../phantom_animation_editor.rb'
2
+
3
+ require 'phantom_svg'
4
+ require 'rapngasm'
5
+ require 'fileutils'
6
+ require 'tmpdir'
7
+ require_relative 'frame_list.rb'
8
+ require_relative 'frame.rb'
9
+
10
+ class PhantomAnimationEditor::Adapter
11
+ def initialize
12
+ end
13
+
14
+ def import(frame_list, filename)
15
+ new_frames = []
16
+ @loader = Phantom::SVG::Base.new
17
+ @loader.add_frame_from_file(filename)
18
+
19
+ Dir.mktmpdir do |dir|
20
+ new_frames = import_frames(dir, frame_list)
21
+ end
22
+
23
+ data = {frames: new_frames, phantom_frames: @loader.frames}
24
+ data
25
+ end
26
+
27
+ def import_frames(dir, frame_list)
28
+ new_frames = []
29
+ @loader.frames.each_with_index do |frame, i|
30
+ tmp_filename = "#{dir}/#{i}.svg"
31
+ @loader.save_svg_frame(tmp_filename, frame)
32
+ new_frames << PhantomAnimationEditor::Frame.new(tmp_filename, frame_list)
33
+ end
34
+ new_frames
35
+ end
36
+
37
+ def export(frame_list, filename, frames_status, loop_status)
38
+ @frames = frame_list.list
39
+ @filename = check_filename(filename)
40
+ @frames_status = frames_status
41
+ @loop_status = loop_status
42
+ @loader = frame_list.phantom_svg
43
+
44
+ set_params
45
+ save
46
+ end
47
+
48
+ def set_params
49
+ @frames.each_with_index do |frame, i|
50
+ @loader.frames[i].duration = frame.delay * 0.001
51
+ end
52
+ @loader.loops = 1 unless @loop_status
53
+ end
54
+
55
+ def save
56
+ if @filename.include?('.svg')
57
+ save_svg
58
+ else
59
+ save_apng
60
+ end
61
+
62
+ GC.start
63
+ end
64
+
65
+ def save_svg
66
+ @loader.save_svg(@filename)
67
+ save_svg_frames if @frames_status
68
+ end
69
+
70
+ def save_svg_frames
71
+ @loader.frames.each_with_index do |frame, i|
72
+ dest = "#{File.dirname(@filename)}/#{File.basename(@filename, '.svg')}"
73
+ FileUtils.mkdir_p(dest) unless File.exist?(dest)
74
+ @loader.save_svg_frame("#{dest}/#{i}.svg", frame)
75
+ end
76
+ end
77
+
78
+ def save_apng
79
+ # TODO: macのrubyだと保存時に落ちる
80
+ @loader.save_apng(@filename)
81
+ save_apng_frames if @frames_status
82
+ end
83
+
84
+ def save_apng_frames
85
+ # TODO: phantom_svgにapngの各フレームの保存関数がない
86
+ apngasm = APNGAsm.new
87
+ apngasm.disassemble(@filename)
88
+ dest = "#{File.dirname(@filename)}/#{File.basename(@filename, '.png')}"
89
+ FileUtils.mkdir_p(dest) unless File.exist?(dest)
90
+ apngasm.save_pngs(dest)
91
+ end
92
+
93
+ def check_filename(filename)
94
+ filename = if filename.include?('.svg') || filename.include?('.png')
95
+ filename
96
+ else
97
+ "#{filename}.svg"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,363 @@
1
+ require_relative '../phantom_animation_editor.rb'
2
+
3
+ require 'gtk3'
4
+ require_relative 'frame_list.rb'
5
+ require_relative 'frame.rb'
6
+ require_relative 'adapter.rb'
7
+
8
+ class PhantomAnimationEditor::EditorWindow
9
+ def initialize(width = 800, height = 600)
10
+ @play = false
11
+ @loop_status = true
12
+ @frames_status = false
13
+
14
+ @builder = Gtk::Builder.new
15
+ @builder.add_from_file(File.expand_path('../layout.glade', __FILE__))
16
+
17
+ @window_base = @builder['editor_window']
18
+ @window_base.set_default_size(width, height)
19
+
20
+ $preview = @builder['preview_image']
21
+
22
+ @frame_hbox = Gtk::Box.new(:horizontal)
23
+ @frame_list = PhantomAnimationEditor::FrameList.new(@frame_hbox)
24
+
25
+ @scrolled_window = @builder['frame_list_scrolled_window']
26
+ @scrolled_window.add_with_viewport(@frame_hbox)
27
+
28
+ @first_button = @builder['first_button']
29
+ @first_button.signal_connect('clicked') do
30
+ move_to_first
31
+ end
32
+
33
+ @back_button = @builder['back_button']
34
+ @back_button.signal_connect('clicked') do
35
+ move_to_prev
36
+ end
37
+
38
+ @play_button = @builder['play_button']
39
+ @play_button.signal_connect('clicked') do
40
+ if @play
41
+ stop_animation
42
+ elsif @frame_list.size > 1
43
+ play_animation
44
+ end
45
+ end
46
+
47
+ @forward_button = @builder['forward_button']
48
+ @forward_button.signal_connect('clicked') do
49
+ move_to_next
50
+ end
51
+
52
+ @last_button = @builder['last_button']
53
+ @last_button.signal_connect('clicked') do
54
+ move_to_last
55
+ end
56
+
57
+ @add_frame_button = @builder['add_frame_button']
58
+ @add_frame_button.signal_connect('clicked') do
59
+ add_dialog
60
+ end
61
+
62
+ @import_button = @builder['file_chooser']
63
+ add_filter(@import_button)
64
+ @import_button.signal_connect('file_set') do |response|
65
+ file_import(File.expand_path(response.filename))
66
+ end
67
+
68
+ @export_button = @builder['export_button']
69
+ @export_button.signal_connect('clicked') do
70
+ export_dialog if @frame_list.size > 0
71
+ end
72
+
73
+ @loop_checkbutton = @builder['loop_checkbutton']
74
+ @loop_checkbutton.active = true
75
+ @loop_checkbutton.signal_connect('toggled') do
76
+ @loop_status = @loop_checkbutton.active?
77
+ @play_loop.set_active(@loop_status)
78
+ end
79
+
80
+ @frames_checkbutton = @builder['frames_checkbutton']
81
+ @frames_checkbutton.signal_connect('toggled') do
82
+ @frames_status = @frames_checkbutton.active?
83
+ end
84
+
85
+ @file_new = @builder['menu_file_new']
86
+ @file_new.signal_connect('activate') do
87
+ stop_animation if @play
88
+ label = Gtk::Label.new('New File?')
89
+ label.show
90
+ dialog = create_confirm_dialog(label, "new")
91
+ dialog.destroy
92
+ end
93
+
94
+ @file_import = @builder['menu_file_import']
95
+ @file_import.signal_connect('activate') do
96
+ import_dialog
97
+ end
98
+
99
+ @file_export = @builder['menu_file_export']
100
+ @file_export.signal_connect('activate') do
101
+ export_dialog if @frame_list.size > 0
102
+ end
103
+
104
+ @file_export_frames = @builder['menu_file_export_frames']
105
+ @file_export_frames.signal_connect('activate') do
106
+ @frames_checkbutton.set_active(true)
107
+ @frames_status = true
108
+ export_dialog if @frame_list.size > 0
109
+ end
110
+
111
+ @file_quit = @builder['menu_file_quit']
112
+ @file_quit.signal_connect('activate') do
113
+ label = Gtk::Label.new('Quit?')
114
+ label.show
115
+ dialog = create_confirm_dialog(label, "quit")
116
+ dialog.destroy
117
+ end
118
+
119
+ @frame_add = @builder['menu_frame_add']
120
+ @frame_add.signal_connect('activate') do
121
+ add_dialog
122
+ end
123
+
124
+ @frame_next = @builder['menu_frame_next']
125
+ @frame_next.signal_connect('activate') do
126
+ move_to_next
127
+ end
128
+
129
+ @frame_prev = @builder['menu_frame_prev']
130
+ @frame_prev.signal_connect('activate') do
131
+ move_to_prev
132
+ end
133
+
134
+ @frame_last = @builder['menu_frame_last']
135
+ @frame_last.signal_connect('activate') do
136
+ move_to_last
137
+ end
138
+
139
+ @frame_first = @builder['menu_frame_first']
140
+ @frame_first.signal_connect('activate') do
141
+ move_to_first
142
+ end
143
+
144
+ @frame_delete = @builder['menu_frame_delete']
145
+ @frame_delete.signal_connect('activate') do
146
+ if @frame_list.size > 1
147
+ @frame_list.delete_at(@frame_list.cur)
148
+ end
149
+ end
150
+
151
+ @play_play = @builder['menu_play_play']
152
+ @play_play.signal_connect('activate') do
153
+ if @frame_list.size > 1 && !@play
154
+ play_animation
155
+ end
156
+ end
157
+
158
+ @play_stop = @builder['menu_play_stop']
159
+ @play_stop.signal_connect('activate') do
160
+ stop_animation if @play
161
+ end
162
+
163
+ @play_loop = @builder['menu_play_loop']
164
+ @play_loop.set_active(@loop_status)
165
+ @play_loop.signal_connect('toggled') do
166
+ @loop_status = @play_loop.active?
167
+ @loop_checkbutton.set_active(@loop_status)
168
+ end
169
+
170
+ @help_about = @builder['menu_help_about']
171
+ @help_about.signal_connect('activate') do
172
+ help_dialog
173
+ end
174
+
175
+ @window_base.signal_connect('destroy') do
176
+ Gtk.main_quit
177
+ end
178
+
179
+ @window_base.show_all
180
+ Gtk.main
181
+ end
182
+
183
+ def move_to_next
184
+ if @frame_list.cur != nil && @frame_list.cur < @frame_list.size - 1
185
+ swap_frame(@frame_list.cur, @frame_list.cur + 1)
186
+ end
187
+ end
188
+
189
+ def move_to_prev
190
+ if @frame_list.cur != nil && @frame_list.cur != 0
191
+ swap_frame(@frame_list.cur, @frame_list.cur - 1)
192
+ end
193
+ end
194
+
195
+ def move_to_last
196
+ if @frame_list.size > 1
197
+ swap_frame(@frame_list.cur, @frame_list.size - 1)
198
+ end
199
+ end
200
+
201
+ def move_to_first
202
+ if @frame_list.size > 1
203
+ swap_frame(@frame_list.cur, 0)
204
+ end
205
+ end
206
+
207
+ def add_dialog
208
+ dialog = create_dialog('Open File', Gtk::FileChooserAction::OPEN, Gtk::Stock::OPEN)
209
+ dialog.set_select_multiple(true)
210
+ add_filter(dialog)
211
+
212
+ if dialog.run == Gtk::ResponseType::ACCEPT
213
+ dialog.filenames.each do |filename|
214
+ create_frame(File.expand_path(filename))
215
+ end
216
+ @window_base.show_all
217
+ end
218
+
219
+ dialog.destroy
220
+ end
221
+
222
+ def import_dialog
223
+ dialog = create_dialog('Import File', Gtk::FileChooserAction::OPEN, Gtk::Stock::OPEN)
224
+ add_filter(dialog)
225
+
226
+ if dialog.run == Gtk::ResponseType::ACCEPT
227
+ file_import(File.expand_path(dialog.filename))
228
+ @import_button.filename = dialog.filename
229
+ end
230
+
231
+ dialog.destroy
232
+ end
233
+
234
+ def export_dialog
235
+ dialog = create_dialog('Save File', Gtk::FileChooser::Action::SAVE, Gtk::Stock::SAVE)
236
+ add_filter(dialog, %w(png svg))
237
+ dialog.do_overwrite_confirmation = true
238
+
239
+ if dialog.run == Gtk::ResponseType::ACCEPT
240
+ file_export(File.expand_path(dialog.filename))
241
+ end
242
+
243
+ dialog.destroy
244
+ end
245
+
246
+ def help_dialog
247
+ # TODO dialog message
248
+ dialog = Gtk::MessageDialog.new(
249
+ message: 'Phantom animation editor is a software to create an animation file.',
250
+ parent: @window_base)
251
+ dialog.run
252
+ dialog.destroy
253
+ end
254
+
255
+ def create_dialog(title, action, stock)
256
+ Gtk::FileChooserDialog.new(title: title,
257
+ parent: @window_base,
258
+ action: action,
259
+ buttons: [[Gtk::Stock::CANCEL, Gtk::ResponseType::CANCEL],
260
+ [stock, Gtk::ResponseType::ACCEPT]])
261
+ end
262
+
263
+ def add_filter(dialog, types = %w(png jpg jpeg gif svg))
264
+ dialog.add_filter(create_multiple_filter(types))
265
+ types.each do |type|
266
+ dialog.add_filter(create_filter(type))
267
+ end
268
+ end
269
+
270
+ def create_multiple_filter(types)
271
+ filter = Gtk::FileFilter.new
272
+ filter.name = 'Image File'
273
+ types.each do |type|
274
+ filter.add_pattern("*.#{type}")
275
+ end
276
+ filter
277
+ end
278
+
279
+ def create_filter(type)
280
+ filter = Gtk::FileFilter.new
281
+ filter.name = "#{type} File"
282
+ filter.add_pattern("*.#{type}")
283
+ filter
284
+ end
285
+
286
+ def create_confirm_dialog(title, mode)
287
+ dialog = Gtk::Dialog.new
288
+ dialog.child.pack_start(title, expand: true, fill: true, padding: 30)
289
+ dialog.add_buttons(['Yes', Gtk::ResponseType::YES], ['No', Gtk::ResponseType::NO])
290
+
291
+ if dialog.run == Gtk::ResponseType::YES
292
+ @import_button.filename = 'blank'
293
+ @frame_list.delete_all if mode == 'new'
294
+ Gtk.main_quit if mode == 'quit'
295
+ end
296
+
297
+ dialog
298
+ end
299
+
300
+ def create_frame(filename)
301
+ frame = PhantomAnimationEditor::Frame.new(filename, @frame_list)
302
+ @frame_list << frame
303
+ $preview.set_pixbuf(frame.pixbuf)
304
+ @frame_list.frame_hbox.pack_start(frame, expand: false, fill: false, padding: 10)
305
+ end
306
+
307
+ def swap_frame(old_position, new_position)
308
+ @frame_list.swap(old_position, new_position)
309
+ @frame_list.cur = new_position
310
+ $preview.set_pixbuf(@frame_list.pixbuf(@frame_list.cur))
311
+ view_reload
312
+ end
313
+
314
+ def view_reload
315
+ @frame_list.view_reload
316
+ @window_base.show_all
317
+ end
318
+
319
+ def play_animation
320
+ @play = true
321
+ image = Gtk::Image.new(stock: Gtk::Stock::MEDIA_STOP, size: Gtk::IconSize::IconSize::BUTTON)
322
+ @play_button.set_image(image)
323
+
324
+ @frame_list.cur = 0
325
+ @handle = GLib::Idle.add {
326
+ unless @loop_status
327
+ stop_animation if @frame_list.cur == @frame_list.size - 1
328
+ end
329
+
330
+ $preview.set_pixbuf(@frame_list.pixbuf(@frame_list.cur))
331
+ @frame_list.cur - 1 < 0 ? delay_num = @frame_list.size - 1 : delay_num = @frame_list.cur - 1
332
+ sleep(@frame_list.delay(delay_num) * 0.001)
333
+ @frame_list.cur + 1 >= @frame_list.size ? @frame_list.cur = 0 : @frame_list.cur += 1
334
+ }
335
+ end
336
+
337
+ def stop_animation
338
+ GLib::Source.remove(@handle)
339
+
340
+ @play = false
341
+ image = Gtk::Image.new(stock: Gtk::Stock::MEDIA_PLAY, size: Gtk::IconSize::IconSize::BUTTON)
342
+ @play_button.set_image(image)
343
+ end
344
+
345
+ def file_import(filename)
346
+ adapter = PhantomAnimationEditor::Adapter.new
347
+ data = adapter.import(@frame_list, filename)
348
+
349
+ data[:frames].each do |frame|
350
+ @frame_list << frame
351
+ @frame_list.frame_hbox.pack_start(frame, expand: false, fill: false, padding: 10)
352
+ end
353
+ @frame_list.phantom_svg.frames.concat(data[:phantom_frames])
354
+
355
+ $preview.set_pixbuf(@frame_list.pixbuf(@frame_list.cur)) if @frame_list.size > 0
356
+ @window_base.show_all
357
+ end
358
+
359
+ def file_export(filename)
360
+ adapter = PhantomAnimationEditor::Adapter.new
361
+ adapter.export(@frame_list, filename, @frames_status, @loop_status)
362
+ end
363
+ end