cool-bun-demo/image_converter.rb

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