synopsis
Convert static diagrams into dynamic diagrams loaded in chunks
programmatic
1. PIL
- Creating a background image
- Split the original image into N pieces and synthesize them into the corresponding positions of the background image to get N material images.
- Synthesize GIFs from N images
2. pygifsicle
Optimize composite GIFs (lossless compression, reduced size)
Note: You need to install gifsicle on your computer, official website: /gifsicle/.
If you can't read English, there is a lot of information on the Internet, (actually, it doesn't affect the normal use without installing, just not optimized GIFs)
3. tkinter
For the implementation of graphical interfaces, easy to operate
4. pyinstaller
Used to package scripts into exe
source code (computing)
/tianshl/
Script Introduction
- Introduction: Convert image to gif Command Line Mode
- Use: python -h
- Example: python -p /Users/tianshl/Documents/
img2gif_gui.py
- Introduction: Convert image to gif Graphical interface
- Usage: python img2gif_gui.py
Packaged as an exe
pyinstaller -F -w -i img2gif_gui.py # After executing the command, the exe file is in the dist directory. # My packaged exe: /download/xiaobuding007/12685554
rendering (visual representation of how things will turn out)
command-line mode
graphical interface
coding
(Dependency)
Pillow==7.2.0 pygifsicle==1.0.1
(command-line mode)
# -*- coding: utf-8 -*- """ ********************************************************** * Author : tianshl * Email : xiyuan91@ * Last modified : 2020-07-29 14:58:57 * Filename : * Description : Picture rotation * Documents : /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' (level=, format=LOG_FORMAT) log = (__name__) class Img2Gif: """ Picture rotation chart """ def __init__(self, img_path, blocks=16, mode='append', random_block=False): """ Initialization :param img_path: image address :param blocks: number of blocks :param mode: display mode append: append, flow: flow, random: random :param random_block: random split """ = mode if mode in ['flow', 'append', 'random'] else 'append' = blocks self.random_block = random_block # Background image self.img_background = None self.img_path = img_path self.img_dir, self.img_name = (img_path) self.img_name = (self.img_name)[0] self.gif_path = (self.img_dir, '{}.gif'.format(self.img_name)) def get_ranges(self): """ Get the number of horizontal and vertical blocks """ if not self.random_block: w = int( ** 0.5) return w, w ranges = list() for w in range(2, int( ** 0.5) + 1): if % w == 0: ((w, // w)) if ranges: return (ranges) else: return , 1 def materials(self): """ Material """ ('Split image') img_origin = (self.img_path) (width, height) = img_origin.size self.img_background = (img_origin.mode, img_origin.size) # of unidirectional splits blocks_w, blocks_h = self.get_ranges() block_width = width // blocks_w block_height = height // blocks_h img_tmp = (self.img_background) # Every frame in the motion picture _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 in ['flow', 'random']: img_tmp = (self.img_background) img_tmp.paste(block_img, (w * block_width, h * block_height)) _materials.append((img_tmp)) # Randomly upset the order if == 'random': (_materials) ('Split complete') # The last ten frames show the original [_materials.append((img_origin)) for _ in range(10)] return _materials def gif(self): """ Synthesized gifs """ materials = () ('Composite GIF') self.img_background.save(self.gif_path, save_all=True, loop=True, append_images=materials, duration=250) ('Synthesis complete') ('Compressed GIF') optimize(self.gif_path) ('Compression complete') if __name__ == '__main__': parser = () parser.add_argument("-p", "--img_path", required=True, help="Picture path") parser.add_argument("-b", "--blocks", type=int, default=16, help="Blocks") parser.add_argument("-r", "--random_block", type=bool, default=False, help="Randomized block splitting.") parser.add_argument( '-m', '--mode', default='append', choices=['append', 'flow', 'random'], help="Block display modes append: append, flow: flow, random: random" ) args = parser.parse_args() Img2Gif(**args.__dict__).gif()
img2gif_gui.py (graphical interface)
# -*- coding: utf-8 -*- """ ********************************************************** * Author : tianshl * Email : xiyuan91@ * Last modified : 2020-07-29 14:58:57 * Filename : img2gif_gui.py * Description : Image rotation * Documents : /gifsicle/ * ******************************************************** """ import copy import random from tkinter import * from tkinter import ttk, messagebox from import askopenfilename, asksaveasfilename from PIL import Image, ImageTk from pygifsicle import optimize class Img2Gif(Frame): """ Graphical interface """ def __init__(self): """ Initialization """ Frame.__init__(self) # Setting up window information self.__set_win_info() # Render window self._gif_pane = None self.__render_pane() def __set_win_info(self): """ Setting Window Information """ # Get screen resolution win_w = self.winfo_screenwidth() win_h = self.winfo_screenheight() # Set window size/position self._width = 260 self._height = 300 ('{}x{}+{}+{}'.format( self._width, self._height, (win_w - self._width) // 2, (win_h - self._height) // 2) ) # Setup window immutable (width=False, height=False) @staticmethod def __destroy_frame(frame): """ Destroy frame """ if frame is None: return for widget in frame.winfo_children(): () () def __render_pane(self): """ Render window """ self._main_pane = Frame(, width=self._width, height=self._height) self._main_pane.pack() # Set the window title ('Image to GIF') # Select the picture image_path_label = Label(self._main_pane, text='Select Image', 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) # of blocks split blocks_label = Label(self._main_pane, text='Number of split blocks', 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='(lump (of earth))').place(x=200, y=50) # Random splits random_block_label = Label(self._main_pane, text='Randomized splitting', relief=RIDGE, padx=10) random_block_label.place(x=10, y=90) self._random_block = BooleanVar(value=False) random_block_check_button = ( self._main_pane, variable=self._random_block, width=0, onvalue=True, offvalue=False ) random_block_check_button.place(x=90, y=90) # Motion picture mode mode_label = Label(self._main_pane, text='Moving Picture Mode', relief=RIDGE, padx=10) mode_label.place(x=10, y=130) self._mode = StringVar(value='append') (self._main_pane, text='Additional', variable=self._mode, value='append').place(x=90, y=130) (self._main_pane, text='Streaming', variable=self._mode, value='flow').place(x=145, y=130) (self._main_pane, text='Random', variable=self._mode, value='random').place(x=200, y=130) # Delay per frame duration_label = Label(self._main_pane, text='Delay per frame', 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='(millisecond)').place(x=200, y=170) # of whole frames whole_frames_label = Label(self._main_pane, text='Number of whole frames', 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='(one of a pair (scrolls))').place(x=200, y=210) # Start the conversion execute_button = (self._main_pane, text='Commencement of execution', width=23, command=self.__show_gif) execute_button.place(x=10, y=250) def __select_image(self, event): """ Select image """ image_path = askopenfilename(title='Select Image', 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): """ Get the number of chunks of the image to split horizontally and vertically """ 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: ((horizontally, blocks // horizontally)) if ranges: return (ranges) else: return blocks, 1 def __generate_materials(self): """ Generate N material images from the original """ image_path = self._image_path_entry.get() if not image_path: (title='Error', message='Please select a picture') return self._image_origin = (image_path) # Get image resolution (width, height) = self._image_origin.size # Create the base map self._image_background = (self._image_origin.mode, self._image_origin.size) image_tmp = (self._image_background) # Get the number of horizontal and vertical blocks horizontally_blocks, vertically_blocks = self.__block_ranges() # Calculate the size of each piece 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 mode gif_mode = self._mode.get() # Generate N-frame stock footage 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 # Rightmost column Width + Error if h_idx + 1 == horizontally_blocks: _block_width += width_diff _block_height = (v + 1) * block_height # Last line Height + error 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 = (self._image_background) image_tmp.paste(block_img, (h * block_width, v * block_height)) ((image_tmp)) # Randomize the order when mode=random if gif_mode == 'random': (materials) # of whole frames [((self._image_origin)) for _ in range(self._whole_frames_scale.get())] return materials def __show_gif(self): """ Show GIFs """ self._materials = self.__generate_materials() if not self._materials: return self._main_pane.place(x=0, y=-1 * self._height) self._gif_pane = Frame(, width=self._width, height=self._height) self._gif_pane.pack() # Set the window title ('Preview GIF') label_width = 240 label = Label(self._gif_pane, width=label_width, height=label_width) (x=8, y=5) button_save = (self._gif_pane, text='Save', width=9, command=self.__save_gif) button_save.place(x=8, y=250) button_cancel = (self._gif_pane, text='Return', width=9, command=self.__show_main_pane) button_cancel.place(x=138, y=250) # Size (width, height) = self._image_origin.size # Frame rate duration = self._duration_scale.get() # Zoom gif_size = (label_width, int(height / width * label_width)) frames = [((gif_size, )) for img in self._materials] # of frames idx_max = len(frames) def show(idx): """ Showcase Pictures """ frame = frames[idx] (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): """ Storing GIFs """ gif_path = asksaveasfilename(title='Save GIF', filetypes=[('GIF', '.gif')]) if not gif_path: return gif_path += '' if gif_path.endswith('.gif') or gif_path.endswith('.GIF') else '.gif' # Store GIFs (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 ) # Optimized GIF optimize(gif_path) (title='Hints', message='Saved successfully') self.__show_main_pane() def __show_main_pane(self): """ Cancel save """ self.__destroy_frame(self._gif_pane) self._main_pane.place(x=0, y=0) if __name__ == '__main__': Img2Gif().mainloop()
summarize
The above is a personal experience, I hope it can give you a reference, and I hope you can support me more.