Circle Drawing (#166)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

  1. Please do not post any solutions or spoiler discussion for this
    quiz until 48 hours have passed from the time on this message.

  2. Support Ruby Q. 2 by submitting ideas as often as you can! (A
    permanent, new website is in the works for Ruby Q. 2. Until then,
    please visit the temporary website at

    http://splatbang.com/rubyquiz/.

  3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby T. follow the discussion. Please reply to
the original quiz message, if you can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Circle Drawing (#166)

This week we’re going to keep it simple… very simple.

Given a radius, draw an ASCII circle.

For example:

ruby circle.rb 7

Should produce a circle of radius 7:

     #####
   ##     ##
  #         #
 #           #
 #           #
#             #
#             #
#             #
#             #
#             #
 #           #
 #           #
  #         #
   ##     ##
     #####

Note that most fonts do not have a square aspect ratio, which is why the
above output may look like an oval, despite my calculations for a
circle. It
is acceptable if your code produces similar output.

However, for extra credit you may support an additional argument that
specifies the aspect ratio (height divided by width).

ruby circle.rb 7 1.4

This should draw a circle of radius 7 with aspect ratio of 1.4. If done
correctly, your output will actually look like a circle (assuming 1.4 is
an
accurate measure of the actual aspect ratio).

Matthew M. wrote:

However, for extra credit

What about bonus points for using only integer arithmetic and no
transcendentals?
I wrote C code for that which is hiding somewhere :slight_smile:

Hi,

For example:

ruby circle.rb 7

Should produce a circle of radius 7

I’m not sure if this is intentional but the circle is 15 characters
high. Of course, the line has to be counted in too.
Nevertheless … :slight_smile:

Regards,
Thomas.

On 6/14/08, ThoML [email protected] wrote:

I’m not sure if this is intentional but the circle is 15 characters
high. Of course, the line has to be counted in too.
Nevertheless … :slight_smile:

Regards,
Thomas.

Depends on where you are measuring the radius:

outside: 7.5
inside: 6.5 (white space is 13 characters high)
center: 7 (center of bottom to center of top is 14)

On Sat, Jun 14, 2008 at 8:09 AM, ThoML [email protected] wrote:

Nevertheless … :slight_smile:

Regards,
Thomas.

2*7 = 15, simple LOL.

I finally decided against it because of simplicity, but I believe that
it is more beautyful to add a “middle” line, especially for small r’s.

Robert


http://ruby-smalltalk.blogspot.com/


As simple as possible, but not simpler.
Albert Einstein

Regards,
Thomas.

Depends on where you are measuring the radius:

outside: 7.5
inside: 6.5 (white space is 13 characters high)
center: 7 (center of bottom to center of top is 14)

It was quite intentional that my circle of radius 7 took up 15 rows of
characters. This is a common issue when dealing with computer
graphics: how do you measure distance on a field of discrete elements?

In computer graphics, this is often not a big deal when drawing 3d
objects, especially if you have blurring, other post-processing, or
anti-aliasing going on. It is much more important when you are trying
to render a HUD or UI elements, for example, that you want pixel-
perfect to the artwork provided. Many graphics cards have a setting
you can enable/disable to offset coordinates by half a pixel…
Putting it into the correct mode and setting your texturing unit to
point sampling mode (as opposed to tri-/bi-linear sampling) will give
you pixel-perfect results.

So, in the case as I presented it, I was measuring from the center of
the character cell, which is 15 rows high if measured from the top
edge of the top row to the bottom edge of the bottom row
. But as Eric
pointed out, it’s only 14 if you measure from character cell center’s.

Matthew M. ha scritto:

This week we’re going to keep it simple… very simple.
## ##
# #
specifies the aspect ratio (height divided by width).

ruby circle.rb 7 1.4

This should draw a circle of radius 7 with aspect ratio of 1.4. If done
correctly, your output will actually look like a circle (assuming 1.4 is an
accurate measure of the actual aspect ratio).

Here my solution. It is available on pastie:

http://pastie.org/215379
http://pastie.org/215380 (specs)

and it is also attached below:

Solution to Ruby Q. #166 - Circle Drawing

Usage:

Circle.new(5).to_s

or:

Circle.new(5, 2).to_s

or:

Circle.new(5, 2, ‘x’).to_s

Objects of class Circle draw circles on stdout. The aspect ratio

correction is actually made drawing an ellipse with semimajor axis

(a) equals to the given circle radius and semiminor axis (b) equals

to a / aspect_ratio.

Circle class is responsible to

* initialize a Circle object with the given radius, aspect ratio

and drawing char

* initialize a canvas

* draw the circle on its internal canvas

* convert the canvas to string for output on stdout

class Circle

cx, cy are the coordinates of the circle’s center.

attr_reader :cx, :cy

attr_reader :radius

w, h are width and height of the canvas

attr_reader :w, :h

canvas is a linear array that is initially filled with spaces

attr_reader :canvas

Initialize a Circle object passing a value for radius, aspect

ratio and drawing character.

def initialize(radius, aspect_ratio = 1.0, char = ‘#’)

@radius = radius.to_i
@aspect_ratio = aspect_ratio.to_f
@char = char

fail "Error: radius must be > 0" if @radius <= 0
fail "Error: aspect ratio must be > 0" if @aspect_ratio <= 0

# a is the semimajor axis of the ellipse and is equal to the given
# radius
@a = @radius

# b is the semiminor axis of the ellipse and is calculated from a
# and the given aspect ratio
@b = (@a / @aspect_ratio).ceil

# calculate the size of the canvas
@w, @h = (@a + 1) * 2, (@b + 1) * 2

# center coordinates correspond to the size of semiaxis.
@cx, @cy = @a, @b

# initialize the canvas with spaces
@canvas = Array.new(@w * @h, ' ')

# draw ellipse on canvas
draw_ellipse(@a, @b)

end

Print circle on stdout.

def to_s
result = “”
(0…@h - 1).each do |line|
result << @canvas[line * @w…line * @w + @w - 1].to_s << “\n”
end
result
end

private

Draw the given character on canvas to the given coordinates.

def point(x, y)
@canvas[y * @w + x] = @char
end

Translates and mirrors point (x, y) in the quadrants taking

advantage of the simmetries in the ellipse. Thus, for a given

point (x, y) the method plot three other points in the remaining

quadrants.

def plot_four_points(x, y)
point(@cx + x, @cy + y)
point(@cx - x, @cy + y)
point(@cx + x, @cy - y)
point(@cx - x, @cy - y)
end

Draw an ellipse on canvas. This method implements a Bresenham

based algorithm by John Kennedy

(http://homepage.smc.edu/kennedy_john/BELIPSE.PDF)

The method calculates two set of points in the first quadrant. The

first set starts on the positive x axis and wraps in a

counterclockwise direction until the tangent line slope is equal

to -1. The second set starts on the positive y axis and wraps in

a clockwise direction until the tangent line slope is equal to -1.

def draw_ellipse(a, b)
a_square = 2 * a2
b_square = 2 * b
2

draw_first_set(a, b, a_square, b_square)
draw_second_set(a, b, a_square, b_square)

end

The method increments y and decides when to decrement x testing

the sign of a function. In this case, the decision function is

(2*ellipse_error+x_change) and its value is calculated

iteratively.

def draw_first_set(a, b, a_square, b_square)

x, y = a, 0
x_change, y_change = b**2 * (1 - 2 * a), a**2
stopping_x, stopping_y = b_square * a, 0
ellipse_error = 0

while(stopping_x >= stopping_y) do
  plot_four_points(x, y)
  y += 1
  stopping_y += a_square
  ellipse_error += y_change
  y_change += a_square
  if (2 * ellipse_error + x_change) > 0
    x -= 1
    stopping_x -= b_square
    ellipse_error += x_change
    x_change += b_square
  end
end

end

The method increments x and decides when to decrement y testing

the sign of a function. In this case, the decision function is

(2*ellipse_error+y_change) and its value is calculated

iteratively.

def draw_second_set(a, b, a_square, b_square)

x, y = 0, b
x_change, y_change = b**2, a**2 * (1 - 2 * b)
stopping_x, stopping_y = 0, a_square * b
ellipse_error = 0

while stopping_x <= stopping_y do
  plot_four_points(x, y)
  x += 1
  stopping_x += b_square
  ellipse_error += x_change
  x_change += b_square
  if (2 * ellipse_error + y_change) > 0
    y -= 1
    stopping_y -= a_square
    ellipse_error += y_change
    y_change += a_square
  end
end

end

end

Usage:

ruby circle.rb 7 #=> print out a circle of radius 7

ruby circle.rb 7 1.8 #=> print out a circle of radius 7 and aspect

ratio 1.8

ruby circle.rb 7 1.8 x #=> print out a circle of radius 7 and aspect

ratio 1.8

using the ascii char ‘x’

print Circle.new(ARGV[0], ARGV[1] || 1.0, ARGV[2] || ‘#’).to_s if $0 ==
FILE

Here’s my solution, it creates a buffer to draw into, once done it puts
it on
the screen:

----- circle.rb -----
class Circle
def initialize(rad, asp)
@rad, @asp = rad, asp # radius, horizontal aspect ratio
@height = rad2+1 # hight/width of the pictures
@width = (@height
asp).round
@buf = Array.new(@height, ’ ').map { |e| Array.new(@width, ’ ') }
end
def draw
(0…Math::PI2).step(1/(@rad@asp2)) do |deg|
@buf[((@height/2) + (Math::sin(deg)
@rad)).round]
[((@width/2) + (Math::cos(deg)@rad@asp)).round] = ‘#’
end
@buf.map { |l| l.join + “\n”}.join
end
end
puts Circle.new((ARGV[0] || 7).to_i, (ARGV[1] || 1).to_f).draw
----- end circle.rb -----

My solution makes circles with r=7 14 characters wide. This may be
incorrect. It’s rather simple though.

Regards,
Thomas.

def draw_circle(r, ratio=1.0)
lines = []
a = 0.0
t2 = ratio / 2.0
(t2 - 0.1).step(r, ratio) do |h|
b = Math.sqrt(2.0 * h * r - h ** 2).round
u = r - b
v = [1.0, b - a].max
w = (r - u - v) * 2.0
lines << [(m = ’ ’ * u), (l = ‘#’ * v), ’ ’ * w, l, m].join
a = b
end
out = lines.join("\n")
puts out
puts out.reverse
end

if FILE == $0
draw_circle(*ARGV.map {|e| e.to_f})
end

My solution follows…

Regards,

Bill

Ruby Q. #166

This draws a circle of the specified radius, modified

by an optional aspect ratio and thickness factor.

implementation notes:

- for the fun of it, i forbade use of sqrt() and trancendentals

- the circle is not drawn into a buffer before being printed

- some values are empirically derived (thickness factor, in

parciular)

bugs:

- the thickness factor causes bloat in small circles

ARGV.length >= 1 or abort(“usage: #$0 radius [aspect] [thickness]”)

radius = ARGV.shift.to_f
radius > 0 or abort(“please provide radius of circle”)

aspect = ARGV.shift.to_f
aspect > 0 or aspect = 1.0

thick = ARGV.shift.to_f
thick > 0 or thick = 1.0

hradius = (radius * aspect).ceil + (thick/2.0).round
vradius = radius.ceil + (thick/2.0).round

def get_radius_ch(rsq, dsq, tfactor)
(rsq - dsq).abs <= tfactor ? “*” : " "
end

tfactor = (thick * 4.0) + 2.5
rsq = radius**2
(-vradius).upto(vradius) do |y|
(-hradius).upto(hradius) do |x|
print(get_radius_ch(rsq, (x * (1.0/aspect))2 + y2, tfactor))
end
puts
end

example: radius 7.0, aspect 1.0, thickness 1.0

$ ruby 166_circle.rb 7 1 1

*****

** **

* *

* *

* *

* *

* *

* *

* *

* *

* *

* *

* *

** **

*****

example: radius 10.0, aspect 2.0, thickness 5.0

$ ruby 166_circle.rb 10 2 5

*****

*******************

*************************

******** ********

******* *******

****** ******

***** *****

***** *****

***** *****

**** ****

***** *****

***** *****

***** *****

**** ****

***** *****

***** *****

***** *****

****** ******

******* *******

******** ********

*************************

*******************

*****

circle.rb

def draw(mtrx)
mtrx.each do |file|
puts file.to_s
end
nil
end

def new_mtrx(rdx,ratio)
size = rdx * (ratio.ceil) * 2
mtrx = Array.new(size).map!{ Array.new(size) }
(0…size-1).each do |file|
(0…size-1).each do |col|
mtrx[file][col] = " "
end
end
mtrx
end

def load_mtrx(mtrx,rdx,ratio=1)
(1…360).each do |grado|
x = ((Math.sin grado) * (rdx * ratio)) + (rdx * ratio)
y = ((Math.cos grado) * rdx) + rdx
mtrx[x.to_i][y.to_i] = “#”
end
nil
end

def main
rdx = ARGV[0].to_i
ratio = ARGV[1].to_f
mtrx = new_mtrx(rdx,ratio)
load_mtrx(mtrx,rdx,ratio)
draw(mtrx)
end

main

end file

Oops… I just realized that I made an error in canvas size calculation…

Here’s the fixed code:

http://pastie.org/215379
http://pastie.org/215380 (specs)

Andrea

Here is my solution.

Aaron

class Circle
attr_accessor :radius, :aspect_ratio

def initialize(radius, aspect_ratio)
self.radius = radius
self.aspect_ratio = aspect_ratio == 0 ? 1 : aspect_ratio
end

def to_s
(0…y_diameter).inject(’’) do |rows, y|
rows + (0…x_diameter).inject(’’) do |row, x|
row + (on_circle?(x, y) ? ‘#’ : ’ ')
end + “\n”
end
end

private

def y_diameter
radius * 2
end

def x_diameter
y_diameter * aspect_ratio
end

def on_circle?(x,y)
Math.sqrt((x/aspect_ratio-radius)**2 + (y-radius)**2).round ==
radius
end
end

print Circle.new(ARGV[0].to_i, ARGV[1].to_f).to_s

Here’s mine before I delved into trying to add the aspect ratio feature.

class Circle
def initialize(radius)
@radius = radius.to_i
end

def draw
(0…@radius2).each do |x|
(0…@radius
2).each do |y|
print distance_from_center(x,y).round == @radius ? ‘#’ : ‘.’
end
puts
end
end

def distance_from_center(x,y)
a = calc_side(x)
b = calc_side(y)
return Math.sqrt(a2 + b2)
end

def calc_side(z)
z < @radius ? (@radius - z) : (z - @radius)
end
end

Circle.new(ARGV.shift).draw

http://www.5valleys.com/

http://www.workingwithrails.com/person/8078

Don’t reinvent the wheel (no pun intended :slight_smile:

require ‘cairo’
radius = ARGV[0].to_f
aspect = (ARGV[1]||1.0).to_f
linewidth = (ARGV[2]||1.0).to_f
width = (aspect * radius * 2 + 1 + linewidth + 0.5).to_i
height = (radius * 2 + 1 + linewidth + 0.5).to_i
Cairo::ImageSurface.new(width, height) do |surface|
cr = Cairo::Context.new(surface)
cr.set_antialias(Cairo::ANTIALIAS_NONE)
cr.set_source_rgb(0,0,0)
cr.paint
cr.save
cr.scale(aspect, 1.0)
cr.arc(width / 2.0 / aspect + 0.5, height / 2.0 + 0.5, radius, 0, 2

  • Math::PI)
    cr.restore
    cr.set_source_rgb(1,1,1)
    cr.set_line_width(linewidth)
    cr.stroke
    height.times { |row|
    puts cr.target.data[row * width * 4, width * 4].unpack(“N*”).map
    { |x| (x >> 8) > 0 ? ‘#’ : ’ ’ }.join
    }
    end

Matthew M. ha scritto:

Should produce a circle of radius 7:
# #

A bit of ruby art :slight_smile:

http://pastie.org/216459
http://pastie.org/216462

I don’t know if my benchmark is done properly… I do it quickly and just
for fun :slight_smile: Please give me feedback…

Benchmark details:

num_of_runs = 100
radius = 7
ratio = 2.0

This benchmark has been executed on a MacBook 2,16Ghz with 2Gb of RAM
running Linux Ubuntu 7.10.

Andrea

Just make test pass… nothing clever!

Jean L.

---- code -----------------

def symbolify value

res = value.to_s

def res.delete other_str
“”
end

res

end

1000.times do |i|
s = symbolify(i)
raise “Not a string!” unless s.is_a? String
raise “Invalid chars!” unless s.delete("?*()-").empty?

x = eval(s)
raise “Decode failed!” unless i == x
end

Here’s mine

initial values

r = ARGV[0].to_i; k = 360.0/(Math::PI*2)

if r < 1 write error and exit

puts “USAGE: circle.rb radius [with radius >= 1]”; exit if r < 1

get some points and map them on a matrix

m = Array.new®.collect{|e|Array.new®.fill(" “)}
j = [0.0,90.0,(1…p=((vr=(r-1))2)).to_a.collect{|e|
e
(90.0/p.to_f) }].each{|a|
vl=[Math.sin(a/k),Math.cos(a/k)].collect{|c| (c
vr).round }
m[vl[0]][vl[1]]=”#" }
s = m.collect{|a| w=a.join(""); w.reverse+w }.join("\n")

print the result

puts s.reverse+"\n"+s

Just make test pass…

According to my watch, you’re about 24 hours too early.