Skip to content

Instantly share code, notes, and snippets.

@jspillers
Created June 29, 2011 13:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspillers/1053837 to your computer and use it in GitHub Desktop.
Save jspillers/1053837 to your computer and use it in GitHub Desktop.
Solution for codebrawl PNG pixelation challenge
require 'rubygems'
require 'chunky_png'
# Pixelator will pixelate your pngs
# defaults to a 10 pixel block, but will gladly accept
# larger or smaller blocks
# also includes a pointelize method which does color averaged dots
# instead of blocks
class Pixelator
include ChunkyPNG::Color
def initialize(image_path)
@image = ChunkyPNG::Image.from_file(image_path)
end
# anytime we set the block size the number of block rows
# and block columns needs to be updated as well
def set_block_size(blck_size)
@block_size = blck_size
@col_blocks = (@image.width.to_f / @block_size.to_f)
@row_blocks = (@image.height.to_f / @block_size.to_f)
end
def pixelate(blck_size=10)
set_block_size(blck_size)
# iterate through each "block" as defined by the block size
each_block do |x, y|
# setting current block size handles partial blocks at the ends of rows, columns, or both
@curr_x_block_size = x + 1 > @col_blocks ? @image.width - (@col_blocks.to_i * @block_size) : @block_size
@curr_y_block_size = y + 1 > @row_blocks ? @image.height - (@row_blocks.to_i * @block_size) : @block_size
# gets an array containing the averages for each color channel
averages = average_color_for_block(x, y, @curr_x_block_size, @curr_y_block_size)
# set every pixel from the current block to the averaged color value
each_pixel_from_block(@curr_x_block_size, @curr_y_block_size) do |xp, yp|
@image[(x * @block_size) + xp, (y * @block_size) + yp] = rgba(averages[0], averages[1], averages[2], averages[3])
end
end
@image
end
def pointelize(blck_size=10, padding=2)
set_block_size(blck_size)
each_block do |x, y|
# setting current block size handles partial blocks at the ends of rows, columns, or both
@curr_x_block_size = x + 1 > @col_blocks ? @image.width - (@col_blocks.to_i * @block_size) : @block_size
@curr_y_block_size = y + 1 > @row_blocks ? @image.height - (@row_blocks.to_i * @block_size) : @block_size
averages = average_color_for_block(x, y, @curr_x_block_size, @curr_y_block_size)
averaged_color = rgba(averages[0], averages[1], averages[2], averages[3])
# paint the block all white
each_pixel_from_block(@curr_x_block_size, @curr_y_block_size) do |xp, yp|
@image[(x * @block_size) + xp, (y * @block_size) + yp] = rgba(0, 0, 0, averages[3])
end
# draw the circle
@image.circle(
x * @block_size + (@block_size / 2),
y * @block_size + (@block_size / 2),
(@block_size - padding) / 2,
averaged_color,
averaged_color
)
end
@image
end
private
def average_color_for_block(x, y, x_block_size, y_block_size)
red = []
green = []
blue = []
alpha = []
# iterate through the pixels in the current block and pull out all the color info
each_pixel_from_block(x_block_size, y_block_size) do |xp, yp|
pixel = @image[(x * @block_size) + xp, (y * @block_size) + yp]
red << r(pixel)
green << g(pixel)
blue << b(pixel)
alpha << a(pixel)
end
averages = []
# average each color channel
[red, green, blue, alpha].each do |color|
averages << color.inject{ |sum,i| sum + i } / color.size
end
averages
end
def each_block
@row_blocks.ceil.times do |y|
@col_blocks.ceil.times do |x|
yield(x,y)
end
end
end
def each_pixel_from_block(x_block_size, y_block_size)
(0..x_block_size - 1).each do |xp|
(0..y_block_size - 1).each do |yp|
yield(xp, yp)
end
end
end
end
# demo usage of each method
p = Pixelator.new('input.png')
i = p.pointelize(20, 5)
i.save("pointelized.png")
p = Pixelator.new('input.png')
i = p.pixelate
i.save("pixelated.png")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment