3
2

More than 3 years have passed since last update.

pythonistaで画像を角丸にトリミング

Last updated at Posted at 2020-01-12

環境

iOS 13.3
pythonista3 ver. 3.2

動機

iOSのホーム画面もどきのアプリをpythonistaで作っていたところ、アイコン画像を角丸にトリミングしたかったので、色々調べました。

ディレクトリ構造

以下の構造で実行しているものとします。

root/
 ├ icons/
 └ exe/
   └ iconEditor.py

プログラム本体

iconEditor.py

from PIL import Image, ImageDraw, ImageFilter
import photos
import pathlib

def crop_center(pil_img, crop_width, crop_height):
    img_width, img_height = pil_img.size
    return pil_img.crop((
        (img_width - crop_width) // 2,
        (img_height - crop_height) // 2,
        (img_width + crop_width) // 2,
        (img_height + crop_height) // 2
        ))

def crop_max_square(pil_img):
    return crop_center(pil_img, min(pil_img.size), min(pil_img.size))

def make(pil_img, r=100, fil=True):
    mask = DrawBack(pil_img, r)
    if fil:
        mask = mask.filter(ImageFilter.SMOOTH)
    result = pil_img.copy()
    result.putalpha(mask)

    return result


def DrawBack(img, r=100):
    #角丸四角を描画する[3]を参考。というかほぼコピペ。
    mask = Image.new("L", img.size, 0)
    draw = ImageDraw.Draw(mask)
    rx = r
    ry = r
    fillcolor = "#ffffff"
    draw.rectangle((0,ry)+(mask.size[0]-1,mask.size[1]-1-ry), fill=fillcolor)
    draw.rectangle((rx,0)+(mask.size[0]-1-rx,mask.size[1]-1), fill=fillcolor)
    draw.pieslice((0,0)+(rx*2,ry*2), 180, 270, fill=fillcolor)
    draw.pieslice((0,mask.size[1]-1-ry*2)+(rx*2,mask.size[1]-1), 90, 180, fill=fillcolor)
    draw.pieslice((mask.size[0]-1-rx*2,mask.size[1]-1-ry*2)+
    (mask.size[0]-1,mask.size[1]-1), 0, 180, fill=fillcolor)
    draw.pieslice((mask.size[0]-1-rx*2,0)+
    (mask.size[0]-1,ry*2), 270, 360, fill=fillcolor)
    return mask


def iconGenerate(iconpath, r=100):
    im = Image.open(iconpath)

    newname = ''.join(['../icons/', str(pathlib.Path(iconpath).stem), '.PNG'])
    thumb_width = 300

    im_square = crop_max_square(im).resize((thumb_width, thumb_width), Image.LANCZOS)
    im_thumb = make(im_square, r, False)
    im_thumb.save(newname)
    return im_thumb

if __name__ == '__main__':
    temp = pathlib.Path('../icons')
    for p in temp.iterdir():
        print('・' + p.name)

    isfin = False
    while not(isfin):
        print('which file?(if new, input new)')
        fname = input()

        if fname == 'new':
            asset = photos.pick_asset(title='Pick a image', multi=False)
            try:
                image = asset.get_image()
                print('Input new name with .png or .PNG')
                newname = input('newname : ')
                image.save('../icons/'+newname)
                fname = newname

            except AttributeError:
                #選択をしなかった場合はnoneなのでこのエラーが起きる
                print('Nothing chosen.')
                continue

        try:
            r = int(input('r : '))
            im = iconGenerate('../icons/'+fname, r)
            print('Success!')
            im.show()
            isfin = True

        except Exception as e:
            print(e)
            print('Some Error Occored')

        print('Exit?(y / n)')
        ans = input()
        isfin = False if ans == 'n' else True
        ## End while
    print('Exit this file.')

各関数について

crop_center(pil_img, crop_width, crop_height)

crop_max_square(pil_img)

参考文献[1]のサイト様からコピペです。

make(pil_img, r=100, fil=True)

pil_img : PIL image オブジェクト
r : 角丸の円部分の半径??
fil : filterの有無。いらないかも。

参考文献[2]のサイト様のmask_circle_transparent関数を基に、簡略化してmaskを角丸にしたもの。

DrawBack(img, r=100)

img : PIL image オブジェクト
r : 角丸の円部分の半径?

参考文献[3]のサイト様のプログラムを関数化したものです。
アイコン画像という正方形画像を作りたい都合上、半径rx,ryは等しい値のrを使います。

iconGenerate(iconpath, r=100)

iconpath : 使いたい画像の保存場所へのパス
r : 角丸の円部分の半径?

このプログラムのメイン部分。


im = Image.open(iconpath)

newname = ''.join(['../icons/', str(pathlib.Path(iconpath).stem), '.PNG'])
thumb_width = 300

まずパスからPIL imageオブジェクトを生成して、保存名を決めます。
pathlib.Path(iconpath)でPathオブジェクトを作って、stemはパスからファイル名だけ(拡張子を含まない)を取りだしています。それをstrでキャストして、joinで結合させています。

thumb_width は正方形の1辺の長さを決めます。[1][2]のサイト様で使用されていた変数名をそのまま使ってます。


im_square = crop_max_square(im).resize((thumb_width, thumb_width), Image.LANCZOS)
im_thumb = make(im_square, r, False)

im_thumb.save(newname)
return im_thumb

im_square は正方形に画像をトリミングしています。[1][2]を参考。
im_thumb で角丸を被せています。
その後保存して、そのイメージを返しています。

if __name__ == '__main__' 以下の部分

蛇足だとは思いますが、プログラム全体をコピペして動作させた場合の挙動について説明します。


temp = pathlib.Path('../icons')
for p in temp.iterdir():
    print('・' + p.name)

保存場所のファイルのパスをPathオブジェクトで生成して、そのフォルダにあるファイルを列挙させています。
isfin は Trueの場合whileループを終了させる変数です。


if fname == 'new':
            asset = photos.pick_asset(title='Pick a image', multi=False)
            try:
                image = asset.get_image()
                print('Input new name with .png or .PNG')
                newname = input('newname : ')
                image.save('../icons/'+newname)
                fname = newname

            except AttributeError:
                #選択をしなかった場合はnoneなのでこのエラーが起きる
                print('Nothing chosen.')
                continue

次に適用する画像を一覧を参考に選択します。

もし写真アプリから直接選ぶ場合はnewと打ち込みましょう。

newを入力した場合、写真が表示されます。最近保存した写真じゃないと表示されないかも?
一般に


asset = photos.pick_asset(title='Pick a image', multi=False)
try:
    image = asset.get_image()

except AttributeError:
    print('Nothing chosen.')

image.save(path)

とすることでプログラムで指定したpathに画像を保存できます。


r = int(input('r : '))
im = iconGenerate('../icons/'+fname, r)
print('Success!')
im.show()
isfin = True

print('Exit?(y / n)')
ans = input()
isfin = False if ans == 'n' else True

入力されたファイル名で画像を変換して保存します。
im.show() でコンソールに変換後に表示します。(確認)
その後、もう一度変換を行うか聞いて、n以外の回答をした場合は再びwhileループに入ります。

後書き

このプログラムを利用して起こったバグがあればコメントにお願いします。可読性の点などでこうした方がいいのでは?などのコメントも積極的にお願いします!
筆者のスキルは高くないので、フィードバックがあると喜びます。

参考文献

[1]Python, Pillowで画像の一部をトリミング(切り出し/切り抜き)
[2]Python, Pillowで正方形・円形のサムネイル画像を一括作成
[3]画像処理についてあれこれ: Pillowを使用して角丸四角を描画する

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2