ruby_spriter 0.6.7.1 → 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 -524
  4. data/Gemfile +17 -17
  5. data/LICENSE +21 -21
  6. data/README.md +183 -950
  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 -214
  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 -224
  17. data/lib/ruby_spriter/ghost_edge_cleaner.rb +164 -0
  18. data/lib/ruby_spriter/gimp_processor.rb +1188 -1058
  19. data/lib/ruby_spriter/metadata_manager.rb +117 -116
  20. data/lib/ruby_spriter/platform.rb +137 -137
  21. data/lib/ruby_spriter/processor.rb +1230 -891
  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 -92
  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,157 +1,157 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubySpriter::CompressionManager do
6
- let(:input_file) { 'E:/test/input.png' }
7
- let(:output_file) { 'E:/test/output.png' }
8
- let(:temp_file) { 'E:/test/temp.png' }
9
-
10
- before do
11
- allow(File).to receive(:exist?).and_return(true)
12
- allow(File).to receive(:readable?).and_return(true)
13
- allow(File).to receive(:size).with(input_file).and_return(100000)
14
- allow(File).to receive(:size).with(output_file).and_return(80000)
15
- allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
16
- allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
17
- end
18
-
19
- describe '.compress' do
20
- it 'compresses PNG file using ImageMagick' do
21
- magick_cmd = RubySpriter::Platform.imagemagick_convert_cmd
22
-
23
- expect(Open3).to receive(:capture3) do |cmd|
24
- expect(cmd).to include(magick_cmd.split.first) # Check for 'convert' or 'magick'
25
- expect(cmd).to include(input_file)
26
- expect(cmd).to include(output_file)
27
- expect(cmd).to include('-strip')
28
- expect(cmd).to include('-define png:compression-level=9')
29
- expect(cmd).to include('-define png:compression-filter=5')
30
- expect(cmd).to include('-define png:compression-strategy=1')
31
- ['', '', double(success?: true)]
32
- end
33
-
34
- described_class.compress(input_file, output_file)
35
- end
36
-
37
- it 'raises error if compression fails' do
38
- allow(Open3).to receive(:capture3).and_return(['', 'error', double(success?: false)])
39
-
40
- expect {
41
- described_class.compress(input_file, output_file)
42
- }.to raise_error(RubySpriter::ProcessingError, /Failed to compress/)
43
- end
44
-
45
- it 'validates input file exists and is readable' do
46
- expect(RubySpriter::Utils::FileHelper).to receive(:validate_readable!).with(input_file)
47
-
48
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
49
-
50
- described_class.compress(input_file, output_file)
51
- end
52
-
53
- it 'validates output file was created' do
54
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
55
- expect(RubySpriter::Utils::FileHelper).to receive(:validate_exists!).with(output_file)
56
-
57
- described_class.compress(input_file, output_file)
58
- end
59
- end
60
-
61
- describe '.compress_with_metadata' do
62
- let(:metadata) { { columns: 4, rows: 4, frames: 16 } }
63
-
64
- before do
65
- allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
66
- allow(RubySpriter::MetadataManager).to receive(:embed)
67
- allow(FileUtils).to receive(:mv)
68
- allow(File).to receive(:exist?).with(temp_file).and_return(true)
69
- allow(FileUtils).to receive(:rm_f)
70
- end
71
-
72
- it 'reads metadata before compression' do
73
- expect(RubySpriter::MetadataManager).to receive(:read).with(input_file)
74
-
75
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
76
-
77
- described_class.compress_with_metadata(input_file, output_file)
78
- end
79
-
80
- it 'compresses file with metadata preservation' do
81
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
82
-
83
- expect(described_class).to receive(:compress).with(input_file, anything, debug: false)
84
- expect(RubySpriter::MetadataManager).to receive(:embed)
85
-
86
- described_class.compress_with_metadata(input_file, output_file)
87
- end
88
-
89
- it 're-embeds metadata after compression' do
90
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
91
-
92
- expect(RubySpriter::MetadataManager).to receive(:embed) do |temp, output, **opts|
93
- expect(opts[:columns]).to eq(4)
94
- expect(opts[:rows]).to eq(4)
95
- expect(opts[:frames]).to eq(16)
96
- end
97
-
98
- described_class.compress_with_metadata(input_file, output_file)
99
- end
100
-
101
- it 'handles files without metadata' do
102
- allow(RubySpriter::MetadataManager).to receive(:read).and_return(nil)
103
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
104
-
105
- # Should just compress without re-embedding metadata
106
- expect(RubySpriter::MetadataManager).not_to receive(:embed)
107
-
108
- described_class.compress_with_metadata(input_file, output_file)
109
- end
110
-
111
- it 'cleans up temp file after successful compression' do
112
- allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
113
- allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
114
- allow(RubySpriter::MetadataManager).to receive(:embed)
115
-
116
- # Simulate temp file creation
117
- temp_path = nil
118
- allow(described_class).to receive(:compress) do |input, output|
119
- temp_path = output
120
- end
121
-
122
- expect(FileUtils).to receive(:rm_f).with(anything)
123
-
124
- described_class.compress_with_metadata(input_file, output_file)
125
- end
126
- end
127
-
128
- describe '.compression_stats' do
129
- it 'returns compression statistics' do
130
- original_size = 100000
131
- compressed_size = 80000
132
-
133
- allow(File).to receive(:size).with(input_file).and_return(original_size)
134
- allow(File).to receive(:size).with(output_file).and_return(compressed_size)
135
-
136
- stats = described_class.compression_stats(input_file, output_file)
137
-
138
- expect(stats[:original_size]).to eq(original_size)
139
- expect(stats[:compressed_size]).to eq(compressed_size)
140
- expect(stats[:saved_bytes]).to eq(20000)
141
- expect(stats[:reduction_percent]).to be_within(0.1).of(20.0)
142
- end
143
-
144
- it 'handles case where compressed file is larger' do
145
- original_size = 100000
146
- compressed_size = 120000
147
-
148
- allow(File).to receive(:size).with(input_file).and_return(original_size)
149
- allow(File).to receive(:size).with(output_file).and_return(compressed_size)
150
-
151
- stats = described_class.compression_stats(input_file, output_file)
152
-
153
- expect(stats[:saved_bytes]).to eq(-20000)
154
- expect(stats[:reduction_percent]).to be_within(0.1).of(-20.0)
155
- end
156
- end
157
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::CompressionManager do
6
+ let(:input_file) { 'E:/test/input.png' }
7
+ let(:output_file) { 'E:/test/output.png' }
8
+ let(:temp_file) { 'E:/test/temp.png' }
9
+
10
+ before do
11
+ allow(File).to receive(:exist?).and_return(true)
12
+ allow(File).to receive(:readable?).and_return(true)
13
+ allow(File).to receive(:size).with(input_file).and_return(100000)
14
+ allow(File).to receive(:size).with(output_file).and_return(80000)
15
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
16
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
17
+ end
18
+
19
+ describe '.compress' do
20
+ it 'compresses PNG file using ImageMagick' do
21
+ magick_cmd = RubySpriter::Platform.imagemagick_convert_cmd
22
+
23
+ expect(Open3).to receive(:capture3) do |cmd|
24
+ expect(cmd).to include(magick_cmd.split.first) # Check for 'convert' or 'magick'
25
+ expect(cmd).to include(input_file)
26
+ expect(cmd).to include(output_file)
27
+ expect(cmd).to include('-strip')
28
+ expect(cmd).to include('-define png:compression-level=9')
29
+ expect(cmd).to include('-define png:compression-filter=5')
30
+ expect(cmd).to include('-define png:compression-strategy=1')
31
+ ['', '', double(success?: true)]
32
+ end
33
+
34
+ described_class.compress(input_file, output_file)
35
+ end
36
+
37
+ it 'raises error if compression fails' do
38
+ allow(Open3).to receive(:capture3).and_return(['', 'error', double(success?: false)])
39
+
40
+ expect {
41
+ described_class.compress(input_file, output_file)
42
+ }.to raise_error(RubySpriter::ProcessingError, /Failed to compress/)
43
+ end
44
+
45
+ it 'validates input file exists and is readable' do
46
+ expect(RubySpriter::Utils::FileHelper).to receive(:validate_readable!).with(input_file)
47
+
48
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
49
+
50
+ described_class.compress(input_file, output_file)
51
+ end
52
+
53
+ it 'validates output file was created' do
54
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
55
+ expect(RubySpriter::Utils::FileHelper).to receive(:validate_exists!).with(output_file)
56
+
57
+ described_class.compress(input_file, output_file)
58
+ end
59
+ end
60
+
61
+ describe '.compress_with_metadata' do
62
+ let(:metadata) { { columns: 4, rows: 4, frames: 16 } }
63
+
64
+ before do
65
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
66
+ allow(RubySpriter::MetadataManager).to receive(:embed)
67
+ allow(FileUtils).to receive(:mv)
68
+ allow(File).to receive(:exist?).with(temp_file).and_return(true)
69
+ allow(FileUtils).to receive(:rm_f)
70
+ end
71
+
72
+ it 'reads metadata before compression' do
73
+ expect(RubySpriter::MetadataManager).to receive(:read).with(input_file)
74
+
75
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
76
+
77
+ described_class.compress_with_metadata(input_file, output_file)
78
+ end
79
+
80
+ it 'compresses file with metadata preservation' do
81
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
82
+
83
+ expect(described_class).to receive(:compress).with(input_file, anything, debug: false)
84
+ expect(RubySpriter::MetadataManager).to receive(:embed)
85
+
86
+ described_class.compress_with_metadata(input_file, output_file)
87
+ end
88
+
89
+ it 're-embeds metadata after compression' do
90
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
91
+
92
+ expect(RubySpriter::MetadataManager).to receive(:embed) do |temp, output, **opts|
93
+ expect(opts[:columns]).to eq(4)
94
+ expect(opts[:rows]).to eq(4)
95
+ expect(opts[:frames]).to eq(16)
96
+ end
97
+
98
+ described_class.compress_with_metadata(input_file, output_file)
99
+ end
100
+
101
+ it 'handles files without metadata' do
102
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(nil)
103
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
104
+
105
+ # Should just compress without re-embedding metadata
106
+ expect(RubySpriter::MetadataManager).not_to receive(:embed)
107
+
108
+ described_class.compress_with_metadata(input_file, output_file)
109
+ end
110
+
111
+ it 'cleans up temp file after successful compression' do
112
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
113
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
114
+ allow(RubySpriter::MetadataManager).to receive(:embed)
115
+
116
+ # Simulate temp file creation
117
+ temp_path = nil
118
+ allow(described_class).to receive(:compress) do |input, output|
119
+ temp_path = output
120
+ end
121
+
122
+ expect(FileUtils).to receive(:rm_f).with(anything)
123
+
124
+ described_class.compress_with_metadata(input_file, output_file)
125
+ end
126
+ end
127
+
128
+ describe '.compression_stats' do
129
+ it 'returns compression statistics' do
130
+ original_size = 100000
131
+ compressed_size = 80000
132
+
133
+ allow(File).to receive(:size).with(input_file).and_return(original_size)
134
+ allow(File).to receive(:size).with(output_file).and_return(compressed_size)
135
+
136
+ stats = described_class.compression_stats(input_file, output_file)
137
+
138
+ expect(stats[:original_size]).to eq(original_size)
139
+ expect(stats[:compressed_size]).to eq(compressed_size)
140
+ expect(stats[:saved_bytes]).to eq(20000)
141
+ expect(stats[:reduction_percent]).to be_within(0.1).of(20.0)
142
+ end
143
+
144
+ it 'handles case where compressed file is larger' do
145
+ original_size = 100000
146
+ compressed_size = 120000
147
+
148
+ allow(File).to receive(:size).with(input_file).and_return(original_size)
149
+ allow(File).to receive(:size).with(output_file).and_return(compressed_size)
150
+
151
+ stats = described_class.compression_stats(input_file, output_file)
152
+
153
+ expect(stats[:saved_bytes]).to eq(-20000)
154
+ expect(stats[:reduction_percent]).to be_within(0.1).of(-20.0)
155
+ end
156
+ end
157
+ end