From: Ruby Quiz Date: 2007-06-14T21:15:37+09:00 Subject: [SUMMARY] Mexican Blanket (#127) 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...