Several solutions to this quiz called out to RMagick to draw pictures of their
work. That made for some pretty output. One such solution was from Justin
Ethier and I want to take a look at that code.
Justin observed that each row of the pattern is just the previous row shifted
over one pixel. In order to build those rows, Justing decided to just build one
long pattern and slice it as needed. Here's that chunk of code:
def create_gradient(colors, width=5)
pattern = []
for i in 0...(width)
(width-i).times { pattern.push(colors[0]) }
(i+1).times { pattern.push(colors[1]) }
end
for i in 0...(width)
(i+1).times { pattern.push(colors[2]) }
(width-i-1).times { pattern.push(colors[1]) }
end
pattern
end
# ...
You pass this method an Array of three colors and a width for a full band of
color. It begins by fading the first color into the second by iteratively
adding thinner and thinner lines to the gradient pattern. It then repeats the
process in reverse to fade the second color into the third.
Justin's blankets also include solid color bands created by the following
method:
# ...
def create_solid(colors, width)
pattern = []
for color in colors
width.times { pattern.push(color) }
end
pattern
end
# ...
This methods works exactly like create_gradient() except there's no blending of
colors.
With the pieces to create the structure in place, we are now ready for some
rendering code:
# ...
def draw_ascii(pattern, width)
for i in 0...(pattern.size-width+1)
puts pattern.slice(i, width).join
end
end
# ...
As you can see, generating the ASCII output is trivial. The long pattern is
simply divided into a moving window of width slices. Each slice is then printed
as one row of output.
RMagick rendering takes a little more work, but still isn't hard:
# ...
require 'RMagick'
include Magick
def draw(filename, pattern, width, height)
canvas = Magick::ImageList.new
canvas.new_image(width, height, Magick::HatchFill.new('white', 'white'))
pts = Magick::Draw.new
for y in 0... height
line = pattern.slice(y, width)
x = 0
for color in line
pts.fill(color)
pts.point(x, y)
x = x + 1
end
end
pts.draw(canvas)
canvas.write(filename)
end
# ...
This method begins by preparing a canvas on which it can draw points. From
there it does the same looping over the pattern we saw earlier, but this time
points are plotted on the canvas. When all the marks have been made, the image
is flushed to a file on the disk.
The last bit of code puts the generators and renderers to work:
# ...
draw_ascii(create_gradient(['R', 'B', 'Y']), 28)
mex_flag = create_solid(['rgb(0, 64, 0)', 'white', 'red'], 5)
border = create_solid(['rgb(0, 64, 0)'], 25)
pattern = create_gradient(['red', 'blue', 'yellow'])
pattern = pattern + mex_flag
pattern = pattern + border
pattern = pattern + create_gradient(['black', 'red', 'orange'])
pattern = pattern + border
pattern = pattern + mex_flag.reverse
pattern = pattern + create_gradient(['red', 'purple', 'black'], 8)
draw("mexican_blanket.jpg", pattern, 100, 200)
You can see here that the ASCII renderer is fed a trivial pattern created from a
single gradient. The pattern built for the image file is more complex,
combining several different patterns. In both cases though, it's a single call
to the drawing routines we examined above to show results.
My thanks to all the weavers. I'll bet you never knew you had such a talent for
fabric.
Tomorrow we will try some non-traditional arithmetic...