I believe that big brothers who are interested in NBA will not be unfamiliar with star card, although I don’t know how many big brothers who are interested in NBA in our circle. However, it is not a problem if you are not interested. The method described in this article is a general method of image composition.

Let’s look at a star card:

The card can be divided into five sections

  1. Player action chart
  2. Player’s name
  3. The team logo
  4. Floor background
  5. Decorative border drawing

Today we are going to do is to find the 5 material and then put them together in Python, so this time I must have big brotherhood in doubt, use PS directly set up to no good, reasoning that it really convenient and quick, but the premise is you only do this card, when you want to make star card about 450 players in the league, You’ll need a script to help you do it (not familiar with Photoshop, if it works, let me know).

This article will require some basic Python knowledge, so if you don’t know anything about Python, you should learn some basic knowledge.

OK, let’s get started!

To prepare material

As mentioned at the beginning, we need five things. I’m going to give you some of these five materials to practice with.

The above image actually only has 4 materials, one more is the player name, the player name we can use ImageDraw and ImageFont to load the player name during the composition process.

In order to avoid font path and Chinese garbled problems, I also provide a Microsoft Yahei font.

You can clone or download the materials here, and note that all the materials and images in this article are for communication study only.

Start coding

Our scene is to make star cards for all players in the league, so all players are naturally checked out from the database, here for practice, we can mock some data (although, to be fair, Bosh can not be placed in the SUPER, but there is only one decorative border picture, So reluctantly and my emperor on the same level).

mock_data = [
    {
        'id': 1966.'cn_name': 'lebron James'.'team_id': 5.'category': 'SUPER'
    },
    {
        'id': 1977.'cn_name': 'Chris Bosh'.'team_id': 14.'category': 'SUPER'}]Copy the code

Once we have the data, we walk through the players, find the attributes we need, and pass them into a combination function.

def compose_all(all): for player in all: id = player['id'] # if id == 1966: if True: category = player['category'] player_img = str(id) + '.png' team_id = player['team_id'] team_img = str(team_id) + '.png'  name = player['cn_name'] category = player_category.index(category) + 1 category_img = 'card_bg_' + str(category) + '.png' output_name = str(id) + '.png' print('start compose ' + str(id)) compose(player_img, name, team_img, category_img, output_name)Copy the code

Here is a personal habit, because I often write some scripts on the server, so if True: that place is used for debugging, when a player debugging ok, comment out, run the code, so that you can no longer adjust the indentation, I don’t know other big brother this place like to write.

In this case I’m going to rank players by default (based on some data)

player_category = ["SUPER"."CORE"."BLUE"."SIX"."BENCH"]Copy the code

PNG and card_BG_2.png respectively. Check if you have all 5 materials:

  1. Player action map -> player_img
  2. Player name -> name
  3. Team logo -> team_img
  4. Backplane background – > None
  5. Card_bg_n. PNG (n corresponds to tap)

Also missing is a backplane background, because every player backplane background is the same, so it is good to use directly in the composition function.

Before we go to combine the star cards, there is a problem that we need to solve. We can’t guarantee that all the materials are in the same directory, so we need to specify a directory for each material, so that we can combine the star cards without any problems.

team_path = './logo/'
player_path = './player_img/'
output_path = './trading_cards/'
font_file = './assets/msyh.ttf'
card_decorate_path = './assets/'Copy the code

After setting the path, we write our composite function. In order for this function to work properly, we need to import three modules.

import os
import numpy as np
from PIL import Image, ImageFont, ImageDrawCopy the code

If you are prompted that the module is not found, use the following command to install it

pip install Pillow
pip install numpyCopy the code

See Pillow for detailed documentation on image processing

So here’s our composition function

def compose(player_img, name, team_logo, category_img, output_name):

    card_bg = card_decorate_path + 'bg.png'
    player_img_offset_height = 15

    if not os.path.isfile(player_path + player_img):
        need_manual_compose.append(player_img)
        print(player_path + player_img + ' is not exist')
        return

    player_img = Image.open(player_path + player_img).convert('RGBA')
    bg_img = Image.open(card_decorate_path + category_img).convert('RGBA')
    card_bg_img = Image.open(card_bg).convert('RGBA')
    logo = Image.open(team_path + team_logo).convert('RGBA')

    logo = logo.resize((100.100), Image.ANTIALIAS)

    card_bg_img.paste(player_img, (35,player_img_offset_height), player_img)
    card_bg_img.paste(bg_img, (0.0), bg_img)
    card_bg_img.paste(logo, (95.315), logo)

    font = ImageFont.truetype(font_file, 20)
    d = ImageDraw.Draw(card_bg_img)

    try:
        name = unicode(name, 'utf-8')
    except NameError:
        name = name
    d.text((12.12), name, font=font, fill=(255.255.255))

    card_bg_img.save(output_path + output_name, quality=100)Copy the code

A few points need to be addressed:

  1. Some of the stars may not be able to find the material, so the players can not find the record, the final manual processing.
  2. We need convert(‘RGBA’) since we are using the alpha channel
  3. Before paste the Image, make sure that the paste is the same as the area pixels. If they are different, use the resize function to make them the same. The image. ANTIALIAS parameter is used to anti-aliasing, so that the edges of the Image will be smoother.
  4. Picture B is pasted on picture A, and a.paste(b, (x,y), b), (x,y) is used as the coordinate of the upper left corner. The third parameter B is used as a mask. If this parameter is not used, the transparent part of picture B will also be overlaid on picture A.
  5. This program runs on both python2 and python3.

OK, let’s take a look at the results, shall we

Well, that seems to be good, but people are going to find out that Bosh’s hand is missing, so all that smooth sailing stuff is a lie.

After my own observation, I found that most of the stars’ action maps are similar to Lebron’s (i.e. the player’s action is at the bottom of the picture), and moving the paste coordinate down would result in a large area of blank space in the main area of the stars’ card. One plan is not, another plan, we can do special treatment to the similar bosh action diagram, move down their paste coordinates can be.

The human eye will know which action graph is higher and which action graph is lower, so how does Python know?

As you can see, the size of each action diagram is the same, but the distribution of specific actions in the picture is different.

This is where we need numpy to help us transform the image into a pixel matrix, and then we scan the matrix line by line and record where the valid pixels appear, so that we can determine which action images are high.

def calculateUsefulHeight(img):
    img = Image.open(player_path + img).convert('RGBA')
    w, h = img.size
    mat = np.array(img)

    for i in range(mat.shape[0) :if not allEqual(mat[i]):
            return h - i

def allEqual(line):
    w = len(line)
    if not w:
        return True
    init_value = line[0] [3]
    step = 10
    for i in range(int(round(w/step))):
        if line[i * step][3] == init_value:
            continue
        else:
            return False
    return TrueCopy the code

Then add code for special handling of the height of the action diagram to the composition function.

def compose(player_img, name, team_logo, category_img, output_name): ... # deal with high player image h = calculateUsefulHeight(player_img) # If h > 310: offset = h - 310 player_img_offset_height += offsetCopy the code

Let’s run it again and see how it looks.

Good, this is ok, especially in a flash run out of 450 looks good star card or very cool.

OK, this should be the end, the source code is available here, which contains all the images, materials and code involved in this article.

If you have better design and layout, welcome to communicate with me.