"Collage" of images -- more pychological randomness

Lately I have posted an occasional coding challenge
and gotten responses.

This time, I thought I’d at least post my own
solution.

I’m sure someone can improve on the algorithm or
coding style or both.

Problem: Given a set of N (square) images, use a
subset of these to create a “collage” of pictures
in HTML.

Let the grid size be 8x8, but let it be reasonably
configurable.

Let image sizes be 4x4, 3x3, 2x2, and 1x1. (I assumed
64 pixels = 1 unit; and yes, I hardcoded it, and yes,
that’s bad form). Let there be at least one 4x4; at
least two 3x3 if they’ll fit; at least 5 2x2 if they’ll
fit; and the rest 1x1.

Constraint: The edges of the pictures should
touch if possible! This depends on how well your
browser renders.

My solution prints out an ASCII version of the grid
and then outputs HTML.

A lot of the weirdness in the HTML is so that the
results will be “pretty”; and there are some specific
workarounds for IE bugs (where Konqueror rendered it
perfectly).

Comments welcome.

Cheers,
Hal

···

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

class Array

def randomize!
arr=self.dup
result = arr.collect { arr.slice!(rand arr.length) }
self.replace result
end

end

class Range
def rand
self.to_a[Kernel.rand(self.size)]
end
end

class Grid

def initialize(pix,size)
@pix = pix
@size = size
@grid = Array.new(@size)
@grid.map! {|e| Array.new(@size) }
@list = []
end

def
@grid[x][y]
end

def []=(x,y,val)
@grid[x][y] = val
end

def size
@size
end

def inrange?(x,y,wide)
(x + wide) <= @size and
(y + wide) <= @size
end

def empty?(x,y,wide)
return false if !inrange?(x,y,wide)
x.upto(x+wide-1) do |i|
y.upto(y+wide-1) do |j|
return false if @grid[i][j]
end
end
true
end

def place(x,y,wide,char)
return false if !empty?(x,y,wide)
x.upto(x+wide-1) do |i|
y.upto(y+wide-1) do |j|
@grid[i][j] = char
end
end
@list << [x,y,wide]
true
end

def inspect
str = ""
0.upto(@size-1) do |y|
0.upto(@size-1) do |x|
str << " #{self[x,y] || ‘-’}"
end
str << "\n"
end
str
end

def randxy(wide)
xr = 0…(@size-wide)
yr = 0…(@size-wide)
[xr.rand,yr.rand]
end

def fit?(wide)
0.upto(@size-wide) do |i|
0.upto(@size-wide) do |j|
return true if empty?(i,j,wide)
end
end
false
end

def unused
count = 0
0.upto(@size-1) do |i|
0.upto(@size-1) do |j|
count += 1 if @grid[i][j]==nil
end
end
count
end

def next_unused
i = j = 0
0.upto(@size-1) do |i|
0.upto(@size-1) do |j|
return [i,j] if @grid[i][j].nil?
end
end
nil
end

def list
@list.sort! {|x,y| x[1]*10+x[0] <=> y[1]*10+y[0] }
end

def rowcol
rc = self.list.map {|x| [x[1], x[0], x[2]] }
arr = Array.new(@size)
arr.map! {|x| Array.new }
rc.each {|e| arr[e[0]] << [e[1],e[2]] }
arr
end

def html
pic = -1
puts ""
puts “


# Next three lines: Workaround for IE bug
puts ""
10.times { puts “” }
puts “”
###
rowcol.each do |row|
puts “”
# Next line: Workaround for IE bug
puts " "
# Note: Some of the weird formatting is so that spaces will not
# appear between the images. This may not be an issue in some
# browsers.
row.each do |col,span|
puts "<td colspan=#{span} rowspan=#{span}"
puts " cellpadding=0 cellspacing=0"
puts " align=center valign=center "
puts " border=0><img src=#{@pix[pic+=1]} "
puts " width=#{span64} height=#{span64}"
print " border=0>"
end
# Next line: Workaround for IE bug
puts " "
puts ""
end
puts "
"
puts ""
end

end

pix = %w[a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7]
pix.randomize!
pix.map! {|x| x + “.jpg” }

grid = Grid.new(pix,8)

ok = false
begin
x,y = grid.randxy(4)
ok = grid.place(x,y,4,“A”)
end until ok

(“B”…“C”).each do |code|
ok = false
begin
if !grid.fit?(3)
# puts "No fit! (3)"
break
end
x,y = grid.randxy(3)
ok = grid.place(x,y,3,code)
end until ok
end

(“D”…“H”).each do |code|
ok = false
begin
if !grid.fit?(2)
# puts "No fit! (2)"
break
end
x,y = grid.randxy(2)
ok = grid.place(x,y,2,code)
end until ok
end

code = "a"
grid.unused.times do
x,y = grid.next_unused
grid.place(x,y,1,code.dup)
code.succ!
end

p grid # ASCII just to eyeball it…

puts

grid.html # Actual HTML

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

The problem you wrote seems fairly specific – you gave the size of the
grid and images. Are you interested in the specific solution for this
problem, or a generic solution for generic grids and generic image sizes?

ok = false
begin
x,y = grid.randxy(4)
ok = grid.place(x,y,4,“A”)
end until ok

Is there any reason why you want the position of the 4x4 image randomized?
Why can’t it be (0,0) every time?

Cheers,

···


Daniel Carrera
Graduate Teaching Assistant. Math Dept.
University of Maryland. (301) 405-5137

“at least two 3x3 if they’ll fit” seems to mean:

n_3x3 = 0 or n_3x3 >= 2

“at least 5 2x2 if they’ll fit” seems to mean:

n_2x2 = 0 or n_2x2 >= 5

is this correct?

-a

···

On Sun, 4 May 2003, Hal E. Fulton wrote:

Let image sizes be 4x4, 3x3, 2x2, and 1x1. (I assumed 64 pixels = 1 unit;
and yes, I hardcoded it, and yes, that’s bad form). Let there be at least
one 4x4; at least two 3x3 if they’ll fit; at least 5 2x2 if they’ll fit; and
the rest 1x1.

Ara Howard
NOAA Forecast Systems Laboratory
Information and Technology Services
Data Systems Group
R/FST 325 Broadway
Boulder, CO 80305-3328
Email: ara.t.howard@fsl.noaa.gov
Phone: 303-497-7238
Fax: 303-497-7259
====================================

The problem you wrote seems fairly specific – you gave the size of the
grid and images. Are you interested in the specific solution for this
problem, or a generic solution for generic grids and generic image sizes?

I’m flexible.

Two things I’d be interested in:

  1. Better algorithm or coding idioms
  2. A more general solution

Is there any reason why you want the position of the 4x4 image randomized?
Why can’t it be (0,0) every time?

The whole point (in my mind) is to generate a
“random-looking” collage that will be pleasing
to the eye. The randomness should be apparent
both in space and time (e.g., suppose the
collage is re-generated every day).

One constraint I wouldn’t necessarily want to
lose is the squareness of the images. That’s
a big simplifying assumption. To allow images
of arbitrary shapes might lead to a much more
sophisticated problem.

As for the hardcoding of the numbers of images
of various sizes: I just wanted a distribution
such that

  1. The larger the image, the fewer of that size;
  2. The curve should be fairly “smooth”

Does that make my thoughts more clear? Or less? :slight_smile:

Cheers,
Hal

···

----- Original Message -----
From: “Daniel Carrera” dcarrera@math.umd.edu
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Saturday, May 03, 2003 8:54 PM
Subject: Re: “Collage” of images – more pychological randomness

Let image sizes be 4x4, 3x3, 2x2, and 1x1. (I assumed 64 pixels = 1
unit;
and yes, I hardcoded it, and yes, that’s bad form). Let there be at
least
one 4x4; at least two 3x3 if they’ll fit; at least 5 2x2 if they’ll fit;
and
the rest 1x1.

“at least two 3x3 if they’ll fit” seems to mean:

n_3x3 = 0 or n_3x3 >= 2

“at least 5 2x2 if they’ll fit” seems to mean:

n_2x2 = 0 or n_2x2 >= 5

is this correct?

That’s probably what I said, but not what I meant.
My mistake. The words “at least” should be omitted
or replaced with “at most.”

The code speaks more eloquently than I do: Aim for
two 3x3s, but fewer if they won’t all fit.

Of course, that rule isn’t sacred. It’s tied to the
grid size I picked and it’s related to the rules of
distribution that I mentioned in another email (a
smooth descending curve).

Hal

···

----- Original Message -----
From: “ahoward” ahoward@fsl.noaa.gov
Newsgroups: comp.lang.ruby
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Sunday, May 04, 2003 2:35 PM
Subject: Re: “Collage” of images – more pychological randomness

On Sun, 4 May 2003, Hal E. Fulton wrote:

Is there any reason why you want the position of the 4x4 image randomized?
Why can’t it be (0,0) every time?

The whole point (in my mind) is to generate a
“random-looking” collage that will be pleasing
to the eye. The randomness should be apparent
both in space and time (e.g., suppose the
collage is re-generated every day).

Yeah, I didn’t realize the random-looking scenario. You see, the optimal
solution is the least random. For instance, in this case the optimal
solution is:

1 1 1 1 1 1 + +

# # x x x + +

# # x x x * *

# # x x x * *

@ @ @ @ * * + +
@ @ @ @ * * + +
@ @ @ @ + + * *
@ @ @ @ + + * *

But that’s very non-random-looking.

Humans have a poor intuition as to what random looks like. Humans
subconciously tend to equate random with “almost balanced but not quite”.
So, to make it “look nice”, we should put the big square near the center,
but a bit off, and try to balance the distribution of images. For
instance:

    • 1 * * 1 + +
    • 1 * * 1 + +
      1 @ @ @ @ * * 1
      1 @ @ @ @ * * 1
      1 @ @ @ @ x x x
      1 @ @ @ @ x x x
        • 1 x x x
        • 1 1 1 1

As you can see, this has only 1 3-box.

Perhaps we should try a different set of constraints (given from highest
priority to lowest):

  • Bigger boxes closer to the center, but not smack in the middle.
  • Avoid putting boxes of the same size together.
  • Aim for balance.

This is not random, but it looks random. Here is one solution:

    • 1 * * x x x
    • 1 * * x x x
      1 @ @ @ @ x x x
      1 @ @ @ @ * * 1
      1 @ @ @ @ x x x
      1 @ @ @ @ x x x
    • 1 * * x x x
    • 1 * * 1 1 1

Such algorithm would be difficult to write and slow, and in your case I
expect that all solutions are reflections of the one above. Perhaps you
will be satisfied with hard-coding the above arrangement and simply
randomly selecting one of the 4 possible reflections.

To give you more variety, here are two variations (I’ll only show the
bottom two lines, since that’s what I’m changing):

1 + + * * x x x
1 + + * * 1 1 1

        • 1 x x x
        • 1 1 1 1

You can add those so that you get a total of 3*4 = 12 possible choices.
Then use our beloved weighted-random function to give preference to the 4
reflections arrangement above.

Yes, I realize that hard-coding all this is very bad form, but it might
give you the best results for your purposes.

···


Daniel Carrera
Graduate Teaching Assistant. Math Dept.
University of Maryland. (301) 405-5137

I thought *'s were 2-squares - should the fourth line end ‘1 1 1’ ?

···

On Sun, May 04, 2003 at 12:26:39PM +0900, Daniel Carrera wrote:

  • Bigger boxes closer to the center, but not smack in the middle.
  • Avoid putting boxes of the same size together.
  • Aim for balance.

This is not random, but it looks random. Here is one solution:

    • 1 * * x x x
    • 1 * * x x x
      1 @ @ @ @ x x x
      1 @ @ @ @ * * 1
      1 @ @ @ @ x x x
      1 @ @ @ @ x x x
    • 1 * * x x x
    • 1 * * 1 1 1