Class: RubySpriter::GhostEdgeCleaner

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_spriter/ghost_edge_cleaner.rb

Overview

GhostEdgeCleaner removes semi-transparent “ghost” pixels around sprite edges using multi-pass alpha channel cleanup while preserving anti-aliasing

Constant Summary collapse

MAX_PASSES =

Maximum cleanup passes to prevent infinite loops

3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input_image, output_image, config) ⇒ GhostEdgeCleaner

Returns a new instance of GhostEdgeCleaner.



15
16
17
18
19
20
21
22
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 15

def initialize(input_image, output_image, config)
  @input_image = input_image
  @output_image = output_image
  @config = config
  @ghost_pixels_detected = 0
  @passes_performed = 0
  @processing_time = 0
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



10
11
12
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 10

def config
  @config
end

#ghost_pixels_detectedObject (readonly)

Returns the value of attribute ghost_pixels_detected.



11
12
13
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 11

def ghost_pixels_detected
  @ghost_pixels_detected
end

#input_imageObject (readonly)

Returns the value of attribute input_image.



10
11
12
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 10

def input_image
  @input_image
end

#output_imageObject (readonly)

Returns the value of attribute output_image.



10
11
12
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 10

def output_image
  @output_image
end

#passes_performedObject (readonly)

Returns the value of attribute passes_performed.



11
12
13
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 11

def passes_performed
  @passes_performed
end

#processing_timeObject (readonly)

Returns the value of attribute processing_time.



11
12
13
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 11

def processing_time
  @processing_time
end

Instance Method Details

#clean_edgesObject

Clean edges by removing low-alpha pixels



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 74

def clean_edges
  threshold = @config.ghost_threshold

  # Use ImageMagick to selectively remove pixels below alpha threshold
  # This preserves RGB data and anti-aliasing for pixels above threshold
  threshold_fraction = threshold / 255.0

  # Use direct -fx operation on alpha channel with explicit RGB preservation
  # Use png:color-type=6 to force RGBA output (handles grayscale inputs)
  # Use 'u' to refer to current channel value in -channel context
  convert_cmd = Platform.imagemagick_convert_cmd
  cmd = "#{convert_cmd} #{Utils::PathHelper.quote_path(@output_image)} " \
        "-define png:color-type=6 " \
        "-alpha set " \
        "-channel A " \
        "-fx \"u < #{threshold_fraction} ? 0 : u\" " \
        "+channel " \
        "#{Utils::PathHelper.quote_path(@output_image)}"

  stdout, stderr, status = Open3.capture3(cmd)

  unless status.success?
    warn "Failed to clean edges: #{stderr}"
    return false
  end

  true
end

#detect_ghost_pixelsObject

Detect pixels with alpha below threshold



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 49

def detect_ghost_pixels
  threshold = @config.ghost_threshold

  # Use ImageMagick to count pixels with alpha below threshold
  # Alpha values are 0-255, threshold is 0-255
  threshold_fraction = threshold / 255.0

  convert_cmd = Platform.imagemagick_convert_cmd
  cmd = "#{convert_cmd} #{Utils::PathHelper.quote_path(@input_image)} " \
        "-channel A " \
        "-separate " \
        "-threshold #{(threshold_fraction * 100).to_i}% " \
        "-format '%[fx:w*h*(1-mean)]' " \
        "info:"

  stdout, stderr, status = Open3.capture3(cmd)

  if status.success?
    stdout.strip.gsub("'", '').to_f.to_i
  else
    0
  end
end

#multi_pass_cleanupObject

Perform multiple cleanup passes



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 104

def multi_pass_cleanup
  passes = 0
  previous_ghost_count = @ghost_pixels_detected

  MAX_PASSES.times do
    passes += 1

    # Perform cleanup pass
    clean_edges

    # Check if we still have ghost pixels
    current_ghost_count = detect_ghost_pixels_from_file(@output_image)

    # Stop if no improvement or no ghost pixels remain
    break if current_ghost_count == 0
    break if current_ghost_count >= previous_ghost_count

    previous_ghost_count = current_ghost_count
  end

  # Update instance variable for reporting
  @passes_performed = passes

  passes
end

#processObject

Main processing method



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 25

def process
  start_time = Time.now

  # Copy input to output as starting point
  FileUtils.cp(@input_image, @output_image)

  # Detect ghost pixels before cleanup
  @ghost_pixels_detected = detect_ghost_pixels

  # Perform multi-pass cleanup if multi_pass enabled
  if @config.multi_pass
    @passes_performed = multi_pass_cleanup
  else
    # Single pass cleanup
    clean_edges
    @passes_performed = 1
  end

  @processing_time = Time.now - start_time

  true
end

#reportObject

Generate processing report



131
132
133
134
135
136
137
138
139
# File 'lib/ruby_spriter/ghost_edge_cleaner.rb', line 131

def report
  {
    ghost_pixels_detected: @ghost_pixels_detected,
    threshold_used: @config.ghost_threshold,
    passes_performed: @passes_performed,
    processing_time: @processing_time.round(3),
    max_passes: MAX_PASSES
  }
end