Python圖片轉(zhuǎn)gif方式(將靜態(tài)圖轉(zhuǎn)化為分塊加載的動(dòng)態(tài)圖)
簡(jiǎn)介
將靜態(tài)圖轉(zhuǎn)化為分塊加載的動(dòng)態(tài)圖
方案
1. PIL
- 創(chuàng)建背景圖
- 將原圖拆分成N塊并依次合成到背景圖的相應(yīng)位置, 得到N張素材圖
- 將N張素材圖合成GIF
2. pygifsicle
對(duì)合成的GIF進(jìn)行優(yōu)化(無(wú)損壓縮, 精簡(jiǎn)體積)
注意: 需要電腦安裝gifsicle, 官網(wǎng): https://www.lcdf.org/gifsicle/
若看不懂英文, 網(wǎng)上資料一大把, (其實(shí)不安裝也不影響正常使用, 只是沒(méi)有優(yōu)化GIF而已)
3. tkinter
用于圖形化界面的實(shí)現(xiàn), 便于操作
4. pyinstaller
用于將腳本打包成exe
源碼
https://gitee.com/tianshl/img2gif.git
腳本介紹
img2gif.py
- 簡(jiǎn)介: 將圖片轉(zhuǎn)成gif 命令行模式
- 使用: python img2gif.py -h
- 示例: python img2gif.py -p /Users/tianshl/Documents/sample.jpg
img2gif_gui.py
- 簡(jiǎn)介: 將圖片轉(zhuǎn)成gif 圖像化界面
- 使用: python img2gif_gui.py
打包成exe
pyinstaller -F -w -i gif.ico img2gif_gui.py # 執(zhí)行完指令后, exe文件在dist目錄下 # 我打包的exe: https://download.csdn.net/download/xiaobuding007/12685554
效果圖
命令行模式
圖形化界面
代碼
requirements.txt (依賴(lài))
Pillow==7.2.0 pygifsicle==1.0.1
img2gif.py (命令行模式 )
# -*- coding: utf-8 -*- """ ********************************************************** * Author : tianshl * Email : xiyuan91@126.com * Last modified : 2020-07-29 14:58:57 * Filename : img2gif.py * Description : 圖片轉(zhuǎn)動(dòng)圖 * Documents : https://www.lcdf.org/gifsicle/ * ******************************************************** """ import argparse import copy import logging import os import random from PIL import Image from pygifsicle import optimize LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) log = logging.getLogger(__name__) class Img2Gif: """ 圖片轉(zhuǎn)動(dòng)圖 """ def __init__(self, img_path, blocks=16, mode='append', random_block=False): """ 初始化 :param img_path: 圖片地址 :param blocks: 分塊數(shù) :param mode: 展示模式 append: 追加, flow: 流式, random: 隨機(jī) :param random_block: 隨機(jī)拆分 """ self.mode = mode if mode in ['flow', 'append', 'random'] else 'append' self.blocks = blocks self.random_block = random_block # 背景圖 self.img_background = None self.img_path = img_path self.img_dir, self.img_name = os.path.split(img_path) self.img_name = os.path.splitext(self.img_name)[0] self.gif_path = os.path.join(self.img_dir, '{}.gif'.format(self.img_name)) def get_ranges(self): """ 獲取橫向和縱向塊數(shù) """ if not self.random_block: w = int(self.blocks ** 0.5) return w, w ranges = list() for w in range(2, int(self.blocks ** 0.5) + 1): if self.blocks % w == 0: ranges.append((w, self.blocks // w)) if ranges: return random.choice(ranges) else: return self.blocks, 1 def materials(self): """ 素材 """ log.info('分割圖片') img_origin = Image.open(self.img_path) (width, height) = img_origin.size self.img_background = Image.new(img_origin.mode, img_origin.size) # 單方向分割次數(shù) blocks_w, blocks_h = self.get_ranges() block_width = width // blocks_w block_height = height // blocks_h img_tmp = copy.copy(self.img_background) # 動(dòng)圖中的每一幀 _materials = list() for h in range(blocks_h): for w in range(blocks_w): block_box = (w * block_width, h * block_height, (w + 1) * block_width, (h + 1) * block_height) block_img = img_origin.crop(block_box) if self.mode in ['flow', 'random']: img_tmp = copy.copy(self.img_background) img_tmp.paste(block_img, (w * block_width, h * block_height)) _materials.append(copy.copy(img_tmp)) # 隨機(jī)打亂順序 if self.mode == 'random': random.shuffle(_materials) log.info('分割完成') # 最后十幀展示原圖 [_materials.append(copy.copy(img_origin)) for _ in range(10)] return _materials def gif(self): """ 合成gif """ materials = self.materials() log.info('合成GIF') self.img_background.save(self.gif_path, save_all=True, loop=True, append_images=materials, duration=250) log.info('合成完成') log.info('壓縮GIF') optimize(self.gif_path) log.info('壓縮完成') if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("-p", "--img_path", required=True, help="圖片路徑") parser.add_argument("-b", "--blocks", type=int, default=16, help="塊數(shù)") parser.add_argument("-r", "--random_block", type=bool, default=False, help="隨機(jī)拆分塊數(shù)") parser.add_argument( '-m', '--mode', default='append', choices=['append', 'flow', 'random'], help="塊展示模式 append: 追加, flow: 流式, random: 隨機(jī)" ) args = parser.parse_args() Img2Gif(**args.__dict__).gif()
img2gif_gui.py (圖形化界面)
# -*- coding: utf-8 -*- """ ********************************************************** * Author : tianshl * Email : xiyuan91@126.com * Last modified : 2020-07-29 14:58:57 * Filename : img2gif_gui.py * Description : 圖片轉(zhuǎn)動(dòng)圖 * Documents : https://www.lcdf.org/gifsicle/ * ******************************************************** """ import copy import random from tkinter import * from tkinter import ttk, messagebox from tkinter.filedialog import askopenfilename, asksaveasfilename from PIL import Image, ImageTk from pygifsicle import optimize class Img2Gif(Frame): """ 圖形化界面 """ def __init__(self): """ 初始化 """ Frame.__init__(self) # 設(shè)置窗口信息 self.__set_win_info() # 渲染窗口 self._gif_pane = None self.__render_pane() def __set_win_info(self): """ 設(shè)置窗口信息 """ # 獲取屏幕分辨率 win_w = self.winfo_screenwidth() win_h = self.winfo_screenheight() # 設(shè)置窗口尺寸/位置 self._width = 260 self._height = 300 self.master.geometry('{}x{}+{}+{}'.format( self._width, self._height, (win_w - self._width) // 2, (win_h - self._height) // 2) ) # 設(shè)置窗口不可變 self.master.resizable(width=False, height=False) @staticmethod def __destroy_frame(frame): """ 銷(xiāo)毀frame """ if frame is None: return for widget in frame.winfo_children(): widget.destroy() frame.destroy() def __render_pane(self): """ 渲染窗口 """ self._main_pane = Frame(self.master, width=self._width, height=self._height) self._main_pane.pack() # 設(shè)置窗口標(biāo)題 self.master.title('圖片轉(zhuǎn)GIF') # 選擇圖片 image_path_label = Label(self._main_pane, text='選擇圖片', relief=RIDGE, padx=10) image_path_label.place(x=10, y=10) self._image_path_entry = Entry(self._main_pane, width=13) self._image_path_entry.place(x=90, y=7) image_path_button = Label(self._main_pane, text='···', relief=RIDGE, padx=5) image_path_button.bind('<Button-1>', self.__select_image) image_path_button.place(x=220, y=10) # 拆分塊數(shù) blocks_label = Label(self._main_pane, text='拆分塊數(shù)', relief=RIDGE, padx=10) blocks_label.place(x=10, y=50) self._blocks_scale = Scale( self._main_pane, from_=2, to=100, orient=HORIZONTAL, sliderlength=10 ) self._blocks_scale.set(16) self._blocks_scale.place(x=90, y=33) Label(self._main_pane, text='(塊)').place(x=200, y=50) # 隨機(jī)拆分 random_block_label = Label(self._main_pane, text='隨機(jī)拆分', relief=RIDGE, padx=10) random_block_label.place(x=10, y=90) self._random_block = BooleanVar(value=False) random_block_check_button = ttk.Checkbutton( self._main_pane, variable=self._random_block, width=0, onvalue=True, offvalue=False ) random_block_check_button.place(x=90, y=90) # 動(dòng)圖模式 mode_label = Label(self._main_pane, text='動(dòng)圖模式', relief=RIDGE, padx=10) mode_label.place(x=10, y=130) self._mode = StringVar(value='append') ttk.Radiobutton(self._main_pane, text='追加', variable=self._mode, value='append').place(x=90, y=130) ttk.Radiobutton(self._main_pane, text='流式', variable=self._mode, value='flow').place(x=145, y=130) ttk.Radiobutton(self._main_pane, text='隨機(jī)', variable=self._mode, value='random').place(x=200, y=130) # 每幀延時(shí) duration_label = Label(self._main_pane, text='每幀延時(shí)', relief=RIDGE, padx=10) duration_label.place(x=10, y=170) self._duration_scale = Scale( self._main_pane, from_=50, to=1000, orient=HORIZONTAL, sliderlength=10 ) self._duration_scale.set(250) self._duration_scale.place(x=90, y=152) Label(self._main_pane, text='(毫秒)').place(x=200, y=170) # 整圖幀數(shù) whole_frames_label = Label(self._main_pane, text='整圖幀數(shù)', relief=RIDGE, padx=10) whole_frames_label.place(x=10, y=210) self._whole_frames_scale = Scale( self._main_pane, from_=0, to=20, orient=HORIZONTAL, sliderlength=10 ) self._whole_frames_scale.set(10) self._whole_frames_scale.place(x=90, y=193) Label(self._main_pane, text='(幀)').place(x=200, y=210) # 開(kāi)始轉(zhuǎn)換 execute_button = ttk.Button(self._main_pane, text='開(kāi)始執(zhí)行', width=23, command=self.__show_gif) execute_button.place(x=10, y=250) def __select_image(self, event): """ 選擇圖片 """ image_path = askopenfilename(title='選擇圖片', filetypes=[ ('PNG', '*.png'), ('JPG', '*.jpg'), ('JPG', '*.jpeg'), ('BMP', '*.bmp'), ('ICO', '*.ico') ]) self._image_path_entry.delete(0, END) self._image_path_entry.insert(0, image_path) def __block_ranges(self): """ 獲取圖片橫向和縱向需要拆分的塊數(shù) """ blocks = self._blocks_scale.get() if not self._random_block.get(): n = int(blocks ** 0.5) return n, n ranges = list() for horizontally in range(1, blocks + 1): if blocks % horizontally == 0: ranges.append((horizontally, blocks // horizontally)) if ranges: return random.choice(ranges) else: return blocks, 1 def __generate_materials(self): """ 根據(jù)原圖生成N張素材圖 """ image_path = self._image_path_entry.get() if not image_path: messagebox.showerror(title='錯(cuò)誤', message='請(qǐng)選擇圖片') return self._image_origin = Image.open(image_path) # 獲取圖片分辨率 (width, height) = self._image_origin.size # 創(chuàng)建底圖 self._image_background = Image.new(self._image_origin.mode, self._image_origin.size) image_tmp = copy.copy(self._image_background) # 獲取橫向和縱向塊數(shù) horizontally_blocks, vertically_blocks = self.__block_ranges() # 計(jì)算每塊尺寸 block_width = width // horizontally_blocks block_height = height // vertically_blocks width_diff = width - block_width * horizontally_blocks height_diff = height - block_height * vertically_blocks # GIF模式 gif_mode = self._mode.get() # 生成N幀圖片素材 materials = list() for v_idx, v in enumerate(range(vertically_blocks)): for h_idx, h in enumerate(range(horizontally_blocks)): _block_width = (h + 1) * block_width # 最右一列 寬度+誤差 if h_idx + 1 == horizontally_blocks: _block_width += width_diff _block_height = (v + 1) * block_height # 最后一行 高度+誤差 if v_idx + 1 == vertically_blocks: _block_height += height_diff block_box = (h * block_width, v * block_height, _block_width, _block_height) block_img = self._image_origin.crop(block_box) if gif_mode in ['flow', 'random']: image_tmp = copy.copy(self._image_background) image_tmp.paste(block_img, (h * block_width, v * block_height)) materials.append(copy.copy(image_tmp)) # mode=random時(shí)隨機(jī)打亂順序 if gif_mode == 'random': random.shuffle(materials) # 整圖幀數(shù) [materials.append(copy.copy(self._image_origin)) for _ in range(self._whole_frames_scale.get())] return materials def __show_gif(self): """ 展示GIF """ self._materials = self.__generate_materials() if not self._materials: return self._main_pane.place(x=0, y=-1 * self._height) self._gif_pane = Frame(self.master, width=self._width, height=self._height) self._gif_pane.pack() # 設(shè)置窗口標(biāo)題 self.master.title('預(yù)覽GIF') label_width = 240 label = Label(self._gif_pane, width=label_width, height=label_width) label.place(x=8, y=5) button_save = ttk.Button(self._gif_pane, text='保存', width=9, command=self.__save_gif) button_save.place(x=8, y=250) button_cancel = ttk.Button(self._gif_pane, text='返回', width=9, command=self.__show_main_pane) button_cancel.place(x=138, y=250) # 尺寸 (width, height) = self._image_origin.size # 幀速 duration = self._duration_scale.get() # 縮放 gif_size = (label_width, int(height / width * label_width)) frames = [ImageTk.PhotoImage(img.resize(gif_size, Image.ANTIALIAS)) for img in self._materials] # 幀數(shù) idx_max = len(frames) def show(idx): """ 展示圖片 """ frame = frames[idx] label.configure(image=frame) idx = 0 if idx == idx_max else idx + 1 self._gif_pane.after(duration, show, idx % idx_max) show(0) def __save_gif(self): """ 存儲(chǔ)GIF """ gif_path = asksaveasfilename(title='保存GIF', filetypes=[('GIF', '.gif')]) if not gif_path: return gif_path += '' if gif_path.endswith('.gif') or gif_path.endswith('.GIF') else '.gif' # 存儲(chǔ)GIF Image.new(self._image_origin.mode, self._image_origin.size).save( gif_path, save_all=True, loop=True, duration=self._duration_scale.get(), append_images=self._materials ) # 優(yōu)化GIF optimize(gif_path) messagebox.showinfo(title='提示', message='保存成功') self.__show_main_pane() def __show_main_pane(self): """ 取消保存 """ self.__destroy_frame(self._gif_pane) self._main_pane.place(x=0, y=0) if __name__ == '__main__': Img2Gif().mainloop()
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
一篇文章徹底搞懂Python中可迭代(Iterable)、迭代器(Iterator)與生成器(Generator)的概念
這篇文章主要給大家介紹了如何通過(guò)一篇文章徹底搞懂Python中可迭代(Iterable)、迭代器(Iterator)與生成器(Generator)的概念,對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05Python測(cè)試Kafka集群(pykafka)實(shí)例
今天小編就為大家分享一篇Python測(cè)試Kafka集群(pykafka)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12Python中計(jì)時(shí)程序運(yùn)行時(shí)間的幾種常用方法
這篇文章主要介紹了Python中計(jì)時(shí)程序運(yùn)行時(shí)間的幾種常用方法,分別是一般方法、基于上下文管理器和基于裝飾器,每種方法都有其適用場(chǎng)景和優(yōu)缺點(diǎn),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-04-04使用OpenCV實(shí)現(xiàn)人臉圖像卡通化的示例代碼
這篇文章主要介紹了使用OpenCV實(shí)現(xiàn)人臉圖像卡通化的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Python通過(guò)rembg實(shí)現(xiàn)圖片背景去除功能
在圖像處理領(lǐng)域,背景移除是一個(gè)常見(jiàn)且重要的任務(wù),Python中的rembg庫(kù)就是一個(gè)強(qiáng)大的工具,它基于深度學(xué)習(xí)技術(shù),能夠準(zhǔn)確、快速地移除圖像背景,本文將結(jié)合多個(gè)實(shí)際案例,詳細(xì)介紹rembg庫(kù)的安裝、基本用法、高級(jí)功能以及在實(shí)際項(xiàng)目中的應(yīng)用,需要的朋友可以參考下2024-09-09