PhantomAnimationEditor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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