python小程序实现Pic2Icon转换
文章目录
- python小程序实现Pic2Icon转换
- 前言
- 一、程序使用步骤
-
- 1.双击打开程序,等待命令行弹出提示信息。
- 2.将所要转换的图片的文件拖入命令框后按下回车,等待程序执行完毕。
- 3.程序会将icon图标输出到图片所在目录的Output_Icon文件夹下。
- 二、代码说明
-
- 源代码展示
- 1.导入库
- 2.定义类Picture和类操作
- 三、编写经历
- 可优化
前言
该程序用来将普通格式的图片转换成*.ico系统可用图标文件。你可以将转换出的图标用于设置文件夹图标、快捷方式图标、程序图标等其他用途。
做程序的契机是前两天在用pipinstaller做python软件的时候发现一个小问题,
pipinstaller命令有一个参数:-i +icon图片路径
可以指定打包制成的*.exe系统执行文件的图标显示,但是这个icon图片的格式只能是*.ico。如果需要自定义一张图片作为程序的图标,可能就需要自己找图片转icon的网站进行转换。度娘提供的前几个网站着实不太行,分辨率、形变度感人。
于是估摸着自己写个小程序实现图片的转化。
提示:
本文第一部分是软件使用步骤的描述,软件已经打包成可执行程序发布在Github上,不需要用户拥有python和相关的模块。
GitHub相关项目release链接
本文第二部分是代码的说明,包括作者写该代码的一些经历。因为相关的文章比较少而且有很多都过时了,所以这次的编写还是很费精力的,把过程写出来也比较有趣。
一、程序使用步骤
程序下载地址https://github.com/NNUwdl/Pic2Icon/releases/tag/0.1_Version
1.双击打开程序,等待命令行弹出提示信息。
2.将所要转换的图片的文件拖入命令框后按下回车,等待程序执行完毕。
3.程序会将icon图标输出到图片所在目录的Output_Icon文件夹下。
简单介绍下如何改变文件夹、快捷方式的图标:1.右键打开文件夹的属性 2.打开自定义选项更改文件夹的图标 3.在弹出的对话框中选择"浏览",选择需要替换成的图片,再点击应用就可以了。
二、代码说明
源代码展示
1.导入库
这里主要用到模块的是numpy、pillow、opencv和os:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/3 1:08
# @Author : Xilun Wu
# @email : nnuwxl@gmail.com
# @File : Pic2Icon.py
import os
from PIL import Image
import numpy as np
import cv2
import sys
import time
class Picture:
def __init__(self, file):
self.ext = ['jpg', 'jpeg', 'png']
self.files = [file]
self.savepath = ""
def handle_picture(self, file):
print("Converting to four channel RGBA images...")
time.sleep(0.5)
img = Image.open(file).convert('RGBA')
width = img.size[0]
height = img.size[1]
x = width / height
matrix = np.asarray(img)
print("The image resolution is being sampled at 2048x2048 size...")
time.sleep(0.5)
if width >= height:
matrix = cv2.resize(matrix, (2048, int(2048 / x)))
m = np.zeros((2048, 2048, 4), dtype=np.int)
a = int(2048 / x)
b = int((2048 - a) / 2)
for i1 in range(2048):
for i2 in range(2048):
if i1 >= b and i1 < b + a:
m[i1][i2] = m[i1][i2] + matrix[i1 - b][i2]
else:
matrix = cv2.resize(matrix, (int(x * 2048), 2048))
m = np.zeros((2048, 2048, 4), dtype=np.int)
a = int(2048 * x)
b = int((2048 - a) / 2)
for i1 in range(2048):
for i2 in range(2048):
if i2 >= b and i2 < b + a:
m[i1][i2] = m[i1][i2] + matrix[i1][i2 - b]
img = Image.fromarray(np.uint8(m), "RGBA")
print("Output image in *.ico format...")
time.sleep(0.5)
savefile0 = os.path.dirname(self.files[0])
savefile = os.path.join(savefile0, "Output_Icon")
if os.path.exists(savefile) == False:
print("Creating the output folder:{}".format(savefile))
time.sleep(1)
os.mkdir(savefile)
savepath = (self.files[0].split('\\')[-1]).split('.')[0] + ".ico"
savepath = os.path.join(savefile, savepath)
img.save(savepath, sizes=[(int(2048), int(2048))], quality=100)
print("Output complete:{}".format(savepath))
time.sleep(1)
self.savepath = savepath
def run(self):
for file in self.files:
if os.path.exists(file) == False:
print("Input image path error, please check and try again!")
print(file)
time.sleep(3)
sys.exit(0)
if file.split('.')[-1] not in self.ext:
print("Sorry, the program does not support images in this format.")
print("Surpported format: *.jpg, *.jpeg, *.png")
time.sleep(3)
sys.exit(0)
if file.split('.')[-1] in self.ext:
self.handle_picture(file)
if __name__ == "__main__":
try:
file = input("Please enter the path of the image:")
if file[0] == '\"' and file[-1] == "\"":
file = file[1:-1]
file = r"{}".format(file)
ins = Picture(file)
ins.run()
print("Opening the output Icon...")
time.sleep(0.7)
message = os.popen('\"{}\"'.format(ins.savepath))
print("The conversion has been completed, and if it fails to open,\n"
" it may be because your system does not have a default application to open the icon image.")
print("\nProcess done。\n")
except Exception as e:
print("Something went wrong:\n{}".format(e))
print("\nYou can send an error message and a screenshot of the program to the author's email for help\n")
finally:
print("Thank you for using this software.\n@Author : NNUwxl\n"
"Having additional question can email me at : nnuwxl@gmail.com")
input()
2.定义类Picture和类操作
这里类操作的步骤是
1.先把读进来的图片用模块PIL.Image.convert转化成RGBA四通道图
2.利用函数numpy.asarray()将图片的所有像素转化成数组的格式
3.利用cv2.resize()函数将上一步中得到的数组转化成2048x2048的数组(可以理解为图片重采用成了分辨率2048x2048的大小)
4.当然为了保证图像不形变,空出部分用数组[0 0 0 0]填充
5.最后用PIL.Image对象的from方法生成Image对象,然后用Image对象的save方法保存icon
class Picture:
def __init__(self, file):
self.ext = ['jpg', 'jpeg', 'png']
self.files = [file]
self.savepath = ""
def handle_picture(self, file):
print("Converting to four channel RGBA images...")
time.sleep(0.5)
img = Image.open(file).convert('RGBA')
width = img.size[0]
height = img.size[1]
x = width / height
matrix = np.asarray(img)
print("The image resolution is being sampled at 2048x2048 size...")
time.sleep(0.5)
if width >= height:
matrix = cv2.resize(matrix, (2048, int(2048 / x)))
m = np.zeros((2048, 2048, 4), dtype=np.int)
a = int(2048 / x)
b = int((2048 - a) / 2)
for i1 in range(2048):
for i2 in range(2048):
if i1 >= b and i1 < b + a:
m[i1][i2] = m[i1][i2] + matrix[i1 - b][i2]
else:
matrix = cv2.resize(matrix, (int(x * 2048), 2048))
m = np.zeros((2048, 2048, 4), dtype=np.int)
a = int(2048 * x)
b = int((2048 - a) / 2)
for i1 in range(2048):
for i2 in range(2048):
if i2 >= b and i2 < b + a:
m[i1][i2] = m[i1][i2] + matrix[i1][i2 - b]
img = Image.fromarray(np.uint8(m), "RGBA")
print("Output image in *.ico format...")
time.sleep(0.5)
savefile0 = os.path.dirname(self.files[0])
savefile = os.path.join(savefile0, "Output_Icon")
if os.path.exists(savefile) == False:
print("Creating the output folder:{}".format(savefile))
time.sleep(1)
os.mkdir(savefile)
savepath = (self.files[0].split('\\')[-1]).split('.')[0] + ".ico"
savepath = os.path.join(savefile, savepath)
img.save(savepath, sizes=[(int(2048), int(2048))], quality=100)
print("Output complete:{}".format(savepath))
time.sleep(1)
self.savepath = savepath
三、编写经历
博主一开始接触的python关于图像处理的模块是opencv(cv2),所以一开始是想要利用opencv库完成代码的编写。
因为对opencv模块有一定的了解,所以,很快啊,cv2.imread()、cv2.imwrite()代码初步就写完了。
众所周知这两个函数对于中文路径的识别会出现问题,所以要添加如下代码,并用其中函数替代:
# 读取图像,解决imread不能读取中文路径的问题, 返回RGB图像
def cv_imread(filePath):
cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), -1) # BGR
## imdecode读取的是rgb,如果后续需要opencv处理的话,需要转换成bgr,转换后图片颜色会变化
# cv_img=cv2.cvtColor(cv_img,cv2.COLOR_RGB2BGR) # RGB
return cv_img
# 读取图像,解决imwrite不能读取中文路径的问题
def cv_imwrite(filePath, inputimg):
cv_img = cv2.imencode(".ico", inputimg)[1].tofile(filePath)
return cv_img
接下来遇到的问题就是opencv模块并不支持*.ico格式的图片输出。
在网上查阅相关资料发现*.ico图片格式其实和*.bmp图片格式是一样的。只要将*.bmp图片的后缀名改为.ico就可以将图片转化为可供windows使用的icon图标。
于是题主就使用opencv库将图片转化为bmp格式,再用os库rename函数,将输出的bmp重命名成.ico。这样输出的图标虽然可以用图片浏览器打开,但是在设置为文件夹图标的时候会有各种各样的显示问题。个人猜测可能是由于icon图标的分辨率都是16x16、256x256这样的正方形,如果图片是非正方形就会出现显示问题。
于是利用cv2.resize函数将cv2.imread读出的图像数组转化为正方形数组,再进行输出。
但是这样得到的图片会有拉伸形变问题。如果我不想要拉伸图片理应将扩充的像素块设置成透明,这样就需要图片拥有四通道’RGBA’,而简单翻阅opencv文档博主发现imwrite并不支持输出四通道图片。
唔,这么看来opencv库属实不太行,上次想用它实现对视频中音频的提取,最后发现人家只支持对视频中的图像进行处理,压根就没在意对视频中的音频进行处理;这次又是它不支持对四通道图片的输出。
满满的绝望。
无奈之下咱们换库,opencv模块不行咱用pillow.
pillow的Image.convert(‘RGBA’)可以将图片转化成四通道对象;
pillow的Image.size可以返回图片的分辨率数组;
pillow的Image.fromarray可以从数组读取成Image对象;
pillow的Image.save 可以直接保存*.ico图标文件;
opencv模块,不行,pillow模块,行!
于是就利用pillow读取图片并转化成四通道,numpy.asarray()读取成数组,cv2.resize()扩充成想要的分辨率,将空缺部分用array[0 0 0 0]替代,最后再用Image.save进行输出。
但是就是在最后一步的输出发生了问题,输出icon的分辨率始终只用16x16。这图能看?
于是只能吭哧吭哧去读pillow的文档Pillow官方文档
好家伙,还需要我指定一个size,最大还不能超过256x256。行吧,先输出试试
这样终于能够输出256x256的图标了。
有没有发现256x256的分辨率根本不够看,特别是在作为文件夹icon的时候,一张图能给你糊成马赛克。可是pillow库最大只支持256x256怎么办?
博主尝试修改pillow的轮子看看能不能突破限制。
找到Image.save中控制icon输出的代码部分:
好家伙,它真给限制成256x256的了。
但是没有关西,我们这里给它稍微改一改:
然后尝试输出一下:
成了!
好家伙,我直接好家伙,你这块代码搁这虚空挡拆呢。明明没有限制你偏要人为加一手限制。
不知道的还以为你要出付费DLC后续解锁呢
但是就这分辨率对于更精细的图肯定还是不够的用,于是我们直接上2048x2048:
终于舒服了。
可优化
还有一些问题等待解决。 比如可以使用python的窗口模块让用户界面更友好。比如将图片重采样成2048x2048时运用的是cv2.resize函数将数组扩充,个人发现这样的方法会增加图片的锐度,因为它好像并不会改变数组的值,比如说重采样的点在原图中位于两个颜色的交界处,那它其实应该取中间色,但是resize函数并没有这样做。