0

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.

Claudio
  • 7,474
  • 3
  • 18
  • 48
Ethan Chan
  • 153
  • 11
  • 1
    You are probably looking for *"seam carving"*, a.k.a. *"liquid resizng"*. You can do it with **wand**, described here https://docs.wand-py.org/en/0.6.5/guide/resizecrop.html – Mark Setchell Oct 01 '22 at 19:14
  • @MarkSetchell it does not work as expected in case of a thin border as it will be resized too not recognized as special for being kept as it is. – Claudio Oct 01 '22 at 22:23

2 Answers2

2

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.)

Magikarp
  • 171
  • 1
  • 11
1

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
Claudio
  • 7,474
  • 3
  • 18
  • 48