#!/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 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 pp rows[50].colors_with_usage