origami envelopes

I've always been fond of origami, and in some periods I also had time to fold some as a hobby. Alas, this is not the case any more... most of the times.

I'm also proud to produce my greeting cards for birthdays and occasions, when I remember to actually make one (which happens seldom... but happens). Some time ago I stumbled upon a neat design for an origami envelope - although I don't remember where I saw it, I've found a couple of web sites that include it (e.g. here). So... two of "my" things coming together...

Then I'm fond of Perl, of course. So why not kicking it in and use it to add an image to the back of the envelope... automatically?

This is where Image::Magick comes to the rescue. The plan is very basic:

  • take an image
  • manipulate (i.e. cut, rotate and translate) to fit in the right place in the paper sheet
  • print, fold and be happy

Of course you'll still be in charge of taking a suitable image and printing/folding it, but Perl and Image::Magick can be helpful to address the second bullet. In particular, it will let you start from an image and obtain a printable PDF.

The script can be found here; we'll just give a few hints here.

First of all we have to read the image:

my $image = Image::Magick->new();
$image->Read($input);

Some preliminar scaling can be handy now: if the image is too big, it is likely to eat up memory and time without particular advantages; if it is too little, it will not be positioned accurately. So here we go:

my $scale = get_scale($width, $height);
if (defined $scale) {
   ($width, $height) = map {int($_ * $scale / 100.0)} ($width, $height);
   $image->Scale(width => $width, height => $height);
   ($width, $height) = get_size($image);
}

The function get_scale just calculates the right scale based on a configurable parameter, giving out a percentage of the "right" scale. Note that we have to recalculate $width and $height after the operation, because the scaling is likely to have changed them and we have to update out values.

Another thing to take into account is that the aspect ratio of the target image should be set according to the space that we have to fill. This calls for an initial crop to set the aspect ratio near to 1.45 (calculated empirically):

my ($cropx, $cropy) = get_crop($width, $height);
if (defined $cropx) {
   my $geometry = "${cropx}x$cropy+0+0";
   $image->Crop(gravity => 'Center', geometry => $geometry);
   ($width, $height) = get_size($image);
} ## end if (defined $cropx)

This took me some time to figure out. If you want to use the gravity, you have to use the geometry. Otherwise, you can set width and height, but you'll also have to set the right offsets for the crop. It's a matter of taste and of the data you have at hand.

It's now time to rotate the image by 45 degrees (the direction depends on the image orientation, but you can choose what you want of course):

my $rotation = $width > $height ? -45 : +45;
$image->Rotate(gravity => 'Center', degrees => $rotation);
($image, $width, $height) = blind_clone($image);

The rotation part happens to be tricky in the latest available version of PerlMagick, because it leaves the object in a condition that is not immediately evident for following operations. This is why there is the blind_clone trick, that makes sure to somehow "reset" the object:

sub blind_clone {
   my $original = shift;
   my $clone = Image::Magick->new(@_, magick => 'jpg');
   $clone->BlobToImage($original->ImageToBlob(magick => 'jpg'));
   return ($clone, get_size($clone));
}

Now we can be sure that the following crop is not influenced by strange offsets, so we are ready to chop of a couple of corners by performing an x-axis crop:

my $w = int(0.8367 * $width);
my $geometry_crop = "${w}x$height+0+0";
$image->Crop(gravity => 'Center', geometry => $geometry_crop,);
($width, $height) = get_size($image);

Again, the operation changed the image's width and height, so we reload them for next operations. There are two last steps to do: one is to put some border around the rotated and cropped image, in order to put it in the right position inside the sheet (sorry, it's only A4 for the moment):

$image->Border(geometry => '22.435%x35.719%', bordercolor => 'White');

and then save the output file. We'll need a new "refresh" of the object here, so before going on we need to call blind_clone once again:

($image, $width, $height) = blind_clone($image, page => 'A4');

and then

$image->Write($output);

So that's it... just give origami-envelope a try!

2 Comments

FYI, the aspect ratio for A4 paper is exactly 140/99.

Leave a comment

About Flavio Poletti

user-pic I blog about Perl.