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,116 +1,117 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
-
5
- module RubySpriter
6
- # Manages PNG metadata for spritesheets
7
- class MetadataManager
8
- METADATA_PREFIX = 'SPRITESHEET'
9
-
10
- # Embed metadata into PNG file
11
- # @param input_file [String] Source PNG file
12
- # @param output_file [String] Destination PNG file with metadata
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
16
- # @param debug [Boolean] Enable debug output
17
- def self.embed(input_file, output_file, columns:, rows:, frames:, debug: false)
18
- Utils::FileHelper.validate_readable!(input_file)
19
-
20
- metadata_str = build_metadata_string(columns, rows, frames)
21
-
22
- cmd = build_embed_command(input_file, output_file, metadata_str)
23
-
24
- if debug
25
- Utils::OutputFormatter.indent("DEBUG: Metadata command: #{cmd}")
26
- end
27
-
28
- stdout, stderr, status = Open3.capture3(cmd)
29
-
30
- unless status.success?
31
- raise ProcessingError, "Failed to embed metadata: #{stderr}"
32
- end
33
-
34
- Utils::FileHelper.validate_exists!(output_file)
35
- end
36
-
37
- # Read metadata from PNG file
38
- # @param file [String] PNG file path
39
- # @return [Hash, nil] Metadata hash or nil if not found
40
- def self.read(file)
41
- Utils::FileHelper.validate_readable!(file)
42
-
43
- cmd = build_read_command(file)
44
- stdout, stderr, status = Open3.capture3(cmd)
45
-
46
- return nil unless status.success?
47
-
48
- parse_metadata(stdout)
49
- end
50
-
51
- # Verify and print metadata from file
52
- # @param file [String] PNG file path
53
- def self.verify(file)
54
- Utils::OutputFormatter.header("Spritesheet Metadata Verification")
55
-
56
- puts "File: #{file}"
57
- puts "Size: #{Utils::FileHelper.format_size(File.size(file))}\n\n"
58
-
59
- metadata = read(file)
60
-
61
- if metadata
62
- Utils::OutputFormatter.success("Metadata Found")
63
- puts "\n Grid Layout:"
64
- Utils::OutputFormatter.indent("Columns: #{metadata[:columns]}")
65
- Utils::OutputFormatter.indent("Rows: #{metadata[:rows]}")
66
- Utils::OutputFormatter.indent("Total Frames: #{metadata[:frames]}")
67
- Utils::OutputFormatter.indent("Metadata Version: #{metadata[:version]}")
68
- else
69
- Utils::OutputFormatter.warning("No spritesheet metadata found in this file")
70
- puts "\nThis file may not have been created by Ruby Spriter,"
71
- puts "or the metadata was stripped during processing."
72
- end
73
-
74
- puts "\n" + "=" * 60 + "\n"
75
- end
76
-
77
- private_class_method def self.build_metadata_string(columns, rows, frames)
78
- "#{METADATA_PREFIX}|columns=#{columns}|rows=#{rows}|frames=#{frames}|version=#{METADATA_VERSION}"
79
- end
80
-
81
- private_class_method def self.build_embed_command(input_file, output_file, metadata_str)
82
- magick_cmd = Platform.imagemagick_convert_cmd
83
-
84
- [
85
- magick_cmd,
86
- Utils::PathHelper.quote_path(input_file),
87
- '-set', 'comment', Utils::PathHelper.quote_arg(metadata_str),
88
- Utils::PathHelper.quote_path(output_file)
89
- ].join(' ')
90
- end
91
-
92
- private_class_method def self.build_read_command(file)
93
- magick_cmd = Platform.imagemagick_identify_cmd
94
-
95
- [
96
- magick_cmd,
97
- '-format', Utils::PathHelper.quote_arg('%c'),
98
- Utils::PathHelper.quote_path(file)
99
- ].join(' ')
100
- end
101
-
102
- private_class_method def self.parse_metadata(output)
103
- # Look for SPRITESHEET metadata pattern
104
- match = output.match(/#{METADATA_PREFIX}\|columns=(\d+)\|rows=(\d+)\|frames=(\d+)\|version=([\d.]+)/)
105
-
106
- return nil unless match
107
-
108
- {
109
- columns: match[1].to_i,
110
- rows: match[2].to_i,
111
- frames: match[3].to_i,
112
- version: match[4]
113
- }
114
- end
115
- end
116
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module RubySpriter
6
+ # Manages PNG metadata for spritesheets
7
+ class MetadataManager
8
+ METADATA_PREFIX = 'SPRITESHEET'
9
+ METADATA_VERSION = RubySpriter::VERSION
10
+
11
+ # Embed metadata into PNG file
12
+ # @param input_file [String] Source PNG file
13
+ # @param output_file [String] Destination PNG file with metadata
14
+ # @param columns [Integer] Number of columns in grid
15
+ # @param rows [Integer] Number of rows in grid
16
+ # @param frames [Integer] Total number of frames
17
+ # @param debug [Boolean] Enable debug output
18
+ def self.embed(input_file, output_file, columns:, rows:, frames:, debug: false)
19
+ Utils::FileHelper.validate_readable!(input_file)
20
+
21
+ metadata_str = build_metadata_string(columns, rows, frames)
22
+
23
+ cmd = build_embed_command(input_file, output_file, metadata_str)
24
+
25
+ if debug
26
+ Utils::OutputFormatter.indent("DEBUG: Metadata command: #{cmd}")
27
+ end
28
+
29
+ stdout, stderr, status = Open3.capture3(cmd)
30
+
31
+ unless status.success?
32
+ raise ProcessingError, "Failed to embed metadata: #{stderr}"
33
+ end
34
+
35
+ Utils::FileHelper.validate_exists!(output_file)
36
+ end
37
+
38
+ # Read metadata from PNG file
39
+ # @param file [String] PNG file path
40
+ # @return [Hash, nil] Metadata hash or nil if not found
41
+ def self.read(file)
42
+ Utils::FileHelper.validate_readable!(file)
43
+
44
+ cmd = build_read_command(file)
45
+ stdout, stderr, status = Open3.capture3(cmd)
46
+
47
+ return nil unless status.success?
48
+
49
+ parse_metadata(stdout)
50
+ end
51
+
52
+ # Verify and print metadata from file
53
+ # @param file [String] PNG file path
54
+ def self.verify(file)
55
+ Utils::OutputFormatter.header("Spritesheet Metadata Verification")
56
+
57
+ puts "File: #{file}"
58
+ puts "Size: #{Utils::FileHelper.format_size(File.size(file))}\n\n"
59
+
60
+ metadata = read(file)
61
+
62
+ if metadata
63
+ Utils::OutputFormatter.success("Metadata Found")
64
+ puts "\n Grid Layout:"
65
+ Utils::OutputFormatter.indent("Columns: #{metadata[:columns]}")
66
+ Utils::OutputFormatter.indent("Rows: #{metadata[:rows]}")
67
+ Utils::OutputFormatter.indent("Total Frames: #{metadata[:frames]}")
68
+ Utils::OutputFormatter.indent("Metadata Version: #{metadata[:version]}")
69
+ else
70
+ Utils::OutputFormatter.warning("No spritesheet metadata found in this file")
71
+ puts "\nThis file may not have been created by Ruby Spriter,"
72
+ puts "or the metadata was stripped during processing."
73
+ end
74
+
75
+ puts "\n" + "=" * 60 + "\n"
76
+ end
77
+
78
+ private_class_method def self.build_metadata_string(columns, rows, frames)
79
+ "#{METADATA_PREFIX}|columns=#{columns}|rows=#{rows}|frames=#{frames}|version=#{METADATA_VERSION}"
80
+ end
81
+
82
+ private_class_method def self.build_embed_command(input_file, output_file, metadata_str)
83
+ magick_cmd = Platform.imagemagick_convert_cmd
84
+
85
+ [
86
+ magick_cmd,
87
+ Utils::PathHelper.quote_path(input_file),
88
+ '-set', 'comment', Utils::PathHelper.quote_arg(metadata_str),
89
+ Utils::PathHelper.quote_path(output_file)
90
+ ].join(' ')
91
+ end
92
+
93
+ private_class_method def self.build_read_command(file)
94
+ magick_cmd = Platform.imagemagick_identify_cmd
95
+
96
+ [
97
+ magick_cmd,
98
+ '-format', Utils::PathHelper.quote_arg('%c'),
99
+ Utils::PathHelper.quote_path(file)
100
+ ].join(' ')
101
+ end
102
+
103
+ private_class_method def self.parse_metadata(output)
104
+ # Look for SPRITESHEET metadata pattern
105
+ match = output.match(/#{METADATA_PREFIX}\|columns=(\d+)\|rows=(\d+)\|frames=(\d+)\|version=([\d.]+)/)
106
+
107
+ return nil unless match
108
+
109
+ {
110
+ columns: match[1].to_i,
111
+ rows: match[2].to_i,
112
+ frames: match[3].to_i,
113
+ version: match[4]
114
+ }
115
+ end
116
+ end
117
+ end
@@ -1,82 +1,137 @@
1
- # frozen_string_literal: true
2
-
3
- module RubySpriter
4
- # Platform detection and configuration
5
- class Platform
6
- PLATFORM_TYPE = case RUBY_PLATFORM
7
- when /mingw|mswin|windows/i then :windows
8
- when /linux/i then :linux
9
- when /darwin/i then :macos
10
- else :unknown
11
- end
12
-
13
- # GIMP executable paths by platform
14
- GIMP_DEFAULT_PATHS = {
15
- windows: 'C:\\Program Files\\GIMP 3\\bin\\gimp-console-3.0.exe',
16
- linux: '/usr/bin/gimp',
17
- macos: '/Applications/GIMP.app/Contents/MacOS/gimp'
18
- }.freeze
19
-
20
- # Alternative GIMP paths to search
21
- GIMP_ALTERNATIVE_PATHS = {
22
- windows: [
23
- 'C:\\Program Files\\GIMP 3\\bin\\gimp-console-3.0.exe',
24
- 'C:\\Program Files (x86)\\GIMP 3\\bin\\gimp-console-3.0.exe',
25
- 'C:\\Program Files\\GIMP 2\\bin\\gimp-console-2.10.exe',
26
- 'C:\\Program Files (x86)\\GIMP 2\\bin\\gimp-console-2.10.exe'
27
- ].freeze,
28
- linux: [
29
- '/usr/bin/gimp',
30
- '/usr/local/bin/gimp',
31
- '/snap/bin/gimp',
32
- '/opt/gimp/bin/gimp'
33
- ].freeze,
34
- macos: [
35
- '/Applications/GIMP.app/Contents/MacOS/gimp',
36
- '/Applications/GIMP-2.10.app/Contents/MacOS/gimp'
37
- ].freeze
38
- }.freeze
39
-
40
- class << self
41
- # Get the current platform type
42
- def current
43
- PLATFORM_TYPE
44
- end
45
-
46
- # Check if running on Windows
47
- def windows?
48
- PLATFORM_TYPE == :windows
49
- end
50
-
51
- # Check if running on Linux
52
- def linux?
53
- PLATFORM_TYPE == :linux
54
- end
55
-
56
- # Check if running on macOS
57
- def macos?
58
- PLATFORM_TYPE == :macos
59
- end
60
-
61
- # Get default GIMP path for current platform
62
- def default_gimp_path
63
- GIMP_DEFAULT_PATHS[PLATFORM_TYPE]
64
- end
65
-
66
- # Get alternative GIMP paths for current platform
67
- def alternative_gimp_paths
68
- GIMP_ALTERNATIVE_PATHS[PLATFORM_TYPE] || []
69
- end
70
-
71
- # Get ImageMagick convert command name
72
- def imagemagick_convert_cmd
73
- windows? ? 'magick convert' : 'convert'
74
- end
75
-
76
- # Get ImageMagick identify command name
77
- def imagemagick_identify_cmd
78
- windows? ? 'magick identify' : 'identify'
79
- end
80
- end
81
- end
82
- end
1
+ # frozen_string_literal: true
2
+
3
+ module RubySpriter
4
+ # Platform detection and configuration
5
+ class Platform
6
+ PLATFORM_TYPE = case RUBY_PLATFORM
7
+ when /mingw|mswin|windows/i then :windows
8
+ when /linux/i then :linux
9
+ when /darwin/i then :macos
10
+ else :unknown
11
+ end
12
+
13
+ # GIMP executable paths by platform
14
+ GIMP_DEFAULT_PATHS = {
15
+ windows: 'C:\\Program Files\\GIMP 3\\bin\\gimp-console-3.0.exe',
16
+ linux: '/usr/bin/gimp',
17
+ macos: '/Applications/GIMP.app/Contents/MacOS/gimp'
18
+ }.freeze
19
+
20
+ # Alternative GIMP paths to search
21
+ GIMP_ALTERNATIVE_PATHS = {
22
+ windows: [
23
+ 'C:\\Program Files\\GIMP 3\\bin\\gimp-console-3.0.exe',
24
+ 'C:\\Program Files (x86)\\GIMP 3\\bin\\gimp-console-3.0.exe',
25
+ 'C:\\Program Files\\GIMP 2\\bin\\gimp-console-2.10.exe',
26
+ 'C:\\Program Files (x86)\\GIMP 2\\bin\\gimp-console-2.10.exe'
27
+ ].freeze,
28
+ linux: [
29
+ '/usr/bin/gimp',
30
+ '/usr/local/bin/gimp',
31
+ '/snap/bin/gimp',
32
+ '/opt/gimp/bin/gimp',
33
+ 'flatpak:org.gimp.GIMP' # Flatpak GIMP
34
+ ].freeze,
35
+ macos: [
36
+ '/Applications/GIMP.app/Contents/MacOS/gimp',
37
+ '/Applications/GIMP-2.99.app/Contents/MacOS/gimp', # GIMP 3.x dev
38
+ '/Applications/GIMP-3.0.app/Contents/MacOS/gimp', # GIMP 3.x release
39
+ '/Applications/GIMP-2.10.app/Contents/MacOS/gimp'
40
+ ].freeze
41
+ }.freeze
42
+
43
+ class << self
44
+ # Get the current platform type
45
+ def current
46
+ PLATFORM_TYPE
47
+ end
48
+
49
+ # Check if running on Windows
50
+ def windows?
51
+ PLATFORM_TYPE == :windows
52
+ end
53
+
54
+ # Check if running on Linux
55
+ def linux?
56
+ PLATFORM_TYPE == :linux
57
+ end
58
+
59
+ # Check if running on macOS
60
+ def macos?
61
+ PLATFORM_TYPE == :macos
62
+ end
63
+
64
+ # Get default GIMP path for current platform
65
+ def default_gimp_path
66
+ GIMP_DEFAULT_PATHS[PLATFORM_TYPE]
67
+ end
68
+
69
+ # Get alternative GIMP paths for current platform
70
+ def alternative_gimp_paths
71
+ GIMP_ALTERNATIVE_PATHS[PLATFORM_TYPE] || []
72
+ end
73
+
74
+ # Get ImageMagick convert command name
75
+ def imagemagick_convert_cmd
76
+ windows? ? 'magick convert' : 'convert'
77
+ end
78
+
79
+ # Get ImageMagick identify command name
80
+ def imagemagick_identify_cmd
81
+ windows? ? 'magick identify' : 'identify'
82
+ end
83
+
84
+ # Detect GIMP version from version string output
85
+ # @param version_output [String] Output from gimp --version command
86
+ # @return [Hash] Version information with :major, :minor, :patch, :full keys, or nil if parse fails
87
+ def detect_gimp_version(version_output)
88
+ return nil if version_output.nil? || version_output.empty?
89
+
90
+ # Match version pattern: "version X.Y.Z" or "version X.Y"
91
+ match = version_output.match(/version\s+(\d+)\.(\d+)(?:\.(\d+))?/i)
92
+ return nil unless match
93
+
94
+ {
95
+ major: match[1].to_i,
96
+ minor: match[2].to_i,
97
+ patch: match[3]&.to_i || 0,
98
+ full: match[1..3].compact.join('.')
99
+ }
100
+ end
101
+
102
+ # Get GIMP version from executable path
103
+ # @param gimp_path [String] Path to GIMP executable or flatpak:app.id
104
+ # @return [Hash] Version information, or nil if detection fails
105
+ def get_gimp_version(gimp_path)
106
+ return nil if gimp_path.nil? || gimp_path.empty?
107
+
108
+ require 'open3'
109
+
110
+ # Handle Flatpak GIMP
111
+ if gimp_path.start_with?('flatpak:')
112
+ flatpak_app = gimp_path.sub('flatpak:', '')
113
+ stdout, stderr, status = Open3.capture3("flatpak run #{flatpak_app} --version")
114
+ return nil unless status.success?
115
+ return detect_gimp_version(stdout + stderr)
116
+ end
117
+
118
+ return nil unless File.exist?(gimp_path)
119
+
120
+ stdout, stderr, status = Open3.capture3("#{quote_path_simple(gimp_path)} --version")
121
+ return nil unless status.success?
122
+
123
+ detect_gimp_version(stdout + stderr)
124
+ rescue StandardError
125
+ nil
126
+ end
127
+
128
+ private
129
+
130
+ # Simple path quoting helper for Platform module
131
+ def quote_path_simple(path)
132
+ return path unless path.include?(' ')
133
+ windows? ? "\"#{path}\"" : "'#{path}'"
134
+ end
135
+ end
136
+ end
137
+ end