[萌新]python小程序实现Pic2Icon转换

python小程序实现Pic2Icon转换


前言

该程序用来将普通格式的图片转换成*.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.双击打开程序,等待命令行弹出提示信息。

[萌新]python小程序实现Pic2Icon转换

2.将所要转换的图片的文件拖入命令框后按下回车,等待程序执行完毕。

[萌新]python小程序实现Pic2Icon转换
[萌新]python小程序实现Pic2Icon转换

3.程序会将icon图标输出到图片所在目录的Output_Icon文件夹下。

[萌新]python小程序实现Pic2Icon转换


简单介绍下如何改变文件夹、快捷方式的图标:1.右键打开文件夹的属性 2.打开自定义选项更改文件夹的图标 3.在弹出的对话框中选择"浏览",选择需要替换成的图片,再点击应用就可以了。
[萌新]python小程序实现Pic2Icon转换

二、代码说明

源代码展示

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 helpn")
    finally:
        print("Thank you for using this software.n@Author : NNUwxln"
              "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并不支持输出四通道图片。
[萌新]python小程序实现Pic2Icon转换
唔,这么看来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进行输出。
[萌新]python小程序实现Pic2Icon转换
但是就是在最后一步的输出发生了问题,输出icon的分辨率始终只用16x16。这图能看?

[萌新]python小程序实现Pic2Icon转换

(我没有缩小,图片本来就只有这么大)

于是只能吭哧吭哧去读pillow的文档Pillow官方文档

[萌新]python小程序实现Pic2Icon转换
好家伙,还需要我指定一个size,最大还不能超过256x256。行吧,先输出试试
[萌新]python小程序实现Pic2Icon转换
这样终于能够输出256x256的图标了。
[萌新]python小程序实现Pic2Icon转换

有没有发现256x256的分辨率根本不够看,特别是在作为文件夹icon的时候,一张图能给你糊成马赛克。可是pillow库最大只支持256x256怎么办?
博主尝试修改pillow的轮子看看能不能突破限制。

找到Image.save中控制icon输出的代码部分:
[萌新]python小程序实现Pic2Icon转换
好家伙,它真给限制成256x256的了。
但是没有关西,我们这里给它稍微改一改:
[萌新]python小程序实现Pic2Icon转换
然后尝试输出一下:
[萌新]python小程序实现Pic2Icon转换

成了!👍
好家伙,我直接好家伙,你这块代码搁这虚空挡拆呢。明明没有限制你偏要人为加一手限制。
不知道的还以为你要出付费DLC后续解锁呢🙊

但是就这分辨率对于更精细的图肯定还是不够的用,于是我们直接上2048x2048:
[萌新]python小程序实现Pic2Icon转换

[萌新]python小程序实现Pic2Icon转换

终于舒服了。

可优化

还有一些问题等待解决。 比如可以使用python的窗口模块让用户界面更友好。比如将图片重采样成2048x2048时运用的是cv2.resize函数将数组扩充,个人发现这样的方法会增加图片的锐度,因为它好像并不会改变数组的值,比如说重采样的点在原图中位于两个颜色的交界处,那它其实应该取中间色,但是resize函数并没有这样做。

匿名

发表评论

匿名网友