157 lines
3.3 KiB
Ruby
Executable File
157 lines
3.3 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
|
|
require 'rmagick'
|
|
|
|
# [ ] load the image
|
|
# [ ] map each pixel to a 12 bit color, with pixels less than 25% transparent as mask
|
|
# [ ] sort the color counts
|
|
# [ ] transparent pixels become the mask
|
|
# [ ] turn the big color counts into three bitplames worth of data
|
|
# [ ] turn the little color counts into up to 8 sprites worth of data strips & color changes
|
|
|
|
image = Magick::Image.read('topaz.png').first
|
|
|
|
rows = []
|
|
|
|
class MaskPixel
|
|
def initialize; end
|
|
|
|
def color? = false
|
|
end
|
|
|
|
class Color
|
|
attr_reader :red, :green, :blue
|
|
|
|
def initialize(red:, green:, blue:)
|
|
@red = red
|
|
@green = green
|
|
@blue = blue
|
|
end
|
|
|
|
WHITE = new(red: 15, green: 15, blue: 15)
|
|
BLACK = new(red: 0, green: 0, blue: 0)
|
|
|
|
def ==(other)
|
|
red == other.red && green == other.green && blue == other.blue
|
|
end
|
|
|
|
def -(other)
|
|
red - other.red + green - other.green + blue - other.blue
|
|
end
|
|
|
|
alias eql? ==
|
|
|
|
def hash
|
|
red * 256 + green * 16 + blue
|
|
end
|
|
end
|
|
|
|
class BitplanePixel
|
|
attr_reader :color
|
|
|
|
def initialize(color:)
|
|
@color = color
|
|
end
|
|
|
|
def color? = true
|
|
end
|
|
|
|
MAX_ALPHA = Magick::QuantumRange * 0.25
|
|
BIT_SHIFT = Math.log2(Magick::QuantumRange + 1) - 4
|
|
|
|
class PixelRow
|
|
def initialize
|
|
@row = []
|
|
end
|
|
|
|
def add(pixel:, x:)
|
|
@row[x] = pixel
|
|
end
|
|
|
|
def colors_with_usage
|
|
@row.find_all(&:color?).each_with_object({}) do |pixel, obj|
|
|
obj[pixel.color] ||= 0
|
|
obj[pixel.color] += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
image.each_pixel do |px, x, y|
|
|
rows[y] ||= PixelRow.new
|
|
|
|
pixel = if px.alpha < MAX_ALPHA
|
|
MaskPixel.new
|
|
else
|
|
BitplanePixel.new(
|
|
color: Color.new(
|
|
red: px.red >> BIT_SHIFT,
|
|
green: px.green >> BIT_SHIFT,
|
|
blue: px.blue >> BIT_SHIFT
|
|
)
|
|
)
|
|
end
|
|
|
|
rows[y].add(pixel:, x:)
|
|
end
|
|
|
|
prioritized_colors = rows[50].colors_with_usage.to_a.sort { |a, b| b.last <=> a.last }
|
|
|
|
presets_by_pixel_color = [
|
|
nil, Color::BLACK, Color::WHITE
|
|
]
|
|
|
|
unavailable_bitplane_colors = {
|
|
0 => true,
|
|
1 => true,
|
|
2 => true
|
|
}
|
|
|
|
MAX_BITPLANE_COLORS = 8
|
|
|
|
bitplane_colors = [
|
|
nil,
|
|
Color::BLACK,
|
|
Color::WHITE
|
|
]
|
|
|
|
color_to_pixel_map = {}
|
|
|
|
# take 5 + BLACK + WHITE colors into bitplane_colors
|
|
|
|
prioritized_colors.map(&:first).each do |color|
|
|
maybe_preset_index = presets_by_pixel_color.find_index { |c| c && color == c }
|
|
|
|
if !maybe_preset_index.nil?
|
|
bitplane_colors[maybe_preset_index] = color
|
|
color_to_pixel_map[color] = maybe_preset_index
|
|
|
|
unavailable_bitplane_colors[maybe_preset_index] = true
|
|
elsif bitplane_colors.count < MAX_BITPLANE_COLORS
|
|
next_available_bitplane_color = 8.times.find { |i| !unavailable_bitplane_colors[i] }
|
|
|
|
bitplane_colors[next_available_bitplane_color] = color
|
|
color_to_pixel_map[color] = next_available_bitplane_color
|
|
|
|
unavailable_bitplane_colors[next_available_bitplane_color] = true
|
|
else
|
|
closest_match = bitplane_colors.each_with_index.map do |bp_color, index|
|
|
next unless bp_color
|
|
|
|
[(bp_color - color).abs, index]
|
|
end.compact.min_by(&:first)
|
|
|
|
pp color
|
|
pp closest_match
|
|
pp bitplane_colors[closest_match.last]
|
|
puts '************************'
|
|
|
|
color_to_pixel_map[color] = closest_match.last
|
|
end
|
|
end
|
|
|
|
pp prioritized_colors
|
|
pp bitplane_colors
|
|
pp color_to_pixel_map
|
|
|
|
# .to_a.sort(&:last).reverse
|