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,65 +1,65 @@
1
- # frozen_string_literal: true
2
-
3
- module RubySpriter
4
- module Utils
5
- # Console output formatting utilities
6
- class OutputFormatter
7
- ICONS = {
8
- success: '✅',
9
- error: '❌',
10
- warning: '⚠️',
11
- info: 'ℹ️',
12
- clean: '🧹',
13
- note: '📝'
14
- }.freeze
15
-
16
- class << self
17
- # Print section header
18
- # @param title [String] Section title
19
- # @param width [Integer] Header width
20
- def header(title, width = 60)
21
- puts "\n" + "=" * width
22
- puts title
23
- puts "=" * width + "\n"
24
- end
25
-
26
- # Print success message
27
- # @param message [String] Message to print
28
- def success(message)
29
- puts "#{ICONS[:success]} #{message}"
30
- end
31
-
32
- # Print error message
33
- # @param message [String] Message to print
34
- def error(message)
35
- puts "#{ICONS[:error]} #{message}"
36
- end
37
-
38
- # Print warning message
39
- # @param message [String] Message to print
40
- def warning(message)
41
- puts "#{ICONS[:warning]} #{message}"
42
- end
43
-
44
- # Print info message
45
- # @param message [String] Message to print
46
- def info(message)
47
- puts "#{ICONS[:info]} #{message}"
48
- end
49
-
50
- # Print note message
51
- # @param message [String] Message to print
52
- def note(message)
53
- puts "#{ICONS[:note]} #{message}"
54
- end
55
-
56
- # Print indented message
57
- # @param message [String] Message to print
58
- # @param indent [Integer] Number of spaces to indent
59
- def indent(message, indent = 6)
60
- puts " " * indent + message
61
- end
62
- end
63
- end
64
- end
65
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubySpriter
4
+ module Utils
5
+ # Console output formatting utilities
6
+ class OutputFormatter
7
+ ICONS = {
8
+ success: '✅',
9
+ error: '❌',
10
+ warning: '⚠️',
11
+ info: 'ℹ️',
12
+ clean: '🧹',
13
+ note: '📝'
14
+ }.freeze
15
+
16
+ class << self
17
+ # Print section header
18
+ # @param title [String] Section title
19
+ # @param width [Integer] Header width
20
+ def header(title, width = 60)
21
+ puts "\n" + "=" * width
22
+ puts title
23
+ puts "=" * width + "\n"
24
+ end
25
+
26
+ # Print success message
27
+ # @param message [String] Message to print
28
+ def success(message)
29
+ puts "#{ICONS[:success]} #{message}"
30
+ end
31
+
32
+ # Print error message
33
+ # @param message [String] Message to print
34
+ def error(message)
35
+ puts "#{ICONS[:error]} #{message}"
36
+ end
37
+
38
+ # Print warning message
39
+ # @param message [String] Message to print
40
+ def warning(message)
41
+ puts "#{ICONS[:warning]} #{message}"
42
+ end
43
+
44
+ # Print info message
45
+ # @param message [String] Message to print
46
+ def info(message)
47
+ puts "#{ICONS[:info]} #{message}"
48
+ end
49
+
50
+ # Print note message
51
+ # @param message [String] Message to print
52
+ def note(message)
53
+ puts "#{ICONS[:note]} #{message}"
54
+ end
55
+
56
+ # Print indented message
57
+ # @param message [String] Message to print
58
+ # @param indent [Integer] Number of spaces to indent
59
+ def indent(message, indent = 6)
60
+ puts " " * indent + message
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,59 +1,59 @@
1
- # frozen_string_literal: true
2
-
3
- module RubySpriter
4
- module Utils
5
- # Cross-platform path handling utilities
6
- class PathHelper
7
- class << self
8
- # Quote a file path for shell execution
9
- # @param path [String] The path to quote
10
- # @return [String] Properly quoted path
11
- def quote_path(path)
12
- if Platform.windows?
13
- "\"#{path}\""
14
- else
15
- # Need 4 backslashes because gsub interprets backslashes in replacement string
16
- "'#{path.gsub("'", "\\\\'")}'"
17
- end
18
- end
19
-
20
- # Quote a command argument for shell execution
21
- # @param arg [String] The argument to quote
22
- # @return [String] Properly quoted argument
23
- def quote_arg(arg)
24
- if Platform.windows?
25
- "\"#{arg}\""
26
- else
27
- # Need 4 backslashes because gsub interprets backslashes in replacement string
28
- "'#{arg.gsub("'", "\\\\'")}'"
29
- end
30
- end
31
-
32
- # Normalize path for Python scripts (GIMP)
33
- # @param path [String] The path to normalize
34
- # @return [String] Normalized path with forward slashes
35
- def normalize_for_python(path)
36
- abs_path = File.absolute_path(path)
37
-
38
- if Platform.windows?
39
- # Use forward slashes for Python raw strings
40
- abs_path.gsub('\\', '/')
41
- else
42
- abs_path
43
- end
44
- end
45
-
46
- # Convert path to native format
47
- # @param path [String] The path to convert
48
- # @return [String] Path with platform-appropriate separators
49
- def to_native(path)
50
- if Platform.windows?
51
- path.gsub('/', '\\')
52
- else
53
- path.gsub('\\', '/')
54
- end
55
- end
56
- end
57
- end
58
- end
59
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubySpriter
4
+ module Utils
5
+ # Cross-platform path handling utilities
6
+ class PathHelper
7
+ class << self
8
+ # Quote a file path for shell execution
9
+ # @param path [String] The path to quote
10
+ # @return [String] Properly quoted path
11
+ def quote_path(path)
12
+ if Platform.windows?
13
+ "\"#{path}\""
14
+ else
15
+ # Need 4 backslashes because gsub interprets backslashes in replacement string
16
+ "'#{path.gsub("'", "\\\\'")}'"
17
+ end
18
+ end
19
+
20
+ # Quote a command argument for shell execution
21
+ # @param arg [String] The argument to quote
22
+ # @return [String] Properly quoted argument
23
+ def quote_arg(arg)
24
+ if Platform.windows?
25
+ "\"#{arg}\""
26
+ else
27
+ # Need 4 backslashes because gsub interprets backslashes in replacement string
28
+ "'#{arg.gsub("'", "\\\\'")}'"
29
+ end
30
+ end
31
+
32
+ # Normalize path for Python scripts (GIMP)
33
+ # @param path [String] The path to normalize
34
+ # @return [String] Normalized path with forward slashes
35
+ def normalize_for_python(path)
36
+ abs_path = File.absolute_path(path)
37
+
38
+ if Platform.windows?
39
+ # Use forward slashes for Python raw strings
40
+ abs_path.gsub('\\', '/')
41
+ else
42
+ abs_path
43
+ end
44
+ end
45
+
46
+ # Convert path to native format
47
+ # @param path [String] The path to convert
48
+ # @return [String] Path with platform-appropriate separators
49
+ def to_native(path)
50
+ if Platform.windows?
51
+ path.gsub('/', '\\')
52
+ else
53
+ path.gsub('\\', '/')
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,86 +1,86 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'fileutils'
5
-
6
- module RubySpriter
7
- module Utils
8
- # Splits a spritesheet into individual frame images
9
- class SpritesheetSplitter
10
- # Split spritesheet into individual frames
11
- # @param spritesheet_file [String] Path to spritesheet PNG
12
- # @param output_dir [String] Directory to save individual frames
13
- # @param columns [Integer] Number of columns in grid
14
- # @param rows [Integer] Number of rows in grid
15
- # @param frames [Integer] Total number of frames to extract
16
- def split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
17
- FileUtils.mkdir_p(output_dir)
18
-
19
- OutputFormatter.header("Extracting Frames")
20
- OutputFormatter.indent("Splitting spritesheet into #{frames} frames to disk...")
21
- OutputFormatter.indent("Output directory: #{output_dir}")
22
-
23
- # Get spritesheet dimensions
24
- dimensions = get_image_dimensions(spritesheet_file)
25
- tile_width = dimensions[:width] / columns
26
- tile_height = dimensions[:height] / rows
27
-
28
- # Extract each frame
29
- spritesheet_basename = File.basename(spritesheet_file, '.*')
30
-
31
- frames.times do |i|
32
- frame_number = i + 1
33
- row = i / columns
34
- col = i % columns
35
-
36
- x_offset = col * tile_width
37
- y_offset = row * tile_height
38
-
39
- frame_filename = "FR#{format('%03d', frame_number)}_#{spritesheet_basename}.png"
40
- frame_path = File.join(output_dir, frame_filename)
41
-
42
- extract_tile(spritesheet_file, frame_path, tile_width, tile_height, x_offset, y_offset)
43
- end
44
-
45
- OutputFormatter.indent("✅ Frames extracted successfully\n")
46
- end
47
-
48
- private
49
-
50
- def get_image_dimensions(image_file)
51
- cmd = [
52
- 'magick',
53
- 'identify',
54
- '-format', '%wx%h',
55
- PathHelper.quote_path(image_file)
56
- ].join(' ')
57
-
58
- stdout, stderr, status = Open3.capture3(cmd)
59
-
60
- unless status.success?
61
- raise ProcessingError, "Could not get image dimensions: #{stderr}"
62
- end
63
-
64
- width, height = stdout.strip.split('x').map(&:to_i)
65
- { width: width, height: height }
66
- end
67
-
68
- def extract_tile(source_file, output_file, width, height, x_offset, y_offset)
69
- cmd = [
70
- 'magick',
71
- 'convert',
72
- PathHelper.quote_path(source_file),
73
- '-crop', "#{width}x#{height}+#{x_offset}+#{y_offset}",
74
- '+repage',
75
- PathHelper.quote_path(output_file)
76
- ].join(' ')
77
-
78
- stdout, stderr, status = Open3.capture3(cmd)
79
-
80
- unless status.success?
81
- raise ProcessingError, "Could not extract frame: #{stderr}"
82
- end
83
- end
84
- end
85
- end
86
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'fileutils'
5
+
6
+ module RubySpriter
7
+ module Utils
8
+ # Splits a spritesheet into individual frame images
9
+ class SpritesheetSplitter
10
+ # Split spritesheet into individual frames
11
+ # @param spritesheet_file [String] Path to spritesheet PNG
12
+ # @param output_dir [String] Directory to save individual frames
13
+ # @param columns [Integer] Number of columns in grid
14
+ # @param rows [Integer] Number of rows in grid
15
+ # @param frames [Integer] Total number of frames to extract
16
+ def split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
17
+ FileUtils.mkdir_p(output_dir)
18
+
19
+ OutputFormatter.header("Extracting Frames")
20
+ OutputFormatter.indent("Splitting spritesheet into #{frames} frames to disk...")
21
+ OutputFormatter.indent("Output directory: #{output_dir}")
22
+
23
+ # Get spritesheet dimensions
24
+ dimensions = get_image_dimensions(spritesheet_file)
25
+ tile_width = dimensions[:width] / columns
26
+ tile_height = dimensions[:height] / rows
27
+
28
+ # Extract each frame
29
+ spritesheet_basename = File.basename(spritesheet_file, '.*')
30
+
31
+ frames.times do |i|
32
+ frame_number = i + 1
33
+ row = i / columns
34
+ col = i % columns
35
+
36
+ x_offset = col * tile_width
37
+ y_offset = row * tile_height
38
+
39
+ frame_filename = "FR#{format('%03d', frame_number)}_#{spritesheet_basename}.png"
40
+ frame_path = File.join(output_dir, frame_filename)
41
+
42
+ extract_tile(spritesheet_file, frame_path, tile_width, tile_height, x_offset, y_offset)
43
+ end
44
+
45
+ OutputFormatter.indent("✅ Frames extracted successfully\n")
46
+ end
47
+
48
+ private
49
+
50
+ def get_image_dimensions(image_file)
51
+ cmd = [
52
+ 'magick',
53
+ 'identify',
54
+ '-format', '%wx%h',
55
+ PathHelper.quote_path(image_file)
56
+ ].join(' ')
57
+
58
+ stdout, stderr, status = Open3.capture3(cmd)
59
+
60
+ unless status.success?
61
+ raise ProcessingError, "Could not get image dimensions: #{stderr}"
62
+ end
63
+
64
+ width, height = stdout.strip.split('x').map(&:to_i)
65
+ { width: width, height: height }
66
+ end
67
+
68
+ def extract_tile(source_file, output_file, width, height, x_offset, y_offset)
69
+ cmd = [
70
+ 'magick',
71
+ 'convert',
72
+ PathHelper.quote_path(source_file),
73
+ '-crop', "#{width}x#{height}+#{x_offset}+#{y_offset}",
74
+ '+repage',
75
+ PathHelper.quote_path(output_file)
76
+ ].join(' ')
77
+
78
+ stdout, stderr, status = Open3.capture3(cmd)
79
+
80
+ unless status.success?
81
+ raise ProcessingError, "Could not extract frame: #{stderr}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,7 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
- module RubySpriter
4
- VERSION = '0.6.7'
5
- VERSION_DATE = '2025-10-24'
6
- METADATA_VERSION = '0.6'
7
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubySpriter
4
+ VERSION = '0.7.0.1'
5
+ VERSION_DATE = '2025-11-06'
6
+ end