This week's Ruby Quiz is to create a tool that will allow you to preview an image file at the command-line in pure text.
Your program will need to read in an image, in whatever format you want to support, and respond with a text representation of the image that fits in the terminal.
> This week's Ruby Quiz is to create a tool that will allow you to preview an > image file at the command-line in pure text.
> Your program will need to read in an image, in whatever format you want to > support, and respond with a text representation of the image that fits in the > terminal.
Ruby Quiz wrote: > This week's Ruby Quiz is to create a tool that will allow you to preview an > image file at the command-line in pure text.
> Your program will need to read in an image, in whatever format you want to > support, and respond with a text representation of the image that fits in the > terminal.
I never participate in these, but this one is tempting.
>> This week's Ruby Quiz is to create a tool that will allow you to >> preview an >> image file at the command-line in pure text.
>> Your program will need to read in an image, in whatever format you >> want to >> support, and respond with a text representation of the image that fits >> in the >> terminal.
> I never participate in these, but this one is tempting.
+--- Kero ------------------------- kero@chello@nl ---+ | all the meaningless and empty words I spoke | | Promises -- The Cranberries | +--- M38c --- http://members.chello.nl/k.vangelder ---+
This solution uses the RMagick lib to read the image and to find edges because trying to find apropiate letters for halftoning is hard on all the different terminals.
I had another version without the generator/enumerator stuff and it was a lot faster. But speed isn't an issue this week and it's so much smoother than fiddling with indexes.
pixels = Array.new 0.upto(image.rows) do |y| 0.upto(image.columns) do |x| pixel = image.pixel_color(x, y).intensity pixels << pixel unless pixels.include? pixel end end pixels.sort! { |a, b| b <=> a }
0.upto(image.rows) do |y| 0.upto(image.columns) do |x| 2.times { print text[pixels.index(image.pixel_color(x, y).intensity)] } end puts end
# Resize too-large images. The resulting image is going to be # about twice the size of the input, so if the original image is too # large we need to make it smaller so the ASCII version won't be too # big. The `change_geometry' method computes new dimensions for an # image based on the geometry argument. The '320x320>' argument says # "If the image is too big to fit in a 320x320 square, compute the # dimensions of an image that will fit, but retain the original aspect # ratio. If the image is already smaller than 320x320, keep the same # dimensions." img.change_geometry('320x320>') do |cols, rows| img.resize!(cols, rows) if cols != img.columns || rows != img.rows end
# Compute the image size in ASCII "pixels" and resize the image to have # those dimensions. The resulting image does not have the same aspect # ratio as the original, but since our "pixels" are twice as tall as # they are wide we'll get our proportions back (roughly) when we render. pr = img.rows / FONT_ROWS pc = img.columns / FONT_COLS img.resize!(pc, pr)
# Draw the image surrounded by a border. The `view' method is slow but # it makes it easy to address individual pixels. In grayscale images, # all three RGB channels have the same value so the red channel is as # good as any for choosing which character to represent the intensity of # this particular pixel. border = '+' + ('-' * pc) + '+' puts border img.view(0, 0, pc, pr) do |view| pr.times do |i| putc '|' pc.times { |j| putc CHARS[view[i][j].red/16] } puts '|' end end puts border
I'm surprised how easy this Quiz was because of ImageMagick and RMagick (based on the other submissions, I didn't try it.) Kudos to the developers of those projects (though I imagine only the RMagick developer will be reading this.)
Of course you quiz developers deserve kudos for your clever solutions using RMagick.
I wonder how long the code would be for a solution that did NOT use RMagick or any other image library? Maybe future submissions will answer this question for me...
On Oct 9, 2005, at 6:57 PM, Ryan Leavengood wrote:
> I'm surprised how easy this Quiz was because of ImageMagick and > RMagick (based on the other submissions, I didn't try it.)
Indeed. I only found RMagick two weeks ago, but it impressed me enough to make the quiz. It's a bit of a bear to get installed (because of ImageMagick, not the Ruby bindings), but worth the effort.
I second the thanks to the developer for giving us such great tools to work with!
I tried this one out. I'm not sure if I was successful or not...
My first instinct was to rank characters for intensity levels programmaticly. I then came to the realization that not all characters have the same distribution of intensity, so I thought I would try splitting them up, and matching them with sets of pixel intensities (instead of just one).
ruby create_tables.rb num [extended] does the first part - pass it a number (number of pixels sqare that will be replaced by one character). uses extended ASCII if the second argument is applied.
ruby i2a.rb src_img outfile num [normalized] does the second part. takes num x num clumps of pixels from src_image and finds the closest character from the appropriate table (by Euclidean distance), puts it out to outfile. If normalized is supplied, the image is normalized for maximum contrast in the source image.
The underscore also rendered to pure white, so I had to remove it manully from the YAML tables. Those tables are available also.
I know this code needs some refactoring/reengineering, and I didn't take the time to look into how *Magick did things like scaling. Feel free to chide me on any points that are inefficient/bad/wrong/stupid.
I went into this quiz using the following approach. I split the image into pieces and find for each piece of the image the letter most similar to this piece. I did it all with image magick. There is room for improvement in the distance function, but it works ok. I'm sure it is the slowest solution so far, but it works.
On 10/10/05, Brian Schröder <ruby.br...@gmail.com> wrote:
> I went into this quiz using the following approach. I split the image > into pieces and find for each piece of the image the letter most > similar to this piece. I did it all with image magick. There is room > for improvement in the distance function, but it works ok. I'm sure it > is the slowest solution so far, but it works.
i'm completely new to ncurses, so could someone with more exprerience (or another OS) try this and tell me whats happening? On windows all the colors are very pale (are there consts for lighter colors?) ----------------------------------------------------------------------- require 'RMagick' require "ncurses"
Ncurses.initscr puts "Usage: #{$0} <img> [size]" or exit if !ARGV[0] puts "Sorry, no colors!" or exit unless Ncurses.has_colors?
COLORS.size.times do |bg| COLORS.size.times do |fg| next if fg == bg i = (bg*COLORS.size) + fg Ncurses.init_pair(i, COLORS[fg][0], COLORS[bg][0]) GRADIENT.each do |gr, c| r = COLORS[fg][1][0] * gr + COLORS[bg][1][0] * (255-gr) g = COLORS[fg][1][1] * gr + COLORS[bg][1][1] * (255-gr) b = COLORS[fg][1][2] * gr + COLORS[bg][1][2] * (255-gr) COLORMAP[[r, g, b]] = [i, c] end end end
Best quiz yet. Er, well, quiz which held my attention longest anyway. :)
My solution is um, less... elegant, than some of the others, but in a lot of ways, like a parent who mistakenly thinks their ugly child is cute I kinda like it. I don't really share my code a lot (maybe for obvious reasons) but I'm just so pleased with how easily ruby lets me hack things to high heaven and this one in particular makes me smile. Take special note that it only works on 24 bit .bmp files and put your hard hat on if you think you're going to feed anything but that into it. ;) Okay, enough blather, without further ado, the first code I've shared with you guys:
#It only supports 24bit bmp files, and it even chokes on most of them ;) #It creates one character per pixel (which has obvious implications) #But it's 40 lines of pure ruby no lib use binary file up-hackery... #And quite frankly, thats what I do.
MyPixel = Struct.new( 'MyPixel', :r, :g, :b )
the_gradient = %w|D Y 8 S 6 5 J j t c + i ! ; : .| ###############PUT YOUR FILENAME HERE######################################### the_file = File.new('ducky.bmp', 'rb') the_file.read(2) #BM the_file.read(4).unpack('V')[0] #filesize the_file.read(4) #unused the_file.read(4).unpack('V')[0] #offset from beginning to bitmap data the_file.read(4).unpack('V')[0] #size of bitmap header image_x = the_file.read(4).unpack('V')[0] #width x in pixels image_y = the_file.read(4).unpack('V')[0] #height y in pixels the_file.read(2).unpack('v')[0] #planes? the_file.read(2).unpack('v')[0] #bits per pixel the_file.read(24) #unused
the_bitmap = [] puts "CRRRRUNCHHHH --- please wait, reading file..." image_y.times do |row| the_bitmap[row] = [] image_x.times do |col| the_bitmap[row][col] = MyPixel.new( 0, 0, 0 ) the_bitmap[row][col].b = the_file.read(1).unpack('c')[0] the_bitmap[row][col].g = the_file.read(1).unpack('c')[0] the_bitmap[row][col].r = the_file.read(1).unpack('c')[0] end end
puts "output coming:" the_output = File.new('output.asciiart', 'w') (image_y-1).downto(0) do |row| image_x.times do |col| the_avg = (the_bitmap[row][col].b+the_bitmap[row][col].g+the_bitmap[row][col].r)/3 the_output.write(the_gradient[the_avg>>4]) end the_output.write("\n") end
File.open('output.txt', 'w') do |fo| by_start.step(bmo, -(image_x * 3)) do |by_ptr| image_x.times do |x| t = 0; 3.times {|n| t += bmp[by_ptr + (x * 3) + n] } fo.putc( Gradient[ (t / 3 ) >> 4 ] ) end fo.puts end end #------------------------------------------------------------------------
There's one change in effect from your original and I suspect you may have intended it differently ...
the_file.read(1).unpack('c')[0]
... gives a signed byte so, when you take the average by adding and dividing by 3, you can be adding negatives. Replacing those with ('C') gives you unsigned bytes and the overall result matches the output from the code above.
thank you very very much. I've been struggling with a problem in the graphics library I'm using for lisp (lispworks capi library) which requires me to generate a bitmap file from an array of pixels. I couldn't decipher any of the specs I've found for the format because they go into loads of extraneous detail about color tables and compression which I don't need to know about. Your code is the clearest definition of the spec I need (assuming it's correct). So thanks
On 10/12/05, Harold Hausman <hhaus...@gmail.com> wrote:
> Best quiz yet. Er, well, quiz which held my attention longest anyway. :)
> My solution is um, less... elegant, than some of the others, but in a lot > of > ways, like a parent who mistakenly thinks their ugly child is cute I kinda > like it. I don't really share my code a lot (maybe for obvious reasons) > but > I'm just so pleased with how easily ruby lets me hack things to high > heaven > and this one in particular makes me smile. Take special note that it only > works on 24 bit .bmp files and put your hard hat on if you think you're > going to feed anything but that into it. ;) Okay, enough blather, without > further ado, the first code I've shared with you guys:
> #It only supports 24bit bmp files, and it even chokes on most of them ;) > #It creates one character per pixel (which has obvious implications) > #But it's 40 lines of pure ruby no lib use binary file up-hackery... > #And quite frankly, thats what I do.
> MyPixel = Struct.new( 'MyPixel', :r, :g, :b )
> the_gradient = %w|D Y 8 S 6 5 J j t c + i ! ; : .| > ###############PUT YOUR FILENAME > HERE######################################### > the_file = File.new('ducky.bmp', 'rb') > the_file.read(2) #BM > the_file.read(4).unpack('V')[0] #filesize > the_file.read(4) #unused > the_file.read(4).unpack('V')[0] #offset from beginning to bitmap data > the_file.read(4).unpack('V')[0] #size of bitmap header > image_x = the_file.read(4).unpack('V')[0] #width x in pixels > image_y = the_file.read(4).unpack('V')[0] #height y in pixels > the_file.read(2).unpack('v')[0] #planes? > the_file.read(2).unpack('v')[0] #bits per pixel > the_file.read(24) #unused