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

# 環境
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]の[サイト様](https://note.nkmk.me/python-pillow-image-crop-trimming/)からコピペです。

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

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

参考文献[2]の[サイト様](https://note.nkmk.me/python-pillow-square-circle-thumbnail/)のmask_circle_transparent関数を基に、簡略化してmaskを角丸にしたもの。

### DrawBack(img, r=100)
img : PIL image オブジェクト
r : 角丸の円部分の半径？

参考文献[3]の[サイト様](http://kyle-in-jp.blogspot.com/2019/06/pillow.html?m=1)のプログラムを関数化したものです。
アイコン画像という正方形画像を作りたい都合上、半径rx,ryは等しい値のrを使います。

### iconGenerate(iconpath, r=100)
iconpath : 使いたい画像の保存場所へのパス
r : 角丸の円部分の半径？

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

```python

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]のサイト様で使用されていた変数名をそのまま使ってます。

```python

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\_\_' 以下の部分

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

```python

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

```

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

```python

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を入力した場合、写真が表示されます。最近保存した写真じゃないと表示されないかも？
一般に

```python

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

except AttributeError:
	print('Nothing chosen.')

image.save(path)

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

```python

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で画像の一部をトリミング（切り出し/切り抜き）](https://note.nkmk.me/python-pillow-image-crop-trimming/)
[2][Python, Pillowで正方形・円形のサムネイル画像を一括作成](https://note.nkmk.me/python-pillow-square-circle-thumbnail/)
[3][画像処理についてあれこれ: Pillowを使用して角丸四角を描画する](http://kyle-in-jp.blogspot.com/2019/06/pillow.html?m=1)