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
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe RubySpriter::Utils::FileHelper do
|
|
6
|
-
describe '.spritesheet_filename' do
|
|
7
|
-
it 'generates correct filename from video' do
|
|
8
|
-
result = described_class.spritesheet_filename('/path/to/video.mp4')
|
|
9
|
-
expect(result).to eq('/path/to/video_spritesheet.png')
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it 'handles different extensions' do
|
|
13
|
-
result = described_class.spritesheet_filename('C:\\videos\\clip.avi')
|
|
14
|
-
expect(result).to match(/clip_spritesheet\.png$/)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
describe '.output_filename' do
|
|
19
|
-
it 'generates filename with suffix' do
|
|
20
|
-
result = described_class.output_filename('/path/to/image.png', 'scaled')
|
|
21
|
-
expect(result).to eq('/path/to/image-scaled.png')
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
it 'preserves directory path' do
|
|
25
|
-
result = described_class.output_filename('/some/deep/path/file.png', 'nobg')
|
|
26
|
-
expect(result).to start_with('/some/deep/path/')
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
describe '.format_size' do
|
|
31
|
-
it 'formats bytes correctly' do
|
|
32
|
-
expect(described_class.format_size(500)).to eq('500 bytes')
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it 'formats kilobytes correctly' do
|
|
36
|
-
expect(described_class.format_size(2048)).to eq('2.0 KB')
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
it 'formats megabytes correctly' do
|
|
40
|
-
expect(described_class.format_size(5_242_880)).to eq('5.0 MB')
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
describe '.validate_exists!' do
|
|
45
|
-
it 'raises error for non-existent file' do
|
|
46
|
-
expect {
|
|
47
|
-
described_class.validate_exists!('/nonexistent/file.txt')
|
|
48
|
-
}.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it 'does not raise for existing file' do
|
|
52
|
-
file = File.join(@test_dir, 'test.txt')
|
|
53
|
-
File.write(file, 'test')
|
|
54
|
-
|
|
55
|
-
expect {
|
|
56
|
-
described_class.validate_exists!(file)
|
|
57
|
-
}.not_to raise_error
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
describe '.validate_readable!' do
|
|
62
|
-
it 'validates file exists and is readable' do
|
|
63
|
-
file = File.join(@test_dir, 'test.txt')
|
|
64
|
-
File.write(file, 'test')
|
|
65
|
-
|
|
66
|
-
expect {
|
|
67
|
-
described_class.validate_readable!(file)
|
|
68
|
-
}.not_to raise_error
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
describe '.unique_filename' do
|
|
73
|
-
it 'returns original filename when file does not exist' do
|
|
74
|
-
result = described_class.unique_filename('/path/to/output.png')
|
|
75
|
-
expect(result).to eq('/path/to/output.png')
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it 'adds timestamp when file exists' do
|
|
79
|
-
file = File.join(@test_dir, 'existing.png')
|
|
80
|
-
File.write(file, 'test')
|
|
81
|
-
|
|
82
|
-
result = described_class.unique_filename(file)
|
|
83
|
-
expect(result).to match(/existing_\d{8}_\d{6}_\d{3}\.png$/)
|
|
84
|
-
expect(result).not_to eq(file)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it 'handles files with multiple dots in name' do
|
|
88
|
-
file = File.join(@test_dir, 'my.sprite.sheet.png')
|
|
89
|
-
File.write(file, 'test')
|
|
90
|
-
|
|
91
|
-
result = described_class.unique_filename(file)
|
|
92
|
-
expect(result).to match(/my\.sprite\.sheet_\d{8}_\d{6}_\d{3}\.png$/)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it 'preserves directory path' do
|
|
96
|
-
file = File.join(@test_dir, 'subdir', 'output.png')
|
|
97
|
-
FileUtils.mkdir_p(File.dirname(file))
|
|
98
|
-
File.write(file, 'test')
|
|
99
|
-
|
|
100
|
-
result = described_class.unique_filename(file)
|
|
101
|
-
expect(result).to start_with(File.join(@test_dir, 'subdir'))
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it 'generates different filenames for consecutive calls' do
|
|
105
|
-
file = File.join(@test_dir, 'test.png')
|
|
106
|
-
File.write(file, 'test')
|
|
107
|
-
|
|
108
|
-
result1 = described_class.unique_filename(file)
|
|
109
|
-
sleep(0.01) # Small delay to ensure different timestamps
|
|
110
|
-
result2 = described_class.unique_filename(file)
|
|
111
|
-
|
|
112
|
-
expect(result1).not_to eq(result2)
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
describe '.ensure_unique_output' do
|
|
117
|
-
it 'returns original path when overwrite is true' do
|
|
118
|
-
file = File.join(@test_dir, 'output.png')
|
|
119
|
-
File.write(file, 'test')
|
|
120
|
-
|
|
121
|
-
result = described_class.ensure_unique_output(file, overwrite: true)
|
|
122
|
-
expect(result).to eq(file)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
it 'returns original path when file does not exist and overwrite is false' do
|
|
126
|
-
file = File.join(@test_dir, 'new_output.png')
|
|
127
|
-
|
|
128
|
-
result = described_class.ensure_unique_output(file, overwrite: false)
|
|
129
|
-
expect(result).to eq(file)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
it 'returns unique filename when file exists and overwrite is false' do
|
|
133
|
-
file = File.join(@test_dir, 'existing_output.png')
|
|
134
|
-
File.write(file, 'test')
|
|
135
|
-
|
|
136
|
-
result = described_class.ensure_unique_output(file, overwrite: false)
|
|
137
|
-
expect(result).to match(/existing_output_\d{8}_\d{6}_\d{3}\.png$/)
|
|
138
|
-
expect(result).not_to eq(file)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
it 'defaults to overwrite false when not specified' do
|
|
142
|
-
file = File.join(@test_dir, 'default_test.png')
|
|
143
|
-
File.write(file, 'test')
|
|
144
|
-
|
|
145
|
-
result = described_class.ensure_unique_output(file)
|
|
146
|
-
expect(result).not_to eq(file)
|
|
147
|
-
expect(result).to match(/default_test_\d{8}_\d{6}_\d{3}\.png$/)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::Utils::FileHelper do
|
|
6
|
+
describe '.spritesheet_filename' do
|
|
7
|
+
it 'generates correct filename from video' do
|
|
8
|
+
result = described_class.spritesheet_filename('/path/to/video.mp4')
|
|
9
|
+
expect(result).to eq('/path/to/video_spritesheet.png')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'handles different extensions' do
|
|
13
|
+
result = described_class.spritesheet_filename('C:\\videos\\clip.avi')
|
|
14
|
+
expect(result).to match(/clip_spritesheet\.png$/)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '.output_filename' do
|
|
19
|
+
it 'generates filename with suffix' do
|
|
20
|
+
result = described_class.output_filename('/path/to/image.png', 'scaled')
|
|
21
|
+
expect(result).to eq('/path/to/image-scaled.png')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'preserves directory path' do
|
|
25
|
+
result = described_class.output_filename('/some/deep/path/file.png', 'nobg')
|
|
26
|
+
expect(result).to start_with('/some/deep/path/')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '.format_size' do
|
|
31
|
+
it 'formats bytes correctly' do
|
|
32
|
+
expect(described_class.format_size(500)).to eq('500 bytes')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'formats kilobytes correctly' do
|
|
36
|
+
expect(described_class.format_size(2048)).to eq('2.0 KB')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'formats megabytes correctly' do
|
|
40
|
+
expect(described_class.format_size(5_242_880)).to eq('5.0 MB')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '.validate_exists!' do
|
|
45
|
+
it 'raises error for non-existent file' do
|
|
46
|
+
expect {
|
|
47
|
+
described_class.validate_exists!('/nonexistent/file.txt')
|
|
48
|
+
}.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'does not raise for existing file' do
|
|
52
|
+
file = File.join(@test_dir, 'test.txt')
|
|
53
|
+
File.write(file, 'test')
|
|
54
|
+
|
|
55
|
+
expect {
|
|
56
|
+
described_class.validate_exists!(file)
|
|
57
|
+
}.not_to raise_error
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '.validate_readable!' do
|
|
62
|
+
it 'validates file exists and is readable' do
|
|
63
|
+
file = File.join(@test_dir, 'test.txt')
|
|
64
|
+
File.write(file, 'test')
|
|
65
|
+
|
|
66
|
+
expect {
|
|
67
|
+
described_class.validate_readable!(file)
|
|
68
|
+
}.not_to raise_error
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '.unique_filename' do
|
|
73
|
+
it 'returns original filename when file does not exist' do
|
|
74
|
+
result = described_class.unique_filename('/path/to/output.png')
|
|
75
|
+
expect(result).to eq('/path/to/output.png')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'adds timestamp when file exists' do
|
|
79
|
+
file = File.join(@test_dir, 'existing.png')
|
|
80
|
+
File.write(file, 'test')
|
|
81
|
+
|
|
82
|
+
result = described_class.unique_filename(file)
|
|
83
|
+
expect(result).to match(/existing_\d{8}_\d{6}_\d{3}\.png$/)
|
|
84
|
+
expect(result).not_to eq(file)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'handles files with multiple dots in name' do
|
|
88
|
+
file = File.join(@test_dir, 'my.sprite.sheet.png')
|
|
89
|
+
File.write(file, 'test')
|
|
90
|
+
|
|
91
|
+
result = described_class.unique_filename(file)
|
|
92
|
+
expect(result).to match(/my\.sprite\.sheet_\d{8}_\d{6}_\d{3}\.png$/)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'preserves directory path' do
|
|
96
|
+
file = File.join(@test_dir, 'subdir', 'output.png')
|
|
97
|
+
FileUtils.mkdir_p(File.dirname(file))
|
|
98
|
+
File.write(file, 'test')
|
|
99
|
+
|
|
100
|
+
result = described_class.unique_filename(file)
|
|
101
|
+
expect(result).to start_with(File.join(@test_dir, 'subdir'))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'generates different filenames for consecutive calls' do
|
|
105
|
+
file = File.join(@test_dir, 'test.png')
|
|
106
|
+
File.write(file, 'test')
|
|
107
|
+
|
|
108
|
+
result1 = described_class.unique_filename(file)
|
|
109
|
+
sleep(0.01) # Small delay to ensure different timestamps
|
|
110
|
+
result2 = described_class.unique_filename(file)
|
|
111
|
+
|
|
112
|
+
expect(result1).not_to eq(result2)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe '.ensure_unique_output' do
|
|
117
|
+
it 'returns original path when overwrite is true' do
|
|
118
|
+
file = File.join(@test_dir, 'output.png')
|
|
119
|
+
File.write(file, 'test')
|
|
120
|
+
|
|
121
|
+
result = described_class.ensure_unique_output(file, overwrite: true)
|
|
122
|
+
expect(result).to eq(file)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns original path when file does not exist and overwrite is false' do
|
|
126
|
+
file = File.join(@test_dir, 'new_output.png')
|
|
127
|
+
|
|
128
|
+
result = described_class.ensure_unique_output(file, overwrite: false)
|
|
129
|
+
expect(result).to eq(file)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'returns unique filename when file exists and overwrite is false' do
|
|
133
|
+
file = File.join(@test_dir, 'existing_output.png')
|
|
134
|
+
File.write(file, 'test')
|
|
135
|
+
|
|
136
|
+
result = described_class.ensure_unique_output(file, overwrite: false)
|
|
137
|
+
expect(result).to match(/existing_output_\d{8}_\d{6}_\d{3}\.png$/)
|
|
138
|
+
expect(result).not_to eq(file)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'defaults to overwrite false when not specified' do
|
|
142
|
+
file = File.join(@test_dir, 'default_test.png')
|
|
143
|
+
File.write(file, 'test')
|
|
144
|
+
|
|
145
|
+
result = described_class.ensure_unique_output(file)
|
|
146
|
+
expect(result).not_to eq(file)
|
|
147
|
+
expect(result).to match(/default_test_\d{8}_\d{6}_\d{3}\.png$/)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe RubySpriter::Utils::PathHelper do
|
|
6
|
-
describe '.quote_path' do
|
|
7
|
-
context 'on Windows' do
|
|
8
|
-
before do
|
|
9
|
-
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it 'wraps path in double quotes' do
|
|
13
|
-
expect(described_class.quote_path('C:\\test\\file.txt')).to eq('"C:\\test\\file.txt"')
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
context 'on Unix-like systems' do
|
|
18
|
-
before do
|
|
19
|
-
allow(RubySpriter::Platform).to receive(:windows?).and_return(false)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it 'wraps path in single quotes' do
|
|
23
|
-
expect(described_class.quote_path('/test/file.txt')).to eq("'/test/file.txt'")
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'escapes single quotes in path' do
|
|
27
|
-
result = described_class.quote_path("/test/file's.txt")
|
|
28
|
-
# Should wrap in single quotes and escape internal single quotes
|
|
29
|
-
expect(result).to start_with("'")
|
|
30
|
-
expect(result).to end_with("'")
|
|
31
|
-
expect(result).to include("\\'") # Should contain escaped single quote
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
describe '.normalize_for_python' do
|
|
37
|
-
it 'returns absolute path' do
|
|
38
|
-
result = described_class.normalize_for_python('.')
|
|
39
|
-
# Should return an absolute path (Unix: starts with /, Windows: starts with drive letter)
|
|
40
|
-
is_unix_absolute = result.start_with?('/')
|
|
41
|
-
is_windows_absolute = result.match?(/^[A-Z]:/i)
|
|
42
|
-
expect(is_unix_absolute || is_windows_absolute).to be true
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
context 'on Windows' do
|
|
46
|
-
before do
|
|
47
|
-
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'converts backslashes to forward slashes' do
|
|
51
|
-
allow(File).to receive(:absolute_path).and_return('C:\\test\\file.txt')
|
|
52
|
-
expect(described_class.normalize_for_python('file.txt')).to eq('C:/test/file.txt')
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
describe '.to_native' do
|
|
58
|
-
context 'on Windows' do
|
|
59
|
-
before do
|
|
60
|
-
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it 'converts forward slashes to backslashes' do
|
|
64
|
-
expect(described_class.to_native('C:/test/file.txt')).to eq('C:\\test\\file.txt')
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
context 'on Unix-like systems' do
|
|
69
|
-
before do
|
|
70
|
-
allow(RubySpriter::Platform).to receive(:windows?).and_return(false)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it 'converts backslashes to forward slashes' do
|
|
74
|
-
expect(described_class.to_native('C:\\test\\file.txt')).to eq('C:/test/file.txt')
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::Utils::PathHelper do
|
|
6
|
+
describe '.quote_path' do
|
|
7
|
+
context 'on Windows' do
|
|
8
|
+
before do
|
|
9
|
+
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'wraps path in double quotes' do
|
|
13
|
+
expect(described_class.quote_path('C:\\test\\file.txt')).to eq('"C:\\test\\file.txt"')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context 'on Unix-like systems' do
|
|
18
|
+
before do
|
|
19
|
+
allow(RubySpriter::Platform).to receive(:windows?).and_return(false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'wraps path in single quotes' do
|
|
23
|
+
expect(described_class.quote_path('/test/file.txt')).to eq("'/test/file.txt'")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'escapes single quotes in path' do
|
|
27
|
+
result = described_class.quote_path("/test/file's.txt")
|
|
28
|
+
# Should wrap in single quotes and escape internal single quotes
|
|
29
|
+
expect(result).to start_with("'")
|
|
30
|
+
expect(result).to end_with("'")
|
|
31
|
+
expect(result).to include("\\'") # Should contain escaped single quote
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '.normalize_for_python' do
|
|
37
|
+
it 'returns absolute path' do
|
|
38
|
+
result = described_class.normalize_for_python('.')
|
|
39
|
+
# Should return an absolute path (Unix: starts with /, Windows: starts with drive letter)
|
|
40
|
+
is_unix_absolute = result.start_with?('/')
|
|
41
|
+
is_windows_absolute = result.match?(/^[A-Z]:/i)
|
|
42
|
+
expect(is_unix_absolute || is_windows_absolute).to be true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'on Windows' do
|
|
46
|
+
before do
|
|
47
|
+
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'converts backslashes to forward slashes' do
|
|
51
|
+
allow(File).to receive(:absolute_path).and_return('C:\\test\\file.txt')
|
|
52
|
+
expect(described_class.normalize_for_python('file.txt')).to eq('C:/test/file.txt')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '.to_native' do
|
|
58
|
+
context 'on Windows' do
|
|
59
|
+
before do
|
|
60
|
+
allow(RubySpriter::Platform).to receive(:windows?).and_return(true)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'converts forward slashes to backslashes' do
|
|
64
|
+
expect(described_class.to_native('C:/test/file.txt')).to eq('C:\\test\\file.txt')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'on Unix-like systems' do
|
|
69
|
+
before do
|
|
70
|
+
allow(RubySpriter::Platform).to receive(:windows?).and_return(false)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'converts backslashes to forward slashes' do
|
|
74
|
+
expect(described_class.to_native('C:\\test\\file.txt')).to eq('C:/test/file.txt')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe RubySpriter::Utils::SpritesheetSplitter do
|
|
6
|
-
describe '#split_into_frames' do
|
|
7
|
-
let(:spritesheet_file) { 'spritesheet.png' }
|
|
8
|
-
let(:output_dir) { '/tmp/frames' }
|
|
9
|
-
let(:columns) { 4 }
|
|
10
|
-
let(:rows) { 4 }
|
|
11
|
-
let(:frames) { 16 }
|
|
12
|
-
let(:tile_width) { 100 }
|
|
13
|
-
let(:tile_height) { 100 }
|
|
14
|
-
|
|
15
|
-
let(:splitter) { described_class.new }
|
|
16
|
-
|
|
17
|
-
before do
|
|
18
|
-
allow(FileUtils).to receive(:mkdir_p)
|
|
19
|
-
allow(File).to receive(:exist?).and_return(true)
|
|
20
|
-
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
21
|
-
|
|
22
|
-
# Mock ImageMagick identify to return dimensions
|
|
23
|
-
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
24
|
-
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it 'creates output directory for frames' do
|
|
29
|
-
expect(FileUtils).to receive(:mkdir_p).with(output_dir)
|
|
30
|
-
|
|
31
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it 'extracts each frame with FR prefix and zero-padded numbers' do
|
|
35
|
-
# Expect ImageMagick convert commands for each frame
|
|
36
|
-
expect(Open3).to receive(:capture3).with(/identify/).once.and_return(
|
|
37
|
-
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
(1..frames).each do |i|
|
|
41
|
-
expect(Open3).to receive(:capture3).with(/convert.*FR#{format('%03d', i)}_/).and_return(
|
|
42
|
-
['', '', instance_double(Process::Status, success?: true)]
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it 'calculates tile dimensions from spritesheet size and grid' do
|
|
50
|
-
# For 400x400 image with 4x4 grid, tiles should be 100x100
|
|
51
|
-
expect(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
52
|
-
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
# Check that crop parameters use 100x100
|
|
56
|
-
expect(Open3).to receive(:capture3).with(/100x100\+0\+0/).and_return(
|
|
57
|
-
['', '', instance_double(Process::Status, success?: true)]
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
61
|
-
|
|
62
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it 'includes spritesheet basename in frame output names' do
|
|
66
|
-
basename = File.basename(spritesheet_file, '.*')
|
|
67
|
-
|
|
68
|
-
# Mock identify first
|
|
69
|
-
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
70
|
-
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
# Expect convert with frame filename
|
|
74
|
-
expect(Open3).to receive(:capture3).with(/FR001_#{basename}\.png/).and_return(
|
|
75
|
-
['', '', instance_double(Process::Status, success?: true)]
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it 'raises ProcessingError when ImageMagick fails' do
|
|
82
|
-
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
83
|
-
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
allow(Open3).to receive(:capture3).with(/convert/).and_return(
|
|
87
|
-
['', 'Error message', instance_double(Process::Status, success?: false)]
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
expect {
|
|
91
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
92
|
-
}.to raise_error(RubySpriter::ProcessingError, /Could not extract frame/)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it 'displays progress information' do
|
|
96
|
-
expect(RubySpriter::Utils::OutputFormatter).to receive(:header).with(/Extracting Frames/)
|
|
97
|
-
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Splitting spritesheet into #{frames} frames/)
|
|
98
|
-
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Output directory:/)
|
|
99
|
-
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Frames extracted successfully/)
|
|
100
|
-
|
|
101
|
-
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::Utils::SpritesheetSplitter do
|
|
6
|
+
describe '#split_into_frames' do
|
|
7
|
+
let(:spritesheet_file) { 'spritesheet.png' }
|
|
8
|
+
let(:output_dir) { '/tmp/frames' }
|
|
9
|
+
let(:columns) { 4 }
|
|
10
|
+
let(:rows) { 4 }
|
|
11
|
+
let(:frames) { 16 }
|
|
12
|
+
let(:tile_width) { 100 }
|
|
13
|
+
let(:tile_height) { 100 }
|
|
14
|
+
|
|
15
|
+
let(:splitter) { described_class.new }
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
allow(FileUtils).to receive(:mkdir_p)
|
|
19
|
+
allow(File).to receive(:exist?).and_return(true)
|
|
20
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
21
|
+
|
|
22
|
+
# Mock ImageMagick identify to return dimensions
|
|
23
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
24
|
+
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'creates output directory for frames' do
|
|
29
|
+
expect(FileUtils).to receive(:mkdir_p).with(output_dir)
|
|
30
|
+
|
|
31
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'extracts each frame with FR prefix and zero-padded numbers' do
|
|
35
|
+
# Expect ImageMagick convert commands for each frame
|
|
36
|
+
expect(Open3).to receive(:capture3).with(/identify/).once.and_return(
|
|
37
|
+
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
(1..frames).each do |i|
|
|
41
|
+
expect(Open3).to receive(:capture3).with(/convert.*FR#{format('%03d', i)}_/).and_return(
|
|
42
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'calculates tile dimensions from spritesheet size and grid' do
|
|
50
|
+
# For 400x400 image with 4x4 grid, tiles should be 100x100
|
|
51
|
+
expect(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
52
|
+
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Check that crop parameters use 100x100
|
|
56
|
+
expect(Open3).to receive(:capture3).with(/100x100\+0\+0/).and_return(
|
|
57
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
61
|
+
|
|
62
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'includes spritesheet basename in frame output names' do
|
|
66
|
+
basename = File.basename(spritesheet_file, '.*')
|
|
67
|
+
|
|
68
|
+
# Mock identify first
|
|
69
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
70
|
+
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Expect convert with frame filename
|
|
74
|
+
expect(Open3).to receive(:capture3).with(/FR001_#{basename}\.png/).and_return(
|
|
75
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'raises ProcessingError when ImageMagick fails' do
|
|
82
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
83
|
+
["400x400\n", '', instance_double(Process::Status, success?: true)]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
allow(Open3).to receive(:capture3).with(/convert/).and_return(
|
|
87
|
+
['', 'Error message', instance_double(Process::Status, success?: false)]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
expect {
|
|
91
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
|
|
92
|
+
}.to raise_error(RubySpriter::ProcessingError, /Could not extract frame/)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'displays progress information' do
|
|
96
|
+
expect(RubySpriter::Utils::OutputFormatter).to receive(:header).with(/Extracting Frames/)
|
|
97
|
+
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Splitting spritesheet into #{frames} frames/)
|
|
98
|
+
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Output directory:/)
|
|
99
|
+
expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Frames extracted successfully/)
|
|
100
|
+
|
|
101
|
+
splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|