ruby_spriter 0.6.7 → 0.7.0.1

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -3
  3. data/CHANGELOG.md +1035 -405
  4. data/Gemfile +17 -17
  5. data/LICENSE +21 -21
  6. data/README.md +183 -902
  7. data/bin/ruby_spriter +20 -20
  8. data/lib/ruby_spriter/background_sampler.rb +140 -0
  9. data/lib/ruby_spriter/batch_processor.rb +268 -212
  10. data/lib/ruby_spriter/cell_cleanup_config.rb +21 -0
  11. data/lib/ruby_spriter/cell_cleanup_gimp_script.rb +123 -0
  12. data/lib/ruby_spriter/cell_cleanup_processor.rb +230 -0
  13. data/lib/ruby_spriter/cli.rb +676 -612
  14. data/lib/ruby_spriter/compression_manager.rb +101 -101
  15. data/lib/ruby_spriter/consolidator.rb +179 -179
  16. data/lib/ruby_spriter/dependency_checker.rb +224 -174
  17. data/lib/ruby_spriter/ghost_edge_cleaner.rb +164 -0
  18. data/lib/ruby_spriter/gimp_processor.rb +1188 -667
  19. data/lib/ruby_spriter/metadata_manager.rb +117 -116
  20. data/lib/ruby_spriter/platform.rb +137 -82
  21. data/lib/ruby_spriter/processor.rb +1230 -886
  22. data/lib/ruby_spriter/smoke_detector.rb +223 -0
  23. data/lib/ruby_spriter/threshold_stepper.rb +227 -0
  24. data/lib/ruby_spriter/utils/file_helper.rb +82 -82
  25. data/lib/ruby_spriter/utils/image_helper.rb +16 -0
  26. data/lib/ruby_spriter/utils/output_formatter.rb +65 -65
  27. data/lib/ruby_spriter/utils/path_helper.rb +59 -59
  28. data/lib/ruby_spriter/utils/spritesheet_splitter.rb +86 -86
  29. data/lib/ruby_spriter/version.rb +6 -7
  30. data/lib/ruby_spriter/video_processor.rb +357 -139
  31. data/lib/ruby_spriter.rb +38 -34
  32. data/ruby_spriter.gemspec +44 -42
  33. data/spec/fixtures/centered_sprite_with_inner_bg.png +0 -0
  34. data/spec/fixtures/centered_sprite_with_inner_bg_inner_removed.png +0 -0
  35. data/spec/fixtures/centered_sprite_with_inner_bg_threshold_stepped.png +0 -0
  36. data/spec/fixtures/complex_background_sprite.png +0 -0
  37. data/spec/fixtures/death - bloodborne rekconing.mp4 +0 -0
  38. data/spec/fixtures/death - bloodborne rekconing_spritesheet-nobg-global.png +0 -0
  39. data/spec/fixtures/death - bloodborne rekconing_spritesheet.png +0 -0
  40. data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431-nobg-global.png +0 -0
  41. data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431.png +0 -0
  42. data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738-nobg-global.png +0 -0
  43. data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738.png +0 -0
  44. data/spec/fixtures/has_inner_bg.png +0 -0
  45. data/spec/fixtures/has_small_inner_bg.png +0 -0
  46. data/spec/fixtures/smoke_effect_sprite.png +0 -0
  47. data/spec/fixtures/spritesheet_with_metadata.png +0 -0
  48. data/spec/fixtures/test_sprite.png +0 -0
  49. data/spec/fixtures/test_sprite_smoke_processed.png +0 -0
  50. data/spec/fixtures/test_video_spritesheet.png +0 -0
  51. data/spec/fixtures/transparent_bg_sprite.png +0 -0
  52. data/spec/fixtures/walk_north_sprite-sheet.png +0 -0
  53. data/spec/integration/no_fuzzy_mode_spec.rb +100 -0
  54. data/spec/ruby_spriter/batch_processor_spec.rb +509 -200
  55. data/spec/ruby_spriter/cli_spec.rb +2026 -1892
  56. data/spec/ruby_spriter/compression_manager_spec.rb +157 -157
  57. data/spec/ruby_spriter/consolidator_spec.rb +538 -538
  58. data/spec/ruby_spriter/gimp_processor_spec.rb +523 -425
  59. data/spec/ruby_spriter/platform_spec.rb +92 -82
  60. data/spec/ruby_spriter/processor_spec.rb +911 -735
  61. data/spec/ruby_spriter/utils/file_helper_spec.rb +150 -150
  62. data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -78
  63. data/spec/ruby_spriter/utils/spritesheet_splitter_spec.rb +104 -104
  64. data/spec/ruby_spriter/video_processor_spec.rb +346 -29
  65. data/spec/spec_helper.rb +41 -41
  66. data/spec/tmp/cli_test_output.png +0 -0
  67. data/spec/tmp/cli_test_output_20251105_114647_395.png +0 -0
  68. data/spec/tmp/combined_test.png +0 -0
  69. data/spec/tmp/compat_test.png +0 -0
  70. data/spec/tmp/compat_test_20251030_174233_361.png +0 -0
  71. data/spec/tmp/final_all_features.png +0 -0
  72. data/spec/tmp/final_test_all_features.png +0 -0
  73. data/spec/tmp/full_pipeline_test.png +0 -0
  74. data/spec/tmp/inner_test.png +0 -0
  75. data/spec/tmp/integration_test.png +0 -0
  76. data/spec/tmp/validation_test.png +0 -0
  77. data/spec/unit/background_sampler_spec.rb +132 -0
  78. data/spec/unit/cell_cleanup_config_spec.rb +32 -0
  79. data/spec/unit/cell_cleanup_gimp_script_spec.rb +59 -0
  80. data/spec/unit/cell_cleanup_processor_spec.rb +261 -0
  81. data/spec/unit/ghost_edge_cleaner_spec.rb +223 -0
  82. data/spec/unit/gimp_processor_single_point_selection_spec.rb +81 -0
  83. data/spec/unit/smoke_detector_spec.rb +246 -0
  84. data/spec/unit/threshold_stepper_spec.rb +195 -0
  85. metadata +56 -10
@@ -1,612 +1,676 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
-
5
- module RubySpriter
6
- # Command-line interface
7
- class CLI
8
- PRESETS = {
9
- thumbnail: { columns: 3, frame_count: 9, max_width: 240 },
10
- preview: { columns: 4, frame_count: 16, max_width: 400 },
11
- detailed: { columns: 10, frame_count: 50, max_width: 320 },
12
- contact: { columns: 8, frame_count: 64, max_width: 160 }
13
- }.freeze
14
-
15
- def self.start(args)
16
- new.parse_and_run(args)
17
- end
18
-
19
- def parse_and_run(args)
20
- options = {}
21
-
22
- # Handle context-sensitive help before validation
23
- if args.include?('--help') || args.include?('-h')
24
- handle_context_sensitive_help(args)
25
- end
26
-
27
- parser = build_option_parser(options)
28
-
29
- parser.parse!(args)
30
-
31
- # Handle special commands that don't need full processing
32
- if options[:check_dependencies]
33
- checker = DependencyChecker.new(verbose: true)
34
- checker.print_report
35
- exit(checker.all_satisfied? ? 0 : 1)
36
- end
37
-
38
- # Validate mutually exclusive options
39
- if options[:extract] && options[:split]
40
- raise ValidationError, "--extract and --split are mutually exclusive"
41
- end
42
-
43
- # Validate --add-meta cannot be combined with processing options
44
- if options[:add_meta] && (options[:scale_percent] || options[:remove_bg] || options[:sharpen])
45
- raise ValidationError, "--add-meta cannot be combined with processing options (--scale, --remove-bg, --sharpen)"
46
- end
47
-
48
- # Run processor
49
- processor = Processor.new(options)
50
- processor.run
51
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
52
- puts "Error: #{e.message}"
53
- puts "\nUse --help for usage information"
54
- exit 1
55
- end
56
-
57
- private
58
-
59
- def build_option_parser(options)
60
- OptionParser.new do |opts|
61
- opts.banner = "Usage: ruby_spriter [options]"
62
-
63
- add_header(opts)
64
- add_input_options(opts, options)
65
- add_spritesheet_options(opts, options)
66
- add_gimp_options(opts, options)
67
- add_bg_removal_options(opts, options)
68
- add_consolidation_options(opts, options)
69
- add_preset_options(opts, options)
70
- add_other_options(opts, options)
71
- end
72
- end
73
-
74
- def add_header(opts)
75
- opts.separator ""
76
- opts.separator "Ruby Spriter v#{VERSION} - Professional MP4 to Spritesheet Converter with Advanced Image Processing"
77
- opts.separator "Platform: #{Platform.current.to_s.capitalize}"
78
- opts.separator ""
79
- opts.separator "Get mode-specific help:"
80
- opts.separator " ruby_spriter --video --help"
81
- opts.separator " ruby_spriter --image --help"
82
- opts.separator " ruby_spriter --consolidate --help"
83
- opts.separator " ruby_spriter --batch --help"
84
- opts.separator " ruby_spriter --split --help"
85
- opts.separator ""
86
- end
87
-
88
- def add_input_options(opts, options)
89
- opts.separator "Input Options:"
90
-
91
- opts.on("-v", "--video FILE", "Input video file (MP4)") do |v|
92
- options[:video] = v
93
- end
94
-
95
- opts.on("-i", "--image FILE", "Input image file (PNG) for direct processing") do |i|
96
- options[:image] = i
97
- end
98
-
99
- opts.on("--batch", "Batch process all MP4 files in directory") do
100
- options[:batch] = true
101
- end
102
-
103
- opts.on("--dir DIRECTORY", "Directory for batch processing") do |d|
104
- options[:dir] = d
105
- end
106
-
107
- opts.on("--outputdir DIRECTORY", "Output directory for batch processing") do |d|
108
- options[:outputdir] = d
109
- end
110
-
111
- opts.on("--batch-consolidate", "Consolidate all spritesheets after batch processing") do
112
- options[:batch_consolidate] = true
113
- end
114
-
115
- opts.on("--consolidate [FILES]", Array, "Consolidate spritesheets (comma-separated files or use with --dir)") do |c|
116
- options[:consolidate_mode] = true
117
- options[:consolidate] = c if c && !c.empty?
118
- end
119
-
120
- opts.on("--verify FILE", "Verify spritesheet metadata") do |v|
121
- options[:verify] = v
122
- end
123
-
124
- opts.on("--split R:C", "Split image into frames (rows:columns, e.g., 4:4)") do |s|
125
- options[:split] = s
126
- end
127
-
128
- opts.on("--override-md", "Override embedded metadata when using --split") do
129
- options[:override_md] = true
130
- end
131
-
132
- opts.on("--extract FRAMES", "Extract specific frames by number (comma-separated, e.g., 1,2,4,5,8)") do |e|
133
- options[:extract] = e
134
- end
135
-
136
- opts.on("--columns NUM", Integer, "Number of columns for extracted spritesheet (default: 4)") do |c|
137
- options[:columns] = c
138
- end
139
-
140
- opts.on("--add-meta R:C", "Add spritesheet metadata to image (rows:columns, e.g., 4:4)") do |m|
141
- options[:add_meta] = m
142
- end
143
-
144
- opts.on("--overwrite-meta", "Overwrite existing metadata when using --add-meta") do
145
- options[:overwrite_meta] = true
146
- end
147
-
148
- opts.separator ""
149
- end
150
-
151
- def add_spritesheet_options(opts, options)
152
- opts.separator "Spritesheet Options:"
153
-
154
- opts.on("-o", "--output FILE", "Output file path") do |o|
155
- options[:output] = o
156
- end
157
-
158
- opts.on("-f", "--frames COUNT", Integer, "Number of frames to extract (default: 16)") do |f|
159
- options[:frame_count] = f
160
- end
161
-
162
- opts.on("-c", "--columns COUNT", Integer, "Grid columns (default: 4)") do |c|
163
- options[:columns] = c
164
- end
165
-
166
- opts.on("-w", "--width PIXELS", Integer, "Max frame width (default: 320)") do |w|
167
- options[:max_width] = w
168
- end
169
-
170
- opts.on("-b", "--background COLOR", "Tile background: black, white (default: black)") do |b|
171
- options[:bg_color] = b
172
- end
173
-
174
- opts.on("--save-frames", "Save individual frames to disk (for --video or --extract)") do
175
- options[:save_frames] = true
176
- end
177
-
178
- opts.separator ""
179
- end
180
-
181
- def add_gimp_options(opts, options)
182
- opts.separator "Processing Options:"
183
-
184
- opts.on("-s", "--scale PERCENT", Integer, "Scale image by percentage") do |s|
185
- options[:scale_percent] = s
186
- end
187
-
188
- opts.on("--interpolation METHOD", [:none, :linear, :cubic, :nohalo, :lohalo],
189
- "Interpolation method: none, linear, cubic, nohalo, lohalo (default: nohalo)") do |i|
190
- options[:scale_interpolation] = i.to_s
191
- end
192
-
193
- opts.on("--sharpen", "Apply unsharp mask after scaling (enhances edges)") do
194
- options[:sharpen] = true
195
- end
196
-
197
- opts.on("--sharpen-radius VALUE", Float, "Sharpen radius in pixels (default: 2.0)") do |r|
198
- options[:sharpen_radius] = r
199
- end
200
-
201
- opts.on("--sharpen-gain VALUE", Float, "Sharpen gain/strength (default: 0.5, range: 0.0-2.0+)") do |g|
202
- options[:sharpen_gain] = g
203
- end
204
-
205
- opts.on("--sharpen-threshold VALUE", Float, "Sharpen threshold as fraction (default: 0.03, range: 0.0-1.0)") do |t|
206
- options[:sharpen_threshold] = t
207
- end
208
-
209
- opts.on("-r", "--remove-bg", "Remove background from spritesheet") do
210
- options[:remove_bg] = true
211
- end
212
-
213
- opts.on("-t", "--threshold VALUE", Float, "Feather radius (default: 0.0 = no feathering)") do |t|
214
- options[:bg_threshold] = t
215
- end
216
-
217
- opts.on("-g", "--grow PIXELS", Integer, "Pixels to grow selection (default: 1)") do |g|
218
- options[:grow_selection] = g
219
- end
220
-
221
- opts.separator ""
222
- end
223
-
224
- def add_bg_removal_options(opts, options)
225
- opts.separator "Background Removal Method:"
226
-
227
- opts.on("--fuzzy", "Use fuzzy select (contiguous regions) - DEFAULT") do
228
- options[:fuzzy_select] = true
229
- end
230
-
231
- opts.on("--no-fuzzy", "Use global color select (all matching pixels)") do
232
- options[:fuzzy_select] = false
233
- end
234
-
235
- opts.separator ""
236
- opts.separator "Operation Order:"
237
-
238
- opts.on("--order ORDER", [:scale_first, :bg_first],
239
- "Operation order: scale_first or bg_first (default: scale_first)") do |order|
240
- options[:operation_order] = order == :scale_first ? :scale_then_remove_bg : :remove_bg_then_scale
241
- end
242
-
243
- opts.separator ""
244
- end
245
-
246
- def add_consolidation_options(opts, options)
247
- opts.separator "Consolidation Options:"
248
-
249
- opts.on("--[no-]validate-columns", "Abort if column counts don't match (default: true)") do |v|
250
- options[:validate_columns] = v
251
- end
252
-
253
- opts.separator ""
254
- end
255
-
256
- def add_preset_options(opts, options)
257
- opts.separator "Preset Configurations:"
258
-
259
- preset_descriptions = PRESETS.map do |name, config|
260
- " #{name}: #{config[:columns]}×? grid, #{config[:frame_count]} frames, #{config[:max_width]}px wide"
261
- end.join("\n")
262
-
263
- opts.on("--preset NAME", String, "Apply preset configuration:",
264
- *preset_descriptions.split("\n")) do |preset_name|
265
- preset_key = preset_name.to_sym
266
- unless PRESETS.key?(preset_key)
267
- valid_presets = PRESETS.keys.join(', ')
268
- raise OptionParser::InvalidArgument, "Unknown preset: #{preset_name}. Valid options: #{valid_presets}"
269
- end
270
- options.merge!(PRESETS[preset_key])
271
- end
272
-
273
- opts.separator ""
274
- end
275
-
276
- def add_other_options(opts, options)
277
- opts.separator "Other Options:"
278
-
279
- opts.on("--max-compress", "Apply maximum PNG compression to output") do
280
- options[:max_compress] = true
281
- end
282
-
283
- opts.on("--overwrite", "Overwrite existing output files (default: create unique filenames)") do
284
- options[:overwrite] = true
285
- end
286
-
287
- opts.on("--keep-temp", "Keep temporary files for debugging") do
288
- options[:keep_temp] = true
289
- end
290
-
291
- opts.on("--debug", "Enable debug mode (verbose output + keep temp files)") do
292
- options[:debug] = true
293
- options[:keep_temp] = true
294
- end
295
-
296
- opts.on("-h", "--help", "Show this help message") do
297
- puts opts
298
- exit
299
- end
300
-
301
- opts.on("--version", "Show version information") do
302
- puts "Ruby Spriter v#{VERSION}"
303
- puts "Platform: #{Platform.current.to_s.capitalize}"
304
- puts "Date: #{VERSION_DATE}"
305
- exit
306
- end
307
-
308
- opts.on("--check-dependencies", "Check if all required external tools are installed") do
309
- options[:check_dependencies] = true
310
- end
311
-
312
- opts.separator ""
313
- opts.separator "Examples:"
314
- opts.separator " ruby_spriter --check-dependencies"
315
- opts.separator " ruby_spriter --video input.mp4"
316
- opts.separator " ruby_spriter --video input.mp4 --remove-bg --scale 50"
317
- opts.separator " ruby_spriter --video input.mp4 --scale 50 --interpolation nohalo --sharpen"
318
- opts.separator " ruby_spriter --video input.mp4 --max-compress"
319
- opts.separator " ruby_spriter --image sprite.png --scale 50 --sharpen --sharpen-gain 1.5"
320
- opts.separator " ruby_spriter --image sprite.png --remove-bg --fuzzy"
321
- opts.separator " ruby_spriter --batch --dir videos/"
322
- opts.separator " ruby_spriter --batch --dir videos/ --outputdir output/"
323
- opts.separator " ruby_spriter --batch --dir videos/ --batch-consolidate --max-compress"
324
- opts.separator " ruby_spriter --consolidate file1.png,file2.png,file3.png"
325
- opts.separator " ruby_spriter --consolidate --dir spritesheets/"
326
- opts.separator " ruby_spriter --consolidate --dir spritesheets/ --outputdir output/ --max-compress"
327
- opts.separator " ruby_spriter --verify spritesheet.png"
328
- opts.separator ""
329
- end
330
-
331
- def handle_context_sensitive_help(args)
332
- if args.include?('--video') || args.include?('-v')
333
- show_video_mode_help
334
- elsif args.include?('--image') || args.include?('-i')
335
- show_image_mode_help
336
- elsif args.include?('--consolidate')
337
- show_consolidate_mode_help
338
- elsif args.include?('--batch')
339
- show_batch_mode_help
340
- elsif args.include?('--split')
341
- show_split_mode_help
342
- else
343
- # Default help - let OptionParser handle it
344
- return
345
- end
346
- end
347
-
348
- def show_video_mode_help
349
- puts ""
350
- puts "Video Mode"
351
- puts "=" * 60
352
- puts ""
353
- puts "Convert MP4 videos to spritesheets with advanced image processing."
354
- puts ""
355
- puts "Basic Usage:"
356
- puts " ruby_spriter --video FILE [options]"
357
- puts ""
358
- puts "Required:"
359
- puts " -v, --video FILE Input video file (MP4)"
360
- puts ""
361
- puts "Spritesheet Options:"
362
- puts " -o, --output FILE Output file path"
363
- puts " -f, --frames COUNT Number of frames to extract (default: 16)"
364
- puts " -c, --columns COUNT Grid columns (default: 4)"
365
- puts " -w, --width PIXELS Max frame width (default: 320)"
366
- puts " -b, --background COLOR Tile background: black, white (default: black)"
367
- puts " --save-frames Save individual frames to disk"
368
- puts ""
369
- puts "Image Processing:"
370
- puts " -s, --scale PERCENT Scale image by percentage"
371
- puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
372
- puts ""
373
- puts " --sharpen Apply unsharp mask for edge enhancement"
374
- puts " --sharpen-radius VALUE └─ Sharpen radius in pixels (default: 2.0)"
375
- puts " --sharpen-gain VALUE └─ Sharpen gain/strength (default: 0.5)"
376
- puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
377
- puts ""
378
- puts " -r, --remove-bg Remove background"
379
- puts " --fuzzy └─ Use fuzzy select (contiguous regions) - DEFAULT"
380
- puts " --no-fuzzy └─ Use global color select (all matching pixels)"
381
- puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
382
- puts " -g, --grow PIXELS └─ Grow selection pixels (default: 1)"
383
- puts ""
384
- puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
385
- puts " scale_first or bg_first (default: scale_first)"
386
- puts ""
387
- puts "Output Options:"
388
- puts " --max-compress Apply maximum PNG compression"
389
- puts " --overwrite Overwrite existing files"
390
- puts " --keep-temp Keep temporary files"
391
- puts " --debug Enable debug mode"
392
- puts ""
393
- puts "Examples:"
394
- puts " ruby_spriter --video input.mp4"
395
- puts " ruby_spriter --video input.mp4 --scale 50 --interpolation nohalo"
396
- puts " ruby_spriter --video input.mp4 --remove-bg --fuzzy --threshold 0.5"
397
- puts " ruby_spriter --video input.mp4 --scale 50 --sharpen --max-compress"
398
- puts ""
399
- exit
400
- end
401
-
402
- def show_image_mode_help
403
- puts ""
404
- puts "Image Mode"
405
- puts "=" * 60
406
- puts ""
407
- puts "Process PNG spritesheets with advanced image operations."
408
- puts ""
409
- puts "Basic Usage:"
410
- puts " ruby_spriter --image FILE [options]"
411
- puts ""
412
- puts "Required:"
413
- puts " -i, --image FILE Input image file (PNG)"
414
- puts ""
415
- puts "Image Processing:"
416
- puts " -s, --scale PERCENT Scale image by percentage"
417
- puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
418
- puts ""
419
- puts " --sharpen Apply unsharp mask for edge enhancement"
420
- puts " --sharpen-radius VALUE └─ Sharpen radius in pixels (default: 2.0)"
421
- puts " --sharpen-gain VALUE └─ Sharpen gain/strength (default: 0.5)"
422
- puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
423
- puts ""
424
- puts " -r, --remove-bg Remove background"
425
- puts " --fuzzy └─ Use fuzzy select (contiguous regions) - DEFAULT"
426
- puts " --no-fuzzy └─ Use global color select (all matching pixels)"
427
- puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
428
- puts " -g, --grow PIXELS └─ Grow selection pixels (default: 1)"
429
- puts ""
430
- puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
431
- puts " scale_first or bg_first (default: scale_first)"
432
- puts ""
433
- puts "Frame Extraction & Reassembly:"
434
- puts " --split R:C Split spritesheet into all individual frames (rows:columns)"
435
- puts " --override-md └─ Override embedded metadata"
436
- puts ""
437
- puts " --extract FRAMES Extract specific frames and create new spritesheet (e.g., 1,2,4,5,8)"
438
- puts " --columns NUM └─ Output grid columns (default: 4)"
439
- puts " --save-frames └─ Keep individual extracted frames on disk"
440
- puts ""
441
- puts "Metadata Management:"
442
- puts " --add-meta R:C Add spritesheet metadata (rows:columns, e.g., 4:4)"
443
- puts " --overwrite-meta └─ Replace existing metadata"
444
- puts " -f, --frames COUNT └─ Custom frame count for partial grids"
445
- puts ""
446
- puts "Output Options:"
447
- puts " -o, --output FILE Output file path"
448
- puts " --max-compress Apply maximum PNG compression"
449
- puts " --overwrite Overwrite existing files"
450
- puts " --keep-temp Keep temporary files"
451
- puts " --debug Enable debug mode"
452
- puts ""
453
- puts "Examples:"
454
- puts " ruby_spriter --image sprite.png --scale 50 --interpolation nohalo"
455
- puts " ruby_spriter --image sprite.png --remove-bg --fuzzy --threshold 1.0"
456
- puts " ruby_spriter --image sprite.png --scale 50 --sharpen --sharpen-gain 1.5"
457
- puts " ruby_spriter --image sprite.png --split 4:4 --override-md"
458
- puts " ruby_spriter --image sprite.png --extract 1,2,4,5,8 --columns 3"
459
- puts " ruby_spriter --image sprite.png --extract 1,1,2,2,3,3 --save-frames"
460
- puts " ruby_spriter --image sprite.png --add-meta 4:4"
461
- puts " ruby_spriter --image sprite.png --add-meta 4:4 --frames 14 --output sprite_meta.png"
462
- puts ""
463
- exit
464
- end
465
-
466
- def show_consolidate_mode_help
467
- puts ""
468
- puts "Consolidate Mode"
469
- puts "=" * 60
470
- puts ""
471
- puts "Combine multiple spritesheets into a single consolidated spritesheet."
472
- puts ""
473
- puts "Basic Usage:"
474
- puts " ruby_spriter --consolidate FILE1,FILE2,FILE3 [options]"
475
- puts " ruby_spriter --consolidate --dir DIRECTORY [options]"
476
- puts ""
477
- puts "Input Methods:"
478
- puts " --consolidate [FILES] Comma-separated list of PNG files"
479
- puts " --consolidate --dir DIRECTORY Process all spritesheets in directory"
480
- puts " --[no-]validate-columns └─ Abort if column counts don't match (default: true)"
481
- puts ""
482
- puts "Output Options:"
483
- puts " -o, --output FILE Output file path"
484
- puts " --outputdir DIRECTORY Output directory (when using --dir)"
485
- puts " --max-compress Apply maximum PNG compression"
486
- puts " --overwrite Overwrite existing files"
487
- puts " --keep-temp Keep temporary files"
488
- puts " --debug Enable debug mode"
489
- puts ""
490
- puts "Requirements:"
491
- puts " - All input files must be PNG spritesheets with embedded metadata"
492
- puts " - Column counts must match across all spritesheets (unless --no-validate-columns)"
493
- puts " - Minimum 2 spritesheets required"
494
- puts ""
495
- puts "Examples:"
496
- puts " ruby_spriter --consolidate file1.png,file2.png,file3.png"
497
- puts " ruby_spriter --consolidate --dir spritesheets/"
498
- puts " ruby_spriter --consolidate --dir spritesheets/ --outputdir output/"
499
- puts " ruby_spriter --consolidate --dir spritesheets/ --no-validate-columns"
500
- puts ""
501
- exit
502
- end
503
-
504
- def show_batch_mode_help
505
- puts ""
506
- puts "Batch Mode"
507
- puts "=" * 60
508
- puts ""
509
- puts "Process multiple MP4 videos in a directory with consistent options."
510
- puts ""
511
- puts "Basic Usage:"
512
- puts " ruby_spriter --batch --dir DIRECTORY [options]"
513
- puts ""
514
- puts "Required:"
515
- puts " --batch Enable batch processing mode"
516
- puts " --dir DIRECTORY Directory containing MP4 files"
517
- puts ""
518
- puts "Batch Options:"
519
- puts " --outputdir DIRECTORY Output directory (default: same as input dir)"
520
- puts " --batch-consolidate Consolidate all results into single spritesheet"
521
- puts ""
522
- puts "Spritesheet Options (applied to all videos):"
523
- puts " -f, --frames COUNT Number of frames to extract (default: 16)"
524
- puts " -c, --columns COUNT Grid columns (default: 4)"
525
- puts " -w, --width PIXELS Max frame width (default: 320)"
526
- puts " -b, --background COLOR Tile background: black, white (default: black)"
527
- puts " --save-frames Save individual frames to disk"
528
- puts ""
529
- puts "Image Processing (applied to all videos):"
530
- puts " -s, --scale PERCENT Scale images by percentage"
531
- puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
532
- puts ""
533
- puts " --sharpen Apply unsharp mask for edge enhancement"
534
- puts " --sharpen-radius VALUE └─ Sharpen radius (default: 2.0)"
535
- puts " --sharpen-gain VALUE └─ Sharpen gain (default: 0.5)"
536
- puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
537
- puts ""
538
- puts " -r, --remove-bg Remove background"
539
- puts " --fuzzy └─ Use fuzzy select (DEFAULT)"
540
- puts " --no-fuzzy └─ Use global color select"
541
- puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
542
- puts " -g, --grow PIXELS └─ Grow selection (default: 1)"
543
- puts ""
544
- puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
545
- puts " scale_first or bg_first (default: scale_first)"
546
- puts ""
547
- puts "Output Options:"
548
- puts " --max-compress Apply maximum PNG compression"
549
- puts " --overwrite Overwrite existing files"
550
- puts " --keep-temp Keep temporary files"
551
- puts " --debug Enable debug mode"
552
- puts ""
553
- puts "Behavior:"
554
- puts " - Processes all MP4 files in the specified directory"
555
- puts " - Enforces unique filenames unless --overwrite is specified"
556
- puts " - Continues processing remaining videos if one fails"
557
- puts " - Provides summary of successes and failures"
558
- puts ""
559
- puts "Examples:"
560
- puts " ruby_spriter --batch --dir videos/"
561
- puts " ruby_spriter --batch --dir videos/ --outputdir output/"
562
- puts " ruby_spriter --batch --dir videos/ --scale 50 --sharpen"
563
- puts " ruby_spriter --batch --dir videos/ --batch-consolidate --max-compress"
564
- puts ""
565
- exit
566
- end
567
-
568
- def show_split_mode_help
569
- puts ""
570
- puts "Split Mode"
571
- puts "=" * 60
572
- puts ""
573
- puts "Extract individual frames from a spritesheet (requires --image)."
574
- puts ""
575
- puts "Basic Usage:"
576
- puts " ruby_spriter --image FILE --split R:C [options]"
577
- puts ""
578
- puts "Required:"
579
- puts " -i, --image FILE Input spritesheet file (PNG)"
580
- puts " --split R:C Split format: rows:columns (e.g., 4:4)"
581
- puts " --override-md └─ Override embedded metadata"
582
- puts ""
583
- puts "Format Requirements:"
584
- puts " - Rows and columns must be 1-99"
585
- puts " - Total frames (R × C) must be < 1000"
586
- puts " - Image dimensions must divide evenly by rows and columns"
587
- puts ""
588
- puts "Metadata Behavior:"
589
- puts " - If spritesheet has embedded metadata, it will be used automatically"
590
- puts " - Use --override-md to ignore embedded metadata and use --split value"
591
- puts " - If no metadata exists, --split value is required"
592
- puts ""
593
- puts "Output Options:"
594
- puts " -o, --output FILE Output directory (default: filename_frames/)"
595
- puts " --overwrite Overwrite existing files"
596
- puts " --keep-temp Keep temporary files"
597
- puts " --debug Enable debug mode"
598
- puts ""
599
- puts "Output:"
600
- puts " - Frames are saved as: FR001_filename.png, FR002_filename.png, etc."
601
- puts " - Frame naming uses 3-digit zero-padded format (FR001-FR999)"
602
- puts " - Output directory: filename_frames/ (unless --output specified)"
603
- puts ""
604
- puts "Examples:"
605
- puts " ruby_spriter --image sprite.png --split 4:4"
606
- puts " ruby_spriter --image sprite.png --split 8:8 --override-md"
607
- puts " ruby_spriter --image sprite.png --split 2:5 --output frames/"
608
- puts ""
609
- exit
610
- end
611
- end
612
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module RubySpriter
6
+ # Command-line interface
7
+ class CLI
8
+ PRESETS = {
9
+ thumbnail: { columns: 3, frame_count: 9, max_width: 240 },
10
+ preview: { columns: 4, frame_count: 16, max_width: 400 },
11
+ detailed: { columns: 10, frame_count: 50, max_width: 320 },
12
+ contact: { columns: 8, frame_count: 64, max_width: 160 }
13
+ }.freeze
14
+
15
+ def self.start(args)
16
+ new.parse_and_run(args)
17
+ end
18
+
19
+ def parse_and_run(args)
20
+ options = {}
21
+ options[:fuzzy_select] = false # Default to --no-fuzzy (global color select)
22
+
23
+ # Handle context-sensitive help before validation
24
+ if args.include?('--help') || args.include?('-h')
25
+ handle_context_sensitive_help(args)
26
+ end
27
+
28
+ parser = build_option_parser(options)
29
+
30
+ parser.parse!(args)
31
+
32
+ # Handle special commands that don't need full processing
33
+ if options[:check_dependencies]
34
+ checker = DependencyChecker.new(verbose: true)
35
+ checker.print_report
36
+ exit(checker.all_satisfied? ? 0 : 1)
37
+ end
38
+
39
+ # Validate mutually exclusive options
40
+ if options[:extract] && options[:split]
41
+ raise ValidationError, "--extract and --split are mutually exclusive"
42
+ end
43
+
44
+ # Validate --add-meta cannot be combined with processing options
45
+ if options[:add_meta] && (options[:scale_percent] || options[:remove_bg] || options[:sharpen])
46
+ raise ValidationError, "--add-meta cannot be combined with processing options (--scale, --remove-bg, --sharpen)"
47
+ end
48
+
49
+ # Validate --by-frame flag requirements
50
+ if options[:by_frame]
51
+ unless options[:remove_bg]
52
+ raise ValidationError, "--by-frame requires --remove-bg"
53
+ end
54
+
55
+ unless options[:video] || options[:batch]
56
+ raise ValidationError, "--by-frame requires --video or --batch"
57
+ end
58
+ end
59
+
60
+ # Validate --cleanup-cells flag requirements
61
+ if options[:cleanup_cells]
62
+ unless options[:remove_bg]
63
+ raise ValidationError, "--cleanup-cells requires --remove-bg flag"
64
+ end
65
+
66
+ if options[:by_frame]
67
+ raise ValidationError, "--cleanup-cells cannot be used with --by-frame (redundant)"
68
+ end
69
+
70
+ unless options[:video] || options[:batch]
71
+ raise ValidationError, "--cleanup-cells requires --video or --batch mode"
72
+ end
73
+
74
+ if options[:cell_cleanup_threshold]
75
+ unless options[:cell_cleanup_threshold].between?(1.0, 50.0)
76
+ raise ValidationError, "--cell-cleanup-threshold must be between 1.0 and 50.0"
77
+ end
78
+ end
79
+ end
80
+
81
+ # Run processor
82
+ processor = Processor.new(options)
83
+ processor.run
84
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
85
+ puts "Error: #{e.message}"
86
+ puts "\nUse --help for usage information"
87
+ exit 1
88
+ end
89
+
90
+ private
91
+
92
+ def build_option_parser(options)
93
+ OptionParser.new do |opts|
94
+ opts.banner = "Usage: ruby_spriter [options]"
95
+
96
+ add_header(opts)
97
+ add_input_options(opts, options)
98
+ add_spritesheet_options(opts, options)
99
+ add_gimp_options(opts, options)
100
+ add_bg_removal_options(opts, options)
101
+ add_consolidation_options(opts, options)
102
+ add_preset_options(opts, options)
103
+ add_other_options(opts, options)
104
+ end
105
+ end
106
+
107
+ def add_header(opts)
108
+ opts.separator ""
109
+ opts.separator "Ruby Spriter v#{VERSION} - Professional MP4 to Spritesheet Converter with Advanced Image Processing"
110
+ opts.separator "Platform: #{Platform.current.to_s.capitalize}"
111
+ opts.separator ""
112
+ opts.separator "Get mode-specific help:"
113
+ opts.separator " ruby_spriter --video --help"
114
+ opts.separator " ruby_spriter --image --help"
115
+ opts.separator " ruby_spriter --consolidate --help"
116
+ opts.separator " ruby_spriter --batch --help"
117
+ opts.separator " ruby_spriter --split --help"
118
+ opts.separator ""
119
+ end
120
+
121
+ def add_input_options(opts, options)
122
+ opts.separator "Input Options:"
123
+
124
+ opts.on("-v", "--video FILE", "Input video file (MP4)") do |v|
125
+ options[:video] = v
126
+ end
127
+
128
+ opts.on("-i", "--image FILE", "Input image file (PNG) for direct processing") do |i|
129
+ options[:image] = i
130
+ end
131
+
132
+ opts.on("--batch", "Batch process all MP4 files in directory") do
133
+ options[:batch] = true
134
+ end
135
+
136
+ opts.on("--dir DIRECTORY", "Directory for batch processing") do |d|
137
+ options[:dir] = d
138
+ end
139
+
140
+ opts.on("--outputdir DIRECTORY", "Output directory for batch processing") do |d|
141
+ options[:outputdir] = d
142
+ end
143
+
144
+ opts.on("--batch-consolidate", "Consolidate all spritesheets after batch processing") do
145
+ options[:batch_consolidate] = true
146
+ end
147
+
148
+ opts.on("--consolidate [FILES]", Array, "Consolidate spritesheets (comma-separated files or use with --dir)") do |c|
149
+ options[:consolidate_mode] = true
150
+ options[:consolidate] = c if c && !c.empty?
151
+ end
152
+
153
+ opts.on("--verify FILE", "Verify spritesheet metadata") do |v|
154
+ options[:verify] = v
155
+ end
156
+
157
+ opts.on("--split R:C", "Split image into frames (rows:columns, e.g., 4:4)") do |s|
158
+ options[:split] = s
159
+ end
160
+
161
+ opts.on("--override-md", "Override embedded metadata when using --split") do
162
+ options[:override_md] = true
163
+ end
164
+
165
+ opts.on("--extract FRAMES", "Extract specific frames by number (comma-separated, e.g., 1,2,4,5,8)") do |e|
166
+ options[:extract] = e
167
+ end
168
+
169
+ opts.on("--columns NUM", Integer, "Number of columns for extracted spritesheet (default: 4)") do |c|
170
+ options[:columns] = c
171
+ end
172
+
173
+ opts.on("--add-meta R:C", "Add spritesheet metadata to image (rows:columns, e.g., 4:4)") do |m|
174
+ options[:add_meta] = m
175
+ end
176
+
177
+ opts.on("--overwrite-meta", "Overwrite existing metadata when using --add-meta") do
178
+ options[:overwrite_meta] = true
179
+ end
180
+
181
+ opts.separator ""
182
+ end
183
+
184
+ def add_spritesheet_options(opts, options)
185
+ opts.separator "Spritesheet Options:"
186
+
187
+ opts.on("-o", "--output FILE", "Output file path") do |o|
188
+ options[:output] = o
189
+ end
190
+
191
+ opts.on("-f", "--frames COUNT", Integer, "Number of frames to extract (default: 16)") do |f|
192
+ options[:frame_count] = f
193
+ end
194
+
195
+ opts.on("-c", "--columns COUNT", Integer, "Grid columns (default: 4)") do |c|
196
+ options[:columns] = c
197
+ end
198
+
199
+ opts.on("-w", "--width PIXELS", Integer, "Max frame width (default: 320)") do |w|
200
+ options[:max_width] = w
201
+ end
202
+
203
+ opts.on("-b", "--background COLOR", "Tile background: black, white (default: black)") do |b|
204
+ options[:bg_color] = b
205
+ end
206
+
207
+ opts.on("--save-frames", "Save individual frames to disk (for --video or --extract)") do
208
+ options[:save_frames] = true
209
+ end
210
+
211
+ opts.separator ""
212
+ end
213
+
214
+ def add_gimp_options(opts, options)
215
+ opts.separator "Processing Options:"
216
+
217
+ opts.on("-s", "--scale PERCENT", Integer, "Scale image by percentage") do |s|
218
+ options[:scale_percent] = s
219
+ end
220
+
221
+ opts.on("--interpolation METHOD", [:none, :linear, :cubic, :nohalo, :lohalo],
222
+ "Interpolation method: none, linear, cubic, nohalo, lohalo (default: nohalo)") do |i|
223
+ options[:scale_interpolation] = i.to_s
224
+ end
225
+
226
+ opts.on("--sharpen", "Apply unsharp mask after scaling (enhances edges)") do
227
+ options[:sharpen] = true
228
+ end
229
+
230
+ opts.on("--sharpen-radius VALUE", Float, "Sharpen radius in pixels (default: 2.0)") do |r|
231
+ options[:sharpen_radius] = r
232
+ end
233
+
234
+ opts.on("--sharpen-gain VALUE", Float, "Sharpen gain/strength (default: 0.5, range: 0.0-2.0+)") do |g|
235
+ options[:sharpen_gain] = g
236
+ end
237
+
238
+ opts.on("--sharpen-threshold VALUE", Float, "Sharpen threshold as fraction (default: 0.03, range: 0.0-1.0)") do |t|
239
+ options[:sharpen_threshold] = t
240
+ end
241
+
242
+ opts.on("-r", "--remove-bg", "Remove background from spritesheet") do
243
+ options[:remove_bg] = true
244
+ end
245
+
246
+ opts.on("-t", "--threshold VALUE", Float, "Background color tolerance % (default: 15.0, range: 0-100)") do |t|
247
+ options[:bg_threshold] = t
248
+ end
249
+
250
+ opts.on("-g", "--grow PIXELS", Integer, "Pixels to grow selection (default: 1)") do |g|
251
+ options[:grow_selection] = g
252
+ end
253
+
254
+ opts.on("-f", "--feather PIXELS", Float, "Feather selection edges in pixels (default: 0.0, softer edges)") do |f|
255
+ options[:feather_radius] = f
256
+ end
257
+
258
+ opts.separator ""
259
+ end
260
+
261
+ def add_bg_removal_options(opts, options)
262
+ opts.separator "Background Removal Method:"
263
+
264
+ opts.on("--fuzzy", "Use fuzzy select (contiguous regions only)") do
265
+ options[:fuzzy_select] = true
266
+ end
267
+
268
+ opts.on("--no-fuzzy", "Use global color select (all matching pixels) - DEFAULT") do
269
+ options[:fuzzy_select] = false
270
+ end
271
+
272
+ opts.on('--by-frame', 'Remove background from each frame individually (video/batch mode only)') do
273
+ options[:by_frame] = true
274
+ end
275
+
276
+ opts.on('--cleanup-cells', 'Apply cell-based background cleanup (requires --remove-bg, cannot use with --by-frame)') do
277
+ options[:cleanup_cells] = true
278
+ end
279
+
280
+ opts.on('--cell-cleanup-threshold N', Float,
281
+ 'Minimum percentage for dominant color detection (default: 15.0, range: 1.0-50.0)') do |n|
282
+ options[:cell_cleanup_threshold] = n
283
+ end
284
+
285
+ opts.separator ""
286
+ opts.separator "Background Color Sampling (for --no-fuzzy mode):"
287
+
288
+ opts.on("--bg-sample-offset N", Integer, "Distance from edge to start sampling background colors (default: 5)") do |n|
289
+ options[:bg_sample_offset] = n
290
+ end
291
+
292
+ opts.on("--bg-sample-count N", Integer, "Number of unique background colors to collect (default: 10)") do |n|
293
+ options[:bg_sample_count] = n
294
+ end
295
+
296
+ opts.separator ""
297
+ opts.separator "Operation Order:"
298
+
299
+ opts.on("--order ORDER", [:scale_first, :bg_first],
300
+ "Operation order: scale_first or bg_first (default: scale_first)") do |order|
301
+ options[:operation_order] = order == :scale_first ? :scale_then_remove_bg : :remove_bg_then_scale
302
+ end
303
+
304
+ opts.separator ""
305
+ end
306
+
307
+ def add_consolidation_options(opts, options)
308
+ opts.separator "Consolidation Options:"
309
+
310
+ opts.on("--[no-]validate-columns", "Abort if column counts don't match (default: true)") do |v|
311
+ options[:validate_columns] = v
312
+ end
313
+
314
+ opts.separator ""
315
+ end
316
+
317
+ def add_preset_options(opts, options)
318
+ opts.separator "Preset Configurations:"
319
+
320
+ preset_descriptions = PRESETS.map do |name, config|
321
+ " #{name}: #{config[:columns]}×? grid, #{config[:frame_count]} frames, #{config[:max_width]}px wide"
322
+ end.join("\n")
323
+
324
+ opts.on("--preset NAME", String, "Apply preset configuration:",
325
+ *preset_descriptions.split("\n")) do |preset_name|
326
+ preset_key = preset_name.to_sym
327
+ unless PRESETS.key?(preset_key)
328
+ valid_presets = PRESETS.keys.join(', ')
329
+ raise OptionParser::InvalidArgument, "Unknown preset: #{preset_name}. Valid options: #{valid_presets}"
330
+ end
331
+ options.merge!(PRESETS[preset_key])
332
+ end
333
+
334
+ opts.separator ""
335
+ end
336
+
337
+ def add_other_options(opts, options)
338
+ opts.separator "Other Options:"
339
+
340
+ opts.on("--max-compress", "Apply maximum PNG compression to output") do
341
+ options[:max_compress] = true
342
+ end
343
+
344
+ opts.on("--overwrite", "Overwrite existing output files (default: create unique filenames)") do
345
+ options[:overwrite] = true
346
+ end
347
+
348
+ opts.on("--keep-temp", "Keep temporary files for debugging") do
349
+ options[:keep_temp] = true
350
+ end
351
+
352
+ opts.on("--debug", "Enable debug mode (verbose output + keep temp files)") do
353
+ options[:debug] = true
354
+ options[:keep_temp] = true
355
+ end
356
+
357
+ opts.on("-h", "--help", "Show this help message") do
358
+ puts opts
359
+ exit
360
+ end
361
+
362
+ opts.on("--version", "Show version information") do
363
+ puts "Ruby Spriter v#{VERSION}"
364
+ puts "Platform: #{Platform.current.to_s.capitalize}"
365
+ puts "Date: #{VERSION_DATE}"
366
+ exit
367
+ end
368
+
369
+ opts.on("--check-dependencies", "Check if all required external tools are installed") do
370
+ options[:check_dependencies] = true
371
+ end
372
+
373
+ opts.separator ""
374
+ opts.separator "Examples:"
375
+ opts.separator " ruby_spriter --check-dependencies"
376
+ opts.separator " ruby_spriter --video input.mp4"
377
+ opts.separator " ruby_spriter --video input.mp4 --remove-bg --scale 50"
378
+ opts.separator " ruby_spriter --video input.mp4 --scale 50 --interpolation nohalo --sharpen"
379
+ opts.separator " ruby_spriter --video input.mp4 --max-compress"
380
+ opts.separator " ruby_spriter --image sprite.png --scale 50 --sharpen --sharpen-gain 1.5"
381
+ opts.separator " ruby_spriter --image sprite.png --remove-bg"
382
+ opts.separator " ruby_spriter --batch --dir videos/"
383
+ opts.separator " ruby_spriter --batch --dir videos/ --outputdir output/"
384
+ opts.separator " ruby_spriter --batch --dir videos/ --batch-consolidate --max-compress"
385
+ opts.separator " ruby_spriter --consolidate file1.png,file2.png,file3.png"
386
+ opts.separator " ruby_spriter --consolidate --dir spritesheets/"
387
+ opts.separator " ruby_spriter --consolidate --dir spritesheets/ --outputdir output/ --max-compress"
388
+ opts.separator " ruby_spriter --verify spritesheet.png"
389
+ opts.separator ""
390
+ end
391
+
392
+ def handle_context_sensitive_help(args)
393
+ if args.include?('--video') || args.include?('-v')
394
+ show_video_mode_help
395
+ elsif args.include?('--image') || args.include?('-i')
396
+ show_image_mode_help
397
+ elsif args.include?('--consolidate')
398
+ show_consolidate_mode_help
399
+ elsif args.include?('--batch')
400
+ show_batch_mode_help
401
+ elsif args.include?('--split')
402
+ show_split_mode_help
403
+ else
404
+ # Default help - let OptionParser handle it
405
+ return
406
+ end
407
+ end
408
+
409
+ def show_video_mode_help
410
+ puts ""
411
+ puts "Video Mode"
412
+ puts "=" * 60
413
+ puts ""
414
+ puts "Convert MP4 videos to spritesheets with advanced image processing."
415
+ puts ""
416
+ puts "Basic Usage:"
417
+ puts " ruby_spriter --video FILE [options]"
418
+ puts ""
419
+ puts "Required:"
420
+ puts " -v, --video FILE Input video file (MP4)"
421
+ puts ""
422
+ puts "Spritesheet Options:"
423
+ puts " -o, --output FILE Output file path"
424
+ puts " -f, --frames COUNT Number of frames to extract (default: 16)"
425
+ puts " -c, --columns COUNT Grid columns (default: 4)"
426
+ puts " -w, --width PIXELS Max frame width (default: 320)"
427
+ puts " -b, --background COLOR Tile background: black, white (default: black)"
428
+ puts " --save-frames Save individual frames to disk"
429
+ puts ""
430
+ puts "Image Processing:"
431
+ puts " -s, --scale PERCENT Scale image by percentage"
432
+ puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
433
+ puts ""
434
+ puts " --sharpen Apply unsharp mask for edge enhancement"
435
+ puts " --sharpen-radius VALUE └─ Sharpen radius in pixels (default: 2.0)"
436
+ puts " --sharpen-gain VALUE └─ Sharpen gain/strength (default: 0.5)"
437
+ puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
438
+ puts ""
439
+ puts " -r, --remove-bg Remove background"
440
+ puts " --no-fuzzy └─ Use global color select (all matching pixels) - DEFAULT"
441
+ puts " --fuzzy └─ Use fuzzy select (contiguous regions only)"
442
+ puts " --by-frame └─ Remove background from each frame individually (slower, better quality)"
443
+ puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
444
+ puts " -g, --grow PIXELS └─ Grow selection pixels (default: 1)"
445
+ puts ""
446
+ puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
447
+ puts " scale_first or bg_first (default: scale_first)"
448
+ puts ""
449
+ puts "Output Options:"
450
+ puts " --max-compress Apply maximum PNG compression"
451
+ puts " --overwrite Overwrite existing files"
452
+ puts " --keep-temp Keep temporary files"
453
+ puts " --debug Enable debug mode"
454
+ puts ""
455
+ puts "Examples:"
456
+ puts " ruby_spriter --video input.mp4"
457
+ puts " ruby_spriter --video input.mp4 --scale 50 --interpolation nohalo"
458
+ puts " ruby_spriter --video input.mp4 --remove-bg --threshold 0.5"
459
+ puts " ruby_spriter --video input.mp4 --scale 50 --sharpen --max-compress"
460
+ puts ""
461
+ exit
462
+ end
463
+
464
+ def show_image_mode_help
465
+ puts ""
466
+ puts "Image Mode"
467
+ puts "=" * 60
468
+ puts ""
469
+ puts "Process PNG spritesheets with advanced image operations."
470
+ puts ""
471
+ puts "Basic Usage:"
472
+ puts " ruby_spriter --image FILE [options]"
473
+ puts ""
474
+ puts "Required:"
475
+ puts " -i, --image FILE Input image file (PNG)"
476
+ puts ""
477
+ puts "Image Processing:"
478
+ puts " -s, --scale PERCENT Scale image by percentage"
479
+ puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
480
+ puts ""
481
+ puts " --sharpen Apply unsharp mask for edge enhancement"
482
+ puts " --sharpen-radius VALUE └─ Sharpen radius in pixels (default: 2.0)"
483
+ puts " --sharpen-gain VALUE └─ Sharpen gain/strength (default: 0.5)"
484
+ puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
485
+ puts ""
486
+ puts " -r, --remove-bg Remove background"
487
+ puts " --no-fuzzy └─ Use global color select (all matching pixels) - DEFAULT"
488
+ puts " --fuzzy └─ Use fuzzy select (contiguous regions only)"
489
+ puts " --by-frame └─ Remove background from each frame individually (slower, better quality)"
490
+ puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
491
+ puts " -g, --grow PIXELS └─ Grow selection pixels (default: 1)"
492
+ puts ""
493
+ puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
494
+ puts " scale_first or bg_first (default: scale_first)"
495
+ puts ""
496
+ puts "Frame Extraction & Reassembly:"
497
+ puts " --split R:C Split spritesheet into all individual frames (rows:columns)"
498
+ puts " --override-md └─ Override embedded metadata"
499
+ puts ""
500
+ puts " --extract FRAMES Extract specific frames and create new spritesheet (e.g., 1,2,4,5,8)"
501
+ puts " --columns NUM └─ Output grid columns (default: 4)"
502
+ puts " --save-frames └─ Keep individual extracted frames on disk"
503
+ puts ""
504
+ puts "Metadata Management:"
505
+ puts " --add-meta R:C Add spritesheet metadata (rows:columns, e.g., 4:4)"
506
+ puts " --overwrite-meta └─ Replace existing metadata"
507
+ puts " -f, --frames COUNT └─ Custom frame count for partial grids"
508
+ puts ""
509
+ puts "Output Options:"
510
+ puts " -o, --output FILE Output file path"
511
+ puts " --max-compress Apply maximum PNG compression"
512
+ puts " --overwrite Overwrite existing files"
513
+ puts " --keep-temp Keep temporary files"
514
+ puts " --debug Enable debug mode"
515
+ puts ""
516
+ puts "Examples:"
517
+ puts " ruby_spriter --image sprite.png --scale 50 --interpolation nohalo"
518
+ puts " ruby_spriter --image sprite.png --remove-bg --threshold 1.0"
519
+ puts " ruby_spriter --image sprite.png --scale 50 --sharpen --sharpen-gain 1.5"
520
+ puts " ruby_spriter --image sprite.png --split 4:4 --override-md"
521
+ puts " ruby_spriter --image sprite.png --extract 1,2,4,5,8 --columns 3"
522
+ puts " ruby_spriter --image sprite.png --extract 1,1,2,2,3,3 --save-frames"
523
+ puts " ruby_spriter --image sprite.png --add-meta 4:4"
524
+ puts " ruby_spriter --image sprite.png --add-meta 4:4 --frames 14 --output sprite_meta.png"
525
+ puts ""
526
+ exit
527
+ end
528
+
529
+ def show_consolidate_mode_help
530
+ puts ""
531
+ puts "Consolidate Mode"
532
+ puts "=" * 60
533
+ puts ""
534
+ puts "Combine multiple spritesheets into a single consolidated spritesheet."
535
+ puts ""
536
+ puts "Basic Usage:"
537
+ puts " ruby_spriter --consolidate FILE1,FILE2,FILE3 [options]"
538
+ puts " ruby_spriter --consolidate --dir DIRECTORY [options]"
539
+ puts ""
540
+ puts "Input Methods:"
541
+ puts " --consolidate [FILES] Comma-separated list of PNG files"
542
+ puts " --consolidate --dir DIRECTORY Process all spritesheets in directory"
543
+ puts " --[no-]validate-columns └─ Abort if column counts don't match (default: true)"
544
+ puts ""
545
+ puts "Output Options:"
546
+ puts " -o, --output FILE Output file path"
547
+ puts " --outputdir DIRECTORY Output directory (when using --dir)"
548
+ puts " --max-compress Apply maximum PNG compression"
549
+ puts " --overwrite Overwrite existing files"
550
+ puts " --keep-temp Keep temporary files"
551
+ puts " --debug Enable debug mode"
552
+ puts ""
553
+ puts "Requirements:"
554
+ puts " - All input files must be PNG spritesheets with embedded metadata"
555
+ puts " - Column counts must match across all spritesheets (unless --no-validate-columns)"
556
+ puts " - Minimum 2 spritesheets required"
557
+ puts ""
558
+ puts "Examples:"
559
+ puts " ruby_spriter --consolidate file1.png,file2.png,file3.png"
560
+ puts " ruby_spriter --consolidate --dir spritesheets/"
561
+ puts " ruby_spriter --consolidate --dir spritesheets/ --outputdir output/"
562
+ puts " ruby_spriter --consolidate --dir spritesheets/ --no-validate-columns"
563
+ puts ""
564
+ exit
565
+ end
566
+
567
+ def show_batch_mode_help
568
+ puts ""
569
+ puts "Batch Mode"
570
+ puts "=" * 60
571
+ puts ""
572
+ puts "Process multiple MP4 videos in a directory with consistent options."
573
+ puts ""
574
+ puts "Basic Usage:"
575
+ puts " ruby_spriter --batch --dir DIRECTORY [options]"
576
+ puts ""
577
+ puts "Required:"
578
+ puts " --batch Enable batch processing mode"
579
+ puts " --dir DIRECTORY Directory containing MP4 files"
580
+ puts ""
581
+ puts "Batch Options:"
582
+ puts " --outputdir DIRECTORY Output directory (default: same as input dir)"
583
+ puts " --batch-consolidate Consolidate all results into single spritesheet"
584
+ puts ""
585
+ puts "Spritesheet Options (applied to all videos):"
586
+ puts " -f, --frames COUNT Number of frames to extract (default: 16)"
587
+ puts " -c, --columns COUNT Grid columns (default: 4)"
588
+ puts " -w, --width PIXELS Max frame width (default: 320)"
589
+ puts " -b, --background COLOR Tile background: black, white (default: black)"
590
+ puts " --save-frames Save individual frames to disk"
591
+ puts ""
592
+ puts "Image Processing (applied to all videos):"
593
+ puts " -s, --scale PERCENT Scale images by percentage"
594
+ puts " --interpolation METHOD └─ Interpolation: none, linear, cubic, nohalo, lohalo (default: nohalo)"
595
+ puts ""
596
+ puts " --sharpen Apply unsharp mask for edge enhancement"
597
+ puts " --sharpen-radius VALUE └─ Sharpen radius (default: 2.0)"
598
+ puts " --sharpen-gain VALUE └─ Sharpen gain (default: 0.5)"
599
+ puts " --sharpen-threshold VALUE └─ Sharpen threshold (default: 0.03)"
600
+ puts ""
601
+ puts " -r, --remove-bg Remove background"
602
+ puts " --no-fuzzy └─ Use global color select - DEFAULT"
603
+ puts " --fuzzy └─ Use fuzzy select (contiguous only)"
604
+ puts " --by-frame └─ Remove background from each frame individually (slower, better quality)"
605
+ puts " -t, --threshold VALUE └─ Feather radius (default: 0.0)"
606
+ puts " -g, --grow PIXELS └─ Grow selection (default: 1)"
607
+ puts ""
608
+ puts " --order ORDER Operation order when using BOTH --scale AND --remove-bg:"
609
+ puts " scale_first or bg_first (default: scale_first)"
610
+ puts ""
611
+ puts "Output Options:"
612
+ puts " --max-compress Apply maximum PNG compression"
613
+ puts " --overwrite Overwrite existing files"
614
+ puts " --keep-temp Keep temporary files"
615
+ puts " --debug Enable debug mode"
616
+ puts ""
617
+ puts "Behavior:"
618
+ puts " - Processes all MP4 files in the specified directory"
619
+ puts " - Enforces unique filenames unless --overwrite is specified"
620
+ puts " - Continues processing remaining videos if one fails"
621
+ puts " - Provides summary of successes and failures"
622
+ puts ""
623
+ puts "Examples:"
624
+ puts " ruby_spriter --batch --dir videos/"
625
+ puts " ruby_spriter --batch --dir videos/ --outputdir output/"
626
+ puts " ruby_spriter --batch --dir videos/ --scale 50 --sharpen"
627
+ puts " ruby_spriter --batch --dir videos/ --batch-consolidate --max-compress"
628
+ puts ""
629
+ exit
630
+ end
631
+
632
+ def show_split_mode_help
633
+ puts ""
634
+ puts "Split Mode"
635
+ puts "=" * 60
636
+ puts ""
637
+ puts "Extract individual frames from a spritesheet (requires --image)."
638
+ puts ""
639
+ puts "Basic Usage:"
640
+ puts " ruby_spriter --image FILE --split R:C [options]"
641
+ puts ""
642
+ puts "Required:"
643
+ puts " -i, --image FILE Input spritesheet file (PNG)"
644
+ puts " --split R:C Split format: rows:columns (e.g., 4:4)"
645
+ puts " --override-md └─ Override embedded metadata"
646
+ puts ""
647
+ puts "Format Requirements:"
648
+ puts " - Rows and columns must be 1-99"
649
+ puts " - Total frames (R × C) must be < 1000"
650
+ puts " - Image dimensions must divide evenly by rows and columns"
651
+ puts ""
652
+ puts "Metadata Behavior:"
653
+ puts " - If spritesheet has embedded metadata, it will be used automatically"
654
+ puts " - Use --override-md to ignore embedded metadata and use --split value"
655
+ puts " - If no metadata exists, --split value is required"
656
+ puts ""
657
+ puts "Output Options:"
658
+ puts " -o, --output FILE Output directory (default: filename_frames/)"
659
+ puts " --overwrite Overwrite existing files"
660
+ puts " --keep-temp Keep temporary files"
661
+ puts " --debug Enable debug mode"
662
+ puts ""
663
+ puts "Output:"
664
+ puts " - Frames are saved as: FR001_filename.png, FR002_filename.png, etc."
665
+ puts " - Frame naming uses 3-digit zero-padded format (FR001-FR999)"
666
+ puts " - Output directory: filename_frames/ (unless --output specified)"
667
+ puts ""
668
+ puts "Examples:"
669
+ puts " ruby_spriter --image sprite.png --split 4:4"
670
+ puts " ruby_spriter --image sprite.png --split 8:8 --override-md"
671
+ puts " ruby_spriter --image sprite.png --split 2:5 --output frames/"
672
+ puts ""
673
+ exit
674
+ end
675
+ end
676
+ end