Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Calculate poster pixel sizes from an existing image file

Jerry Stratton, December 2, 2016

Three hallway posters

Three 14-inch by 10-inch posters.

I use Zazzle a lot for posters around the house, usually sizing them to fit either a specific space or even a specific frame that I happen to have. Over the Thanksgiving sale weekend, they had a 65% off sale on all posters, including wrapped canvases, so I loaded up on a bunch of posters I’d been wanting to get. I love the big discounts because they let me experiment without wasting much money. At that discount, the most expensive paper poster I ordered was $8.80, and that was for a custom-sized 37½-inch by 25½-inch poster. Even the 40-inch by 20-inch wrapped canvas was only $54.25.

When uploading an image, I like to upload the image already cropped to the appropriate size—either removing the height or the width, depending on which needs to be modified. It’s easy enough to do on a calculator, but also easy enough to make stupid mistakes, such as forgetting that for a wrapped canvas, the wrapped portion needs to be doubled to account for the correct width and height. I also tend to memorize the standard numbers, and forget that I’ve already cropped an image to remove parts I don’t want on the poster.

For repetitive mathematical tasks that a human such as myself can easily forget a step, I write a script, and that’s what I did.

[toggle code]

  • #!/usr/bin/python
  • import argparse
  • parser = argparse.ArgumentParser(description='Calculate poster pixel sizes for existing image size')
  • parser.add_argument('dimensions', help='width and height of poster', nargs=2, type=float)
  • parser.add_argument('pixels', help='width and height in pixels of image', nargs='*', type=int, default=[4032, 3024])
  • parser.add_argument('--canvas', help='size of canvas sides', type=float, default=0)
  • args = parser.parse_args()
  • if len(args.pixels) != 2:
    • print 'Pixels must be two numbers, width and then height.'
    • parser.print_help()
  • posterWidth = args.dimensions[0]
  • posterHeight = args.dimensions[1]
  • width = posterWidth+args.canvas*2
  • height = posterHeight+args.canvas*2
  • pixelWidth = args.pixels[0]
  • pixelHeight = args.pixels[1]
  • print 'For a', posterWidth, 'by', posterHeight, 'poster, this image should be:',
  • #try adjusting the height
  • newHeight = int(round(pixelWidth*height/width))
  • if newHeight <= pixelHeight:
    • print pixelWidth, 'x', newHeight
  • else:
    • #we need to adjust the width
    • newWidth = int(round(pixelHeight*width/height))
    • print newWidth, 'x', pixelHeight
Bathroom canvas

A ¾-inch wrap canvas, 10-inches by 14-inches.

The script requires providing the poster size as width and then height. If I don’t provide an image size following the poster size, it assumes that I used my dedicated camera and that I haven’t cropped it in any way. That’s the most common image size I use for posters, 4032 by 3024. You’ll want to replace those numbers with the most common image sizes you use.

Unfortunately, Python’s argparse doesn’t provide an easy way to specify that if an optional positional argument is provided, there must be exactly two of them, so the script just checks for that in code.

Then, the script determines what the height of the image needs to be if the height is adjusted. If the new height is possible—that is, if the new height is less than the existing height—then that’s the answer. If the new height is greater than the image’s existing height that’s not doable, so it calculates what the new width will need to be.

Commonly, I use 20-inch by 16-inch posters. Assuming I’m starting from an unmodified RAW camera image, that means I need to adjust the width from 4032 to 3780:

  • $ bin/poster 20 16
  • For a 20.0 by 16.0 poster, this image should be: 3780 x 3024

If, however, I’ve cropped the image to 3264 by 2712 pixels, it’s the height that needs to be modified, to 2611 pixels:

  • $ bin/poster 20 16 3264 2712
  • For a 20.0 by 16.0 poster, this image should be: 3264 x 2611

And of course, it also nicely handles odd custom sizes:

  • $ bin/poster 37.5 25.5
  • For a 37.5 by 25.5 poster, this image should be: 4032 x 2742

The script has only one named option, that this is a wrapped canvas. To specify that this is a wrapped canvas, it is also necessary to specify the size of the wrapped sides. The script doesn’t care about whether you’re using inches or some other unit. But whatever units you choose must match for height, width, and canvas sides if specified.

  • $ bin/poster 40 20 --canvas 1.5
  • For a 40.0 by 20.0 poster, this image should be: 4032 x 2157
  • $ bin/poster 40 20 --canvas .75
  • For a 40.0 by 20.0 poster, this image should be: 4032 x 2089

For example, if I had ordered a 1½-inch wrapped canvas, 40 by 20 inches, I would need to modify the height of an unmodified camera image to 2157 pixels. But if I wanted to get the much cheaper ¾-inch wrap, the height needs to be cropped to 2089 pixels.

It also just occurred to me that since the script is unit-insensitive, it could also be used to quickly calculate the necessary adjustments for Facebook link images:

  • $ bin/poster 1.91 1 1200 600
  • For a 1.91 by 1.0 poster, this image should be: 1146 x 600
  1. <- Model 100 data transfer
  2. Renumber lines ->