上一篇文章介绍了我对小工具的需求,实现过程中用到的 python 库的信息以及 demo 的展示,这篇文章主要就是对其中一些实现细节的总结

代码细节

这个工程是在 python 3.6 下实现的小工具,主要使用到 tkinter 中的若干控件,以及 PIL 的图片生成和图片放缩,以及截图的功能

源码地址:https://github.com/catcheroftime/CreateWxCover

界面布局

代码很简单,一步一步按照预设的布局添加控件即可

def __createMainwinow(self):self.root = tk.Tk()self.root.iconphoto(True, ImageTk.PhotoImage(data=base64.b64decode(logo_ico)))self.root.title("一键生成微信公众号封面")self.root.geometry('960x520')self.root.resizable(0,0)self.control_frame = ttk.Frame(self.root)self.control_frame.pack(anchor='w', padx=10, pady=5)self.__createElementframe()self.__createMoveFrame()# 创建一个Canvas,设置其背景色为白色self.canvas = tk.Canvas(self.root, height=self.cv_height, width=self.cv_width,highlightthickness=0, bg = 'white')self.canvas.pack()self.__createRuler()

选择本地图片功能

点击 打开图片 按键,弹出选择文件选择对话框,而这一部分 tkinter 有现成的 filedialog

from tkinter import filedialogdef __openLocalImage(self):ftypes = [('png files', '*.png'), ('jpg files', '*.jpg') ]file_path = filedialog.askopenfilename(title="选择背景图片", filetypes=ftypes )if file_path:self.picpath.set(file_path)self.__clearImageInfo()self.originimage = Image.open(file_path)self.__canvasShowImage()
  • ftypes 是一个简单的文件筛选的过滤器
  • 当用户选择图片之后,将地址显示在 图片地址 之后的文本框中
  • 删除旧的图片
  • 将原始图片的信息保存到 self.originimage, 因为之后可能需要对图片放大缩小,保存一份原始图片信息
  • 最后按照我们之前定义的规则绘制图片即可

简单看一下绘制图片 __canvasShowImage() 的函数

def __canvasShowImage(self):ratio = self.originimage.width/self.originimage.heightif ratio > 1:self.resizeimage = self.originimage.resize((self.curImageSize, int(self.curImageSize/ratio)),Image.NEAREST)else :self.resizeimage = self.originimage.resize((int(self.curImageSize*ratio), self.curImageSize),Image.NEAREST)self.tk_image = ImageTk.PhotoImage(self.resizeimage)self.canvas_imgIndex = self.canvas.create_image(self.curImageCenterPos[0]-self.resizeimage.width/2, self.curImageCenterPos[1]-self.resizeimage.height/2,anchor ='nw',image=self.tk_image)self.__FontChange()self.__createRuler()

上一篇文章提过我希望文字和图片尽量居中,对一张图片我需要在保证原比例的情况下,进行一定放缩,让其居中,并且考虑到还有移动功能,使用 self.curImageCenterPos 来保存图片当前的位置,初始值为 self.curImageCenterPos = (self.cv_width/2,self.cv_height/2)

哦,对了,引入 PILImageTk 对象主要是因为 self.canvas 自带的 PhotoImageBitmapImage 支持的图片样式太少, 并且刚好不支持 pngjpg 类型

class PhotoImage(Image):"""Widget which can display colored images in GIF, PPM/PGM format."""...class BitmapImage(Image):"""Widget which can display a bitmap."""...

最后我还调用了 __FontChange()__createRuler(), 是因为我画布上只有3个元素 图片文字标尺

并且我定义的 图片文字标尺 的关系是 : 标尺 > 文字 > 图片, 也就是说,标尺文字 之上,文字图片 之上

  • 文字 改动的时候,为了防止 文字 遮盖了 标尺, 需要在绘制完 文字 之后,再绘制 标尺
  • 图片 改动的时候,为了防止 图片 遮盖了 文字标尺,需要在绘制完 图片 之后,再绘制 文字标尺

输入框控件

这里的输入框主要有2个,一个是 文本输入框,另一个是 图片地址展示输入框,以 文本输入 为例 :

self.var_text = tk.StringVar()
self.var_text.trace_add("write", self.__FontChange)
ttk.Entry(self.element_frame, textvariable=self.var_text, width = 30).grid(row=1,column=1)

StringVartkinter 中获取控件值的一个好方法,它很单纯的将控件和控件的值信息隔离开,并且 StringVar 也提供了绑定回调函数等功能

作为文本输入框,当文字改变的时候,需要实时更新画面中的文字信息,所以需要给 self.var_text 绑定一个回调函数 self.var_text.trace_add("write", self.__FontChange)

简单点解释就是 指当文本输入框的值被被写入write 的时候,也就是有改动时候会调用我们定义的 self.__FontChange 函数,而关于 __FontChange 函数可以简单看一下

def __FontChange(self, *args):# 删除旧的字体if self.canvas_textIndex :self.canvas.delete(self.canvas_textIndex)if self.var_text.get():f = font.Font(  family = self.var_fontfamily.get(), size = self.var_fontsize.get(),weight = "bold" if self.fontweight_status else "normal",slant = "italic" if self.fontslant_status else "roman",underline = 1 if self.fontunderline_status else 0,overstrike = 1 if self.fontoverstrike_status else 0 )self.canvas_textIndex = self.canvas.create_text(self.curTextPos[0],self.curTextPos[1],anchor="center", text=self.var_text.get(), font=f, fill = self.color_button["background"])self.__createRuler()

self.canvas 写文字的时候

  • 需要先将原本的文字删除掉, self.canvas_textIndex 是之前文字在画布上的索引
  • 然后获取文本输入框中的文字信息,结合其他文字属性的信息,生成新的 font.Font() 对象,其中包括文字的 字体大小粗细斜体下划线删除线 这些设置
  • 最后在画布上绘制 self.canvas.create_text()
  • 为了防止 文字标尺 遮盖了,重新绘制一遍标尺 self.__createRuler()

下拉菜单控件

下拉菜单有,字体样式的下拉菜单、字体大小的下拉菜单以及移动对象选择的下拉菜单,我这里以字体样式的下拉菜单为例,介绍一下 tkintercombobox 的使用方法

def __createFontFamilyCombobox(self):self.var_fontfamily = tk.StringVar()self.var_fontfamily.trace_add("write", self.__FontChange)# 创建字体的下拉菜单fontfamily_combobox = ttk.Combobox(self.element_frame, textvariable=self.var_fontfamily)fontfamily_combobox['state'] = "readonly"# 获取当前系统中所有的字体,并筛选出首字母是中文的字体           list_families = []for i in font.families():if '\u4e00' <= i[0] <= '\u9fff':list_families.append(i)self.families = tuple(list_families)            fontfamily_combobox['value'] = self.families# 默认选用 微软雅黑字体try:currentindex = self.families.index("微软雅黑")except:currentindex = 0fontfamily_combobox.current(currentindex)return fontfamily_combobox

字体样式下拉菜单 fontfamily_combobox

  • 绑定一个 self.var_fontfamily 对象,并且当值改变的时候调用 self.__FontChange
  • fontfamily_combobox 设置为只读状态,防止用户输入导致样式被改动
  • 通过 font.families() 获取当前系统所有字体样式
    系统的样式太多是一方面原因,另一方面原因是我大部分都是中文,而且恰好字体名称此时一般也是中文,所以我筛选出中文的字体名称
  • 最后下拉菜单默认值设置为 微软雅黑

颜色选择器

颜色选择器,tkinter 也提供了超简单的对象 colorchooser,仅仅需要一行代码 colorchooser.askcolor(), 简单看一下我摘录出的部分代码

from tkinter import colorchooser...
self.color_button = tk.Button(self.font_frame, text ="", width = 3, background="#3C70C6",command = self.__ColorChange)
...def __ColorChange(self):result = colorchooser.askcolor()if result[1]:self.color_button["background"] = result[1]self.__FontChange()

我定义了一个按键 self.color_button, 这个按键的背景色就是当前文字的颜色,并且点击的时候调用 self.__ColorChange() 函数

  • 打开颜色选择器,判断用户选择结果
  • 结果不为空的时候,将得到的颜色结果( result 的结构 ((128.5, 0.0, 255.99609375), '#8000ff') ),也就是 result[1] 赋给 self.color_button 的背景色 background 属性
  • 最后重新绘制一下画布上的文字即可

按键样式

我最初的本意是实现 windows 下自带的 画图 中对文本的编辑功能的样式,最后功能实现了,但是在样式上还是有点不太如意

先看一下我的实现,我也还是摘录出的部分代码

def __changeFontweightStyle(self):if not self.fontweight_status:self.fontweight_checkbutton['style'] = 'check_bold.TButton'else:self.fontweight_checkbutton['style'] = 'bold.TButton'self.fontweight_status = bool(1-self.fontweight_status)self.__FontChange()# 粗体
def __createFontBoldCheckButton(self):self.fontweight_status = Falsettkstyle = ttk.Style()ttkstyle.configure('bold.TButton', font=('宋体', 8, 'bold'))ttkstyle.configure('check_bold.TButton', font=('宋体', 8, 'bold'),background ="#0078D7")self.fontweight_checkbutton = ttk.Button(self.font_frame, text ="B", style='bold.TButton', width=2, takefocus=False,  command = self.__changeFontweightStyle)return self.fontweight_checkbutton

我就是单纯使用 ttk.Button 按键来实现的,通过点击改变不同的样式,来实现选中和未选中的样式

  • takefocus 设置成 False 是不想按键在焦点的情况下出现虚线的外框
  • style 就是设置按键的样式

最后效果不太理想的原因主要就是 ttk.Style() 是可以设置整体控件的风格,主要是以下几种

('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')

而我使用风格 vista 虽然按键更好看,但是背景色这个属性 background 显示的就是不太明显,当我在设置其他风格的时候,就比较明显了,但是不好看

感觉还是我对 ttk.Style 的用法存在问题

保存图片

保存图片我使用的是比较原始的办法,直接截图,截取画布区域保存成图片

def __saveImage(self):self.__clearRuler()self.canvas.update()# 获取初始位置x0 = self.root.winfo_rootx() + self.canvas.winfo_x()y0 = self.root.winfo_rooty() + self.canvas.winfo_y()# 获取结束位置x1 = x0 + self.canvas.winfo_width()y1 = y0 + self.canvas.winfo_height()size = (x0, y0, x1, y1)pic = ImageGrab.grab(size)pic.save("./cover.png")self.__createRuler()

因为保存图片的时候不能将 标尺 也保存下来,所有先清空 标尺 ,然后找出画布 左上角右下角 的2个点,截图,保存图片

其实使用截图会存在一个问题,因为是通过截取桌面指定区域来实现的保存图片,所以当 canvas 不能完整在桌面上显示的时候,截图的结果其实就错误了,如下图

这也是我在上一篇提到的选择 940:400 大小画布的原因

现在电脑的屏幕分辨率最少都是 1366*768, 在 2.35:1 的前提下我们将画布大小固定在 940:400,这样封面大小感觉在电脑上呈现时应该还不错

我需要保证最后画布可以完整的展示在桌面上

当然最后保存图片也有其它的实现方式

  • 比如通过 PIL 生成底片,然后对应将导入的图片和写入的文字直接写到底片中,用户对应的操作,也对应在该底片上操作,最后保存图片也可以,但是我觉得还是比较麻烦;
  • 或许 canvas 提供类似于获取画布上所有信息的简单接口,直接保存图片也可以,但是在看 canvas 接口的时候没有太仔细去找

所有选择 截图 这种比较容易讨巧,但是可能会有风险的方案了

打包细节

因为最后我只想打包出一个 exe 的文件,那么我使用到图片等资源文件也需要想办法打包到 exe 中,所有第一时间想到的办法是,将图片通过 base64 编码成二进制信息,并且以文件名作为二进制字符串的对象名,保存到一个 python 文件中

所以先简单实现一个转换的脚本 pictobase64.py

import base64
import glob
import os# 使用 pyinstaller 时将资源文件直接导入到 exe 时执行的脚本
# 执行此文件,生成 `resource.py` 文件# 需要导入到 EXE 的图片地址
import_pictures = glob.glob(r'./resource/*')# 导出途径
target_path = r'./resource.py'# 删除旧的文件
if os.path.exists(target_path):os.remove(target_path)# 以追加的形式将通过 base64 转换后图片信息存储导出的文件中
with open(target_path,"a+") as f:for file_path in import_pictures:# 以 文件名称_文件类型 作为二进制信息的对象名file_name = (os.path.split(file_path)[-1]).replace('.', '_')with open(file_path,"rb") as pic:b64str = base64.b64encode(pic.read())write_data = f'{file_name} = {b64str}'f.write(write_data)f.write('\n')

我们将 ./resource/ 路径下的所有的文件通过 base64 编码成二进制信息后存入 resource.py

使用起来也还算简单,通过执行 pictobase64.py 生成 resource.py 文件,导入 from resource import *

写一段简单的测试代码

import tkinter as tk
from PIL import ImageGrab, ImageTk, Image
from resource import *
import base64root = tk.Tk()
root.iconphoto(True, ImageTk.PhotoImage(data=base64.b64decode(logo_ico)))
root.title("一键生成微信公众号封面")
root.geometry('960x520')
root.resizable(0,0)
root.mainloop()

总结

主要总结了一些我觉得还挺值得记录的细节,可能介绍的有点乱,感兴趣的可以自己看一下源码

完整的源码地址:https://github.com/catcheroftime/CreateWxCover

微信公众号封面一键生成器-续相关推荐

  1. 在线生成制作Pornhub的微信公众号封面图

    在线生成制作Pornhub的微信公众号封面图 在线生成制作Pornhub的微信公众号封面图 本工具在线制作具有PornHub风格的Logo.支持自定义文字内容.排列结构.颜色以及文字大小.可以用于微信 ...

  2. 微信公众号实现“一键关注”功能

    背景:之前用的一键关注要么通过图文,要么通过二维码,这样甚是麻烦,而且引导不到位,而看到朋友圈打广告的一些公众号可以直接关注,于是一直研究这个功能,今天终于实现了,分享给所有朋友,希望能帮助到所有朋友 ...

  3. 微信公众号(一键互粉)增粉平台的源码分享

    最近放假,一直在研究微信一键互粉的功能,终于开发出来了,很感谢一些CSDN上的网友的思路,有自己的积分系统,VIP功能可提交快速增粉/阅读/点赞/转发/收藏这些功能,普通用户则只能使用一键互粉. 个人 ...

  4. 如何将微信公众号上的文章下载下来?

    我最近发现微信公众号发布的的文章里边,有很多不错的图片,想要保存下来,可你习惯性的点击"图片另存为",发现有些图片的格式不是jpg格式,很难保存下来,选择截图又不是很清楚,那么该怎 ...

  5. 教你如何一键提取微信公众号文章的封面图

    有很多小伙伴问小编,看到一个别人发的公众号文章封面图特别好看,可是在文章里面却没有显示,这要怎么才能把这张图下载下来呢?今天就教你们,如何去抓取公众号文章封面图自己使用,详细步骤请看本文. 第一步 复 ...

  6. 数据库怎么用Java做封面_一个毫无用处的公众号封面生成器

    一个毫无用处的公众号封面生成器 对于一个没有任何艺术细胞的人,写公众号最头疼的无异于文章封面了. 要么选不好图,选好图了呢,公众号这个2.35:1的诡异比例还会涉及到裁剪的问题... 所以或许你已经发 ...

  7. WordPress插件-Erphp Weixin Scan关注微信公众号一键登录网站

    介绍: Erphp Weixin Scan是某板兔开发的一款关注微信公众号一键登录网站的WordPress插件. 目前只有认证的服务号有生成带参数的二维码接口权限(注意不要使用测试号,测试号虽然有服务 ...

  8. 微信公众号开发系列教程一(调试环境部署续:vs远程调试)

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

  9. 详谈外部H5页面跳转微信一键关注微信公众号的方案

    外部H5页面内实现关注公众号的微信JSSDK没有相关接口开放,第三方浏览器打开微信的接口,微信只给部分合作平台开放了接口权限,任何第三方想调用只能是通过一些技术手段来请求接口,获取秘钥(ticket) ...

最新文章

  1. Panabit 安装笔记之FreeBsd 6.2的安装
  2. android 控件随手指移动_液体流动控件,隔壁产品都馋哭了
  3. 海洋女神建新installshield交流群了,原来的老群都满了,请加新群哦,记得认真填写验证信息...
  4. 联赛前(伪)数据结构专题总结
  5. C语言指针和链表的体会
  6. 杭电c语言课程设计实验7,杭电1072 BFS 大神给看看啊 郁闷整整10个小时了 不知道哪里错wa...
  7. Netty关闭客户端
  8. 电气论文:负荷区间预测(机器学习简单实现)
  9. 聊聊Java中的并发队列中 有界队列和无界队列的区别
  10. linux vi编辑器 Ctrl s,Linux命令-----vi/vim编辑器
  11. 项目管理技术和工具TT
  12. POJ2586-Y2K Accounting Bug
  13. 【强化学习】手把手教你实现游戏通关AI(1)——游戏界面实现
  14. 【转】Mac 下钥匙串不能授权访问怎么解决--不错
  15. java dispose事件_求助!!为什么我的dispose()不起作用
  16. Hive编程指南01
  17. Squid之传统代理和透明代理解析实验步骤
  18. 如何提高游戏后台数据查找效率
  19. [.NET基础]走进各种位运算
  20. 机器视觉汽车配件检测流程介绍

热门文章

  1. 【活动时间调整】博客搬家,有礼相送
  2. 古体字与简体字对照表_汉字繁体字(正体字)与简体字对照表
  3. Tree Traversal(二叉树的遍历)
  4. tree traversal (树的遍历) - postorder traversal (后序遍历)
  5. 如何在拍照时摆POSS
  6. 拍照爱摆“剪刀手”?呵呵,1.5米内100%还原指纹
  7. java obd_XTOOL X100 PAD3通过OBD给2014 BMW CAS4 Key编程
  8. svg格式图像导出为png图片
  9. 亚麻种子的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  10. 如何培养孩子的阅读兴趣