This example needs Ruby 1.9.
Two ways to run this:
$ ./pixelizer.rb infile.png > outfile.png
$ ./pixelizer.rb - > outfile.png < infile.png
| #!/usr/bin/env ruby | |
| require 'chunky_png' | |
| class Pixelizer | |
| include ChunkyPNG | |
| # The ChunkyPNG::Image instance. | |
| attr_reader :png | |
| # The pixelizer accepts an IO object, like a file handle or $stdin. | |
| def initialize(io) | |
| @png = Image.from_io io | |
| end | |
| # Pixelizes using a very simple algorithm. Every block of 10x10 pixels | |
| # is evaluated, and replaced by one single color closest to the set of | |
| # colors within that block. You can overwrite the 10x10 pixels by setting | |
| # the first argument. | |
| def pixelize!(block_size = 10) | |
| dimension = @png.dimension | |
| @block_point = Point.new 0, 0 | |
| while @block_point.within_bounds? dimension | |
| y_delta = dimension.height - @block_point.y | |
| @block_height = y_delta < block_size ? y_delta : block_size | |
| while @block_point.within_bounds? dimension | |
| x_delta = dimension.width - @block_point.x | |
| @block_width = x_delta < block_size ? x_delta : block_size | |
| pixelize_block! | |
| @block_point.x += block_size | |
| end | |
| @block_point.x = 0 | |
| @block_point.y += block_size | |
| end | |
| end | |
| protected | |
| # Replaces the block with a single color. | |
| def pixelize_block! | |
| new_color = blur_color | |
| for block_y in 0...@block_height | |
| for block_x in 0...@block_width | |
| @png[@block_point.x + block_x, @block_point.y + block_y] = new_color | |
| end | |
| end | |
| end | |
| # Calculates the new color for a block based on surrounding colors. | |
| # Completely transparent pixels don't count of course. | |
| def blur_color | |
| colors = {r: [], g: [], b: [], a: []} | |
| for block_y in 0...@block_height | |
| for block_x in 0...@block_width | |
| color = @png[@block_point.x + block_x, @block_point.y + block_y] | |
| unless Color.fully_transparent? color | |
| colors.keys.each do |channel| | |
| colors[channel] << Color.send(channel, color) | |
| end | |
| end | |
| end | |
| end | |
| return Color::TRANSPARENT if colors[:r].empty? | |
| Color.rgba *(colors.map do |chan, set| | |
| set.inject(:+) / set.length | |
| end) | |
| end | |
| end | |
| unless ARGV[0] | |
| $stderr.puts "Usage: #{$0} [input.png | -] > output.png" | |
| exit 1 | |
| end | |
| begin | |
| io = ARGV[0] == '-' ? $stdin : File.open(ARGV[0], 'rb') | |
| pix = Pixelizer.new io | |
| pix.pixelize! | |
| pix.png.write $stdout | |
| rescue | |
| $stderr.puts "Sorry, something went wrong.\n#{$!}" | |
| exit 1 | |
| end |