SoFunction
Updated on 2024-10-29

Python image to gif way (convert static image to dynamic image loaded in chunks)

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.