From: Daniel Moore Date: 2009-02-16T04:45:24+09:00 Subject: [SUMMARY] Mathematical Image Generator (#191) This week's quiz was about creating interesting mathematical images using randomly generated formulae. Visit http://rubyquiz.strd6.com/quizzes/191/#summary to view the summary with images included. The overall architecture of the application is straightforward: * generate (or accept as input) mathematical formulae * compute the RGB values for each pixel * output an image Lars's solution provides a simple shell script which randomly generates formulae of depth 4 and saves image out as a png file. Lars's solution also makes it easy to add custom functions to the generator by pushing them on to an array. Alex Fenton provides a desktop application (using [wxRuby][1]) that allows for custom input of formulae and can save the images in multiple formats. If you are interested in learning wxRuby do examine Alex's solution as it provides a good example of a wxRuby application and is well commented. The two solutions provided slightly different conversions from function results to pixel colors. Alex's treated everything <= 0 as 0, while Lars mapped -1 to 0 and 1 to 255, with 0 being 128 (on an 8-bit RBG scale). This causes some images to appear wildly different, observe: ![image with zero at midrange][2a] ![image with zero and less cutoff][2b] One aspect of the challenge was to generate the images quickly. Here are the results of the submissions using ruby-prof to benchmark. ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux] width: 256 height: 256 r: cos(sin((sin(-x * PI)) ** 3 * PI) * PI) g: (sin(((-y + y) / 2) ** 3 * PI)) ** 3 b: ((cos(-x * y * PI) + cos(cos(y * PI) * PI)) / 2 + sin(x * PI) * cos(y * PI) * x * -x * sin(x * PI)) / 2 Lars Haugseth Total: 6.860000 %self total self wait child calls name 44.61 6.86 3.06 0.00 3.80 256 Integer#upto-1 (ruby_runtime:0} 18.95 1.30 1.30 0.00 0.00 1179648 Float#* (ruby_runtime:0} 6.71 0.46 0.46 0.00 0.00 327680 Math#cos (ruby_runtime:0} 5.25 0.36 0.36 0.00 0.00 393216 Float#+ (ruby_runtime:0} 4.96 0.34 0.34 0.00 0.00 327680 Math#sin (ruby_runtime:0} 4.23 0.29 0.29 0.00 0.00 262400 Float#/ (ruby_runtime:0} 3.50 0.24 0.24 0.00 0.00 196608 Float#** (ruby_runtime:0} 3.21 0.22 0.22 0.00 0.00 196608 String#<< (ruby_runtime:0} 2.92 0.20 0.20 0.00 0.00 262144 Float#-@ (ruby_runtime:0} 2.33 0.16 0.16 0.00 0.00 196608 Integer#chr (ruby_runtime:0} 1.90 0.13 0.13 0.00 0.00 196608 Float#to_i (ruby_runtime:0} 1.46 0.10 0.10 0.00 0.00 65792 Fixnum#to_f (ruby_runtime:0} 0.00 6.86 0.00 0.00 6.86 1 Integer#upto (ruby_runtime:0} 0.00 6.86 0.00 0.00 6.86 1 Kernel#eval (ruby_runtime:0} 0.00 0.00 0.00 0.00 0.00 257 Fixnum#- (ruby_runtime:0} 0.00 6.86 0.00 0.00 6.86 1 ImageGenerator#generate_pixels (solution.rb:48} Alex Fenton Total: 7.800000 %self total self wait child calls name 41.15 5.90 3.21 0.00 2.69 196608 Proc#call (ruby_runtime:0} 15.13 1.18 1.18 0.00 0.00 1179648 Float#* (ruby_runtime:0} 12.18 7.79 0.95 0.00 6.84 256 Integer#upto (ruby_runtime:0} 6.28 0.62 0.49 0.00 0.13 65536 Array#pack (ruby_runtime:0} 4.23 0.33 0.33 0.00 0.00 262400 Float#/ (ruby_runtime:0} 4.10 0.32 0.32 0.00 0.00 327680 Math#cos (ruby_runtime:0} 3.85 0.30 0.30 0.00 0.00 327680 Math#sin (ruby_runtime:0} 3.72 0.29 0.29 0.00 0.00 262144 Float#-@ (ruby_runtime:0} 3.33 0.26 0.26 0.00 0.00 196608 Float#** (ruby_runtime:0} 2.31 0.18 0.18 0.00 0.00 196608 Float#+ (ruby_runtime:0} 1.67 0.13 0.13 0.00 0.00 196608 Float#to_int (ruby_runtime:0} 1.15 0.09 0.09 0.00 0.00 65794 Fixnum#to_f (ruby_runtime:0} 0.90 0.07 0.07 0.00 0.00 65536 String#<< (ruby_runtime:0} 0.00 7.80 0.00 0.00 7.80 1 Integer#downto (ruby_runtime:0} 0.00 7.80 0.00 0.00 7.80 1 MathsDrawing#make_image (solution.rb:74} 0.00 0.00 0.00 0.00 0.00 257 Fixnum#- (ruby_runtime:0} When I first ran the benchmark I noticed a discrepancy in the number of times that various Float and Math methods were called. It turns out that Alex Fenton's solution was computing one extra row of pixels. I corrected this for the results. Here are the sections of code that were used in the benchmark: # Lars Haugseth @pixels = "" eval <<"." 0.upto(@height - 1) do |y_pos| y = (y_pos.to_f / @height) 0.upto(@width - 1) do |x_pos| x = (x_pos.to_f / @width) @pixels << (127.99 + 127.99 * #{@red}).to_i.chr @pixels << (127.99 + 127.99 * #{@green}).to_i.chr @pixels << (127.99 + 127.99 * #{@blue}).to_i.chr end end . # Alex Fenton # The string holding raw image data data = '' x_factor = size_x.to_f y_factor = size_y.to_f # Input values from the range 0 to 1, with origin in the bottom left (size_y - 1).downto(0) do | y | the_y = y.to_f / y_factor 0.upto(size_x - 1) do | x | the_x = x.to_f / x_factor red = @red.call(the_x, the_y) * 255 green = @green.call(the_x, the_y) * 255 blue = @blue.call(the_x, the_y) * 255 data << [red, green, blue].pack("CCC") end end After running several several iterations Lars's code was always about one second faster. I believe this is due to the single `eval` in place of the multiple (196,608) `Proc#call`s. Note that this is on Ruby 1.8.6 and may have different results on 1.9.1. No one got really crazy with performance and created a lookup table for trigonometric functions, but that may be one way to save cycles, especially when creating much larger images. Another possible technique would be to use [ruby2c][3], or something like it, to create a C method instead of using `Proc`s or `eval`. Let us bask in the beauty of these majestic behemoths, these gentle giants of the deep: ![Christmas box magic][4] ![spironator][5] ![winter's delight][6] ![nebulogue][7] ![crystal cavern of the paraboloids][8] ![beam and bend][9] [1]: http://wxruby.rubyforge.org/wiki/wiki.pl [2a]: http://rubyquiz.strd6.com/quizzes/191/image6a.png [2b]: http://rubyquiz.strd6.com/quizzes/191/image6b.png [3]: http://rubyforge.org/projects/ruby2c/ [4]: http://rubyquiz.strd6.com/quizzes/191/1212289356.png [5]: http://rubyquiz.strd6.com/quizzes/191/1212289475.png [6]: http://rubyquiz.strd6.com/quizzes/191/1212289587.png [7]: http://rubyquiz.strd6.com/quizzes/191/1212289672.png [8]: http://rubyquiz.strd6.com/quizzes/191/1212289788.png [9]: http://rubyquiz.strd6.com/quizzes/191/1212289848.png -- -Daniel http://rubyquiz.strd6.com