[SUMMARY] Text Image (#50)

R

Ruby Quiz

I just love it when a totally crazy idea of mine blossoms into a popular quiz.
Who would a thunk it?

As you've probably seen from the solutions, this quiz turns out to be fairly
easy, thanks to great tools like RMagick and GD. Those libraries can do the
heavy lifting of loading an image, resizing it, and dropping it to a smaller
color map. With that, you're code just needs to replace colors with some
symbols.

Here's some RMagick code from Mr. RMagick himself, Timothy Hunter:

require 'RMagick'

CHARS = [ 'W', 'M', '$', '@', '#', '%', '^', 'x', '*', 'o', '=', '+',
':', '~', '.', ' ' ]
FONT_ROWS = 8
FONT_COLS = 4

img = Magick::Image.read(ARGV[0] || "Flower_Hat.jpg").first

# 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)

img = img.quantize(16, Magick::GRAYColorspace)
img = img.normalize

# 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[j].red/16] }
puts '|'
end
end
puts border

That is wonderfully commented code, of course, so I'm not going to repeat what
it does here.

I will mention a trick I found while playing with my own similar solution
though. I couldn't decide how many symbols to use, so I played with different
amounts. After about the third time of changing the Array and the argument to
quantize(), I realized that I could save myself a step. The same can be done
with the above code by changing two lines:

# ...

img = img.quantize(CHARS.size, Magick::GRAYColorspace)

# ...

pc.times { |j| putc CHARS[view[j].red/CHARS.size] }

# ...

With that, you can add and remove CHARS to play around and the code will just do
the right thing.

Here's how our mascot looks, when hit with the above code:

+-------------------------------------------------------------------------+
| .::+====+:: |
| .:==o======ooo*oo+. |
| +oo=====++++===oo***+ |
| :eek:o==++++===++==ooooo*^*. |
| :eek:o====++++====oooooooo*^* |
| .oo=====++++========ooo**^^o |
| .ooo=====++++++======ooo**^^%: |
| :*oo*o==============oooo****^^^. |
| :eek:oo$WW^===========oooo****^^^^^= |
| ooo*WWW%=========oooo^^*^**^^^^^^ |
| oooo=o::eek:=====oooo*%WWW$^^*^^^^*^: |
| =ooo=:+====ooooo**o^WWW$***^****^: |
| .*o*o*ooooo==ooo*^*+====o*^*****^. |
| :eek:%$$$$$WW$$$%%^^^^**********^^= |
| .oWWWWWWWWWWWWWW$%%^**o****^^= :::::: |
| %$$$$$$$W$$W$WWWW$^****^^o. ...::::::::==: |
| =WWWW$$$W$$$$WW$$%^^****+. .........:::::::::::::::::=o+ |
| :$WWWWWWWWWWWW%^****^%%%%%^^**oo====++:::::::::::::::+=o+ |
| :+oo*$$WWWWWWW$%^*^^^^^%$$$%%%^**o===+:::::::::::::::::++==o: |
| .:====o*^%$$$$$%^^%%^^^^%%%%%%%^^**o===+::::::::::::::::::===oo |
| .:+++++==o*^%%%%%%$%%%%^^^^%%%^^^**oo===++:::::::::::::::::+==ooo |
| .:+::::+++=oo*^%%%$%%%%%^^^^^*****ooo===+++::::::::::::::::++===oo= |
| :++::::::+++=oo*^%%%%%%^^****ooooo=====++:::::::::::::++:::++====o*. |
| :+::::::::::+===o**^^^^**ooo=====+=+++++::::::::::::+::==++++===ooo+ |
| :+::::::::::::+++==oooooo====+::::+:::::+::::+::++::+::+==++=====oo= |
| .+::::::::::::::::++=+=====+::::::+:::::+::::+:::+:::+++========ooo= |
| :+::::::::::::::::::+:++++::::::++::::::::::+:::++:++==========ooo+ |
|.++++:::::::::::::::::::::::::::+:::::+::::+:::++++===========oooo: |
|.++++::::::::::::::::::::::::::::::++:+::+=+++==============oooo=. |
| +++++++::::::::::::::::::::::++::++::+==+=+===========o===ooo=: |
| :==++++::::+:::::::::+:+:::::::::+++===++=============ooooo=: |
| .===++++++++++::+++::+++:+++++::+===++====o==========o=o==:. |
| :=======+++++++++++++++++:++===============oo====oo=o==:. |
| :====================++++++==+=========o=====ooo====: |
| .==================================ooooooo=oo===+: |
| :+=============================oooo=o==oo==::. |
| ::==========================o====oo===::. |
| ::++==============+===========+::. |
| ..:::++++==++=+++++++++::.. |
| ....:.::::::... |
+-------------------------------------------------------------------------+

For an interesting different approach, Simon Kroeger wrote some code to outline
the primary subject. That makes our duck look like this:

.:'''''''.
.: :.
.: :.
: :
:' ':
.: :
: .':. ::
:'.' : .. ':
: :..' :' ': :
:. ' : .: :
': '::'':.. ':' . :
:: ''.. :' .:'':.
:: . ': .: ..' . '.
': ''''' .: .:::::''' :.
:: .:': :
.: : .:' :
.' '' '' :
:' :
: :
.' :
: ' :'
: .:
' : .:
' :
: .:
: .:
: .'
'. :'
:. .'
'. .:'
':. .:'
':. .:'
''..........''

I thought that was a surprising variation that got a great amount of detail
across. The bill is probably the easiest to make out here, compared with all
the solutions.

Let's see the code for that:

require 'RMagick'
require 'generator'
require 'enumerator'

puts "Usage: #{$0} <img> [size]" or exit if !ARGV[0]

img, size = Magick::ImageList.new(ARGV[0]), (ARGV[1]||40).to_f
factor = [size*1.5/img.rows, size/img.columns].min

img.resize!(img.columns*factor, 2*(img.rows*factor*0.75).round)
img = img.edge.despeckle.despeckle.normalize.threshold(50)

pixels = img.get_pixels(0, 0, img.columns, img.rows).map{|c| c.red.zero?}

pixels.to_enum:)each_slice, img.columns).each_slice(2) do |l|
puts SyncEnumerator.new(*l).map{|p1, p2|
[' ', "'", ".", ":"] [(p1 ? 0 : 1) + (p2 ? 0 : 2)]}.join('')
end

Here we see RMagick used again to read in the picture and resize it to something
closer to terminal dimensions. Instead of dropping the color map here though,
we get an interesting chain of filters designed to draw out the edges of the
primary subject. (Obviously, this works better on some images than others.)

What's after that? I honestly had no clue. to_enum(), each_slice(),
SyncEnumerator? Did I switch languages and nobody told me? Obviously those two
harmless looking requires at the beginning of the program change some of the
rules and we're going to need to learn a little bit about "generator" and
"enumerator".

So, what's the first step? I tried http://www.ruby-doc.org/ because I'm a wimp.
Yep, there's "generator" but we're in trouble with "enumerator". No
documentation! I clicked the link anyway, to see what I could learn.

Seems each_slice() is added to Enumerable by the library and it expects one
argument. Well, that's something. It's a little hard to tell how it's being
used in Simon's code (complicated by to_enum()), so I figure, just put something
in an Array and try to call it. That should tell us something. irb to the
rescue!
require "enumerator" => true
pixels = (1..10).to_a => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pixels.each_slice(2) { |slice| p slice }
[1, 2]
[3, 4]
[5, 6]
[7, 8]
[9, 10]
=> nil

Perfect! That was all we need to see. It allows you to take off chunks of an
Array, it seems. Dang that's cool! Why didn't any of you tell me that was
there?!

Anybody see the Tic-Tac-Toe code posted to Ruby Talk on Tuesday? Look how
simple drawing the board can be:
board = Array.new(9) { rand > 0.5 ? "X" : "O" } => ["X", "O", "O", "O", "O", "O", "O", "X", "O"]
board.each_slice(3) { |row| puts row.join }
XOO
OOO
OXO
=> nil

Okay, we've got each_slice() figured out. Let's do some more detective work.
Let's see if we can figure out to_enum(). When it is called in Simon's code it
seems to get passed a method name (the one we just learned!) and a number.
Well, we know each_slice() requires a number, so maybe that's the argument to
it? Again, let's just see if we can call it:
pixels => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
enum = pixels.to_enum:)each_slice, 2)
=> #<Enumerable::Enumerator:0x342b58>

Well, we got... something. Hmm, I wonder what it can do?
=> ["reject", "method", "send", "object_id", "enum_for", "singleton_methods",
"member?", "__send__", "equal?", "taint", "find", "frozen?",
"instance_variable_get", "each_with_index", "enum_cons", "kind_of?", "to_a",
"instance_eval", "collect", "all?", "entries", "type", "enum_with_index",
"protected_methods", "extend", "detect", "eql?", "display", "zip",
"instance_variable_set", "hash", "is_a?", "map", "to_s", "any?", "sort",
"class", "each_slice", "min", "tainted?", "private_methods", "find_all",
"untaint", "each", "id", "inspect", "inject", "==", "===", "sort_by", "clone",
"public_methods", "enum_slice", "max", "respond_to?", "select", "freeze",
"__id__", "to_enum", "partition", "=~", "methods", "grep", "nil?", "dup",
"each_cons", "instance_variables", "include?", "instance_of?"]

Okay, it seems to be Enumerable. Let's just see what each entry is:
[1, 2]
[3, 4]
[5, 6]
[7, 8]
[9, 10]
=> nil

Now I get it. We turned an each_slice(2) call into an each() call. That's
interesting.

It always bugs me that Strings iterate over lines instead of characters, by
default, and now I have the tool to fix it:
There's no I in T-E-A-M!
=> nil=> [116, 101, 97, 109]

Notice how I was able to use any?() and to_a() there, because we switched
each_byte() to each() and all other Enumerable methods use each().

One more mysterious piece of the puzzle, but this one is documented, which
almost takes all the fun out of it. Here's the example right out of the
documentation, minus some irb noise:
require "generator" => true
s = SyncEnumerator.new([1, 2, 3], %w{a b c})
=> # said:
s.each { |row| puts row.join(", ") }
1, a
2, b
3, c
=> #<SyncEnumerator:0x1b339c ...>

Obviously, that just let's you traverse two Enumerable objects at once. First
you get the first entry of both, then the second, etc. Nothing too tricky
there.

Still remember the code that started all this?

# ...

pixels.to_enum:)each_slice, img.columns).each_slice(2) do |l|
puts SyncEnumerator.new(*l).map{|p1, p2|
[' ', "'", ".", ":"] [(p1 ? 0 : 1) + (p2 ? 0 : 2)]}.join('')
end

This code iterates over rows of pixels (pixels.to_enum:)each_slice, img.columns)
...), two at-a-time (... .each_slice(2) ...). It then traverses those two rows
pixel-by-pixel in tandem (SyncEnumerator.new(*l).map{|p1, p2| ... }), averaging
the two on/off values (... [' ', "'", ".", ":"] [(p1 ? 0 : 1) + (p2 ? 0 : 2)]
...), and printing the results (puts ... .join('')). Work through that slowly,
until it sinks in. I know it took me a couple of tries.

I better wrap this up, since it's already quite lengthy, but don't forget to
take a peek at the other solutions. Harold Hausman rolled his own code for
analyzing bitmap images and others are now golfing that solution on Ruby Talk.
Rob Rypka and Brian Schroeder also did some sensational work mapping colors to
characters, producing some nice gradients.

A big thank you to all the enlightening solutions to this week's quiz. The
combined intelligence of the Ruby Quiz community is beyond measure.

Tomorrow, I have a new game for you. I figure it's The RubyConf Collective
verses the rest of us in the tournament, right?
 
D

daz

Ruby Quiz wrote
So, what's the first step? I tried http://www.ruby-doc.org/ because I'm a wimp.
Yep, there's "generator" but we're in trouble with "enumerator". No
documentation! I clicked the link anyway, to see what I could learn.

http://www.ruby-lang.org/cgi-bin/cv...numerator.txt?rev=1.2;content-type=text/plain
http://www.ruby-lang.org/cgi-bin/cvsweb.cgi/~checkout~/ruby/enumerator.c?content-type=text/plain


Perfect! That was all we need to see. It allows you to take off chunks of an
Array, it seems. Dang that's cool! Why didn't any of you tell me that was
there?!

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/154552
(my source for the two links above)


daz
 
D

daz

James said:
Gavin, that second link looks like ready to put online RDoc. Any
reason we don't have it on ruby-doc.org?

Possibly because James Britt at ruby-doc.org doesn't
want to conflate 1.9 and 1.8.2 docs (?)

In 1.8.2, Enumerator is an extension;
in 1.9, it's a built-in class with (I think) added functionality.

It would be nice if 1.9 rdoc could be hosted somewhere, though.


daz
 
G

Gavin Sinclair

James said:
Thank you for leading me to the docs!

Gavin, that second link looks like ready to put online RDoc. Any
reason we don't have it on ruby-doc.org?

Wow! Them's some pretty long links! I take it there are some docs for
enumerator in CVS and they could do with some translation into RDoc.
OK, I'll make that my next target.

Thanks,
Gavin
 
J

James Edward Gray II

Wow! Them's some pretty long links! I take it there are some docs
for
enumerator in CVS and they could do with some translation into RDoc.
OK, I'll make that my next target.

Gavin, those are 1.9 docs. If that's a problem, just let me know and
I'll build a patch to add them to Ruby 1.8 and see if I can get it
committed.

James Edward Gray II
 
J

James Britt

daz said:
Possibly because James Britt at ruby-doc.org doesn't
want to conflate 1.9 and 1.8.2 docs (?)

In 1.8.2, Enumerator is an extension;
in 1.9, it's a built-in class with (I think) added functionality.

It would be nice if 1.9 rdoc could be hosted somewhere, though.

There had been an older version on them on ruby-doc, though I think they
got lost in the server shuffle.

I can add them again, if there is sufficient interest. But it seems
that they're a bit avant garde for most people who wouldn't already have
them locally anyway.


James

--

http://www.ruby-doc.org - The Ruby Documentation Site
http://www.rubyxml.com - News, Articles, and Listings for Ruby & XML
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
 
J

James Edward Gray II

There had been an older version on them on ruby-doc, though I think
they got lost in the server shuffle.

I can add them again, if there is sufficient interest. But it
seems that they're a bit avant garde for most people who wouldn't
already have them locally anyway.

I see absolutely no reason not to make every last shred of
documentation available to the users of Ruby. I say we document,
document, document until there is no library left to cover.

Enumerator is a handy tool, soon to be a part of the core. Why would
we *choose* not to educate people about it?!

If that line of thinking is wrong, I don't want to be right! Call me
radical.

James Edward Gray II
 
J

James Britt

James said:
I see absolutely no reason not to make every last shred of
documentation available to the users of Ruby. I say we document,
document, document until there is no library left to cover.

Hosting documentation is trivial. Getting people to write documentation
is not. At least not, it seems, without offering financial renumeration.

James

--

http://www.ruby-doc.org - The Ruby Documentation Site
http://www.rubyxml.com - News, Articles, and Listings for Ruby & XML
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
 
J

James Edward Gray II

Hosting documentation is trivial. Getting people to write
documentation is not. At least not, it seems, without offering
financial renumeration.

Why write documentation when we don't put up what we have?

James Edward Gray II
 
J

James Edward Gray II

I'm sorry, but the more I think about this, the more it bugs me...

Hosting documentation is trivial.

You do this part, right?

You run ads on the site and this (http://www.rubystuff.com/
about.html) also generates income for that service.
Getting people to write documentation is not.

I do this part. I've documented three standard libraries in the last
year. ERb, Delegate, and Forwardable.
At least not, it seems, without offering financial renumeration.

I haven't seen my check yet?!

James Edward Gray II
 
K

Kev Jackson

I do this part. I've documented three standard libraries in the last
year. ERb, Delegate, and Forwardable.
If I could get paid to write docs I'd be all over it like something all
over something else. But if someone could point to some area that needs
documenting I'd be more than happy to help - I was a technical author a
few years ago, and I have contributed code and docs to several open
source projects (mainly Java), I just didn't realise that more hands
were needed - please push links etc to me I'll help out with doco

Kev
 
J

James Britt

Kev said:
If I could get paid to write docs I'd be all over it like something all
over something else. But if someone could point to some area that needs
documenting I'd be more than happy to help - I was a technical author a
few years ago, and I have contributed code and docs to several open
source projects (mainly Java), I just didn't realise that more hands
were needed - please push links etc to me I'll help out with doco

Perhaps the best place to start is here:

http://ruby-doc.org/stdlib/

Items listed to the left in italics do not have sufficient docs.

(That may be true as well of some of the other items, but I believe the
italics are meant to indicate 'needs work.')



James

at RubyConf05

--

http://www.ruby-doc.org - The Ruby Documentation Site
http://www.rubyxml.com - News, Articles, and Listings for Ruby & XML
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
 
J

James Britt

James said:
I'm sorry, but the more I think about this, the more it bugs me...
Why?





You do this part, right?
Indeed.


You run ads on the site and this (http://www.rubystuff.com/ about.html)
also generates income for that service.

Yes. Helps pay some of the bills.
I do this part. I've documented three standard libraries in the last
year. ERb, Delegate, and Forwardable.

There are a few people writing core and stdlib docs on a semi-regular
basis, but by and large, there are large parts of Ruby insufficiently
documented.
I haven't seen my check yet?!

Maybe after the bills get paid.

:)

James


--

http://www.ruby-doc.org - The Ruby Documentation Site
http://www.rubyxml.com - News, Articles, and Listings for Ruby & XML
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
 
K

Kev Jackson

Perhaps the best place to start is here:

http://ruby-doc.org/stdlib/

Items listed to the left in italics do not have sufficient docs.
Ok so I've looked at the site (and at the status report). I presume
that to get working on them I'd need to get the actual ruby sources and
start adding comments/doc/examples into the source files, but then how
do I push changes in the original sources back so that the RDoc can be
extracted/published? If the sources are part of the stdlib (which ships
with ruby itself), is there a bugzilla/issu tracker or something to
attach patches to?

Or is the preferred way to download a doc bundle and manually edit that?

I'll keep searching around myself (this afternoon I'm completely free
which is nice) but any pointers in the preferred way of contributing
would be appreciated

Kev
 
G

Gavin Sinclair

James said:
Gavin, those are 1.9 docs. If that's a problem, just let me know and
I'll build a patch to add them to Ruby 1.8 and see if I can get it
committed.

The stuff on ruby-doc.org/{stdlib,core} is generated from 1.8 source
and library code, so 1.9 stuff won't appear there. There's no reason,
however, that other, arbitrary, documentation can't be hosted on
ruby-doc.org.

So if it's appropriate that it should appear in 1.8, I'd certainly
encourage you to make a patch and get it committed.

Cheers,
Gavin
 
G

Gavin Sinclair

Kev said:
If I could get paid to write docs I'd be all over it like something all
over something else. But if someone could point to some area that needs
documenting I'd be more than happy to help - I was a technical author a
few years ago, and I have contributed code and docs to several open
source projects (mainly Java), I just didn't realise that more hands
were needed - please push links etc to me I'll help out with doco

Hi Kev,

Documentation of core and library classs is written in the source code
(be it C or Ruby). Thus to contribute, you want to avail yourself of
(anonymous) CVS access (it's easy). Then you edit files, create a
patch, and get it committed. That last step has been the problem in
the past; mea culpa. Suggested process: email the patch to me and/or
James Gray (sorry, James!). Bug me mercilessly if nothing's being
done.

Anyway, then it ends up in Ruby itself and appears on ruby-doc.org
hopefully soon after.

Any questions, just email.

Cheers,
Gavin
 
G

Gavin Sinclair

Kev said:
Ok so I've looked at the site (and at the status report). I presume
that to get working on them I'd need to get the actual ruby sources and
start adding comments/doc/examples into the source files, but then how
do I push changes in the original sources back so that the RDoc can be
extracted/published? If the sources are part of the stdlib (which ships
with ruby itself), is there a bugzilla/issu tracker or something to
attach patches to?

Damn, thought I replied earlier but ... it hasn't appeared, so I guess
I didn't. Sorry if this is a duplicate.

You do need to get the Ruby sources (anonymous CVS is best) and work
with those. There's no bugzilla or similar for doc patches. This part
of the process has been a failure and I take responsibility for that.
If you have a patch, email it to me and/or James Gray. One of us will
massage it through ruby-core back into CVS, and James Britt and I will
regenerate the bits and pieces on ruby-doc.org/{core,stdlib}.

Thanks for any help, and feel free to ask any questions.

Cheers,
Gavin
 
D

daz

Gavin said:
Kev said:
[...] to get working on them I'd need to [...]

If you have a patch, email it to me and/or James Gray.

Thanks for any help, and feel free to ask any questions.

Cheers,
Gavin


/**********************************************************************

iconv.c -

$Author: nobu $
$Date: 2005/03/27 23:40:02 $
created at: Wed Dec 1 20:28:09 JST 1999

All the files in this distribution are covered under the Ruby's
license (see the file COPYING).

Documentation by Yukihiro Matsumoto and Gavin Sinclair.

**********************************************************************/


Is there a case for having a $Doc: Name $ in the header ?

Perhaps also a standard way to add contributors names to doc updates
which doesn't wipe out the original documentor's name (as seems to
happen with $Author: Name $ in many patched core files).

The main reason for asking is that, if anyone is going to spend a deal
of time and effort on documentation, recognition should be recorded.
(I hope that's only obvious.)

It would give us somewhere to direct our appreciation, spiritually
(or, perhaps, e-mail-ially).

$Doc: Dave Thomas; gs; jeg; kj $ - which magically expands to HTML
links for each member on ruby-doc-squad-wiki.org might be possible
(later?).


daz
 
G

Gavin Sinclair

daz said:
/**********************************************************************

iconv.c -

$Author: nobu $
$Date: 2005/03/27 23:40:02 $
created at: Wed Dec 1 20:28:09 JST 1999

All the files in this distribution are covered under the Ruby's
license (see the file COPYING).

Documentation by Yukihiro Matsumoto and Gavin Sinclair.

**********************************************************************/

Is there a case for having a $Doc: Name $ in the header ?

What would that do?
Perhaps also a standard way to add contributors names to doc updates
which doesn't wipe out the original documentor's name (as seems to
happen with $Author: Name $ in many patched core files).

But author and documenter are often different people. Recording the
credits in plain text (as above) ensures they are not lost as CVS tags
come and go.
$Doc: Dave Thomas; gs; jeg; kj $ - which magically expands to HTML
links for each member on ruby-doc-squad-wiki.org might be possible
(later?).

Are you suggesting that every person who cvs commits a file is
recorded?

Cheers,
Gavin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top