Python图片转gif(将静态图转化为分块加载的动态图)

简介

将静态图转化为分块加载的动态图

方案

11. PIL: 2 1. 创建背景图 3 2. 将原图拆分成N块并依次合成到背景图的相应位置, 得到N张素材图 4 3.N张素材图合成GIF 5 62. pygifsicle 7 对合成的GIF进行优化(无损压缩, 精简体积) 8 注意: 需要电脑安装gifsicle, 官网: https://www.lcdf.org/gifsicle/, 9 若看不懂英文, 网上资料一大把, (其实不安装也不影响正常使用, 只是没有优化GIF而已) 10 113. tkinter: 12 用于图形化界面的实现, 便于操作 13 144. pyinstaller 15 用于将脚本打包成exe

源码

Fork me on Gitee

https://gitee.com/tianshl/img2gif.git

脚本介绍

img2gif.py
1简介: 将图片转成gif 命令行模式 2使用: python img2gif.py -h 3示例: python img2gif.py -p /Users/tianshl/Documents/sample.jpg
img2gif_gui.py
1简介: 将图片转成gif 图像化界面 2使用: python img2gif_gui.py

打包成exe

1pyinstaller -F -w -i gif.ico img2gif_gui.py 2# 执行完指令后, exe文件在dist目录下 3# 我打包的exe: https://download.csdn.net/download/xiaobuding007/12685554

效果图

命令行模式

图形化界面

代码

requirements.txt (依赖)
1Pillow==7.2.0 2pygifsicle==1.0.1
img2gif.py (命令行模式 )
1# -*- coding: utf-8 -*- 2""" 3 ********************************************************** 4 * Author : tianshl 5 * Email : xiyuan91@126.com 6 * Last modified : 2020-07-29 14:58:57 7 * Filename : img2gif.py 8 * Description : 图片转动图 9 * Documents : https://www.lcdf.org/gifsicle/ 10 * ******************************************************** 11""" 12import argparse 13import copy 14import logging 15import os 16import random 17 18from PIL import Image 19from pygifsicle import optimize 20 21LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 22logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) 23log = logging.getLogger(__name__) 24 25 26class Img2Gif: 27 """ 28 图片转动图 29 """ 30 31 def __init__(self, img_path, blocks=16, mode='append', random_block=False): 32 """ 33 初始化 34 :param img_path: 图片地址 35 :param blocks: 分块数 36 :param mode: 展示模式 append: 追加, flow: 流式, random: 随机 37 :param random_block: 随机拆分 38 """ 39 self.mode = mode if mode in ['flow', 'append', 'random'] else 'append' 40 41 self.blocks = blocks 42 self.random_block = random_block 43 44 # 背景图 45 self.img_background = None 46 47 self.img_path = img_path 48 self.img_dir, self.img_name = os.path.split(img_path) 49 self.img_name = os.path.splitext(self.img_name)[0] 50 51 self.gif_path = os.path.join(self.img_dir, '{}.gif'.format(self.img_name)) 52 53 def get_ranges(self): 54 """ 55 获取横向和纵向块数 56 """ 57 if not self.random_block: 58 w = int(self.blocks ** 0.5) 59 return w, w 60 61 ranges = list() 62 for w in range(2, int(self.blocks ** 0.5) + 1): 63 if self.blocks % w == 0: 64 ranges.append((w, self.blocks // w)) 65 66 if ranges: 67 return random.choice(ranges) 68 else: 69 return self.blocks, 1 70 71 def materials(self): 72 """ 73 素材 74 """ 75 76 log.info('分割图片') 77 img_origin = Image.open(self.img_path) 78 (width, height) = img_origin.size 79 self.img_background = Image.new(img_origin.mode, img_origin.size) 80 81 # 单方向分割次数 82 blocks_w, blocks_h = self.get_ranges() 83 84 block_width = width // blocks_w 85 block_height = height // blocks_h 86 87 img_tmp = copy.copy(self.img_background) 88 # 动图中的每一帧 89 _materials = list() 90 for h in range(blocks_h): 91 for w in range(blocks_w): 92 block_box = (w * block_width, h * block_height, (w + 1) * block_width, (h + 1) * block_height) 93 block_img = img_origin.crop(block_box) 94 if self.mode in ['flow', 'random']: 95 img_tmp = copy.copy(self.img_background) 96 img_tmp.paste(block_img, (w * block_width, h * block_height)) 97 _materials.append(copy.copy(img_tmp)) 98 99 # 随机打乱顺序 100 if self.mode == 'random': 101 random.shuffle(_materials) 102 103 log.info('分割完成') 104 # 最后十帧展示原图 105 [_materials.append(copy.copy(img_origin)) for _ in range(10)] 106 return _materials 107 108 def gif(self): 109 """ 110 合成gif 111 """ 112 113 materials = self.materials() 114 log.info('合成GIF') 115 self.img_background.save(self.gif_path, save_all=True, loop=True, append_images=materials, duration=250) 116 log.info('合成完成') 117 118 log.info('压缩GIF') 119 optimize(self.gif_path) 120 log.info('压缩完成') 121 122 123if __name__ == '__main__': 124 parser = argparse.ArgumentParser() 125 parser.add_argument("-p", "--img_path", required=True, help="图片路径") 126 parser.add_argument("-b", "--blocks", type=int, default=16, help="块数") 127 parser.add_argument("-r", "--random_block", type=bool, default=False, help="随机拆分块数") 128 parser.add_argument( 129 '-m', '--mode', default='append', choices=['append', 'flow', 'random'], 130 help="块展示模式 append: 追加, flow: 流式, random: 随机" 131 ) 132 args = parser.parse_args() 133 134 Img2Gif(**args.__dict__).gif() 135
img2gif_gui.py (图形化界面)
1# -*- coding: utf-8 -*- 2""" 3 ********************************************************** 4 * Author : tianshl 5 * Email : xiyuan91@126.com 6 * Last modified : 2020-07-29 14:58:57 7 * Filename : img2gif_gui.py 8 * Description : 图片转动图 9 * Documents : https://www.lcdf.org/gifsicle/ 10 * ******************************************************** 11""" 12import copy 13import random 14from tkinter import * 15from tkinter import ttk, messagebox 16from tkinter.filedialog import askopenfilename, asksaveasfilename 17 18from PIL import Image, ImageTk 19from pygifsicle import optimize 20 21 22class Img2Gif(Frame): 23 """ 24 图形化界面 25 """ 26 27 def __init__(self): 28 """ 29 初始化 30 """ 31 Frame.__init__(self) 32 33 # 设置窗口信息 34 self.__set_win_info() 35 36 # 渲染窗口 37 self._gif_pane = None 38 self.__render_pane() 39 40 def __set_win_info(self): 41 """ 42 设置窗口信息 43 """ 44 # 获取屏幕分辨率 45 win_w = self.winfo_screenwidth() 46 win_h = self.winfo_screenheight() 47 # 设置窗口尺寸/位置 48 self._width = 260 49 self._height = 300 50 self.master.geometry('{}x{}+{}+{}'.format( 51 self._width, self._height, (win_w - self._width) // 2, (win_h - self._height) // 2) 52 ) 53 # 设置窗口不可变 54 self.master.resizable(width=False, height=False) 55 56 @staticmethod 57 def __destroy_frame(frame): 58 """ 59 销毁frame 60 """ 61 if frame is None: 62 return 63 64 for widget in frame.winfo_children(): 65 widget.destroy() 66 67 frame.destroy() 68 69 def __render_pane(self): 70 """ 71 渲染窗口 72 """ 73 74 self._main_pane = Frame(self.master, width=self._width, height=self._height) 75 self._main_pane.pack() 76 77 # 设置窗口标题 78 self.master.title('图片转GIF') 79 80 # 选择图片 81 image_path_label = Label(self._main_pane, text='选择图片', relief=RIDGE, padx=10) 82 image_path_label.place(x=10, y=10) 83 84 self._image_path_entry = Entry(self._main_pane, width=13) 85 self._image_path_entry.place(x=90, y=7) 86 87 image_path_button = Label(self._main_pane, text='···', relief=RIDGE, padx=5) 88 image_path_button.bind('<Button-1>', self.__select_image) 89 image_path_button.place(x=220, y=10) 90 91 # 拆分块数 92 blocks_label = Label(self._main_pane, text='拆分块数', relief=RIDGE, padx=10) 93 blocks_label.place(x=10, y=50) 94 95 self._blocks_scale = Scale( 96 self._main_pane, from_=2, to=100, orient=HORIZONTAL, sliderlength=10 97 ) 98 self._blocks_scale.set(16) 99 self._blocks_scale.place(x=90, y=33) 100 101 Label(self._main_pane, text='(块)').place(x=200, y=50) 102 103 # 随机拆分 104 random_block_label = Label(self._main_pane, text='随机拆分', relief=RIDGE, padx=10) 105 random_block_label.place(x=10, y=90) 106 107 self._random_block = BooleanVar(value=False) 108 random_block_check_button = ttk.Checkbutton( 109 self._main_pane, variable=self._random_block, 110 width=0, onvalue=True, offvalue=False 111 ) 112 random_block_check_button.place(x=90, y=90) 113 114 # 动图模式 115 mode_label = Label(self._main_pane, text='动图模式', relief=RIDGE, padx=10) 116 mode_label.place(x=10, y=130) 117 118 self._mode = StringVar(value='append') 119 ttk.Radiobutton(self._main_pane, text='追加', variable=self._mode, value='append').place(x=90, y=130) 120 ttk.Radiobutton(self._main_pane, text='流式', variable=self._mode, value='flow').place(x=145, y=130) 121 ttk.Radiobutton(self._main_pane, text='随机', variable=self._mode, value='random').place(x=200, y=130) 122 123 # 每帧延时 124 duration_label = Label(self._main_pane, text='每帧延时', relief=RIDGE, padx=10) 125 duration_label.place(x=10, y=170) 126 self._duration_scale = Scale( 127 self._main_pane, from_=50, to=1000, orient=HORIZONTAL, sliderlength=10 128 ) 129 self._duration_scale.set(250) 130 self._duration_scale.place(x=90, y=152) 131 132 Label(self._main_pane, text='(毫秒)').place(x=200, y=170) 133 134 # 整图帧数 135 whole_frames_label = Label(self._main_pane, text='整图帧数', relief=RIDGE, padx=10) 136 whole_frames_label.place(x=10, y=210) 137 138 self._whole_frames_scale = Scale( 139 self._main_pane, from_=0, to=20, orient=HORIZONTAL, sliderlength=10 140 ) 141 self._whole_frames_scale.set(10) 142 self._whole_frames_scale.place(x=90, y=193) 143 144 Label(self._main_pane, text='(帧)').place(x=200, y=210) 145 146 # 开始转换 147 execute_button = ttk.Button(self._main_pane, text='开始执行', width=23, command=self.__show_gif) 148 execute_button.place(x=10, y=250) 149 150 def __select_image(self, event): 151 """ 152 选择图片 153 """ 154 image_path = askopenfilename(title='选择图片', filetypes=[ 155 ('PNG', '*.png'), ('JPG', '*.jpg'), ('JPG', '*.jpeg'), ('BMP', '*.bmp'), ('ICO', '*.ico') 156 ]) 157 self._image_path_entry.delete(0, END) 158 self._image_path_entry.insert(0, image_path) 159 160 def __block_ranges(self): 161 """ 162 获取图片横向和纵向需要拆分的块数 163 """ 164 blocks = self._blocks_scale.get() 165 if not self._random_block.get(): 166 n = int(blocks ** 0.5) 167 return n, n 168 169 ranges = list() 170 for horizontally in range(1, blocks + 1): 171 if blocks % horizontally == 0: 172 ranges.append((horizontally, blocks // horizontally)) 173 174 if ranges: 175 return random.choice(ranges) 176 else: 177 return blocks, 1 178 179 def __generate_materials(self): 180 """ 181 根据原图生成N张素材图 182 """ 183 image_path = self._image_path_entry.get() 184 if not image_path: 185 messagebox.showerror(title='错误', message='请选择图片') 186 return 187 self._image_origin = Image.open(image_path) 188 189 # 获取图片分辨率 190 (width, height) = self._image_origin.size 191 192 # 创建底图 193 self._image_background = Image.new(self._image_origin.mode, self._image_origin.size) 194 image_tmp = copy.copy(self._image_background) 195 196 # 获取横向和纵向块数 197 horizontally_blocks, vertically_blocks = self.__block_ranges() 198 199 # 计算每块尺寸 200 block_width = width // horizontally_blocks 201 block_height = height // vertically_blocks 202 203 width_diff = width - block_width * horizontally_blocks 204 height_diff = height - block_height * vertically_blocks 205 206 # GIF模式 207 gif_mode = self._mode.get() 208 # 生成N帧图片素材 209 materials = list() 210 for v_idx, v in enumerate(range(vertically_blocks)): 211 for h_idx, h in enumerate(range(horizontally_blocks)): 212 _block_width = (h + 1) * block_width 213 # 最右一列 宽度+误差 214 if h_idx + 1 == horizontally_blocks: 215 _block_width += width_diff 216 217 _block_height = (v + 1) * block_height 218 # 最后一行 高度+误差 219 if v_idx + 1 == vertically_blocks: 220 _block_height += height_diff 221 222 block_box = (h * block_width, v * block_height, _block_width, _block_height) 223 block_img = self._image_origin.crop(block_box) 224 if gif_mode in ['flow', 'random']: 225 image_tmp = copy.copy(self._image_background) 226 image_tmp.paste(block_img, (h * block_width, v * block_height)) 227 materials.append(copy.copy(image_tmp)) 228 229 # mode=random时随机打乱顺序 230 if gif_mode == 'random': 231 random.shuffle(materials) 232 233 # 整图帧数 234 [materials.append(copy.copy(self._image_origin)) for _ in range(self._whole_frames_scale.get())] 235 236 return materials 237 238 def __show_gif(self): 239 """ 240 展示GIF 241 """ 242 243 self._materials = self.__generate_materials() 244 if not self._materials: 245 return 246 247 self._main_pane.place(x=0, y=-1 * self._height) 248 self._gif_pane = Frame(self.master, width=self._width, height=self._height) 249 self._gif_pane.pack() 250 251 # 设置窗口标题 252 self.master.title('预览GIF') 253 254 label_width = 240 255 label = Label(self._gif_pane, width=label_width, height=label_width) 256 label.place(x=8, y=5) 257 258 button_save = ttk.Button(self._gif_pane, text='保存', width=9, command=self.__save_gif) 259 button_save.place(x=8, y=250) 260 261 button_cancel = ttk.Button(self._gif_pane, text='返回', width=9, command=self.__show_main_pane) 262 button_cancel.place(x=138, y=250) 263 264 # 尺寸 265 (width, height) = self._image_origin.size 266 # 帧速 267 duration = self._duration_scale.get() 268 # 缩放 269 gif_size = (label_width, int(height / width * label_width)) 270 271 frames = [ImageTk.PhotoImage(img.resize(gif_size, Image.ANTIALIAS)) for img in self._materials] 272 # 帧数 273 idx_max = len(frames) 274 275 def show(idx): 276 """ 277 展示图片 278 """ 279 frame = frames[idx] 280 label.configure(image=frame) 281 idx = 0 if idx == idx_max else idx + 1 282 self._gif_pane.after(duration, show, idx % idx_max) 283 284 show(0) 285 286 def __save_gif(self): 287 """ 288 存储GIF 289 """ 290 gif_path = asksaveasfilename(title='保存GIF', filetypes=[('GIF', '.gif')]) 291 if not gif_path: 292 return 293 294 gif_path += '' if gif_path.endswith('.gif') or gif_path.endswith('.GIF') else '.gif' 295 # 存储GIF 296 Image.new(self._image_origin.mode, self._image_origin.size).save( 297 gif_path, save_all=True, loop=True, duration=self._duration_scale.get(), append_images=self._materials 298 ) 299 300 # 优化GIF 301 optimize(gif_path) 302 messagebox.showinfo(title='提示', message='保存成功') 303 304 self.__show_main_pane() 305 306 def __show_main_pane(self): 307 """ 308 取消保存 309 """ 310 self.__destroy_frame(self._gif_pane) 311 self._main_pane.place(x=0, y=0) 312 313 314if __name__ == '__main__': 315 Img2Gif().mainloop()
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )