Python generates screenshot GIF animation of meal selection

Time:2021-9-14
catalogue
  • Generating text animation in Python
    • Download emoticons locally
    • Analysis dynamic diagram
    • Generate a single picture
    • Crawling dish data
    • Generate dish name dynamic graph
  • Other operations of PIL operation gif
    • Gif split
    • Gif rewind

Before, a little friend in the group asked what to eat this noon, and then another friend sent the following action chart:

截图吃饭

Personally, I think it’s quite interesting. The screenshot is really like a lottery to choose a dish name at random. Considering that the candidates for dish names in this dynamic picture are not necessarily all the dishes we can eat. We can use Python to generate such a dynamic diagram according to the list of dish names.

What screenshots have you seen before? Choose the moving pictures such as avatars. The moving pictures generated by pictures are relatively simple, which can be done through the image animation workshop tool mentioned in the article. Therefore, this article only demonstrates how to generate text dynamic graph.

Generating text animation in Python

Let’s complete this step by step:

Download emoticons locally

In order to analyze this expression picture, you need to download it first, but the expression dynamic picture of wechat can’t be downloaded directly after testing.

Although it is analyzed by the file monitoring tool, the GIF expression animation is stored inC: \ users \ ASUS \ documents \ wechat files \ your wechat ID \ filestorage \ customemotion \ XX \ XXXXLocation, but you can’t view it with the picture tool. By analyzing binary system with WinHex, we getV1MMWXSuch a file header shows that wechat encrypts the expression to a certain extent. Although it can be decrypted, such a big fight is too troublesome.

A simple solution came to mind later, that is, send the expression to the official account that has permission to login to the backstage, then download it to the official account.

image-20210726163537948

The motion pictures sent by wechat are stored as their ownV1MMWXThe encryption format may be to use its own original compression algorithm to have a larger compression ratio. That means that if we want to directly look at the GIF dynamic diagram stored in local wechat, we can only develop a decoder specifically for this wechat format.

Analysis dynamic diagram

Now I use the gadget imagine and open it with the Animation Workshop:

image-20210726163518497

It can be seen that this moving picture is composed of 22 text pictures, and the frame switching time is 20 milliseconds.

Generate a single picture

After the analysis is completed, we consider using the PIL library to generate a single picture. If the children’s shoes of the library have not been installed, use the following command to install the Library:


pip install pillow

The blue background is selected as the background. Let’s draw the text of the dish name in the middle first:

from PIL import Image, ImageFont, ImageDraw


Text = "Braised Beef Brisket with pearl potatoes"
size = 320
fontsize = (size-20)//len(text)
im = Image.new(mode='RGB', size=(size, size), color="lightblue")

draw = ImageDraw.Draw(im=im)
draw.text(xy=(10, (size-fontsize*1.5)/2),
          text=text, fill=0,
          font=ImageFont.truetype('msyh.ttc', size=fontsize))
im

image-20210726172326328

Due to the inconsistency in the number of names and characters of dishes, automatic text resizing is made in order to fill the whole picture.

Font I chose Microsoft YaHei. Of course, Microsoft YaHei also has three sub fonts. You can view the properties of the font file through the system font installation directory to know the file name corresponding to the font:

image-20210726164518133

It will be troublesome to generate the text with shadow below. My idea is to draw the text in pure black first, and offset the text with black edge and white filling upward by several units:

def text_border(text, x, y, font, shadowcolor, fillcolor):
    draw.text((x - 1, y), text, font=font, fill=shadowcolor)
    draw.text((x + 1, y), text, font=font, fill=shadowcolor)
    draw.text((x, y - 1), text, font=font, fill=shadowcolor)
    draw.text((x, y + 1), text, font=font, fill=shadowcolor)

    draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
    draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
    draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
    draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)

    draw.text((x, y), text, font=font, fill=fillcolor)


Bottomtext = "don't know what to eat? Screenshot to eat"
bottom_fontsize = 27
bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
draw.text(xy=(x, y), text=bottomtext,
          fill=0, font=bottom_font)
text_border(bottomtext, x, y-4,
            bottom_font, 0, (255, 255, 255))
im

image-20210726172847077

The above code selects Chinese amber as the font. The personal method used to draw the text border is relatively simple and rough. If there is a better method, please leave a message.

Considering that the subsequent pictures sent to wechat are very small, just compress the pixel size now:


im.thumbnail((128, 128))
im

image-20210726172948959

Let’s encapsulate the generated code to facilitate subsequent calls:

from PIL import Image, ImageFont, ImageDraw


def text_ IMG (text, bgcolor = "lightblue", bottomtext = "don't know what to eat? Screenshot to eat", size = 360, result_size = (128, 128)):
    def text_border(text, x, y, font, shadowcolor, fillcolor):
        draw.text((x - 1, y), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y), text, font=font, fill=shadowcolor)
        draw.text((x, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x, y + 1), text, font=font, fill=shadowcolor)

        draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)

        draw.text((x, y), text, font=font, fill=fillcolor)

    im = Image.new(mode='RGB', size=(size, size), color=bgcolor)
    draw = ImageDraw.Draw(im=im)
    fontsize = (size-20)//len(text)
    draw.text(xy=(10, (size-fontsize*1.5)/2),
              text=text, fill=0,
              font=ImageFont.truetype('msyh.ttc', size=fontsize))
    bottom_fontsize = (size-20)//len(bottomtext)
    bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
    x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
    draw.text(xy=(x, y), text=bottomtext,
              fill=0, font=bottom_font)
    text_border(bottomtext, x, y-4,
                bottom_font, 0, (255, 255, 255))
    im.thumbnail(result_size)
    return im

Test:

text_ IMG ("fish flavored eggplant")

image-20210726174000710

OK, now we can generate pictures for any dish. But where does the name of the dish come from? I have found a website. Now consider climbing it:

Crawling dish data

The website is: https://m.meishij.net/caipu/

The result of this website is very simple. You can get all the dish names with a simple XPath:

image-20210726174726258

Download Now:


from lxml import etree
import requests

req = requests.get("https://m.meishij.net/caipu/")

html = etree.HTML(req.text)
menu = html.xpath("//dl[@class='recipe_list']//a/text()")
menu = list(set([_.strip(".") for _ in menu]))
print(len(menu), menu[:10], menu[-10:])

3744 [‘spare ribs lotus root soup’, ‘taro balls’,’ seafood soup ‘,’ cold apricot abalone mushrooms’, ‘three juice stew pot’, ‘milk flavored corn juice’, ‘fried beans’,’ eggplant sauce ‘,’ mango glutinous rice dumpling ‘,’ steamed bread ‘] [‘ steamed eggplant ‘,’ broccoli fried chicken ‘,’ old-fashioned cake ‘,’ spare ribs rice cake ‘,’ fried towel gourd ‘,’ steamed spare ribs with taro ‘,’ fried pork with agaric fungus’, ‘oyster sauce and oatmeal’, ‘spicy chicken pieces’,’ lotus leaf cake ‘]

With these dish names, we can already use them to generate dynamic graphs. However, in order to learn how to cook in the future, we can save the dish names. When we want to learn how to cook, open the web page: https://so.meishi.cc/?q= Dish name, search.

Save dish name:

with open("meau.csv", "w", encoding="u8") as f:
    f. Write ("dish name \ n")
    for row in menu:
        f.write(row)
        f.write("\n")

Let’s start to generate the dish name dynamic diagram:

Generate dish name dynamic graph

After all, there are too many 3767 dish names. We can choose 30 dish names to generate a dynamic diagram:


import random

gif_list = random.choices(menu, k=30)
print(gif_list)

[‘steamed egg’, ‘Cinnamon Roll’, ‘fried egg with cold melon’, ‘baked sweet potato with cheese’, ‘banana crisp’, ‘yogurt mousse’, ‘egg vermicelli’, ‘shredded pork tripe with red oil’, ‘corn egg cake’, ‘hot and sour tofu soup’, ‘Stewed Beef Brisket with radish’, ‘balsam pear ribs soup’, ‘dried bean curd mixed with celery’, ‘fried soil with tomato’, ‘steamed eggplant with garlic’, ‘bean paste bread’, ‘fried meat with mushrooms’,’ fried lotus root ‘,’ black pepper beef granules’, ‘pumpkin pancake’, ‘fried cucumber ‘,’ steamed bread with grains’, ‘taoshanpi moon cake’, ‘fried meat with scallions’,’ stir fried beef ‘,’ crucian carp with bean sauce ‘,’ Braised Tofu with shrimp ‘,’ plain dumplings’, ‘cold cucumber’, ‘fish head in casserole’]

PS: it’s better to choose the dish name and write a dead list


import imageio

frames = [text_img(text) for text in gif_list]
imageio.mimsave("meau.gif", frames, 'GIF', duration=0.02)

Generated results:

meau-1627295603332

Generate the complete code of dynamic diagram according to the menu name list

import imageio
from PIL import Image, ImageFont, ImageDraw


def text_ IMG (text, bgcolor = "lightblue", bottomtext = "don't know what to eat? Screenshot to eat", size = 360, result_size = (128, 128)):
    def text_border(text, x, y, font, shadowcolor, fillcolor):
        draw.text((x - 1, y), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y), text, font=font, fill=shadowcolor)
        draw.text((x, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x, y + 1), text, font=font, fill=shadowcolor)

        draw.text((x - 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y - 1), text, font=font, fill=shadowcolor)
        draw.text((x - 1, y + 1), text, font=font, fill=shadowcolor)
        draw.text((x + 1, y + 1), text, font=font, fill=shadowcolor)

        draw.text((x, y), text, font=font, fill=fillcolor)

    im = Image.new(mode='RGB', size=(size, size), color=bgcolor)
    draw = ImageDraw.Draw(im=im)
    fontsize = (size-20)//len(text)
    draw.text(xy=(10, (size-fontsize*1.5)/2),
              text=text, fill=0,
              font=ImageFont.truetype('msyh.ttc', size=fontsize))
    bottom_fontsize = (size-20)//len(bottomtext)
    bottom_font = ImageFont.truetype('STHUPO.TTF', size=bottom_fontsize)
    x, y = (size-bottom_fontsize*len(bottomtext))/2, size-bottom_fontsize*1.2
    draw.text(xy=(x, y), text=bottomtext,
              fill=0, font=bottom_font)
    text_border(bottomtext, x, y-4,
                bottom_font, 0, (255, 255, 255))
    im.thumbnail(result_size)
    return im


def save_meau_gif(savename, meau):
    frames = [text_img(text) for text in meau]
    imageio.mimsave(savename, frames, 'GIF', duration=0.02)

Use example:

meau = [
    "Lotus leaf glutinous rice chicken", "roast mutton", "Black Pepper Steak", "homemade large plate chicken", "mashed garlic beans",
    "Fried beef with onion", "fried egg with towel gourd", "fried egg with mushroom", "sliced tofu with chicken", "Lotus fresh vegetable soup",
    "Fried zucchini", "eggplant and beans", "beef with egg", "mushroom and green vegetables", "ground three delicacies",
    "Braised Pleurotus eryngii with sauce", "chicken wings with fermented bean curd", "lotus root slices with vinegar", "stewed chicken with coconut", "Braised Tofu with mushrooms",
    "Curry chicken leg rice", "mashed potato with chicken juice", "stewed potato with eggplant", "fried udong noodles", "curry potato chicken",
    "Baby dish in soup", "steamed eggplant with minced garlic", "baked sweet potato with cheese", "braised chicken with chestnuts", "towel gourd tofu soup",
]
save_meau_gif("meau.gif", meau)

Generated results:

meau

Since our dynamic graph has been generated! I don’t know what to eat. I can take out screenshots to play with~

Have a nice meal~

Other operations of PIL operation gif

In fact, it can be operated with special motion graph processing software. Here is a supplement. The operation API of Python records:

Gif split

For example, let’s split this picture:

功夫熊

from PIL import Image, ImageSequence

IMG = image. Open ('kung Fu bear. GIF ')
for i, f in enumerate(ImageSequence.Iterator(img), 1):
    f. Save (f 'split / split - {I}. PNG')

Split result:

image-20210726191539826

Gif rewind

Now let’s put the above dynamic diagram upside down:

from PIL import Image, ImageSequence
import imageio

Im = image. Open ('kung Fu bear. GIF ')
sequence = [f.copy() for f in ImageSequence.Iterator(im)]
Sequence. Reverse() # reverses the order of the frames in the list through the reverse() function
Sequence [0]. Save ('rewind Kung Fu bear. GIF ', save_all = true, append_images = sequence [1:])

倒放功夫熊

This is the end of this article on generating screenshot GIF animation by python. For more information about generating screenshot GIF animation by python, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!