How to resize an image keeping the thickness of its border?

Question:

I’m making a GUI toolkit for the Python Arcade library, but I am stuck on a problem. I want the user to be able to customize sizes for the GUI widgets and graphics in pixels (width, height). But currently, the graphics are images. I have the images, but I want the user to be able to customize their sizing.

One of the images is shown below. Instead of using PIL to just stretch the width and height of the image, I need something else. Just stretching the width and height will make the border look too thick.

Entry widget

Is there an easy way to cut certain parts of the image to enable easy use for extending it? Borders would look like this. They would be split to extend the image. Some of the parts can be stretched, but some can not.

Asked By: Ethan Chan

||

Answers:

It seems to be no easy way for resizing ( liquid resizing doesn’t work here ) except (as suggested in the question with the second image) dividing the image using PIL crop() into nine (9) sub-images and resize them separately (except the corner sub-images, which won’t become resized). The resized parts are then put together in a new image with the requested new size by pasting them using PIL paste() onto it. The borders are stretched only along their length and not along their thickness. Here how it looks like if the original image becomes resized with the further down provided resizeExceptBorder() function:

original image Original image (200 x 30)

new_img_1 = resizeExceptBorder(PIL_image,(300,90),(5,5,5,5))

resized image 1 Resized image (300 x 90)

new_img_2 = resizeExceptBorder(PIL_image,(400,150),(5,5,5,5))

resized image 2 Resized (400 x 150)

And here the code of the function I have put together for this purpose:

def resizeExceptBorder(PIL_image, newSize, borderWidths):
    """ 
    newSize = (new_width, new_height)
    borderWidths = (leftWidth, rightWidth, topWidth, bottomWidth)"""
    pl_img = PIL_image
    sXr, sYr = newSize # ( 800, 120 ) # resized size X, Y
    lWx, rWx , tWy, bWy  = borderWidths

    sX,  sY  = pl_img.size
    sXi, sYi = sXr-(lWx+rWx), sYr-(tWy+bWy)

    pl_lft_top = pl_img.crop((     0,     0,    lWx, tWy)) 
    pl_rgt_top = pl_img.crop((sX-rWx,     0,    sX,  tWy))
    pl_lft_btm = pl_img.crop((     0, sY-bWy,   lWx,  sY))
    pl_rgt_btm = pl_img.crop((sX-rWx, sY-bWy,    sX,  sY))
    # ---
    pl_lft_lft = pl_img.crop((     0,    tWy,    lWx,sY-bWy)).resize((lWx ,sYi))
    pl_rgt_rgt = pl_img.crop((sX-rWx,    tWy,     sX,sY-bWy)).resize((rWx ,sYi))
    pl_top_top = pl_img.crop((   lWx,      0, sX-rWx,   tWy)).resize((sXi ,tWy))
    pl_btm_btm = pl_img.crop((   lWx, sY-bWy, sX-rWx,    sY)).resize((sXi ,bWy))
    # ---
    pl_mid_mid = pl_img.crop((   lWx,    tWy, sX-rWx,sY-bWy)).resize((sXi,sYi))
    # -------
    pl_new=Image.new(pl_img.mode, (sXr, sYr)) 
    # ---
    pl_new.paste(pl_mid_mid, (    lWx,    tWy))
    # ---
    pl_new.paste(pl_top_top, (    lWx,      0))
    pl_new.paste(pl_btm_btm, (    lWx,sYr-bWy))
    pl_new.paste(pl_lft_lft, (      0,    tWy))
    pl_new.paste(pl_rgt_rgt, (sXr-rWx,    tWy))
    # ---
    pl_new.paste(pl_lft_top, (      0,     0))
    pl_new.paste(pl_rgt_top, (sXr-rWx,     0))
    pl_new.paste(pl_lft_btm, (      0,sYr-bWy))
    pl_new.paste(pl_rgt_btm, (sXr-rWx,sYr-bWy))
    # ---
    return pl_new
#:def
Answered By: Claudio

Your example seems to use a simple style, so a simplified solution could be used for it as well.

from PIL import Image

def resizeImage(im, corner, new_size):
    '''
    corner_size and new_size are 2-element tuples of xy sizes for the corner size and target size.
    '''

    # Get corners from image
    tl = im.crop(0, 0, corner[0], corner[1])
    tr = im.crop(im.size[0] - corner[0], 0, size[0], corner[1])
    bl = im.crop(0, im.size[1] - corner[1], corner[0], size[1])
    br = im.crop(im.size[0] - corner[0], im.size[1] - corner[1], size[0], size[1])

    # Get 1-pixel slices of midsections, then scale them up as needed
    h_slice = im.crop(corner[0] + 1, 0, corner[0] + 2, im.size[1])
    h_slice = h_slice.resize((new_size[0] - 2 * corner[0], im.size[1]))
    v_slice = im.crop(0, corner[1] + 1, im.size[0], corner[1] + 2)
    v_slice = v_slice.resize((im.size[0], new_size[1] - 2 * corner[1]))

    # create new image
    new_im = Image.new('RGBA', new_size)

    # paste on segments and corners
    new_im.paste(tl, (0, 0))
    new_im.paste(tr, (new_size[0] - corner[0], 0))
    new_im.paste(tl, (0, new_size[1] - corner[1]))
    new_im.paste(tl, (new_size[0] - corner[0], new_size[1] - corner[1]))

    return im

This answer assumes that your borders are completely homogenous, in that there’s no difference between any slice of the border (no patterns/textures).

If you do want to account for this, you can check out RenPy’s approach to the problem. I’d track down the source code too, but the solution I proposed is a minimal solution for your specific example with a simple GUI style.

(Note that I have not run this code, so there may be a 1-pixel offset somewhere that I could have missed.)

Answered By: Magikarp
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.