Python工具之一:九宫格图片极致裁剪

工具用途

在读到文章(https://blog.uwa4d.com/archives/PSD4UGUI.html)时,文章中提到“e.通过设定参数即可自动生成九宫图片,优化九宫图片面积”,引发的思考:如何优化九宫图片面积?九宫图片作为拉伸图片使用,拉伸区域越小越能缩小图片面积,达到优化的目的。进一步找到文章(https://gameinstitute.qq.com/community/detail/103423)

文章中提到下图

将连续相同的行(列)裁剪掉,只保留一行(列)即可,依次达到九宫图片面积缩小的目的。

(ps:讲道理,图片处理软件里应该能精准控制裁剪的,为啥要程序写工具来做这件事?美术表示我不会…)

本文中并没有写的上文中那么详细,我的最终目的是给Unity中使用的九宫图片做裁剪,因此文章中有部分内容跟Unity沾边,不过不影响工具的使用,工具使用Python开发。

如下图:Unity中两纯色像素之间有颜色过渡,因此工具没有将图片的连续相同行(列)裁剪到只保留一行(列),而是三行(列)。

开发思考

(1)如何加载、保存、读写一个image,本工具使用OpenCV2

(2)如何判断图片的两行(列)是否完全相同

(3)如何计算出图片的最佳九宫区域

(4)如何裁剪九宫区域

裁剪结果

在这里插入图片描述
看起来很像变成了一个圆形,其实四条边上有三行(列)像素是九宫区域

代码介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 比较两行是否相等
# 相等返回true,否则返回false
def equal_row_pixel(img,row1,row2):
height = img.shape[0] #将tuple中的元素取出,赋值给height,width,channels
width = img.shape[1]
channels = img.shape[2]
if(row1 < 0 or row1 >= height):
return False
if(row2 < 0 or row2 >= height):
return False
for col in range(width):
for channel in range(channels):
if(img[row1][col][channel] != img[row2][col][channel]):
return False
return True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 比较两列是否相等
# 相等返回true,否则返回false
def equal_col_pixel(img,col1,col2):
height = img.shape[0] #将tuple中的元素取出,赋值给height,width,channels
width = img.shape[1]
channels = img.shape[2]
if(col1 < 0 or col1 >= width):
return False
if(col2 < 0 or col2 >= width):
return False
for row in range(height):
for channel in range(channels):
if(img[row][col1][channel] != img[row][col2][channel]):
return False
return True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 横向是否可以处理九宫
# img:图片
# continuous_col_num:连续的列数,大于等于这个值判定为横向可以九宫处理
def horizontal_slice(img,continuous_col_num):
cur_begin_col_index = 0
cur_end_col_index = 0
slice_begin_col_index = 0
slice_end_col_index = 0
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]
for col in range(width):
if(col < width - 1):
if(equal_col_pixel(img,col,col+1) == False):
# begin与end指向不是同一列时,判断连续列数是否满足九宫条件
if((cur_begin_col_index != cur_end_col_index) and (cur_end_col_index - cur_begin_col_index >= continuous_col_num) and (cur_end_col_index - cur_begin_col_index > slice_end_col_index - slice_begin_col_index)):
# 已经达到了连续列数,满足九宫条件
slice_begin_col_index = cur_begin_col_index
slice_end_col_index = cur_end_col_index
cur_begin_col_index = col + 1
cur_end_col_index = col + 1
else:
# 相等,end后移一位
cur_end_col_index = col + 1
if(slice_end_col_index - slice_begin_col_index >= continuous_col_num):
return True,slice_begin_col_index,slice_end_col_index
else:
return False,0,0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 纵向是否可以处理九宫
def vertical_slice(img,continuous_row_num):
cur_begin_row_index = 0
cur_end_row_index = 0
slice_begin_row_index = 0
slice_end_row_index = 0
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]
for row in range(height):
if(row < height - 1):
if(equal_row_pixel(img,row,row+1) == False):
if((cur_begin_row_index != cur_end_row_index) and (cur_end_row_index - cur_begin_row_index >= continuous_row_num) and (cur_end_row_index - cur_begin_row_index > slice_end_row_index - slice_begin_row_index)):
slice_begin_row_index = cur_begin_row_index
slice_end_row_index = cur_end_row_index
cur_begin_row_index = row + 1
cur_end_row_index = row + 1
else:
cur_end_row_index = row + 1
if(slice_end_row_index - slice_begin_row_index >= continuous_row_num):
return True,slice_begin_row_index,slice_end_row_index
else:
return False,0,0

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import cv2
from PIL import Image
import numpy as np
import os
import tkFileDialog
import Tkinter
import tkMessageBox

# 比较两行是否相等
# 相等返回true,否则返回false
def equal_row_pixel(img,row1,row2):
height = img.shape[0] #将tuple中的元素取出,赋值给height,width,channels
width = img.shape[1]
channels = img.shape[2]
if(row1 < 0 or row1 >= height):
return False
if(row2 < 0 or row2 >= height):
return False
for col in range(width):
for channel in range(channels):
if(img[row1][col][channel] != img[row2][col][channel]):
return False
return True

# 比较两列是否相等
# 相等返回true,否则返回false
def equal_col_pixel(img,col1,col2):
height = img.shape[0] #将tuple中的元素取出,赋值给height,width,channels
width = img.shape[1]
channels = img.shape[2]
if(col1 < 0 or col1 >= width):
return False
if(col2 < 0 or col2 >= width):
return False
for row in range(height):
for channel in range(channels):
if(img[row][col1][channel] != img[row][col2][channel]):
return False
return True


# 横向是否可以处理九宫
# img:图片
# continuous_col_num:连续的列数,大于等于这个值判定为横向可以九宫处理
def horizontal_slice(img,continuous_col_num):
cur_begin_col_index = 0
cur_end_col_index = 0
slice_begin_col_index = 0
slice_end_col_index = 0
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]
for col in range(width):
if(col < width - 1):
if(equal_col_pixel(img,col,col+1) == False):
# begin与end指向不是同一列时,判断连续列数是否满足九宫条件
if((cur_begin_col_index != cur_end_col_index) and (cur_end_col_index - cur_begin_col_index >= continuous_col_num) and (cur_end_col_index - cur_begin_col_index > slice_end_col_index - slice_begin_col_index)):
# 已经达到了连续列数,满足九宫条件
slice_begin_col_index = cur_begin_col_index
slice_end_col_index = cur_end_col_index
cur_begin_col_index = col + 1
cur_end_col_index = col + 1
else:
# 相等,end后移一位
cur_end_col_index = col + 1
if(slice_end_col_index - slice_begin_col_index >= continuous_col_num):
return True,slice_begin_col_index,slice_end_col_index
else:
return False,0,0

# 纵向是否可以处理九宫
def vertical_slice(img,continuous_row_num):
cur_begin_row_index = 0
cur_end_row_index = 0
slice_begin_row_index = 0
slice_end_row_index = 0
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]
for row in range(height):
if(row < height - 1):
if(equal_row_pixel(img,row,row+1) == False):
if((cur_begin_row_index != cur_end_row_index) and (cur_end_row_index - cur_begin_row_index >= continuous_row_num) and (cur_end_row_index - cur_begin_row_index > slice_end_row_index - slice_begin_row_index)):
slice_begin_row_index = cur_begin_row_index
slice_end_row_index = cur_end_row_index
cur_begin_row_index = row + 1
cur_end_row_index = row + 1
else:
cur_end_row_index = row + 1
if(slice_end_row_index - slice_begin_row_index >= continuous_row_num):
return True,slice_begin_row_index,slice_end_row_index
else:
return False,0,0

# 标记Sprite的九宫区域
def tag_image_slice_area(img,slice_row_begin,slice_row_end,slice_col_begin,slice_col_end,color):
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2]
for row in range(height): #遍历每一行
for col in range(width): #遍历每一列
if((row >= slice_row_begin and row <= slice_row_end and slice_row_begin != slice_row_end) or (col >= slice_col_begin and col <= slice_col_end and slice_col_begin != slice_col_end)):
alter_image_pixel_color(img,row,col,color)
return img


# 修改img指定像素的颜色
# img:修改的img
# row:行索引
# col:列索引
# color:颜色rgb数组
def alter_image_pixel_color(img,row,col,color):
img.itemset((row, col, 0), color[0])
img.itemset((row, col, 1), color[1])
img.itemset((row, col, 2), color[2])

# 九宫区域裁剪
def tailor_image_slice_area(img,slice_row_begin,slice_row_end,slice_col_begin,slice_col_end):
height = img.shape[0]
width = img.shape[1]
new_width = width - (slice_col_end - slice_col_begin)
new_height = height - (slice_row_end - slice_row_begin)
target = np.zeros(shape=(new_height,new_width,img.shape[2]), dtype=np.uint8)

# img[0:4,0:3] 第0行-第4行,第0列到第3列的交叉区域

# 左上
roiImg = img[0:slice_row_begin,0:slice_col_begin]
target[0:slice_row_begin,0:slice_col_begin] = roiImg

# 右上
roiImg = img[0:slice_row_begin,slice_col_end:width]
target[0:slice_row_begin,slice_col_begin:new_width] = roiImg

# 左下
roiImg = img[slice_row_end:height,0:slice_col_begin]
target[slice_row_begin:new_height,0:slice_col_begin] = roiImg

# 右下
roiImg = img[slice_row_end:height,slice_col_end:width]
target[slice_row_begin:new_height,slice_col_begin:new_width] = roiImg

return target

def load_sprite():
continuous_row_num_input_str = continuous_row_num_input.get() #获取文本框内容
continuous_col_num_input_str = continuous_col_num_input.get()
continuous_row_num = 0
continuous_col_num = 0
try:
if continuous_row_num_input_str != "":
continuous_row_num = float(continuous_row_num_input_str)
if continuous_col_num_input_str != "":
continuous_col_num = float(continuous_col_num_input_str)
except ValueError:
tkMessageBox.showinfo( "Error", "无效的线宽输入")
return
if continuous_row_num <= 0 or continuous_col_num <= 0:
tkMessageBox.showinfo( "Error", "无效的线宽输入")
return

fname = tkFileDialog.askopenfilename(title=u"选择文件")
img1 = cv2.imread(fname,cv2.IMREAD_UNCHANGED)
a1,b1,c1 = horizontal_slice(img1,continuous_col_num)
a2,b2,c2 = vertical_slice(img1,continuous_row_num)

# 九宫区域保留在3像素的宽高
b1 = b1 + 1
c1 = c1 - 1
b2 = b2 + 1
c2 = c2 - 1
if toggle_tailor_hor.get() == 0:
b1 = 0
c1 = 0
if toggle_tailor_ver.get() == 0:
b2 = 0
c2 = 0
new_sprite = tailor_image_slice_area(img1,b2,c2,b1,c1)
cv2.imwrite(fname, new_sprite)
print(fname)

root = Tkinter.Tk()
root.geometry('400x300')
root.title("Sprite九宫区域极致裁剪修改器")

frame = Tkinter.Frame(root)
frame.pack()

toggle_tailor_hor = Tkinter.IntVar()
toggle_tailor_ver = Tkinter.IntVar()

Tkinter.Checkbutton(root, text = "是否横向九宫处理", variable = toggle_tailor_hor,onvalue = 1, offvalue = 0).pack()
Tkinter.Checkbutton(root, text = "是否纵向九宫迷宫", variable = toggle_tailor_ver,onvalue = 1, offvalue = 0).pack()

Tkinter.Label(frame, text="横向最小连续列数").pack()
continuous_row_num_input = Tkinter.Entry(frame)
continuous_row_num_input.pack()

Tkinter.Label(frame, text="纵向最小连续行数").pack()
continuous_col_num_input = Tkinter.Entry(frame)
continuous_col_num_input.pack()

load_sprite_button = Tkinter.Button(root, text="加载Sprite文件并修改",command=load_sprite)
load_sprite_button.pack()

root.mainloop()

工具界面

点击按钮”加载Sprite文件并修改“后,打开文件框选中要处理的图片后进行处理,覆盖原图片,可以根据自己的需要进行修改。

以上知识分享,如有错误,欢迎指出,共同学习,共同进步。

最近在用hexo 和 github page搭 个人博客,地址如下:
http://www.jingfengji.tech/
欢迎大家关注。

最近的一些博客 还是会更新在 CSDN这边,后续以自己个人的博客站点会主。