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.
- checksums.yaml +4 -4
- data/.rspec +3 -3
- data/CHANGELOG.md +1035 -524
- data/Gemfile +17 -17
- data/LICENSE +21 -21
- data/README.md +183 -950
- data/bin/ruby_spriter +20 -20
- data/lib/ruby_spriter/background_sampler.rb +140 -0
- data/lib/ruby_spriter/batch_processor.rb +268 -214
- data/lib/ruby_spriter/cell_cleanup_config.rb +21 -0
- data/lib/ruby_spriter/cell_cleanup_gimp_script.rb +123 -0
- data/lib/ruby_spriter/cell_cleanup_processor.rb +230 -0
- data/lib/ruby_spriter/cli.rb +676 -612
- data/lib/ruby_spriter/compression_manager.rb +101 -101
- data/lib/ruby_spriter/consolidator.rb +179 -179
- data/lib/ruby_spriter/dependency_checker.rb +224 -224
- data/lib/ruby_spriter/ghost_edge_cleaner.rb +164 -0
- data/lib/ruby_spriter/gimp_processor.rb +1188 -1058
- data/lib/ruby_spriter/metadata_manager.rb +117 -116
- data/lib/ruby_spriter/platform.rb +137 -137
- data/lib/ruby_spriter/processor.rb +1230 -891
- data/lib/ruby_spriter/smoke_detector.rb +223 -0
- data/lib/ruby_spriter/threshold_stepper.rb +227 -0
- data/lib/ruby_spriter/utils/file_helper.rb +82 -82
- data/lib/ruby_spriter/utils/image_helper.rb +16 -0
- data/lib/ruby_spriter/utils/output_formatter.rb +65 -65
- data/lib/ruby_spriter/utils/path_helper.rb +59 -59
- data/lib/ruby_spriter/utils/spritesheet_splitter.rb +86 -86
- data/lib/ruby_spriter/version.rb +6 -7
- data/lib/ruby_spriter/video_processor.rb +357 -139
- data/lib/ruby_spriter.rb +38 -34
- data/ruby_spriter.gemspec +44 -42
- data/spec/fixtures/centered_sprite_with_inner_bg.png +0 -0
- data/spec/fixtures/centered_sprite_with_inner_bg_inner_removed.png +0 -0
- data/spec/fixtures/centered_sprite_with_inner_bg_threshold_stepped.png +0 -0
- data/spec/fixtures/complex_background_sprite.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing.mp4 +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738.png +0 -0
- data/spec/fixtures/has_inner_bg.png +0 -0
- data/spec/fixtures/has_small_inner_bg.png +0 -0
- data/spec/fixtures/smoke_effect_sprite.png +0 -0
- data/spec/fixtures/spritesheet_with_metadata.png +0 -0
- data/spec/fixtures/test_sprite.png +0 -0
- data/spec/fixtures/test_sprite_smoke_processed.png +0 -0
- data/spec/fixtures/test_video_spritesheet.png +0 -0
- data/spec/fixtures/transparent_bg_sprite.png +0 -0
- data/spec/fixtures/walk_north_sprite-sheet.png +0 -0
- data/spec/integration/no_fuzzy_mode_spec.rb +100 -0
- data/spec/ruby_spriter/batch_processor_spec.rb +509 -200
- data/spec/ruby_spriter/cli_spec.rb +2026 -1892
- data/spec/ruby_spriter/compression_manager_spec.rb +157 -157
- data/spec/ruby_spriter/consolidator_spec.rb +538 -538
- data/spec/ruby_spriter/gimp_processor_spec.rb +523 -425
- data/spec/ruby_spriter/platform_spec.rb +92 -92
- data/spec/ruby_spriter/processor_spec.rb +911 -735
- data/spec/ruby_spriter/utils/file_helper_spec.rb +150 -150
- data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -78
- data/spec/ruby_spriter/utils/spritesheet_splitter_spec.rb +104 -104
- data/spec/ruby_spriter/video_processor_spec.rb +346 -29
- data/spec/spec_helper.rb +41 -41
- data/spec/tmp/cli_test_output.png +0 -0
- data/spec/tmp/cli_test_output_20251105_114647_395.png +0 -0
- data/spec/tmp/combined_test.png +0 -0
- data/spec/tmp/compat_test.png +0 -0
- data/spec/tmp/compat_test_20251030_174233_361.png +0 -0
- data/spec/tmp/final_all_features.png +0 -0
- data/spec/tmp/final_test_all_features.png +0 -0
- data/spec/tmp/full_pipeline_test.png +0 -0
- data/spec/tmp/inner_test.png +0 -0
- data/spec/tmp/integration_test.png +0 -0
- data/spec/tmp/validation_test.png +0 -0
- data/spec/unit/background_sampler_spec.rb +132 -0
- data/spec/unit/cell_cleanup_config_spec.rb +32 -0
- data/spec/unit/cell_cleanup_gimp_script_spec.rb +59 -0
- data/spec/unit/cell_cleanup_processor_spec.rb +261 -0
- data/spec/unit/ghost_edge_cleaner_spec.rb +223 -0
- data/spec/unit/gimp_processor_single_point_selection_spec.rb +81 -0
- data/spec/unit/smoke_detector_spec.rb +246 -0
- data/spec/unit/threshold_stepper_spec.rb +195 -0
- metadata +56 -10
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::SmokeDetector do
|
|
6
|
+
let(:config) do
|
|
7
|
+
double('InnerBgConfig',
|
|
8
|
+
remove_smoke: false)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
let(:input_image) { 'spec/fixtures/test_sprite.png' }
|
|
12
|
+
let(:output_image) { 'spec/tmp/smoke_processed.png' }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
FileUtils.mkdir_p('spec/tmp')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
after do
|
|
19
|
+
FileUtils.rm_f(output_image) if File.exist?(output_image)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#initialize' do
|
|
23
|
+
it 'accepts input image, output image, and config' do
|
|
24
|
+
detector = described_class.new(input_image, output_image, config)
|
|
25
|
+
expect(detector).to be_a(RubySpriter::SmokeDetector)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#detect' do
|
|
30
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
31
|
+
|
|
32
|
+
it 'identifies smoke-like transparency gradients' do
|
|
33
|
+
smoke_regions = subject.detect
|
|
34
|
+
|
|
35
|
+
expect(smoke_regions).to be_an(Array)
|
|
36
|
+
# May or may not find smoke depending on fixture
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'detects pixels with alpha between 20-80%' do
|
|
40
|
+
smoke_regions = subject.detect
|
|
41
|
+
|
|
42
|
+
# Each region should have alpha characteristics
|
|
43
|
+
smoke_regions.each do |region|
|
|
44
|
+
expect(region).to have_key(:alpha_range)
|
|
45
|
+
expect(region).to have_key(:area)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'filters regions by minimum area (50 pixels)' do
|
|
50
|
+
smoke_regions = subject.detect
|
|
51
|
+
|
|
52
|
+
smoke_regions.each do |region|
|
|
53
|
+
expect(region[:area]).to be >= 50
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#process' do
|
|
59
|
+
context 'when remove_smoke is false' do
|
|
60
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
61
|
+
|
|
62
|
+
it 'detects but does not remove smoke effects' do
|
|
63
|
+
subject.process
|
|
64
|
+
report = subject.report
|
|
65
|
+
|
|
66
|
+
expect(report[:smoke_detected]).to be_a(Integer)
|
|
67
|
+
expect(report[:smoke_removed]).to eq(false)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'copies input to output unchanged' do
|
|
71
|
+
subject.process
|
|
72
|
+
|
|
73
|
+
expect(File.exist?(output_image)).to be true
|
|
74
|
+
|
|
75
|
+
# Files should be identical
|
|
76
|
+
expect(FileUtils.compare_file(input_image, output_image)).to be true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'when remove_smoke is true' do
|
|
81
|
+
let(:config) do
|
|
82
|
+
double('InnerBgConfig',
|
|
83
|
+
remove_smoke: true)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
87
|
+
|
|
88
|
+
it 'detects and removes smoke effects' do
|
|
89
|
+
subject.process
|
|
90
|
+
report = subject.report
|
|
91
|
+
|
|
92
|
+
expect(report[:smoke_detected]).to be_a(Integer)
|
|
93
|
+
expect(report[:smoke_removed]).to eq(true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'creates an output image file' do
|
|
97
|
+
subject.process
|
|
98
|
+
expect(File.exist?(output_image)).to be true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'removes semi-transparent gradient regions' do
|
|
102
|
+
subject.process
|
|
103
|
+
|
|
104
|
+
# Output should exist and be valid
|
|
105
|
+
expect(File.exist?(output_image)).to be true
|
|
106
|
+
|
|
107
|
+
# Check file size is reasonable
|
|
108
|
+
file_size = File.size(output_image)
|
|
109
|
+
expect(file_size).to be > 100
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe '#remove_smoke_regions' do
|
|
115
|
+
let(:config) do
|
|
116
|
+
double('InnerBgConfig',
|
|
117
|
+
remove_smoke: true)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
121
|
+
|
|
122
|
+
it 'removes detected smoke regions' do
|
|
123
|
+
smoke_regions = [
|
|
124
|
+
{ x: 50, y: 50, area: 100, alpha_range: [0.2, 0.8] }
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
FileUtils.cp(input_image, output_image)
|
|
128
|
+
result = subject.remove_smoke_regions(smoke_regions)
|
|
129
|
+
|
|
130
|
+
expect(result).to be true
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#is_smoke_like?' do
|
|
135
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
136
|
+
|
|
137
|
+
it 'returns true for alpha values between 20-80%' do
|
|
138
|
+
expect(subject.is_smoke_like?(0.5)).to be true
|
|
139
|
+
expect(subject.is_smoke_like?(0.3)).to be true
|
|
140
|
+
expect(subject.is_smoke_like?(0.7)).to be true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'returns false for fully transparent pixels' do
|
|
144
|
+
expect(subject.is_smoke_like?(0.0)).to be false
|
|
145
|
+
expect(subject.is_smoke_like?(0.1)).to be false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'returns false for fully opaque pixels' do
|
|
149
|
+
expect(subject.is_smoke_like?(1.0)).to be false
|
|
150
|
+
expect(subject.is_smoke_like?(0.9)).to be false
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe '#report' do
|
|
155
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
156
|
+
|
|
157
|
+
it 'generates a detection report' do
|
|
158
|
+
subject.process
|
|
159
|
+
report = subject.report
|
|
160
|
+
|
|
161
|
+
expect(report).to be_a(Hash)
|
|
162
|
+
expect(report).to have_key(:smoke_detected)
|
|
163
|
+
expect(report).to have_key(:smoke_removed)
|
|
164
|
+
expect(report).to have_key(:smoke_regions)
|
|
165
|
+
expect(report).to have_key(:processing_time)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'includes region details when smoke is detected' do
|
|
169
|
+
subject.process
|
|
170
|
+
report = subject.report
|
|
171
|
+
|
|
172
|
+
expect(report[:smoke_regions]).to be_an(Array)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe 'alpha range detection' do
|
|
177
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
178
|
+
|
|
179
|
+
it 'identifies the alpha range of detected regions' do
|
|
180
|
+
smoke_regions = subject.detect
|
|
181
|
+
|
|
182
|
+
smoke_regions.each do |region|
|
|
183
|
+
alpha_range = region[:alpha_range]
|
|
184
|
+
expect(alpha_range).to be_an(Array)
|
|
185
|
+
expect(alpha_range.length).to eq(2)
|
|
186
|
+
expect(alpha_range[0]).to be >= 0.2
|
|
187
|
+
expect(alpha_range[1]).to be <= 0.8
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe 'minimum area threshold' do
|
|
193
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
194
|
+
|
|
195
|
+
it 'uses 50 pixels as minimum area' do
|
|
196
|
+
smoke_regions = subject.detect
|
|
197
|
+
|
|
198
|
+
# All detected regions should meet minimum
|
|
199
|
+
smoke_regions.each do |region|
|
|
200
|
+
expect(region[:area]).to be >= 50
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe 'performance' do
|
|
206
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
207
|
+
|
|
208
|
+
it 'completes detection in reasonable time' do
|
|
209
|
+
start_time = Time.now
|
|
210
|
+
subject.detect
|
|
211
|
+
elapsed = Time.now - start_time
|
|
212
|
+
|
|
213
|
+
# Should complete in under 15 seconds for small test image
|
|
214
|
+
expect(elapsed).to be < 15
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe 'integration scenarios' do
|
|
219
|
+
context 'with image containing no smoke effects' do
|
|
220
|
+
let(:input_image) { 'spec/fixtures/test_sprite.png' }
|
|
221
|
+
|
|
222
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
223
|
+
|
|
224
|
+
it 'reports zero smoke regions detected' do
|
|
225
|
+
subject.process
|
|
226
|
+
report = subject.report
|
|
227
|
+
|
|
228
|
+
expect(report[:smoke_detected]).to eq(0)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
context 'with grayscale image' do
|
|
233
|
+
let(:input_image) { 'spec/fixtures/test_sprite.png' }
|
|
234
|
+
|
|
235
|
+
subject { described_class.new(input_image, output_image, config) }
|
|
236
|
+
|
|
237
|
+
it 'handles grayscale images correctly' do
|
|
238
|
+
subject.process
|
|
239
|
+
report = subject.report
|
|
240
|
+
|
|
241
|
+
# Should complete without errors
|
|
242
|
+
expect(report[:smoke_detected]).to be >= 0
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'ruby_spriter/threshold_stepper'
|
|
5
|
+
require 'ruby_spriter/gimp_processor'
|
|
6
|
+
require 'tempfile'
|
|
7
|
+
|
|
8
|
+
RSpec.describe RubySpriter::ThresholdStepper do
|
|
9
|
+
let(:input_file) { Tempfile.new(['input', '.png']) }
|
|
10
|
+
let(:output_file) { Tempfile.new(['output', '.png']) }
|
|
11
|
+
let(:background_palette) do
|
|
12
|
+
[
|
|
13
|
+
{ r: 255, g: 255, b: 255 },
|
|
14
|
+
{ r: 250, g: 250, b: 250 },
|
|
15
|
+
{ r: 245, g: 245, b: 245 }
|
|
16
|
+
]
|
|
17
|
+
end
|
|
18
|
+
let(:gimp_processor) { instance_double(RubySpriter::GimpProcessor) }
|
|
19
|
+
let(:options) { { debug: false } }
|
|
20
|
+
let(:stepper) do
|
|
21
|
+
described_class.new(
|
|
22
|
+
input_file.path,
|
|
23
|
+
output_file.path,
|
|
24
|
+
background_palette,
|
|
25
|
+
gimp_processor,
|
|
26
|
+
options
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
after do
|
|
31
|
+
input_file.close
|
|
32
|
+
input_file.unlink
|
|
33
|
+
output_file.close
|
|
34
|
+
output_file.unlink
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#initialize' do
|
|
38
|
+
it 'accepts background palette parameter' do
|
|
39
|
+
expect(stepper.instance_variable_get(:@background_palette)).to eq(background_palette)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'accepts gimp_processor instance' do
|
|
43
|
+
expect(stepper.instance_variable_get(:@gimp_processor)).to eq(gimp_processor)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'uses default threshold values' do
|
|
47
|
+
thresholds = stepper.instance_variable_get(:@threshold_values)
|
|
48
|
+
expect(thresholds).to eq([0.0, 0.5, 1.0, 3.0, 5.0, 10.0])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'accepts custom threshold values' do
|
|
52
|
+
custom_stepper = described_class.new(
|
|
53
|
+
input_file.path,
|
|
54
|
+
output_file.path,
|
|
55
|
+
background_palette,
|
|
56
|
+
gimp_processor,
|
|
57
|
+
{ threshold_values: [1.0, 5.0, 10.0] }
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
thresholds = custom_stepper.instance_variable_get(:@threshold_values)
|
|
61
|
+
expect(thresholds).to eq([1.0, 5.0, 10.0])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#process' do
|
|
66
|
+
it 'generates GIMP script for each threshold value' do
|
|
67
|
+
# Mock GIMP processor to track script executions
|
|
68
|
+
script_calls = []
|
|
69
|
+
allow(gimp_processor).to receive(:execute_python_script) do |script, temp_output|
|
|
70
|
+
script_calls << { script: script, output: temp_output }
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Mock ImageMagick compositing
|
|
75
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
76
|
+
|
|
77
|
+
stepper.process
|
|
78
|
+
|
|
79
|
+
# Should generate 6 scripts (one per default threshold)
|
|
80
|
+
expect(script_calls.length).to eq(6)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'includes background palette colors in GIMP script' do
|
|
84
|
+
generated_script = nil
|
|
85
|
+
allow(gimp_processor).to receive(:execute_python_script) do |script, _|
|
|
86
|
+
generated_script = script
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
91
|
+
|
|
92
|
+
stepper.process
|
|
93
|
+
|
|
94
|
+
# Script should reference the background colors (normalized to 0.0-1.0)
|
|
95
|
+
# Background palette has: {r: 255, g: 255, b: 255}, {r: 250, g: 250, b: 250}, {r: 245, g: 245, b: 245}
|
|
96
|
+
# These become: rgb(1.0, 1.0, 1.0), rgb(0.98..., 0.98..., 0.98...), rgb(0.96..., 0.96..., 0.96...)
|
|
97
|
+
expect(generated_script).to include('Gegl.Color.new') # Uses Gegl.Color
|
|
98
|
+
expect(generated_script).to include('rgb(') # RGB format
|
|
99
|
+
expect(generated_script).to match(/rgb\(1\.0, 1\.0, 1\.0\)/) # First color (255,255,255)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'uses gimp-image-select-color in generated script' do
|
|
103
|
+
generated_script = nil
|
|
104
|
+
allow(gimp_processor).to receive(:execute_python_script) do |script, _|
|
|
105
|
+
generated_script = script
|
|
106
|
+
true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
110
|
+
|
|
111
|
+
stepper.process
|
|
112
|
+
|
|
113
|
+
# Script should use the correct GIMP procedure
|
|
114
|
+
expect(generated_script).to include('gimp-image-select-color')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'applies threshold parameter in GIMP script' do
|
|
118
|
+
generated_scripts = []
|
|
119
|
+
allow(gimp_processor).to receive(:execute_python_script) do |script, _|
|
|
120
|
+
generated_scripts << script
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
125
|
+
|
|
126
|
+
stepper.process
|
|
127
|
+
|
|
128
|
+
# Each script should have a different threshold value
|
|
129
|
+
expect(generated_scripts[0]).to include('threshold')
|
|
130
|
+
expect(generated_scripts[1]).to include('threshold')
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'composites threshold results with ImageMagick' do
|
|
134
|
+
allow(gimp_processor).to receive(:execute_python_script).and_return(true)
|
|
135
|
+
|
|
136
|
+
composite_calls = []
|
|
137
|
+
allow(Open3).to receive(:capture3) do |cmd|
|
|
138
|
+
composite_calls << cmd if cmd.include?('magick')
|
|
139
|
+
['', '', double(success?: true)]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
stepper.process
|
|
143
|
+
|
|
144
|
+
# Should composite the results
|
|
145
|
+
expect(composite_calls.length).to be > 0
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe 'timeout handling' do
|
|
150
|
+
it 'skips threshold on per-threshold timeout' do
|
|
151
|
+
call_count = 0
|
|
152
|
+
allow(gimp_processor).to receive(:execute_python_script) do
|
|
153
|
+
call_count += 1
|
|
154
|
+
if call_count == 2
|
|
155
|
+
# Simulate timeout on second threshold
|
|
156
|
+
sleep 0.1
|
|
157
|
+
raise Timeout::Error, 'Threshold processing timeout'
|
|
158
|
+
end
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
163
|
+
|
|
164
|
+
# Should not raise error, just skip the threshold
|
|
165
|
+
expect { stepper.process }.not_to raise_error
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'reports skipped thresholds' do
|
|
169
|
+
allow(gimp_processor).to receive(:execute_python_script) do
|
|
170
|
+
raise Timeout::Error, 'Threshold processing timeout'
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
174
|
+
|
|
175
|
+
stepper.process
|
|
176
|
+
report = stepper.report
|
|
177
|
+
|
|
178
|
+
expect(report[:skipped_thresholds]).to be > 0
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe '#report' do
|
|
183
|
+
it 'includes processing statistics' do
|
|
184
|
+
allow(gimp_processor).to receive(:execute_python_script).and_return(true)
|
|
185
|
+
allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
|
|
186
|
+
|
|
187
|
+
stepper.process
|
|
188
|
+
report = stepper.report
|
|
189
|
+
|
|
190
|
+
expect(report).to have_key(:thresholds_processed)
|
|
191
|
+
expect(report).to have_key(:skipped_thresholds)
|
|
192
|
+
expect(report).to have_key(:total_time)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
metadata
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_spriter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- scooter-indie
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: |
|
|
14
|
-
Ruby Spriter is a cross-platform tool for creating spritesheets from video files
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
Ruby Spriter is a cross-platform tool for creating professional spritesheets from video files
|
|
14
|
+
with advanced GIMP image processing. Features include edge-based and inner background removal,
|
|
15
|
+
multi-threshold processing, ghost edge prevention, smoke detection, scaling with multiple
|
|
16
|
+
interpolation methods, sharpening, batch processing, spritesheet consolidation, frame extraction,
|
|
17
|
+
and comprehensive metadata management. Designed for game development workflows with Godot Engine.
|
|
17
18
|
email:
|
|
18
19
|
- scooter-indie@users.noreply.github.com
|
|
19
20
|
executables:
|
|
@@ -28,28 +29,56 @@ files:
|
|
|
28
29
|
- README.md
|
|
29
30
|
- bin/ruby_spriter
|
|
30
31
|
- lib/ruby_spriter.rb
|
|
32
|
+
- lib/ruby_spriter/background_sampler.rb
|
|
31
33
|
- lib/ruby_spriter/batch_processor.rb
|
|
34
|
+
- lib/ruby_spriter/cell_cleanup_config.rb
|
|
35
|
+
- lib/ruby_spriter/cell_cleanup_gimp_script.rb
|
|
36
|
+
- lib/ruby_spriter/cell_cleanup_processor.rb
|
|
32
37
|
- lib/ruby_spriter/cli.rb
|
|
33
38
|
- lib/ruby_spriter/compression_manager.rb
|
|
34
39
|
- lib/ruby_spriter/consolidator.rb
|
|
35
40
|
- lib/ruby_spriter/dependency_checker.rb
|
|
41
|
+
- lib/ruby_spriter/ghost_edge_cleaner.rb
|
|
36
42
|
- lib/ruby_spriter/gimp_processor.rb
|
|
37
43
|
- lib/ruby_spriter/metadata_manager.rb
|
|
38
44
|
- lib/ruby_spriter/platform.rb
|
|
39
45
|
- lib/ruby_spriter/processor.rb
|
|
46
|
+
- lib/ruby_spriter/smoke_detector.rb
|
|
47
|
+
- lib/ruby_spriter/threshold_stepper.rb
|
|
40
48
|
- lib/ruby_spriter/utils/file_helper.rb
|
|
49
|
+
- lib/ruby_spriter/utils/image_helper.rb
|
|
41
50
|
- lib/ruby_spriter/utils/output_formatter.rb
|
|
42
51
|
- lib/ruby_spriter/utils/path_helper.rb
|
|
43
52
|
- lib/ruby_spriter/utils/spritesheet_splitter.rb
|
|
44
53
|
- lib/ruby_spriter/version.rb
|
|
45
54
|
- lib/ruby_spriter/video_processor.rb
|
|
46
55
|
- ruby_spriter.gemspec
|
|
56
|
+
- spec/fixtures/centered_sprite_with_inner_bg.png
|
|
57
|
+
- spec/fixtures/centered_sprite_with_inner_bg_inner_removed.png
|
|
58
|
+
- spec/fixtures/centered_sprite_with_inner_bg_threshold_stepped.png
|
|
59
|
+
- spec/fixtures/complex_background_sprite.png
|
|
60
|
+
- spec/fixtures/death - bloodborne rekconing.mp4
|
|
61
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet-nobg-global.png
|
|
62
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet.png
|
|
63
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431-nobg-global.png
|
|
64
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431.png
|
|
65
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738-nobg-global.png
|
|
66
|
+
- spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738.png
|
|
67
|
+
- spec/fixtures/has_inner_bg.png
|
|
68
|
+
- spec/fixtures/has_small_inner_bg.png
|
|
47
69
|
- spec/fixtures/image_without_metadata.png
|
|
70
|
+
- spec/fixtures/smoke_effect_sprite.png
|
|
48
71
|
- spec/fixtures/spritesheet_4x2.png
|
|
49
72
|
- spec/fixtures/spritesheet_4x4.png
|
|
50
73
|
- spec/fixtures/spritesheet_6x2.png
|
|
51
74
|
- spec/fixtures/spritesheet_with_metadata.png
|
|
75
|
+
- spec/fixtures/test_sprite.png
|
|
76
|
+
- spec/fixtures/test_sprite_smoke_processed.png
|
|
52
77
|
- spec/fixtures/test_video.mp4
|
|
78
|
+
- spec/fixtures/test_video_spritesheet.png
|
|
79
|
+
- spec/fixtures/transparent_bg_sprite.png
|
|
80
|
+
- spec/fixtures/walk_north_sprite-sheet.png
|
|
81
|
+
- spec/integration/no_fuzzy_mode_spec.rb
|
|
53
82
|
- spec/ruby_spriter/batch_processor_spec.rb
|
|
54
83
|
- spec/ruby_spriter/cli_spec.rb
|
|
55
84
|
- spec/ruby_spriter/compression_manager_spec.rb
|
|
@@ -65,6 +94,25 @@ files:
|
|
|
65
94
|
- spec/ruby_spriter/utils/spritesheet_splitter_spec.rb
|
|
66
95
|
- spec/ruby_spriter/video_processor_spec.rb
|
|
67
96
|
- spec/spec_helper.rb
|
|
97
|
+
- spec/tmp/cli_test_output.png
|
|
98
|
+
- spec/tmp/cli_test_output_20251105_114647_395.png
|
|
99
|
+
- spec/tmp/combined_test.png
|
|
100
|
+
- spec/tmp/compat_test.png
|
|
101
|
+
- spec/tmp/compat_test_20251030_174233_361.png
|
|
102
|
+
- spec/tmp/final_all_features.png
|
|
103
|
+
- spec/tmp/final_test_all_features.png
|
|
104
|
+
- spec/tmp/full_pipeline_test.png
|
|
105
|
+
- spec/tmp/inner_test.png
|
|
106
|
+
- spec/tmp/integration_test.png
|
|
107
|
+
- spec/tmp/validation_test.png
|
|
108
|
+
- spec/unit/background_sampler_spec.rb
|
|
109
|
+
- spec/unit/cell_cleanup_config_spec.rb
|
|
110
|
+
- spec/unit/cell_cleanup_gimp_script_spec.rb
|
|
111
|
+
- spec/unit/cell_cleanup_processor_spec.rb
|
|
112
|
+
- spec/unit/ghost_edge_cleaner_spec.rb
|
|
113
|
+
- spec/unit/gimp_processor_single_point_selection_spec.rb
|
|
114
|
+
- spec/unit/smoke_detector_spec.rb
|
|
115
|
+
- spec/unit/threshold_stepper_spec.rb
|
|
68
116
|
homepage: https://github.com/scooter-indie/ruby-spriter
|
|
69
117
|
licenses:
|
|
70
118
|
- MIT
|
|
@@ -72,7 +120,6 @@ metadata:
|
|
|
72
120
|
homepage_uri: https://github.com/scooter-indie/ruby-spriter
|
|
73
121
|
source_code_uri: https://github.com/scooter-indie/ruby-spriter
|
|
74
122
|
changelog_uri: https://github.com/scooter-indie/ruby-spriter/blob/main/CHANGELOG.md
|
|
75
|
-
post_install_message:
|
|
76
123
|
rdoc_options: []
|
|
77
124
|
require_paths:
|
|
78
125
|
- lib
|
|
@@ -87,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
87
134
|
- !ruby/object:Gem::Version
|
|
88
135
|
version: '0'
|
|
89
136
|
requirements: []
|
|
90
|
-
rubygems_version: 3.
|
|
91
|
-
signing_key:
|
|
137
|
+
rubygems_version: 3.7.2
|
|
92
138
|
specification_version: 4
|
|
93
|
-
summary: MP4 to Spritesheet converter with GIMP image processing
|
|
139
|
+
summary: Professional MP4 to Spritesheet converter with advanced GIMP image processing
|
|
94
140
|
test_files: []
|