Python generates screenshot GIF animation of meal selection

Hello, I'm Xiao Ming.

Before, a little friend in the group asked what to eat this noon, and then another child named Guagua sent the following picture:

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. I consider using 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 through the file monitoring tool that the gif emoticon is stored in C:\Users\ASUS\Documents\WeChat Files \ your wechat ID\FileStorage\CustomEmotion\xx\xxxx, it cannot be viewed with the image tool. winhex is used to analyze the binary to get the file header such as V1MMWX, which shows that wechat encrypts the expression with a certain program. Although it is possible to decrypt, it is too complicated to go to war in order to download several original gif images.

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.

I don't understand why wechat encrypts several gif pictures. It may also be to use their 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:

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

In order to observe the position of the text in the picture, first use a blue background 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

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:

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 eating"
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

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

Personally, I think the blue background is very good-looking. I think I use blue as the background by default.

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 eating", 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("Yu-Shiang Eggplant")

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:

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 and lotus root soup', 'Taro balls', 'Seafood soup', 'Cold mixed Pleurotus eryngii', 'Three sauce stew pot', 'Milk flavored corn juice', 'sauteed string beans', 'caponata ', 'Mango glutinous rice dumplings', 'Steamed buns'] ['Steamed eggplant', 'Fried chicken with Broccoli', 'Old fashioned cake', 'Spare ribs rice cake', 'Sauteed Sponge Gourd', 'Steamed Spare Ribs with Taro', 'The fungus fried meat', 'Lettuce in oyster sauce', 'Spicy chicken nuggets', 'Lotus-Leaf-Shaped Pancake']

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 eggs', 'Cinnamon roll', 'Scrambled egg with cold melon', 'Baked sweet potato with cheese', 'Banana crisp', 'Yogurt Mousse', 'Egg sausage powder', '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', 'Stir fried tomato', 'Steamed eggplant with minced garlic', 'Bean paste bread', 'Fried meat with mushrooms', 'Stir fried lotus root', 'Diced beef with black pepper', 'Pumpkin pancake', 'Fried cucumber', 'Coarse grain steamed bread', 'Taoshan skin moon cake', 'Fried meat with Scallion', 'Stir fried beef', 'Watercress carp ', 'Braised Tofu with shrimp', 'Plain dumplings', 'Cucumber in Sauce', 'Braised 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:

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 eating", 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", "Pan-Fried Beef Steak with Black Pepper", "Home cooked chicken", "Garlic beans",
    "beef with onions ", "Fried egg with towel gourd", "Fried egg with mushroom", "Chicken sliced tofu", "Egg & Vegetable Soup",
    "Fried zucchini", "Eggplant and beans", "Beef with egg", "Mushrooms and vegetables", "Sauteed Potato, Green Pepper and Eggplant",
    "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", "Chicken juice Mashed Potato", "Stewed potato with eggplant", "Fried Udon ", "Curry potato chicken",
    "Baby vegetable 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:

Since our motion pictures have been generated, we can take out screenshots to play when we don't know what to eat 🐶

😆 I wish you all a happy 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/Kung Fu Bear-{i}.png')

Split result:

GIF rewind

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

im = Image.open('Kung Fu Bear.gif')
sequence = [f.copy() for f in ImageSequence.Iterator(im)]
sequence.reverse()  # Reverse the frames in the list through the reverse() function
sequence[0].save('Inverted Kung Fu Bear.gif', save_all=True, append_images=sequence[1:])

The action is funny 😆~

Keywords: Python

Added by LostinSchool on Fri, 14 Jan 2022 01:41:28 +0200