建立一個程式, 用來處理一般圖片, 移除類似顏色成為透明的背景.
建檔日期: 2019/08/26
更新日期: 2020/09/29
相關軟體資訊:
WIN 10 | Python 3.8.3 | PIL Pillow 7.1.2 | PySimpleGUI 4.29.0.14 |
目標:
- 可開啟任意圖片, 並顯示.
- 點選圖片上任意一點以指定要移除的區域
- 以顏色的偏差作為是否同一區域的認定
- 預設顏色偏差值為16, RGB三色都在偏差值之內, 可以更改偏差值.
- 可以存為 PNG 檔案.
- 不使用遞迴呼叫, 避免出錯.
可以 Undo 一次.
程式畫面
程式碼及說明
- 庫的匯入
import ctypes
from io import BytesIO
from pathlib import Path
from PIL import Image
import PySimpleGUI as sg
- 建立圖片處理的類
class Picture():
def __init__(self):
self.im = None # 處理中的圖片
self.old_im = None # 保留圖片供 Undo
self.default_tolerance = 16 # 預設顏色偏差值
self.tolerances = [0, 8, 16, 32, 64, 128] # 可選顏色偏差值
self.picture = None # 顯示的圖片物件
self.width, self.height = self.size = (1600, 800) # 圖片顯示區尺寸
self.transparency = (0, 0, 0, 0) # 用來取代的透明值
- 根據點的顏色值及偏差值, 計算容許的顏色範圍
def limit(self, color, tolerance):
result = []
for c in color:
result += [
min(max(c-tolerance, 0), 255), min(max(c+tolerance, 0), 255)]
return result
- 背景處理的準備
def change(self, x, y):
r, g, b, a = self.im.getpixel((x, y))
e = self.tolerance
self.r0, self.r1, self.g0, self.g1, self.b0, self.b1 = self.limit(
(r, g, b), self.tolerance)
self.old_im = self.im.copy()
self.update(x, y)
- 檢查該點是否被認為是同色的
def is_same(self, x, y):
r, g, b, a = self.im.getpixel((x, y))
return True if (
self.r0 <= r <= self.r1 and
self.g0 <= g <= self.g1 and
self.b0 <= b <= self.b1 and
a != 0
) else False
- 點檢查更新
- 設定所有的點都未檢
- 檢查起點, 設為已檢, 如果為同色, 則更改為透明點
- 新增檢查點為上下左右四點, 直到無點可檢
def update(self, x, y):
checked = [[False for y in range(self.im.height)]
for x in range(self.im.width)]
to_do = [(x, y)]
while to_do:
temp = []
for x, y in to_do:
checked[x][y] = True
if self.is_same(x, y):
self.im.putpixel((x, y), self.transparency)
if x-1 >= 0 and not checked[x-1][y]:
temp.append((x-1, y))
if x+1 < self.im.width and not checked[x+1][y]:
temp.append((x+1, y))
if y-1 >= 0 and not checked[x][y-1]:
temp.append((x, y-1))
if y+1 < self.im.height and not checked[x][y+1]:
temp.append((x, y+1))
to_do = temp.copy()
- 更新圖片的顯示
def new(self):
if self.picture:
graph.delete_figure(self.picture)
with BytesIO() as output:
self.im.save(output, format="PNG")
data = output.getvalue()
self.picture = graph.draw_image(data=data, location=(self.x0, self.y0))
- 螢幕的圖片點選處理
def click(self, position):
if self.picture is None:
return
x1, y1 = position
x, y = x1-self.x0, self.y0-y1
if not (0<=x<self.im.width and 0<=y<self.im.height):
return
self.change(x, y)
self.new()
- 新圖片檔案的開啟
def open(self):
filename = sg.popup_get_file('Open new image', no_window=True, modal=True)
if filename and Path(filename).is_file():
try:
self.im = Image.open(filename).convert(mode='RGBA')
except:
return
self.x0 = (self.width-self.im.width)//2
self.y0 = (self.height+self.im.height)//2
self.tolerance = self.default_tolerance
window['TOLERANCE'].update(value=self.tolerance)
self.new()
- 圖片儲存為 PNG 檔案
def save(self):
if self.im is None:
return
filename = sg.popup_get_file('Save new image', no_window=True,
modal=True, save_as=True)
if filename:
if not filename.lower().endswith(".png"):
filename += ".png"
self.im.save(filename)
return
- Undo 已保留的前圖片
def undo(self):
if self.old_im:
self.im = self.old_im.copy()
self.new()
- 程式預設
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
font = ('Courier New', 16, 'bold')
p = Picture()
- 程式視窗布局
layout = [
[sg.Button("OPEN", font=font),
sg.Button("SAVE", font=font),
sg.Button("UNDO", font=font),
sg.Text("Tolerance", font=font),
sg.Combo(p.tolerances, p.default_tolerance, font=font, key='TOLERANCE',
enable_events=True)],
[sg.Graph(p.size, (0, 0), p.size, enable_events=True, key='GRAPH',
background_color='darkgreen')],
]
- 程式視窗生成
window = sg.Window('Move Outline of Image', layout, finalize=True)
graph = window['GRAPH']
- 事件處理
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == 'OPEN':
p.open()
elif event == 'SAVE':
p.save()
elif event == 'UNDO':
p.undo()
elif event == 'TOLERANCE':
p.tolerance = values['TOLERANCE']
elif event == 'GRAPH':
p.click(values['GRAPH'])
- 程式視窗關閉
window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結