In Python, Python Image Library 1.1.6, how can I expand the canvas without resizing?
Asked Answered
W

5

30

I am probably looking for the wrong thing in the handbook, but I am looking to take an image object and expand it without resizing (stretching/squishing) the original image.

Toy example: imagine a blue rectangle, 200 x 100, then I perform some operation and I have a new image object, 400 x 300, consisting of a white background upon which a 200 x 100 blue rectangle rests. Bonus if I can control in which direction this expands, or the new background color, etc.

Essentially, I have an image to which I will be adding iteratively, and I do not know what size it will be at the outset.

I suppose it would be possible for me to grab the original object, make a new, slightly larger object, paste the original on there, draw a little more, then repeat. It seems like it might be computationally expensive. However, I thought there would be a function for this, as I assume it is a common operation. Perhaps I assumed wrong.

Weasand answered 15/10, 2009 at 14:23 Comment(1)
Just use ImageOps.expand() to expand the canvas, potentially a different amount on each side. Example here https://mcmap.net/q/472723/-create-an-image-with-border-of-certain-width-in-pythonSustentacular
E
36

The ImageOps.expand function will expand the image, but it adds the same amount of pixels in each direction.

The best way is simply to make a new image and paste:

newImage = Image.new(mode, (newWidth,newHeight))
newImage.paste(srcImage, (x1,y1,x1+oldWidth,y1+oldHeight))

If performance is an issue, make your original image bigger than needed and crop it after the drawing is done.

Eichmann answered 15/10, 2009 at 15:3 Comment(1)
If the image is paletted (srcImage.mode == "P"), the palette has to be copied over also: newImage.putpalette( srcImage.palette.getdata()[ 1 ] ).Homologize
R
18

Based on interjays answer:

#!/usr/bin/env python

from PIL import Image
import math


def resize_canvas(old_image_path="314.jpg", new_image_path="save.jpg",
                  canvas_width=500, canvas_height=500):
    """
    Resize the canvas of old_image_path.

    Store the new image in new_image_path. Center the image on the new canvas.

    Parameters
    ----------
    old_image_path : str
    new_image_path : str
    canvas_width : int
    canvas_height : int
    """
    im = Image.open(old_image_path)
    old_width, old_height = im.size

    # Center the image
    x1 = int(math.floor((canvas_width - old_width) / 2))
    y1 = int(math.floor((canvas_height - old_height) / 2))

    mode = im.mode
    if len(mode) == 1:  # L, 1
        new_background = (255)
    if len(mode) == 3:  # RGB
        new_background = (255, 255, 255)
    if len(mode) == 4:  # RGBA, CMYK
        new_background = (255, 255, 255, 255)

    newImage = Image.new(mode, (canvas_width, canvas_height), new_background)
    newImage.paste(im, (x1, y1, x1 + old_width, y1 + old_height))
    newImage.save(new_image_path)

resize_canvas()
Represent answered 5/1, 2015 at 16:52 Comment(0)
S
6

You might consider a rather different approach to your image... build it out of tiles of a fixed size. That way, as you need to expand, you just add new image tiles. When you have completed all of your computation, you can determine the final size of the image, create a blank image of that size, and paste the tiles into it. That should reduce the amount of copying you're looking at for completing the task.

(You'd likely want to encapsulate such a tiled image into an object that hid the tiling aspects from the other layers of code, of course.)

Subtenant answered 15/10, 2009 at 16:55 Comment(0)
F
0

This code will enlarge a smaller image, preserving aspect ratio, then center it on a standard sized canvas. Also preserves transparency, or defaults to gray background.

Tested with P mode PNG files.

Coded debug final.show() and break for testing. Remove lines and hashtag on final.save(...) to loop and save.

Could parameterize canvas ratio and improve flexibility, but it served my purpose.

"""
Resize ... and reconfigures. images in a specified directory

Use case:  Images of varying size, need to be enlarged to exaxtly 1200 x 1200
"""
import os
import glob

from PIL import Image

# Source directory plus Glob file reference (Windows)
source_path = os.path.join('C:', os.sep, 'path', 'to', 'source', '*.png')

# List of UNC Image File paths
images = glob.glob(source_path)

# Destination directory of modified image (Windows)
destination_path = os.path.join('C:', os.sep, 'path', 'to', 'destination')

for image in images:
    
    original = Image.open(image)

    # Retain original attributes (ancillary chunks)
    info = original.info
    
    # Retain original mode
    mode = original.mode

    # Retain original palette
    if original.palette is not None:
        palette = original.palette.getdata()[1]
    else:
        palette = False

    # Match original aspect ratio
    dimensions = original.getbbox()

    # Identify destination image background color
    if 'transparency' in info.keys():
        background = original.info['transparency']
    else:
        # Image does not have transparency set
        print(image)
        background = (64)

    # Get base filename and extension for destination
    filename, extension = os.path.basename(image).split('.')
    
    # Calculate matched aspect ratio
    if dimensions[2] > dimensions[3]:
        width = int(1200)
        modifier = width / dimensions[2]
        length = int(dimensions[3] * modifier)
    elif dimensions[3] > dimensions[2]:
        length = int(1200)
        modifier = length / dimensions[3]
        width = int(dimensions[2] * modifier)
    else:
        width, length = (1200, 1200)
    
    size = (width, length)

    # Set desired final image size
    canvas = (1200, 1200)
    
    # Calculate center position
    position = (
        int((1200 - width)/2),
        int((1200 - length)/2),
        int((1200 - width)/2) + width,
        int((1200 - length)/2) + length
    )

    # Enlarge original image proportionally
    resized = original.resize(size, Image.LANCZOS)

    # Then create sized canvas
    final = Image.new(mode, canvas, background)

    # Replicate original properties
    final.info = info

    # Replicate original palatte
    if palette:
        final.putpalette(palette)

     # Cemter paste resized image to final canvas
    final.paste(resized, position)

    # Save final image to destination directory
    final.show()

    #final.save("{}\\{}.{}".format(destination_path, filename, extension))

    break
Frequency answered 19/11, 2020 at 6:4 Comment(0)
S
0

You can use for image expanding method Image.crop(), where box coordinates will be bigger than previous image, in this case it will return expanded image. only disadvantage is that you can't chose color of expanded area, default will be black.

import PIL.Image

original_image = PIL.Image.new('1', (200, 100))
expanded_image = original_image((-100, 0, 300, 200))

Will create image with 400x300 pixels, (100 expanded from left, right and bottom)

Selffertilization answered 4/6 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.