Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Rectangle packing...

by suaveant (Parson)
on Apr 22, 2002 at 13:56 UTC ( [id://161056]=perlquestion: print w/replies, xml ) Need Help??

suaveant has asked for the wisdom of the Perl Monks concerning the following question:

Hey all...

I am working on a script that will allow me to edit my digital photographs easily, defining bounds and a photo size (3.5x5 6x4 5x7 8x10) and best working out a size that matches the bounds... that part is simple enough...

The difficult part comes at the end... I want to take N pictures and fit them onto as few 8.5x11 inch pieces of paper as possible for printing (photo paper isn't cheap :). This is no trivial problem, it seems :) being a nasty type of algorithm ( though I don't really mind if it takes a couple minutes to run ). The problem is that I am awful with Calculus/Trigonometry and failed my Algorithms class in college :) Does anyone know where I could find some perl (or possibly C) code that handles this sort of thing, or a site that explains this sort of problem in something simpler than mathspeak?

I can probably fake it with a bunch of predefined page layouts if I stick to 3 sizes of photo, but I would kind of like to do it right... maybe learn something in the process.

                - Ant
                - Some of my best work - (1 2 3)

Replies are listed 'Best First'.
Re: Rectangle packing...
by Albannach (Monsignor) on Apr 22, 2002 at 14:51 UTC
    I always start atStoney Brook when I'm looking for graphics algorithms, and of course they have a nice example of this very problem at that link. Others have already mentioned the knapsack problem but you might also want to try a search on bin packing (it seems that knowing the terminology is sometimes half the battle). I think this problem is a good candidate for a GA solution, and your particular version might be nice to try because you (probably) have only a limited set of different object dimensions to play with. I've played around with 1D bin packing several times for scheduling problems, but I have never gotten around to trying GAs, rather resorting to brute force as the problems have not been enormoous - yet. With 2D problems, you're in a whole new dimension! ;-)

    I did a bit of searching and found:

    • This presentation (PowerPoint I'm afraid) which looks like a nice explanation of the tabu search method applied to the knapsack problem.
    • Yehoshua Perl is a researcher who seems to come up often in reference to this type of problem

    --
    I'd like to be able to assign to an luser

Re: Rectangle packing...
by Fletch (Bishop) on Apr 22, 2002 at 14:15 UTC

    Try googling for `two dimensional knapsack'. That gives some promising looking hits.

    For the non-CS types in the audience, this type of problem is known as `the knapsack problem' because it's analagous to finding the largest amount of fixed sized objects weighing wi units each that can fit in a bag that can hold x units.

    Update: Glancing over some of the google results seems to indicate you've picked a doozy. Having a few fixed layouts for the groupings you're most likely to use probably would be the quicker solution.

    Oh, and: Post #300. Wh00t.

Re: Rectangle packing...
by kappa (Chaplain) on Apr 22, 2002 at 14:16 UTC
    This one is an NP-complete problem. Look here for more information on this particular type which is called "tiling problem".
Re: Rectangle packing...
by particle (Vicar) on Apr 22, 2002 at 14:39 UTC
    since you have such a small problem set, i believe you should solve this problem once (even by hand,) then code the results to a lookup table. my example shows the direction of each photo('L'andscape or 'P'ortrait), and the number (via repetition,) but not the location:

    it's an interesting problem to solve computationally, but it seems best left as an excersize.

    3.5x54x65x78x10
    P
    LL
    PPL
    PLP
    PPPP
    ...

    ~Particle ;Ş

      Yeah... I've already started down that path... in that case I just have to work out something that finds the best combo for all the images I have to get them to fit in the least pages, which I know I can do... won't be fun, but will be a lot easier :)

                      - Ant
                      - Some of my best work - (1 2 3)

Re: Rectangle packing...
by RMGir (Prior) on Apr 22, 2002 at 14:04 UTC
    if($picture->size() eq "8x10") { # it's the only one that'll fit on 8.5x11 $picture->print(); return; } ...
    I'll leave the ... as an exercise for others :))

    Seriously, that's an interesting problem; I'm looking forward to seeing the answers.

    Edit:Did a bit of searching on google, with no luck until I followed the advice below and tried "two dimensional knapsack". But I did come across a commercial library, altough that looks targeted at much more generalized versions of the problem than yours...
    --
    Mike

      One interesting note, which makes a lot of sense.

      Dr. Math points out that the most items you can fit in is the area of the page divided by the area of the smallest item.

      That means that you could never get more than 5 photos on a page no matter what you try (8.5x11/3.5x5 = 93.5/17.5 = 5), so that simplifies your search quite a bit...

      Even with that case, it looks like you have to "waste" one photo worth of blank space, and you can only fit 4...
      --
      Mike

      I read this:
           The problem of packing a set of items into a number of bins such that the total weight, volume, etc. does not exceed some maximum value. A simple algorithm (the first-fit algorithm) takes items in the order they come an places them in the first bin in which they fit. In 1973, J. Ullman proved that this algorithm can differ from an optimal packing by as much at 70% (Hoffman 1998, p. 171). An alternative strategy first orders the items from largest to smallest, then places them sequentially in the first bin in which they fit. In 1973, D. Johnson showed that this strategy is never suboptimal by more than 22%, and furthermore that no efficient bin-packing algorithm can be guaranteed to do better than 22% (Hoffman 1998, p. 172).
      on this website: http://mathworld.wolfram.com/Bin-PackingProblem.html

      Which seems to suggest that the absolute best answer will not always be sufficiently better than a fairly-good answer to justify the trouble of finding it.

Re: Rectangle packing...
by mojotoad (Monsignor) on Apr 22, 2002 at 21:19 UTC
    Seems to me that there will be a limited solution space to explore. You can define a set of "optimal" classes of solutions based on the presence of space hogs.

    So, given 8x10 paper (first restriction) and the potential sizes 3.5x5, 4x6, 5x7, 8x10 (second restriction):

    8x10 is straightforward, a derivative case with only 1 solution.

    Given the presence of a 5x7, the set of solution classes will involve either 1 more 5x7 or two 3.5x5 photos.

    Given the presence of a 4x6, the set of solution classes will involve either one more 4x6 and a 3.5x5 or 2 3.5x5 photos.

    There will be only one maximal solution for sets composed of only 3.5x5 (there will actually be multiple maximal solution classes which vary in the details of the layout, but as for the actual count of photos they will be equivalent. Just pick the one that ends up being less effort to cut after printing).

    So given your whole pile of images, the source set, start with the largest ones. For each "largest" photo, pull smaller photos from the pool in order to construct your solution class, given the size of your initial photo.

    In the case of 4x6, I'm not sure whether it's optimal to cluster the 4x6's with each other or always fill the space with 3.5x5 photos. That's one vaguary that arises in this limited version of the general problem that you can crank on yourself. Plus I've sort of assumed here that the supply of 3.5x5 photos are plentiful compared to the larger ones. That might be a large assumption.

    As for the general problem -- lots of reading suggestions for the generic algorithms have been suggested here. If you want to tackle truly variable-sized photos on variable-sized paper, you're on your own!

    Cool question, btw, ++. And when you come up with a perl solution, post it because I'll be wanting to use it. ;-)

    Matt

Re: Rectangle packing...
by Rex(Wrecks) (Curate) on Apr 22, 2002 at 16:14 UTC
    I know you are trying to do this in Perl, and on your own. However there is a very cheap Windows product that does what you are looking for @ QImage Pro. The site also has some limited technical resources that may be of interest to you and may help complete your task.

    For $35.00 USD it was such a bargain I've never used anything else :)

    Update: If you get anywhere with your solution I would be most interested in seeing the results. I do a ton of digital photo printing and I would be interested in what you code ends up looking like, as well as the algorithm you end up using. Looks like a cool project :)

    "Nothing is sure but death and taxes" I say combine the two and its death to all taxes!
      Nice. I looked around for a little while but didn't find this. Certainly if it works it is worth 35 bucks... thanks! I may still play with mine... but I will certainly try the demo version of this...

                      - Ant
                      - Some of my best work - (1 2 3)

Re: Rectangle packing...
by clintp (Curate) on Apr 22, 2002 at 21:31 UTC
    Since Perl is the Swiss Army Chainsaw, the first obvious step is to cut the photos into little pieces...
      Heieiei, immer chunnt üsi Armee unger d'Reder... hrm
      Sorry, some Swiss German there... :-) They have pocket knives, really, but even these things are pretty good at cutting stuff to pieces... :-P

      --cs

      There are nights when the wolves are silent and only the moon howls. - George Carlin

Re: Rectangle packing...
by Necos (Friar) on Apr 23, 2002 at 15:55 UTC
    This is an interesting problem. One solution (simplified) is:
    $canvas{width} = 8.5; $canvas{height} = 11; foreach(@pictures_to_canvas){ if ( ($canvas{width} > $$_{width}) && ($canvas{height} > $$_{width +}) { $canvas{width} -= $$_{width}; $canvas{height} -= $$_{height}; } #Throw exception here if height or width goes below a min value. } ...
    I hope this code makes sense. If you make width and height "properties" of the photos, it's just a matter of dividing the canvas into vertical and horizontal components, iterating through the photos, and throwing an exception when you've gotten too much on the canvas (or at least, that's how I've thought about it).

    This would even work with while/until/etc... loops, but I thought the foreach was the simplest.

    Hope this helps just a tiny bit.
    Theodore Charles III
    Network Administrator
    Los Angeles Senior High
    4650 W. Olympic Blvd.
    Los Angeles, CA 90019
    323-937-3210 ext. 224
    email->secon_kun@hotmail.com
    perl -e "map{print++$_}split//,Mdbnr;"
Re: Rectangle packing...
by YuckFoo (Abbot) on Apr 23, 2002 at 20:27 UTC
    Interesting problem, interesting enough for me to implement a sub-optimal solution. Optimal solutions be damned, full steam ahead.

    The Page object maintains a list of openings. When a Rect is put in an opening, (up to) two new openings are made. Putting in Rect 'a' generates openings 'b' and 'c':

    
    aaaabbbb
    aaaabbbb
    cccccccc
    cccccccc
    
    
    Really putting in Rect 'a' should generate two openings 'bd' and 'cd' like this:
    
    aaaabbbb
    aaaabbbb
    ccccdddd
    ccccdddd
    
    
    With this improvement it would be a 'first fit' type solution (still sub-optimal). Nevertheless, this solution is not half bad and maybe worth a look.

    YuckFoo

    #!/usr/bin/perl use strict; my ($WIDE, $HIGH) = qw(48 24); my ($rects, $rect, $page); # Make list of random rectangles for my $char ('a'..'z', 'A'..'Z') { my $wide = int(rand($WIDE/2)+1); my $high = int(rand($HIGH/2)+1); push (@{$rects}, Rect->new($wide, $high, $char)); } # Sort list of rectangles $rects = Rect::sortrecs($rects); for $rect (@{$rects}) { $rect->show(); } # While there are rectangles, create a page and pack it while (@{$rects}) { $page = Page->new($WIDE, $HIGH); $rects = $page->packit($rects); $page->show(); } #----------------------------------------------------------- package Page; #----------------------------------------------------------- sub new { my ($pkg, $wide, $high) = @_; my $r = {}; bless $r, $pkg; $r->{wide} = $wide; $r->{high} = $high; $r->{page} = []; $r->{open} = []; push (@{$r->{open}}, [0, 0, $wide-1, $high-1, $wide, $high]); $r->fill(0, 0, $wide, $high, '.'); return $r; } #----------------------------------------------------------- sub fill { my ($p, $begx, $begy, $endx, $endy, $char) = @_; for (my $y = $begy; $y < $begy+$endy; $y++) { for (my $x = $begx; $x < $begx+$endx; $x++) { $p->{page}[$x][$y] = $char; } } } #----------------------------------------------------------- sub packit { my ($p, $rs) = @_; my (@notdone); for my $r (@{$rs}) { # Try packing rectangle into page my $done = packtry($p, $r); if ($done == -1) { # Flip rectangle and try again ($r->{wide}, $r->{high}) = ($r->{high}, $r->{wide}); $done = packtry($p, $r); } if ($done >= 0) { my (@add, $nwx, $nwy, $sex, $sey, $wide, $high); # The opening that was used my $o = $p->{open}[$done]; # Make new opening $nwx = $o->[0] + $r->{wide}; $nwy = $o->[1]; $sex = $o->[2]; $sey = $o->[1] + $r->{high} - 1; $wide = $sex - $nwx + 1; $high = $sey - $nwy + 1; @add = (); if ($wide * $high > 0) { push (@add, [$nwx, $nwy, $sex, $sey, $wide, $high]); } # Make new opening $nwx = $o->[0]; $nwy = $o->[1] + $r->{high}; $sex = $o->[2]; $sey = $o->[3]; $wide = $sex - $nwx + 1; $high = $sey - $nwy + 1; if ($wide * $high > 0) { push (@add, [$nwx, $nwy, $sex, $sey, $wide, $high]); } # Replace old opening with new openings splice(@{$p->{open}}, $done, 1, @add); # to show each step # $p->show(); } else { push(@notdone, $r); } } return \@notdone; } #----------------------------------------------------------- sub packtry { my ($p, $r) = @_; for (my $i = 0; $i < @{$p->{open}}; $i++) { my $o = $p->{open}[$i]; if ($r->{wide} <= $o->[4] && $r->{high} <= $o->[5]) { $p->fill($o->[0], $o->[1], $r->{wide}, $r->{high}, $r->{char} +); return($i); } } return (-1); } #----------------------------------------------------------- sub show { my ($p) = @_; for (my $y = 0; $y < $p->{high}; $y++) { for (my $x = 0; $x < $p->{wide}; $x++) { print "$p->{page}[$x][$y]"; } print "\n"; } print "\n"; for my $o (@{$p->{open}}) { print "open: $o->[0],$o->[1] ". "$o->[2],$o->[3] ". "$o->[4],$o->[5]\n"; } print "\n"; } #----------------------------------------------------------- package Rect; #----------------------------------------------------------- sub new { my ($pkg, $wide, $high, $char) = @_; my $r = {}; bless $r, $pkg; $r->{wide} = $wide; $r->{high} = $high; $r->{char} = $char; return $r; } #----------------------------------------------------------- sub sortrecs { my ($rs) = @_; my @rs = sort { $b->{wide} * $b->{high} <=> $a->{wide} * $a->{high} } @{$rs}; return \@rs; } #----------------------------------------------------------- sub show { my ($r) = @_; print "$r->{wide} x $r->{high}\n"; my $str = $r->{char} x $r->{wide}; for (1..$r->{high}) { print "$str\n"; } print "\n"; }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://161056]
Approved by ariels
Front-paged by ariels
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (5)
As of 2024-04-25 14:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found