【正經的AI on Python入門系列】1.1鬥圖工具的優化——實現文本居中(還混進了一些語法基礎)

clipboard.png

你們還記得上一篇文章0.來學點Python吧!從一個鬥圖小工具開始中最後提到的幾個問題麼,咱們此次就來解決一下其中難度最大的一個:文本居中!
clipboard.pngpython

看,我把代碼優化了!

上次以後,我偷偷把代碼優化了,如今的main方法長這樣:git

# -*- coding:utf-8 -*-
#__author__ = 'akers'
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import sys,platform,operators.image,operators.clipboard


def main(argv):
    # 建立表情圖
    image = operators.image.draw_emo('./resources/background/pander/default.png', './resources/face/jgz/laugth.png', argv[1])
    output = BytesIO()
    image.save(output, format="BMP")
    operators.clipboard.add_bmp(output)
    image.save('output/facing.png')

    # 調試用
    # plt.figure("生成表情包")
    # plt.imshow(target)
    # plt.show()

if __name__ == "__main__":
    main(sys.argv)

對的,我把那些老長老長的代碼藏到別的包裏了,下面是一點題外話,順便介紹一下Python的包與模塊github

Python的包與模塊

以咱們重構後的項目爲例,一個項目的結構大體以下
clipboard.pngsegmentfault

python中的包就是一個包含了__init__.py文件的文件夾,包下可有子包函數

模塊

python的模塊就是一個py文件,其中可包括類、函數、變量等等內容工具

導入包

python中使用import關鍵字引入模塊,import的使用分如下幾種優化

import package[.sub_package].model

導入指定的模塊,調用時需使用模塊全名,例如spa

import operators.image

operators.image.draw_emo

from package[.sub_package] import model

導入指定模塊,調用時直接使用模塊名調試

from operators import image

image.draw_emo()

import package[.sub_package].model import function

導入模塊中的函數或成員、類、變量等code

from operators.image import draw_emo

draw_emo()

Python的包跟模塊的內容還有不少,更詳細的就先不贅述了,而後讓咱們繼續主題,咳咳
clipboard.png

表情包文本居中

首先,咱們來實現文本的居中,咱們先來回顧一下咱們是如何插入文本的:

#底圖上的10,200位置寫入文字
draw.text((10, 200), argv[1],fill='black', font=font)

text函數的第一個參數須要個元組,而元組中的0、1兩個元素分別表明的x和y的座標(以像素爲單位),那實現居中就很簡單了,咱們只須要根據插入文本的具體寬度計算出一個合適的x座標就能夠了。
因而請看大屏幕,啊呸,請看下圖,

clipboard.png
從圖中咱們能夠看到,文本的插入座標能夠用這樣的公式進行計算:(底圖寬-文本寬) / 2,那問題又來了,文本寬咱們怎麼獲得呢?
在PIL中,咱們能夠用draw.textsize獲取文本所佔的大小,返回是個元組:(寬,高),像這樣:

txtSize = draw.textsize(text, imageFont)

因此咱們修改一下draw_text方法:對x座標進行計算

def draw_text_v1(text, image, off_set=200):
    """強化版繪製文字v1,讓文字在x軸上居中
    Args:
        text: 顯示在圖片上的文本
        image: 當前正使用的Image
        off_set: 縱向偏移量

    Returns:
        Image

    Raises:

    """
    # 加入文字
    # ImageDraw爲PIL的繪圖模塊
    _DEFAULT_FONT_SIZE = 30
    draw = ImageDraw.Draw(image)
    imageFont = ImageFont.truetype('./resources/msyh.ttc', _DEFAULT_FONT_SIZE)
    _MAX_TXT_HEIGH = 32
    txtSize = draw.textsize(text, imageFont)
    pos_x = (CONST_IMG_WIDTH - txtSize[0]) / 2 if CONST_IMG_WIDTH > txtSize[0] else 0
    print("當前X座標", pos_x)
    # 默認顯示位置
    pos = (pos_x, off_set)
    draw.text(pos, text, fill='black', font=imageFont)
    del draw
    return image

相信眼尖的強迫症患者可能會發現,這裏面混着一個很奇怪的寫法:

(CONST_IMG_WIDTH - txtSize[0]) / 2 if CONST_IMG_WIDTH > txtSize[0] else 0

這個if跟else是否是有點調皮,其實這個在python 裏相似於c語系裏的 ? :三目運算符:若是if後的條件成立,則取if前的表達式進行返回,不然取else後的表達式,上面的代碼實際等同於:

CONST_IMG_WIDTH > txtSize[0] ? (CONST_IMG_WIDTH - txtSize[0]) / 2 : 0

這個細節又暴露了Python的哲學:我已經有了if else了,還要? :幹嗎,關鍵字的數量也要最簡;而個語法應該也是從C語系轉過來的小夥伴以爲最彆扭的語法之一了吧....

好廢話很少說,來讓咱們看看效果,很中對不對:
clipboard.png

正所謂要精益求精,既然都作了居中,索性把對齊作個參數好了,跟居中同樣的原理,我把左對齊跟右對齊也作了,只須要對draw_text_v1作一點點潤色:

def draw_text_v2(text, image, off_set=(0, 200), allign='center'):
    """強化版繪製文字v2,左中右,想放哪裏放哪裏
    Args:
        text: 顯示在圖片上的文本
        image: 當前正使用的Image
        off_set: 偏移量,用於保留最小編劇,(x, y)以像素未單位
        allign: 排版,left左對齊,center居中,right右對齊

    Returns:
        Image

    Raises:

    """
    # 加入文字
    # ImageDraw爲PIL的繪圖模塊
    _DEFAULT_FONT_SIZE = 30
    draw = ImageDraw.Draw(image)
    imageFont = ImageFont.truetype('./resources/msyh.ttc', _DEFAULT_FONT_SIZE)
    _MAX_TXT_HEIGH = 32
    txtSize = draw.textsize(text, imageFont)
    imageFont = ImageFont.truetype('./resources/msyh.ttc', 30)

    # 計算x座標
    pos_x = {
        # 居中對齊
        'center': lambda max_width, txt_len, off: (max_width / 2 - txt_len / 2 if max_width > txt_len else 0) + off,
        # 左對齊
        'left': lambda max_width, txt_len, off: (off if max_width > txt_len else 0),
        # 右對齊
        'right': lambda max_width, txt_len, off: (max_width - txt_len if max_width > txt_len else 0)
    }[allign if allign in ('center', 'left', 'right') else 'center'](CONST_IMG_WIDTH - 2*off_set[0], txtSize[0], off_set[0])

    # 默認顯示位置
    pos = (pos_x, off_set[1])

    # 底圖上的10,200位置寫入文字
    draw.text(pos, text, fill='black', font=imageFont)
    del draw
    return image

效果好像還不錯:
clipboard.png
clipboard.png

估計又有眼尖的小夥伴發現有個代碼不對了,怎麼pos_x的計算實現是如此的奇葩,我都看不懂了!

pos_x = {
        # 居中對齊
        'center': lambda max_width, txt_len, off: (max_width / 2 - txt_len / 2 if max_width > txt_len else 0) + off,
        # 左對齊
        'left': lambda max_width, txt_len, off: (off if max_width > txt_len else 0),
        # 右對齊
        'right': lambda max_width, txt_len, off: (max_width - txt_len if max_width > txt_len else 0)
    }[allign if allign in ('center', 'left', 'right') else 'center'](CONST_IMG_WIDTH - 2*off_set[0], txtSize[0], off_set[0])

其實這裏嘛,是Python實現switch功能的一個奇技淫巧...對的,你猜的沒錯,Python是沒有switch結構的,由於:「咱們已經有了完美的if...else...要switch幹嗎」,因此若是按照官方建議,這段代碼應該是這樣的:

max_width = CONST_IMG_WIDTH - 2*off_set[0]
    txt_len = txtSize[0]
    if allign == 'center':
        pos_x = (max_width / 2 - txt_len / 2 if max_width > txt_len else 0) + off_set[0]
    else if allign == 'left':
        pos_x = off_set[0] if CONST_IMG_WIDTH - 2*off_set[0] > txt_len else 0
    else if allign == 'right':
        pos_x = max_width - txt_len if max_width > txt_len else 0
    else:
        pos_x = ((max_width / 2 - txt_len / 2 if max_width > txt_len else 0) + off_set[0]

而我在實現上是採用了字典結構加上lamada表達式進行實現的,這裏再多嘴一句,python的lamada表達式只支持單行只支持單行!由於官方以爲若是一個lamada表達式須要多行才能解決問題,那你仍是去定義個函數吧!
啊?你說上面的代碼仍是沒搞懂?嗯...好吧,那我把代碼好好講講。
首先,代碼是使用了一些簡寫的,咱們把它拆出來:

def center(max_width, txt_len, off):
        return (max_width / 2 - txt_len / 2 if max_width > txt_len else 0) + off

    def left(max_width, txt_len, off):
        return off if max_width > txt_len else 0

    def right(max_width, txt_len, off):
        return max_width - txt_len if max_width > txt_len else 0
    
    dict_temp = {
        # 居中對齊
        'center': center,
        # 左對齊
        'left': left,
        # 右對齊
        'right': right
    }

    pos_x_func = dict_temp[allign if allign in ('center', 'left', 'right') else 'center']
    pos_x = pos_x_func(CONST_IMG_WIDTH - 2*off_set[0], txtSize[0], off_set[0])

得益於python動態語言的特性,咱們能夠把全部的中間變量都去掉,就造成了樣例中的那種代碼結構了,這個屬於加大閱讀難度的(但代碼看上去簡潔了啊!)你們搞不懂的也無所謂,我就主要簡單介紹一下lamada表達式了。
從上面分解的代碼能夠看出,lamada實際上也是函數,有入參有返回值,但就是沒了個名字,因此他其實是一個匿名函數,其構成是這樣的:

lamada 參數1 [, 參數n] : 表達式

記住了,Python不支持多行lamada!

感受此次文章基礎語法扯的有點多了,以後的文章對基礎語法的介紹會進一步減小的!
本次的代碼樣例一樣在個人GitHub上能夠找到

好的,那麼接下來

clipboard.png

額...好吧,下次咱們就來解決上面這個問題!

相關文章
相關標籤/搜索