【Python自动化】生成带装饰图形的渐变背景文字封面

2023-10-26

Python自动化专栏,利用文字生成固定比例且带有装饰图形的封面

一、背景介绍

在写博客过程中,经常要用到一些专栏封面、文章封面,其中专栏封面要求的宽高比是1:1,而文章封面的推荐的是16:9。在网上搜索的图片,大部分都不是期望的比例,因此需要在 PS 或者 AI 中裁剪、添加文字

以上的处理过程重复步骤很多,因此考虑用 Python 来实现。简单起见,生成的封面没有以图片作为背景层,而是用渐变填充来替代,与此同时,在封面的左下角和右上角,绘制一些小的半透明装饰图形,让封面增加一些设计感

二、功能介绍

效果预览

cover-overview

功能清单

  • 内置4种渐变背景,并且可以很方便地扩充,配色方案可以参考:UIGradients

  • 内置4种边缘图形:圆环、三角形、正方形、六边形

  • 文字:支持如下两种方案

    • 只有主标题,居中展示
    • 主标题和副标题,副标题宽度不会超过主标题;主标题和副标题之间有一条半透明分割线

三、过程拆解

实现过程可以归纳为如下,主要是4个核心步骤
5-layers

先创建一个封装类 BlogCoverGenerator,在__init__方法中定义需要用到的属性,并定义生成封面的核心方法

注意:后面很多操作都需要用到透明度,因此基础图形的 mode 设置为 RGBA ,并且生成的图片格式需要指定为 PNG 格式

class BlogCoverGenerator:
    def __init__(self,
                 title: str,
                 sub_title: str,
                 title_h_ratio: float=.5,
                 ratio_pair: tuple[int]=(16, 9),
                 bg_gradient: BackgroundGradient=BackgroundGradient.skyline,
                 bg_shape: BackgroundShape=BackgroundShape.circle,
                 min_size='1M'):
        # 封面主标题
        self.title = title
        # 封面副标题
        self.sub_title = sub_title
        # 如果没有副标题,主标题需要垂直居中
        self.no_sub_title = False
        if self.sub_title is None or '' == self.sub_title.strip():
            self.no_sub_title = True
        # 标题区域垂直方向占比(从正中心开始计算),参考值0.3~0.6
        self.title_h_ratio = title_h_ratio
        if self.title_h_ratio < 0.3:
            self.title_h_ratio = 0.3
        elif self.title_h_ratio > 0.6:
            self.title_h_ratio = 0.6
        # 字体位置
        self.zh_font_location = ''
        self.en_font_location = ''
        # 封面宽高比,16:9, 4:3, 1:1等,以16:9为例,需要传入(16, 9)
        self.ratio_pair = ratio_pair
        # 封面渐变背景色
        self.bg_gradient = bg_gradient
        # 封面背景几何图形,目前支持三角形、六边形、圆形
        self.bg_shape = bg_shape
        # 封面大小。如果传入字符串,支持的单位为k, M;也可以传入数值
        self.min_size = self._parse_min_size(min_size)
        if self.min_size > 178956970:
            # ImageDraw.text大小限制
            self.min_size = 178956970

    def generate_cover(self, output_path: str, output_file_name: str):
        width, height = self._get_cover_size()
        img = Image.new('RGBA', (width, height))

        # 第1层,渐变背景
        self._display_gradient_bg(img)
        # 第2层,边缘装饰图形
        img = self._display_decorate_shape(img)
        # 第3层,半透明遮罩
        img = self._display_transparent_mask(img)
        # 第4层,文字
        img = self._display_title(img)
        # 保存
        img.save(os.path.join(output_path, output_file_name))

1.渐变背景层

首先定义一个渐变枚举

from enum import Enum
class BackgroundGradient(Enum):
    skyline = ['#1488CC', '#2B32B2']
    cool_brown = ['#603813', '#b29f94']
    rose_water = ['#E55D87', '#5FC3E4']
    crystal_clear = ['#159957', '#155799']

暂时没有在 Pillow 的文档中找到如何绘制渐变图形,这里只实现了水平方向的渐变色,实现思路是在 start_color 到 end_color 范围内设置一个渐变步长,这个范围和图形的宽度相同,用循环逐一绘制不同颜色的垂直线条。实现代码如下

class BlogCoverGenerator:
    def _display_gradient_bg(self, base_img: Image):
        img_w, img_h = base_img.size
        draw = ImageDraw.Draw(base_img)
        start_color, end_color = self.bg_gradient.value
        if '#' in start_color:
            start_color = ImageColor.getrgb(start_color)
        if '#' in end_color:
            end_color = ImageColor.getrgb(end_color)

        # 水平方向渐变,渐变步长
        step_r = (end_color[0] - start_color[0]) / img_w
        step_g = (end_color[1] - start_color[1]) / img_w
        step_b = (end_color[2] - start_color[2]) / img_w

        for i in range(0, img_w):
            bg_r = round(start_color[0] + step_r * i)
            bg_g = round(start_color[1] + step_g * i)
            bg_b = round(start_color[2] + step_b * i)
            draw.line([(i, 0), (i, img_h)], fill=(bg_r, bg_g, bg_b))

这一步的效果图如下
Step-01

2.装饰图形层

装饰图形层的实现代码很多,这里只介绍思路

定义了一个装饰图形枚举

from enum import Enum
class BackgroundShape(Enum):
    circle = 1
    triangle = 2
    square = 3
    hexagon = 4

因为要绘制的装饰图形是带透明度的,所以要用如下方法把半透明图形混合到底下的渐变背景图层上

Image.alpha_composite(base_img, img_shape)

此外, 最上边的文字层是核心内容,装饰图形层不能盖住文字区域,控制文字区域的参数是 title_h_ratio

Pillow 的 ImageDraw 类有一个绘制正多边形的方法 regular_polygon(), 但是这个方法不支持设置轮廓的宽度,也就是没有提供 width 参数(默认值为1),而这个功能却要指定轮廓宽度。如果用 ImageDraw 的普通方法 polygon(),需要指定各个顶点的坐标,如果多边形要旋转,难度可见一斑

因此,这里用了一个变通的方法,具体实现如下

BlogCoverGenerator
    @staticmethod
    def _width_regular_polygon(draw: ImageDraw, width: int,
                               bounding_circle, n_sides, rotation=0, fill=None, outline=None):
        """
        pillow提供的regular_polygon,不支持对outline设置width,自定义方法,支持轮廓宽度
        """
        start = bounding_circle[2]
        for i in np.arange(0, width, 0.05):
            new_bounding_circle = (bounding_circle[0], bounding_circle[1], start + i)
            draw.regular_polygon(bounding_circle=new_bounding_circle, n_sides=n_sides,
                                 rotation=rotation, fill=fill, outline=outline)

这里的 bounding_circle 参数是一个 tuple 类型 (x, y, r),定义了多边形的外切圆, (x, y) 是圆心坐标,r 是外切圆半径

绘制圆环时调用的另一个方法 ImageDraw.ellipse(),这里需要传入左上角和右下角坐标,可以转化为 bounding_circle,这样就可以和多边形复用位置参数了

这一步的效果图如下(以绘制圆环为例)
Step-02

3.半透明遮罩层

这一步很简单,直接上代码

class BlogCoverGenerator:
    @staticmethod
    def _display_transparent_mask(base_img: Image):
        img_w, img_h = base_img.size
        img_mask = Image.new('RGBA', (img_w, img_h), color=(0, 0, 0, 135))
        return Image.alpha_composite(base_img, img_mask)

4. 文字层

这一步有2个要点,其一,根据标题文字确定选择中文字体还是英文字体

class BlogCoverGenerator:
    def _get_real_font(self, target_title: str, font_size: int):
        for ch in target_title:
            if u'\u4e00' <= ch <= u'\u9fff':
                # 中文字体
                return ImageFont.truetype(self.zh_font_location, font_size)
        # 英文字体
        return ImageFont.truetype(self.en_font_location, font_size)

    def set_fonts(self, zh_font_location: str, en_font_location: str):
        self.zh_font_location = zh_font_location
        self.en_font_location = en_font_location

其二,动态调整字体大小,因此有一个预渲染并检查字体宽度的过程,会使用到 ImageFont.getbbox()

class BlogCoverGenerator:
    def _display_title(self, base_img: Image):
        def get_checked_font_size(target_title: str, font_size: int, max_width: int):
            # 预检查,判断文字宽度是否超出封面
            check_font = self._get_real_font(target_title, font_size)
            _, _, check_w, check_h = check_font.getbbox(target_title)
            if check_w > max_width:
                scale_ratio = max_width / check_w
                font_size = int(font_size * scale_ratio)
            return font_size

这一步的效果图,也就是最终效果了
Step-04

四、完整代码

代码量300+,免费下载请移步CSDN下载地址

参考文档

1.Pillow官网ImageDraw模块

2.如何用Pillow库制作渐变色图片并添加文字

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Python自动化】生成带装饰图形的渐变背景文字封面 的相关文章

  • XPath 语法错误:谓词无效

    我有一个像这样的 XML 文件 cat sample xml
  • 重新格式化 csv 文件

    我有这个 csv 文件 其中只有两个条目 这里是 Meat One Abattoirs Exporters Food Delivery Butchers Retail Meat Dealers Retail Meat Freezer Mea
  • Pulp.pulpTestAll() 测试失败,太多值无法解压

    我的操作系统是window 7 Pulp版本是1 6 1 gurobi版本是7 0 1 可以成功导入gurobipy Pull solvers GUROBI确实通过了测试 所以我可以使用gurobi 然而 pulp solvers CPLE
  • Beautiful Soup 中 find_all 方法的返回类型是什么?

    from bs4 import BeautifulSoup SoupStrainer from urllib request import urlopen import pandas as pd import numpy as np imp
  • Xgboost:bst.best_score、bst.best_iteration 和 bst.best_ntree_limit 有什么区别?

    当我使用 xgboost 训练我的数据时2 cates classification problem 我想使用提前停止来获得最佳模型 但我对在预测中使用哪一个模型感到困惑 因为提前停止将返回 3 个不同的选择 例如 我应该使用 preds
  • python 函数中的任意数量的参数

    我想学习如何在 python 函数中传递任意数量的参数 所以我以递归方式编写了一个简单的 sum 函数 如下所示 def mySum args if len args 1 return args 0 else return args 1 m
  • 为什么 itertools.chain 比扁平列表理解更快?

    在评论中的讨论中这个问题 https stackoverflow com questions 49630581 why does python forbid the use of sum with strings有人提到 虽然连接字符串序列
  • 使用 BeautifulSoup 在 python 中抓取多个页面

    我已经设法编写代码来从第一页中抓取数据 现在我不得不在这段代码中编写一个循环来抓取接下来的 n 页 下面是代码 如果有人可以指导 帮助我编写从剩余页面中抓取数据的代码 我将不胜感激 Thanks from bs4 import Beauti
  • Python 列联表

    作为我正在编写的项目的一部分 我正在生成很多很多列联表 工作流程是 获取具有连续 浮点 行的大型数据数组 并通过分箱将其转换为离散整数值 例如 结果行的值为 0 9 将两行切片为向量 X 和 Y 并生成列联表 https en wikipe
  • dask groupby 不合并分区

    我有一组数据 我想要对其进行一些简单的 groupby count 操作 但我似乎无法使用 dask 来完成此操作 我很可能不理解 dask 中执行 groupby reduce 的方式 特别是当索引位于分组键中时 所以我将用玩具数据来说明
  • 将文件添加到现有 zip 文件

    我正在使用 python 的zipfile module zip 文件位于以下路径 home user a b c test zip并在下面创建另一个文件 home user a b c 1 txt我想将此文件添加到现有的 zip 中 我这
  • GitPython 并向 Git 对象发送命令

    GitPython http gitorious org git python是一种从 python 与 git 交互的方式 我正在尝试访问基本的 git 命令 例如git commit m message 从此模块中 根据this htt
  • 如何使用不同的类和导入动态地使用 Python 日志记录来更改文件句柄

    我无法执行即时日志文件句柄更改 例如 我有3节课 one py import logging class One def init self txt logging debug Hey I m the class One and I say
  • 其中 Py_FileSystemDefaultEncoding 在 python 源代码中设置

    我很好奇python源代码如何设置Py FileSystemDefaultEncoding的值 我收到了一件奇怪的事情 自从Pythondoc https docs python org 2 library sys html sys get
  • 带有第二个 y 轴的 Seaborn 图

    i wanted to know how to make a plot with two y axis so that my plot that looks like this to something more like this by
  • 在 Mac OS X 中安装 Avro

    我正在查看 Avro RPC for Python 网址为https github com phunt avro rpc quickstart python https github com phunt avro rpc quickstar
  • 识别输入的数据类型

    我正在尝试打印用户输入的数据类型并生成如下表 ABCDEFGH String 1 09 float 0 int true bool etc 我正在使用 Python 3 2 3 并且我知道我可以使用type 获取数据的类型 但在Python
  • django:自动为现有用户创建用户配置文件

    我今天在我的项目中添加了一个新的 UserProfile 模型 class UserProfile models Model user models OneToOneField User def unicode self return u
  • 减小散点图的文件大小

    我目前正在尝试减小散点图的文件大小 我的代码如下所示 plt scatter a1 b1 plt savefig test ps 其中 a1 b1 是大小为 400 000 左右的数组 它给出的文件大小为 7 8MB 我尝试过添加 plt
  • 在 Pandas DataFrame 中拆分列表

    我有一个包含多列的 csv 文件 使用 pandas 我将此 csv 文件读入数据帧 并有一个日期时间索引和五六个其他列 其中一列是时间戳列表 下面带有索引的示例 CreateDate TimeStamps 4 1 11 Timestamp

随机推荐

  • 微软RIA服务2009年7月预览版官方手册第2节(翻译:戴石麟)

    2 理解N层Silverlight应用项目 微软 NET RIA服务通过结合ASP NET和Silverlight平台来简化传统N层应用模式 应用逻辑写在中间层上 通过查询 更新 定制方法和服务操作来控制对数据的访问 NET RIA服务特性
  • Verilog入门学习笔记:Verilog基础语法梳理

    无论是学IC设计还是FPGA开发 Verilog都是最基本 最重要的必备技能 但任何一门编程语言的掌握都需要长期学习 并不是简简单单的随便读几本书 随便动动脑筋那么简单 Verilog是一门基于硬件的独特语言 由于它最终所实现的数字电路 具
  • netstat详解

    netstat是控制台命令 是一个监控TCP IP网络的非常有用的工具 它可以显示路由表 实际的网络连接以及每一个网络接口设备的状态信息 netstat可以用于显示与IP TCP UDP和ICMP协议相关的统计数据 一般用于检验本机各端口的
  • vb wor转存html,利用VB操作WORD的基本方法

    利用VB操作WORD的基本方法 通过查阅资料 自我实践 经实验通过 先引用word Application Dim MyWord As Word Application Dim MyWordBook As Word Document Set
  • Numpy:基础数据结构

    1 数组的基本属性 import numpy as np ar np array 1 2 3 4 5 6 7 print ar 输出数组 注意数组的格式 中括号 元素之间没有逗号 和列表区分 print ar ndim 输出数组维度的个数
  • Python @函数装饰器及用法(超级详细)转

    Python 内置的 3 种函数装饰器 分别是 staticmethod classmethod 和 property 其中 staticmethod classmethod 和 property 都是 Python 的内置函数 那么 我们
  • layui子弹框调用父弹框方法

    var thisFrame parent window document getElementById LAY layuiStampDuty1 getElementsByTagName iframe 0 id 获取父级弹框id值 var d
  • 【20220412】文献翻译4:交互中的手势和语言概述

    Gesture and speech in interaction An overview 1 简介 2 什么是共同语言 同声传译 手势 2 1 用手比划 2 2 用头做动作 3 语音和手势是交互的 3 1 交际环境中的手势 3 2 传递意
  • 双向链表

    双向就意味着对于每一个元素 都有两个方向的指向 因此从以下几个方面阐述双向链表 重要方法分析 全部代码 一 重要方法分析 这里的链表实现了我博客中的接口 ILinkedList 与结点 LinkedNode 具体的博客地址 http blo
  • 【Linux】VIM使用

    第一节 Vim常用操作 Vim没有菜单 只有命令 Vim的工作模式有三种 第一种 命令模式 vi vim 文件名 进入命令模式 不可以输入文字 只能识别命令 插入命令 a 在光标所在字符后插入 i 在光标所在字符前插入 o 在光标下插入新行
  • Vue基础精讲 —— Vue的组件之组件的定义、继承、自定义双向绑定、高级属性

    Vue组件基础定义 import Vue from vue const compoent props active type Boolean required true validator value return typeof value
  • UE4-蓝图基础:TimeLine

    一 概念 1 TimeLine 在一定时间内不断执行的一个蓝图节点 2 添加一个空白节点 函数讲解 Play 事件驱动 执行此事件时调用 Play from Start 从头开始执行事件 lt 事件在执行过程中未执行完毕 某一条件改变 事件
  • 回路电感详细介绍(环路电感)

    相比于硬件工程师 PCB工程师对环路电感更敏感 因为环路电感和走线强相关 不管是信号完整性还是电源完整性都涉及到这个概念 一旦电路结构确定 环路电感也随之确定 如果环路电感初期评估失误将会给后期改版带来巨大风险 更多资料请关注公众号 工程师
  • 操作系统 java模拟主存储器空间的分配和回收

    文章目录 实验原理 算法流程图 代码 结果 实验原理 模拟在可变分区管理方式下采用最先适应算法实现主存分配和回收 1 可变分区方式是按作业需要的主存空间大小来分割分区的 当要装入一个作业时 根据作业需要的主存量查看是否有足够的空闲空间 若有
  • 创建第一个Qt Widget项目

    创建第一个Qt Widget项目步骤 1 选择文件 Ctrl n 2 新建文件或项目 3 Qt Widget Application 4 输入项目名称FirstApplication 选择存储的位置 5 选择构建套件Desktop Qt Q
  • numpy一维数组永远为列向量

    import numpy as np a np array 1 3 4 5 print a shape a np transpose a print a shape print a a np ravel a print a shape pr
  • 静态分析简介

    一 程序静态分析简介 Program Static Analysis 程序静态分析简介 Program Static Analysis 是指在不运行代码的方式下 通过词法分析 语法分析 控制流 数据流分析等技术对程序代码进行扫描 验证代码是
  • 【mysql安装报错(已解决)】ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)

    1 说在开头 我的 mysql 版本是 8 0 27 的 安装的时候 感觉每一步都没有错 但是就是不行 到连接本地数据库时 发现一直连不上 搞了好久 一直报下面的错 ERROR 1045 28000 Access denied for us
  • 一文带你聊聊MYSQL的锁和MVCC

    如果你觉得内容对你有帮助的话 不如给个赞 鼓励一下更新 本文内容总结自极客时间 MySQL实战45讲 专栏 LBCC 单版本控制 锁 基于锁的并发控制 这种方案比较简单粗暴 就是一个事务去读取一条数据的时候 就上锁 不允许其他事务来操作 当
  • 【Python自动化】生成带装饰图形的渐变背景文字封面

    Python自动化专栏 利用文字生成固定比例且带有装饰图形的封面 文章目录 一 背景介绍 二 功能介绍 效果预览 功能清单 三 过程拆解 1 渐变背景层 2 装饰图形层 3 半透明遮罩层 4 文字层 四 完整代码 参考文档 一 背景介绍 在