ruby_spriter 0.6.6 → 0.6.7.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.
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'fileutils'
5
+
6
+ module RubySpriter
7
+ # Manages PNG compression with metadata preservation
8
+ class CompressionManager
9
+ # Compress PNG file using ImageMagick with maximum compression
10
+ # @param input_file [String] Source PNG file
11
+ # @param output_file [String] Destination PNG file
12
+ # @param debug [Boolean] Enable debug output
13
+ def self.compress(input_file, output_file, debug: false)
14
+ Utils::FileHelper.validate_readable!(input_file)
15
+
16
+ cmd = build_compression_command(input_file, output_file)
17
+
18
+ if debug
19
+ Utils::OutputFormatter.indent("DEBUG: Compression command: #{cmd}")
20
+ end
21
+
22
+ stdout, stderr, status = Open3.capture3(cmd)
23
+
24
+ unless status.success?
25
+ raise ProcessingError, "Failed to compress PNG: #{stderr}"
26
+ end
27
+
28
+ Utils::FileHelper.validate_exists!(output_file)
29
+ end
30
+
31
+ # Compress PNG file while preserving embedded metadata
32
+ # @param input_file [String] Source PNG file
33
+ # @param output_file [String] Destination PNG file
34
+ # @param debug [Boolean] Enable debug output
35
+ def self.compress_with_metadata(input_file, output_file, debug: false)
36
+ # Read metadata before compression
37
+ metadata = MetadataManager.read(input_file)
38
+
39
+ # Compress the file
40
+ temp_file = output_file.gsub('.png', '_compress_temp.png')
41
+ compress(input_file, temp_file, debug: debug)
42
+
43
+ # Re-embed metadata if it existed
44
+ if metadata
45
+ MetadataManager.embed(
46
+ temp_file,
47
+ output_file,
48
+ columns: metadata[:columns],
49
+ rows: metadata[:rows],
50
+ frames: metadata[:frames],
51
+ debug: debug
52
+ )
53
+
54
+ # Clean up temp file
55
+ FileUtils.rm_f(temp_file) if File.exist?(temp_file)
56
+ else
57
+ # No metadata, just move temp to output
58
+ FileUtils.mv(temp_file, output_file)
59
+ end
60
+ end
61
+
62
+ # Get compression statistics
63
+ # @param original_file [String] Original file path
64
+ # @param compressed_file [String] Compressed file path
65
+ # @return [Hash] Statistics including sizes and reduction percentage
66
+ def self.compression_stats(original_file, compressed_file)
67
+ original_size = File.size(original_file)
68
+ compressed_size = File.size(compressed_file)
69
+ saved_bytes = original_size - compressed_size
70
+ reduction_percent = (saved_bytes.to_f / original_size * 100.0)
71
+
72
+ {
73
+ original_size: original_size,
74
+ compressed_size: compressed_size,
75
+ saved_bytes: saved_bytes,
76
+ reduction_percent: reduction_percent
77
+ }
78
+ end
79
+
80
+ private_class_method def self.build_compression_command(input_file, output_file)
81
+ magick_cmd = Platform.imagemagick_convert_cmd
82
+
83
+ # Use maximum PNG compression settings:
84
+ # - compression-level=9: Maximum zlib compression
85
+ # - compression-filter=5: Paeth filter (best for most images)
86
+ # - compression-strategy=1: Filtered strategy
87
+ # - quality=95: High quality
88
+ # - strip: Remove all metadata (we'll re-add it later)
89
+ [
90
+ magick_cmd,
91
+ Utils::PathHelper.quote_path(input_file),
92
+ '-strip',
93
+ '-define', 'png:compression-level=9',
94
+ '-define', 'png:compression-filter=5',
95
+ '-define', 'png:compression-strategy=1',
96
+ '-quality', '95',
97
+ Utils::PathHelper.quote_path(output_file)
98
+ ].join(' ')
99
+ end
100
+ end
101
+ end
@@ -58,6 +58,39 @@ module RubySpriter
58
58
  }
59
59
  end
60
60
 
61
+ # Find all PNG files with spritesheet metadata in a directory
62
+ # @param directory [String] Directory path to scan
63
+ # @return [Array<String>] Sorted array of spritesheet file paths
64
+ def find_spritesheets_in_directory(directory)
65
+ # Validate directory exists
66
+ unless File.directory?(directory)
67
+ raise ValidationError, "Directory not found: #{directory}"
68
+ end
69
+
70
+ # Find all PNG files
71
+ pattern = File.join(directory, '*.png')
72
+ png_files = Dir.glob(pattern)
73
+
74
+ # Filter to only files with metadata
75
+ spritesheets = png_files.select do |file|
76
+ metadata = MetadataManager.read(file)
77
+ !metadata.nil?
78
+ end
79
+
80
+ # Validate we found at least one
81
+ if spritesheets.empty?
82
+ raise ValidationError, "No PNG files with spritesheet metadata found in directory: #{directory}"
83
+ end
84
+
85
+ # Validate we have at least 2
86
+ if spritesheets.length < 2
87
+ raise ValidationError, "Found only #{spritesheets.length} spritesheet, need at least 2 for consolidation"
88
+ end
89
+
90
+ # Sort alphabetically by filename
91
+ spritesheets.sort
92
+ end
93
+
61
94
  private
62
95
 
63
96
  def validate_files!(files)
@@ -32,12 +32,23 @@ module RubySpriter
32
32
  linux: 'sudo apt install imagemagick',
33
33
  macos: 'brew install imagemagick'
34
34
  }
35
+ },
36
+ xvfb: {
37
+ command: 'xvfb-run --help',
38
+ pattern: /xvfb-run/i,
39
+ install: {
40
+ windows: 'Not required on Windows',
41
+ linux: 'sudo apt install xvfb',
42
+ macos: 'Not required on macOS'
43
+ },
44
+ optional_for: [:windows, :macos] # Only required on Linux
35
45
  }
36
46
  }.freeze
37
47
 
38
48
  def initialize(verbose: false)
39
49
  @verbose = verbose
40
50
  @gimp_path = nil
51
+ @gimp_version = nil
41
52
  end
42
53
 
43
54
  # Check all dependencies
@@ -58,7 +69,7 @@ module RubySpriter
58
69
  # @return [Boolean] true if all dependencies are available
59
70
  def all_satisfied?
60
71
  results = check_all
61
- results.all? { |_tool, status| status[:available] }
72
+ results.all? { |tool, status| status[:available] || status[:optional] }
62
73
  end
63
74
 
64
75
  # Print dependency status report
@@ -70,14 +81,31 @@ module RubySpriter
70
81
  puts "=" * 60
71
82
 
72
83
  results.each do |tool, status|
73
- icon = status[:available] ? "✅" : "❌"
74
- puts "\n#{icon} #{tool.to_s.upcase}"
75
-
84
+ if status[:optional] && !status[:available]
85
+ icon = "⚪"
86
+ optional_text = " (Optional for #{Platform.current})"
87
+ else
88
+ icon = status[:available] ? "✅" : "❌"
89
+ optional_text = ""
90
+ end
91
+
92
+ puts "\n#{icon} #{tool.to_s.upcase}#{optional_text}"
93
+
76
94
  if status[:available]
77
- puts " Found: #{status[:path] || status[:version]}"
95
+ if tool == :gimp && status[:version]
96
+ version_str = "GIMP #{status[:version][:full]}"
97
+ puts " Found: #{status[:path]}"
98
+ puts " Version: #{version_str}"
99
+ else
100
+ puts " Found: #{status[:path] || status[:version]}"
101
+ end
78
102
  else
79
- puts " Status: NOT FOUND"
80
- puts " Install: #{status[:install_cmd]}"
103
+ if status[:optional]
104
+ puts " Status: NOT FOUND (not required for this platform)"
105
+ else
106
+ puts " Status: NOT FOUND"
107
+ puts " Install: #{status[:install_cmd]}"
108
+ end
81
109
  end
82
110
  end
83
111
 
@@ -87,9 +115,16 @@ module RubySpriter
87
115
  # Get the found GIMP executable path
88
116
  attr_reader :gimp_path
89
117
 
118
+ # Get the detected GIMP version info
119
+ attr_reader :gimp_version
120
+
90
121
  private
91
122
 
92
123
  def check_tool(tool, config)
124
+ # Check if this tool is optional for current platform
125
+ optional_platforms = config[:optional_for] || []
126
+ is_optional = optional_platforms.include?(Platform.current)
127
+
93
128
  stdout, stderr, status = Open3.capture3(config[:command])
94
129
  output = stdout + stderr
95
130
 
@@ -98,13 +133,15 @@ module RubySpriter
98
133
  {
99
134
  available: true,
100
135
  version: version,
101
- install_cmd: nil
136
+ install_cmd: nil,
137
+ optional: is_optional
102
138
  }
103
139
  else
104
140
  {
105
141
  available: false,
106
142
  version: nil,
107
- install_cmd: config[:install][Platform.current]
143
+ install_cmd: config[:install][Platform.current],
144
+ optional: is_optional
108
145
  }
109
146
  end
110
147
  rescue StandardError => e
@@ -112,7 +149,8 @@ module RubySpriter
112
149
  {
113
150
  available: false,
114
151
  version: nil,
115
- install_cmd: config[:install][Platform.current]
152
+ install_cmd: config[:install][Platform.current],
153
+ optional: is_optional
116
154
  }
117
155
  end
118
156
 
@@ -121,32 +159,44 @@ module RubySpriter
121
159
  default_path = Platform.default_gimp_path
122
160
  if gimp_exists?(default_path)
123
161
  @gimp_path = default_path
124
- return gimp_status(true, default_path)
162
+ @gimp_version = Platform.get_gimp_version(default_path)
163
+ return gimp_status(true, default_path, @gimp_version)
125
164
  end
126
165
 
127
166
  # Try alternative paths
128
167
  Platform.alternative_gimp_paths.each do |path|
129
168
  if gimp_exists?(path)
130
169
  @gimp_path = path
131
- return gimp_status(true, path)
170
+ @gimp_version = Platform.get_gimp_version(path)
171
+ return gimp_status(true, path, @gimp_version)
132
172
  end
133
173
  end
134
174
 
135
175
  # Not found
136
- gimp_status(false, nil)
176
+ gimp_status(false, nil, nil)
137
177
  end
138
178
 
139
179
  def gimp_exists?(path)
140
180
  return false if path.nil? || path.empty?
181
+
182
+ # Handle Flatpak GIMP
183
+ if path.start_with?('flatpak:')
184
+ flatpak_app = path.sub('flatpak:', '')
185
+ stdout, _stderr, status = Open3.capture3("flatpak list --app | grep #{flatpak_app}")
186
+ return status.success? && !stdout.strip.empty?
187
+ end
188
+
141
189
  File.exist?(path)
142
190
  end
143
191
 
144
- def gimp_status(available, path)
145
- {
192
+ def gimp_status(available, path, version)
193
+ status = {
146
194
  available: available,
147
195
  path: path,
148
196
  install_cmd: gimp_install_command
149
197
  }
198
+ status[:version] = version if version
199
+ status
150
200
  end
151
201
 
152
202
  def gimp_install_command