環境
iOS 13.3
pythonista3 ver. 3.2
動機
iOSのホーム画面もどきのアプリをpythonistaで作っていたところ、アイコン画像を角丸にトリミングしたかったので、色々調べました。
ディレクトリ構造
以下の構造で実行しているものとします。
root/
├ icons/
└ exe/
└ 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を使用して角丸四角を描画する