点阵快速生成方法与font6x8点阵分享

摘要

        本文为利用python代码实现从视频到点阵的快速生成,方便为单片机的动画提供点阵资源。从视频获取连续帧图使用的是ffmpeg。相应的资源文件在附件提供。

        版本信息:python-3.10,selenium-4.9.0,PIL-Image-9.4.0.

样例

根据封面生成的128x64的点阵, light_ratio=200 

      

代码

# -*- coding: utf-8 -*-
# @Time : 2024/4/14 15:01
import os
import time
import subprocess
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service


class GenerateFontBitmapFromVideo:
    def __init__(self, video_path, frames_dir_path, drop_ratio, row_width, column_height, reverse):
        self.video_path = video_path  # 视频文件路径
        self.frames_dir_path = frames_dir_path  # 输出帧图片的目录和文件名格式
        self.drop_ratio = drop_ratio
        self.row_width = row_width
        self.column_height = column_height
        self.reverse = reverse

    def generateFramesFromVideo(self, start_time="00:00:00", duration="10"):
        output_pattern = os.path.join(self.frames_dir_path, 'frame_%04d.png')

        # 构建FFmpeg命令
        ffmpeg_command = [
            'ffmpeg',
            '-i', self.video_path,
            '-ss', start_time,
            '-t', duration,
            output_pattern
        ]

        # 执行FFmpeg命令
        subprocess.run(ffmpeg_command)

    def generateBitmapFromZheTao(self, driver):
        driver.get("https://www.zhetao.com/fontarray.html")
        time.sleep(1)
        rows = driver.find_element(By.ID, "rows")
        coltimes = driver.find_element(By.ID, "coltimes")
        inputs = driver.find_element(By.ID, "selfile")
        text = driver.find_element(By.ID, "chars")
        buttons = driver.find_elements(By.CLASS_NAME, "genbtn")
        store = open("store.txt", "w")
        store.write("u8 frames[][{}*{}]".format(self.row_width // 8, self.column_height) + " = {\n")

        def extract_and_save(content, index):
            # 寻找起始行的索引
            start_marker = "static const unsigned char bitmap_bit_bytes[] = "
            end_marker = "};"
            start_index = content.find(start_marker)

            if start_index == -1:
                print("Start marker not found.")
                return

            start_index += len(start_marker)
            # 寻找结束行的索引
            end_index = content.find(end_marker, start_index)

            if end_index == -1:
                print("End marker not found.")
                return

            # 加上结束标记长度,确保包括结束行
            end_index += len(end_marker)

            # 提取字符串
            extracted_content = content[start_index:end_index]
            # 写入到文件
            store.write(extracted_content.replace(end_marker, "},\n"))

        if self.reverse: driver.find_element(By.ID, "getanti").click()
        for index, frame in enumerate(os.listdir(self.frames_dir_path)):
            if index % self.drop_ratio == 1: continue
            inputs.clear()
            inputs.send_keys(r"{}\{}".format(self.frames_dir_path, frame))
            time.sleep(0.1)

            coltimes.clear()
            coltimes.send_keys(self.row_width // 8)
            rows.clear()
            rows.send_keys(self.column_height)

            buttons[-1].click()
            time.sleep(0.2)
            print(index)
            # 将文本的以static const unsigned char bitmap_byte开头,};结尾的部分写入store.txt
            extract_and_save(text.text, index)

        store.write("};")
        store.close()

    def generateBitmapFromLocal(self, ligth_ratio=128):
        from PIL import Image
        store = open("store.txt", "w")
        store.write("u8 frames[][{}*{}]".format(self.row_width // 8, self.column_height) + " = {\n")
        os.chdir(self.frames_dir_path)
        for index, frame in enumerate(os.listdir(self.frames_dir_path)):
            if index % self.drop_ratio == 1: continue
            image = Image.open(frame).convert('L')

            # 调整图片大小
            image = image.resize([self.row_width, self.column_height])

            # 获取图像的像素数据
            pixels = image.load()

            # 存储点阵数据
            dot_matrix = []

            for y in range(self.column_height):
                byte = 0
                byte_list = []
                for x in range(self.row_width):
                    # 获取灰度值并转换为二值
                    if pixels[x, y] < ligth_ratio:  # 阈值可以根据需要调整
                        byte = (byte << 1) | (self.reverse)
                    else:
                        byte = (byte << 1) | (not self.reverse)

                    # 每8个像素存储一个字节
                    if (x + 1) % 8 == 0:
                        byte_list.append(byte)
                        byte = 0

                # 处理剩余的不足8个像素
                if self.row_width % 8 != 0:
                    byte = byte << (8 - (self.row_width % 8))
                    byte_list.append(byte)

                dot_matrix.extend(byte_list)
            formatted_output = "{\n    "
            for i, byte in enumerate(dot_matrix):
                formatted_output += f"0x{byte:08b}, "
                if (i + 1) % (self.row_width // 8) == 0:
                    formatted_output += "\n    "
            formatted_output = formatted_output.rstrip(", \n") + "\n},"
            print(formatted_output)
            store.write(formatted_output)
        store.write("};")
        store.close()


class Property:
    google_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    video_path = r"C:\Users\GroLin\Downloads\Video\灼眼的夏娜 緋色の空.mp4"
    frames_dir_path = r"C:\Users\GroLin\Downloads\Video\2024上\frames"
    drop_ratio = 2  # 丢帧比例
    reverse = True  # 是否取反

    video_start_time = "00:00:14"  # 视频开始时间
    duration = "10"  # 视频持续时间

    row_width = 128  # 每行的像素数
    column_height = 64  # 每列的像素数
    ligth_ratio = 200  # 阈值

    def __init__(self):
        os.makedirs(self.frames_dir_path, exist_ok=True)

    def getDriver(self):
        try:
            service = Service("chromedriver.exe")
            from selenium.webdriver.chrome.options import Options
            chrome_options = Options()
            chrome_options.binary_location = self.google_path
            return webdriver.Chrome(service=service, options=chrome_options)
        except Exception as e:
            print(e)
            import updateDriver as ud
            ud.update_driver()
            return self.getDriver()


if __name__ == '__main__':
    property = Property()

    GFBFV = GenerateFontBitmapFromVideo(
        property.video_path,
        property.frames_dir_path,
        property.drop_ratio,
        property.row_width,
        property.column_height,
        property.reverse
    )  # 实例化类

    GFBFV.generateFramesFromVideo(property.video_start_time, property.duration)  # 从视频中提取帧图片

    #GFBFV.generateBitmapFromZheTao(property.getDriver())  # 从网站生成点阵

    GFBFV.generateBitmapFromLocal(property.ligth_ratio)  # 从本地图片生成点阵
简要说明
  • Properly类为配置类,可以自定义生成点阵的规格,是否取反等。
  • GenerateFontBitmapFromVideo类提供两个方法来从图片生成点阵,本地生成的可控性更高并且速度更快,这里提供网页生成主要是为了分享这个网站,更适合不了解代码的同学。
  • 将必要文件准备好后放在一个文件夹,如下图(两个图片文件不用在意)

附件

  1.  FFmpeg下载Download FFmpeg
  2. 点阵生成网站https://www.zhetao.com/fontarray.html
  3. updateDriver.py
    # -*- coding: utf-8 -*-
    # @Time : 2023/8/29 13:48
    import requests
    import zipfile
    import os
    import json
    
    root = "."
    
    
    def removeDirs(path, dir):
        if os.path.exists(os.path.join(path, dir)):
            path = os.path.join(path, dir)
            if os.path.isdir(path):
                os.chdir(path)
                absPath = os.getcwd()
                for inner in os.listdir('.'):
                    if os.path.isdir(inner):
                        removeDirs(os.getcwd(), inner)
                        continue
                    os.remove(inner)
                os.chdir(absPath[:absPath.rfind("\\")])
                os.removedirs(dir)
            else:
                os.remove(path)
    
    
    def update_driver():
        url = 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json'
        resp = json.loads(requests.get(url).text)
        for platforms in resp['channels']['Stable']['downloads']['chromedriver']:
            if platforms['platform'] == "win64":
                zips = platforms['url']
                file = requests.get(zips).content
                with open('./chromedriver-win64.zip', 'wb') as f:
                    f.write(file)
                zip_file = zipfile.ZipFile('chromedriver-win64.zip')
                name_ = filter(lambda x: x.endswith(".exe"), zip_file.namelist()).__next__()
                zip_file.extract(name_, root)
                if os.path.exists(f'{root}\chromedriver.exe'):
                    os.remove(f'{root}\chromedriver.exe')
                zip_file.close()
                os.rename(os.path.join(root, name_), f'{root}\chromedriver.exe')
                removeDirs(root, 'chromedriver-win64')
                removeDirs(root, 'chromedriver-win64.zip')
    
    
    if __name__ == '__main__':
        update_driver()
    
  4. 6x8的英文字符点阵文件
    u8 font6x8[][6] = {
    	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },   // ¿Õ¸ñ
    	{ 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 },   // !
    	{ 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 },   // "
    	{ 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #
    	{ 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $
    	{ 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 },   // %
    	{ 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 },   // &
    	{ 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 },   // '
    	{ 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 },   // (
    	{ 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 },   // )
    	{ 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *
    	{ 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +
    	{ 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 },   // ,
    	{ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 },   // -
    	{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },   // .
    	{ 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 },   // /
    	{ 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E },   // 0
    	{ 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 },   // 1
    	{ 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 },   // 2
    	{ 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 3
    	{ 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 },   // 4
    	{ 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 },   // 5
    	{ 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 },   // 6
    	{ 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 },   // 7
    	{ 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 },   // 8
    	{ 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E },   // 9
    	{ 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 },   // :
    	{ 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;
    	{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },   // <
    	{ 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 },   // =
    	{ 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 },   // >
    	{ 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?
    	{ 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E },   // @
    	{ 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C },   // A
    	{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 },   // B
    	{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 },   // C
    	{ 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C },   // D
    	{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 },   // E
    	{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 },   // F
    	{ 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A },   // G
    	{ 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F },   // H
    	{ 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 },   // I
    	{ 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 },   // J
    	{ 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 },   // K
    	{ 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 },   // L
    	{ 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F },   // M
    	{ 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F },   // N
    	{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E },   // O
    	{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 },   // P
    	{ 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E },   // Q
    	{ 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 },   // R
    	{ 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 },   // S
    	{ 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 },   // T
    	{ 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F },   // U
    	{ 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F },   // V
    	{ 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F },   // W
    	{ 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 },   // X
    	{ 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y
    	{ 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z
    	{ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 },   // [
    	{ 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 },   // 55
    	{ 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 },   // ]
    	{ 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^
    	{ 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 },   // _
    	{ 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 },   // '
    	{ 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 },   // a
    	{ 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 },   // b
    	{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 },   // c
    	{ 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F },   // d
    	{ 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 },   // e
    	{ 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 },   // f
    	{ 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C },   // g
    	{ 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 },   // h
    	{ 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 },   // i
    	{ 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 },   // j
    	{ 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 },   // k
    	{ 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 },   // l
    	{ 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 },   // m
    	{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 },   // n
    	{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 },   // o
    	{ 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 },   // p
    	{ 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC },   // q
    	{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 },   // r
    	{ 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 },   // s
    	{ 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 },   // t
    	{ 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C },   // u
    	{ 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C },   // v
    	{ 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C },   // w
    	{ 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 },   // x
    	{ 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C },   // y
    	{ 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z
    	{ 0x08, 0x08, 0x49, 0x2A, 0x14, 0x08 }    // cursor
    };
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值