吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5571|回复: 22
收起左侧

[Python 原创] 生成从充电口才能看清的图片

[复制链接]
Anolecrab 发表于 2022-7-13 16:35
本帖最后由 Anolecrab 于 2022-7-13 16:44 编辑

原文首发于我的个人笔记。发现复制过来无法保持样式,就直接截图了(注重傲视的可以到最后看附件)。
如果只对代码有兴趣,可以翻到最后看代码。
也可以到github查看:https://github.com/craii/draw_twisted_text/blob/main/draw_twisted_text.py


这篇文章的起点来自于微信群。我们这个群里的小伙伴们各个都长得好看、说话又好听,而且还特别好看。
微信群聊,群成员发送了一张扭曲的图片文字变形的图片如下图,如果我们按照其指引——“从手机充电口斜着看”,那么我们可以看到这些黑色色块和线条会“逐渐组合”成文字:肯德基疯狂星期四,群里富哥V50。 image.png


分析
这引起了我的兴趣。搜索关键词“从充电口看的图片”,从结果看,这种图片很久之前就开始在网络上流行,近期出现的次数又多了起来。那么这种图片怎么制作呢? 我们可以尝试分析一下原理
image.png
Graph1 描述了我们从手机充电口方向看向屏幕文字的场景 ,其中蓝色线条为屏幕中的文字,绿色线条为文字在我们眼中的象。为了更好的分析问题,我们将其转换为Graph2 中的模型。注意:红色线条长度相等,蓝色线条长度也相等。同样的,我们可以假设这些线条是屏幕中的文字,其长度可以理解为文字字号大小。  观察两条红色线条,我们可以发现,当相同长度大小的文字,在旋转一定角度后,会造成其投影的变小,即我们看到会被从较大的字号挤压成较小字号,挤压方向与视线方向相同。屏幕旋转 或 观察者主动改变观察角度 都会造成 的字体的象被挤压,挤压程度与观察的角度Ω有关,相关系数为sin Ω。 那么,我们有如下逻辑链条:
  • 屏幕正常展示文字  —->  以一定角度查看文字 —> 看到被挤压的文字 (挤压);
  • 以一定角度查看文字 —-> 看到正常的文字  —->  屏幕拉伸展示文字 (拉伸);
也就是说,我们如果需要从充电口看屏幕看到(看清)正常的文字,那么屏幕在展示时就需要将文字拉伸。且根据上文,如果我们有要求屏幕所展示的被拉伸过的文字要观察者以特定的倾角才看看清,那么久需要将文字拉伸为原尺寸的 sinΩ 倍 (Graph 3 |  Graph 4 所示)。即:为了达到效果,我们需要做如下的工作:
  • 将文字拉伸;
  • 在拉伸文字的同时,保持文字清晰;
实操其实,经过分析,知道原理之后,我们可以直接用ps完成这项工作。但奈何我用ps的经验停留在了CS4版本。那么我们可以用代码来实现。
注意:百度或者微信搜索出来的教程也都是用ps来完成,如果感兴趣,可以搜索相关的教程。对于此问题,个人比较推荐使用微信搜索关键字“从充电口才能看清的图片”。
开工
[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# - * - coding:utf-8 - * -
#  作者:Elias Cheung
#  编写时间:2022/7/12  14:43
from PIL import Image, ImageFont, ImageDraw
from typing import Union, List, NewType
from datetime import datetime
from math import ceil, floor
 
Picture = NewType("Picture", Image)
 
 
def draw_text(text: str, font_family: str, font_size: int = 50,
              text_color: Union[str, tuple] = (0, 0, 0), location: tuple = None,
              bg_color: Union[str, tuple] = "white") -> List[Picture]:
    """
    :param location: location on image where the text would be written onto, if None, it will be reset to(0,2*font_size)
    :param font_family: src of ttf file (win or mac), or name of built-in fonts(ios);
    :param text: text that you want to draw;
    :param text_color: text color, in pattern (r, g, b) or string white, green, ..., e.t.c.;
    :param font_size: font size;
    :param bg_color: canvas color, in pattern (r, g, b) or string white, green, ..., e.t.c.;
    :return: 2 picture in a list; twisted one (1st) and just-crop one (2nd);
    """
    print(f"【{datetime.now()}】创建画板")
    print(f"【{datetime.now()}】开始绘制正文")
 
    font = ImageFont.truetype(font_family, font_size)
    image = Image.new(mode="RGB", size=(len(text) * font_size, len(text) * font_size), color=bg_color)
 
    draw = ImageDraw.Draw(image)
    location = (0, 2 * font_size) if location is None else location
    draw.text(location, text, fill=text_color, font=font)
 
    crop_image = image.crop((0, int(1.8 * font_size), len(text) * font_size, int(3.2 * font_size)))
    twisted_image = crop_image.resize(size=(ceil(len(text)) * font_size, 50 * font_size))
 
    print(f"【{datetime.now()}】正文绘制完毕")
    return [twisted_image, crop_image]
 
 
def refine_drew_text_image(image: Picture, font_size: int = 50,
                           text_color: Union[str, tuple] = (0, 0, 0),
                           bg_color: Union[str, tuple] = (255, 255, 255)) -> Picture:
    """
    :param text_color: text color, in pattern (r, g, b) or string white, green, ..., e.t.c.;
                       MUST BE THE SAME COLOR AS THE ONE YOU USED TO DRAW TEXT WITH FUNCTION draw_text();
    :param bg_color: text color, in pattern (r, g, b) or string white, green, ..., e.t.c.;
                     MUST BE THE SAME COLOR AS THE ONE YOU USED WITH FUNCTION draw_text();
    :param image: image to be refined, come from function draw_text();
    :param font_size: MUST BE EQUAL TO THE FONT SIZE USED IN FUNCTION draw_text();
    :return: refined picture
    """
 
    print(f"【{datetime.now()}】开始优化图片")
    #  核心部分,
    for w in range(image.width):
        for h in range(image.height):
            r_, g_, b_ = image.getpixel((w, h))
            _r, _g, _b = text_color
            if (r_, g_, b_, 0) != (_r, _g, _b, 0):
                image.putpixel((w, h), bg_color)
 
    print(f"【{datetime.now()}】准备输出优化效果图")
    new_image = Image.new(mode="RGB", size=(45 * font_size, 100 * font_size), color=bg_color)
    new_image.paste(image, (18 * font_size, 0))
 
    print(f"【{datetime.now()}】计算输出效果图边界尺寸")
    coordinates_y, coordinates_x = list(), list()
    for w in range(new_image.width):
        for h in range(new_image.height):
            r_, g_, b_ = new_image.getpixel((w, h))
            if (r_, g_, b_) != (255, 255, 255):
                coordinates_y.append(h)
                coordinates_x.append(w)
 
    text_start_y = min(coordinates_y)
    text_end_y = max(coordinates_y)
    text_start_x = min(coordinates_x)
    text_end_x = max(coordinates_x)
 
    crop_box = (text_start_x - 2 * font_size,
                text_start_y - 2 * font_size,
                text_end_x + 2 * font_size,
                text_end_y + 2 * font_size)
    print(f"【{datetime.now()}】确定新边界", crop_box)
 
    new_image = new_image.crop(crop_box)
    return new_image
 
 
def concatenate_images(*images: Picture, strategy: str = "MAX",
                       bg_color: Union[str, tuple] = (255, 255, 255)) -> Picture:
    """
    :param bg_color: background color used as concatenated images; better be THE SAME as used in draw_text() and
                    refine_drew_text_image()
    :param images: one or more image(s) to be concatenated
    :param strategy: MAX or MIN, if MAX, concatenated picture's WIDTH will be set to the LARGEST width of image in image
                    tuple, vice versa;
    :return: concatenated picture
    """
    images: tuple = images
    images_width = [image.width for image in images]
    images_height = [image.height for image in images]
 
    max_width, min_width = max(images_width), min(images_width)
 
    new_image_height = sum(images_height) + 50
    new_image_width = max_width if strategy == "MAX" else min_width
 
    images = tuple(map(lambda img: img.resize(size=(new_image_width, floor(new_image_width * img.height / img.width))),
                       images))
 
    new_image = Image.new(mode="RGB", size=(new_image_width, new_image_height), color=bg_color)
 
    current_y = 0
    for image in images:
        new_image.paste(image, (0, current_y))
        current_y += image.height
 
    return new_image
 
 
if __name__ in "__main__":
    lyris = "生成从充电口才能看清的图片"
    string = "闭上一只眼看从充电口哦"
    font_src = "w6.ttf"  # pc/mac 可用ttf文件的路径替代,ios可使用系统中已安装的字体,输入名称即可
 
    pic = draw_text(text=lyris, font_family=font_src)
    pic[0].show()
    print("上图为中间临时产物,优化中,请耐心等待结果~")
 
    mod_pic = refine_drew_text_image(pic[0])
    mod_pic.show()
 
    hint = draw_text(text=string, font_family=font_src)
 
    last = concatenate_images(pic[0], hint[1], strategy="MAX")
    last.show()
    last.save(f"{lyris}_{datetime.now()}.png")



解析draw_text() image.png draw_text()  的主要作用是生成一张 初步扭曲的图片经过裁切但未拉伸扭曲的图片 image.png
  • 初步扭曲的图片:经过此步骤处理,我们可以看到已经可以看到效果,但图片显得很脏,还需要进一步处理;
  • 经过裁切但未拉伸扭曲的图片: 主要用来生成正常展示的提示文案;
refine_drew_text_image()优化上游产生的图片 image.png image.png refine_drew_text_image() 的作用是将draw_text() 生成的图片进行优化。将其变得干净清晰。draw_text() 生成的图片“显得脏”是因为图片进行拉伸操作时,会增加与字体颜色和背景不同的颜色。那么,优化逻辑就显而易见了,就是遍历 draw_text() 生成的图片的每个像素。将颜色与文字最初的颜色以及背景不同的像素全部填充为背景色。这样可以使得图片在变清晰的同时显得锐利。核心代码 image.png concatenate_images() 将变形文字的图片和描述文字的图片拼接在一起。为了拓展性,我将其写成了可将一张或多张图片拼接成一张新图片。 image.png

BOWEN.png

免费评分

参与人数 3威望 +1 吾爱币 +21 热心值 +2 收起 理由
xiaoyuer66 + 1 我很赞同!
songbing490 + 1 生成个EXE程序吧 让我们这些小白也用下
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

songbing490 发表于 2022-7-13 18:21
这玩意用PS做着可快。。。。拉伸变形就行了
 楼主| Anolecrab 发表于 2022-7-13 20:02
songbing490 发表于 2022-7-13 18:21
这玩意用PS做着可快。。。。拉伸变形就行了

确实,我看的教程也是用ps。但在这里发帖嘛...总不能说用ps拉一下就好嘛... 而且做成代码后有更多的可能性
ciker_li 发表于 2022-7-13 19:02
DeathLYH 发表于 2022-7-13 19:46
牛啊,学习学习
hanqh77 发表于 2022-7-13 20:29
技术学院风格!
充血的二弟 发表于 2022-7-13 20:31
用CTRL T也行
erui 发表于 2022-7-13 22:00
songbing490 发表于 2022-7-13 18:21
这玩意用PS做着可快。。。。拉伸变形就行了

说的是呀,我正好也想说这个。
你是我的人 发表于 2022-7-13 22:09
有点意思
lizf2019 发表于 2022-7-13 22:44
为啥我看不出来任何内容
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-21 03:28

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表